Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@
#include <memory>
#include "../autogen_base.h"
#include "spacetimedb/bsatn/bsatn.h"
#include "RawViewPrimaryKeyDefV10.g.h"
#include "RawTableDefV10.g.h"
#include "RawViewDefV10.g.h"
#include "RawScheduleDefV10.g.h"
#include "CaseConversionPolicy.g.h"
#include "RawRowLevelSecurityDefV9.g.h"
#include "RawHttpRouteDefV10.g.h"
#include "RawReducerDefV10.g.h"
#include "Typespace.g.h"
#include "RawLifeCycleReducerDefV10.g.h"
#include "ExplicitNames.g.h"
#include "RawHttpHandlerDefV10.g.h"
#include "RawReducerDefV10.g.h"
#include "RawTypeDefV10.g.h"
#include "RawRowLevelSecurityDefV9.g.h"
#include "RawHttpRouteDefV10.g.h"
#include "RawSubmoduleV10.g.h"
#include "RawViewDefV10.g.h"
#include "ExplicitNames.g.h"
#include "RawProcedureDefV10.g.h"
#include "CaseConversionPolicy.g.h"
#include "RawScheduleDefV10.g.h"
#include "RawViewPrimaryKeyDefV10.g.h"
#include "RawHttpHandlerDefV10.g.h"

namespace SpacetimeDB::Internal {

SPACETIMEDB_INTERNAL_TAGGED_ENUM(RawModuleDefV10Section, SpacetimeDB::Internal::Typespace, std::vector<SpacetimeDB::Internal::RawTypeDefV10>, std::vector<SpacetimeDB::Internal::RawTableDefV10>, std::vector<SpacetimeDB::Internal::RawReducerDefV10>, std::vector<SpacetimeDB::Internal::RawProcedureDefV10>, std::vector<SpacetimeDB::Internal::RawViewDefV10>, std::vector<SpacetimeDB::Internal::RawScheduleDefV10>, std::vector<SpacetimeDB::Internal::RawLifeCycleReducerDefV10>, std::vector<SpacetimeDB::Internal::RawRowLevelSecurityDefV9>, SpacetimeDB::Internal::CaseConversionPolicy, SpacetimeDB::Internal::ExplicitNames, std::vector<SpacetimeDB::Internal::RawHttpHandlerDefV10>, std::vector<SpacetimeDB::Internal::RawHttpRouteDefV10>, std::vector<SpacetimeDB::Internal::RawViewPrimaryKeyDefV10>)
SPACETIMEDB_INTERNAL_TAGGED_ENUM(RawModuleDefV10Section, SpacetimeDB::Internal::Typespace, std::vector<SpacetimeDB::Internal::RawTypeDefV10>, std::vector<SpacetimeDB::Internal::RawTableDefV10>, std::vector<SpacetimeDB::Internal::RawReducerDefV10>, std::vector<SpacetimeDB::Internal::RawProcedureDefV10>, std::vector<SpacetimeDB::Internal::RawViewDefV10>, std::vector<SpacetimeDB::Internal::RawScheduleDefV10>, std::vector<SpacetimeDB::Internal::RawLifeCycleReducerDefV10>, std::vector<SpacetimeDB::Internal::RawRowLevelSecurityDefV9>, SpacetimeDB::Internal::CaseConversionPolicy, SpacetimeDB::Internal::ExplicitNames, std::vector<SpacetimeDB::Internal::RawHttpHandlerDefV10>, std::vector<SpacetimeDB::Internal::RawHttpRouteDefV10>, std::vector<SpacetimeDB::Internal::RawViewPrimaryKeyDefV10>, std::vector<SpacetimeDB::Internal::RawSubmoduleV10>)
} // namespace SpacetimeDB::Internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

// This was generated using spacetimedb codegen.

#pragma once

#include <cstdint>
#include <string>
#include <vector>
#include <optional>
#include <memory>
#include "../autogen_base.h"
#include "spacetimedb/bsatn/bsatn.h"

namespace SpacetimeDB::Internal {
struct RawModuleDefV10;
} // namespace SpacetimeDB::Internal

namespace SpacetimeDB::Internal {

SPACETIMEDB_INTERNAL_PRODUCT_TYPE(RawSubmoduleV10) {
std::string namespace_;
std::shared_ptr<SpacetimeDB::Internal::RawModuleDefV10> module;

void bsatn_serialize(::SpacetimeDB::bsatn::Writer& writer) const {
::SpacetimeDB::bsatn::serialize(writer, namespace_);
::SpacetimeDB::bsatn::serialize(writer, *module);
}
SPACETIMEDB_PRODUCT_TYPE_EQUALITY(namespace_, module)
};
} // namespace SpacetimeDB::Internal

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/bindings-typescript/src/lib/autogen/types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 61 additions & 1 deletion crates/bindings-typescript/src/lib/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConnectionId } from './connection_id';
import { Identity } from './identity';
import type { ColumnIndex, IndexColumns, IndexOpts } from './indexes';
import type { UntypedSchemaDef } from './schema';
import type { UntypedTableDef } from './table';
import type { UntypedTableSchema } from './table_schema';
import { Timestamp } from './timestamp';
import type {
Expand Down Expand Up @@ -223,6 +224,26 @@ export type QueryBuilder<SchemaDef extends UntypedSchemaDef> = {
> as Tbl['accessorName']]: TableRef<Tbl> & From<Tbl>;
} & {};

/**
* Like `QueryBuilder`, but with declared namespaces also exposed as sub-objects.
* This is the type of the `tables` argument in a `subscribe` query-builder callback.
*
* Root-level tables appear as direct properties (same as `QueryBuilder`).
* Declared namespaces appear as sub-objects — each is itself a `QueryBuilder` for that
* namespace's schema, so `tables.<namespace>.<table>` is fully typed.
*
* When `SchemaDef['namespaces']` is absent or `{}`, no namespace properties appear —
* accessing an undeclared namespace is a compile error.
*/
export type NamespacedQueryBuilder<SchemaDef extends UntypedSchemaDef> =
QueryBuilder<SchemaDef> & {
readonly [NS in keyof NonNullable<SchemaDef['namespaces']>]: NonNullable<
SchemaDef['namespaces']
>[NS] extends UntypedSchemaDef
? QueryBuilder<NonNullable<SchemaDef['namespaces']>[NS]>
: never;
};

/**
* A runtime reference to a table. This materializes the RowExpr for us.
* TODO: Maybe add the full SchemaDef to the type signature depending on how joins will work.
Expand Down Expand Up @@ -335,6 +356,42 @@ export function makeQueryBuilder<SchemaDef extends UntypedSchemaDef>(
return Object.freeze(qb) as QueryBuilder<SchemaDef>;
}

/**
* Builds the namespace-aware `tables` object passed to `subscribe`'s query-builder callback.
*
* Tables whose `sourceName` contains no `.` are placed at the root.
* Tables with a dotted `sourceName` (e.g. `"namespace.table"`) are grouped under a
* sub-object keyed by the namespace alias, with the part after the dot as the
* property key within that namespace.
*/
export function makeFromBuilder<SchemaDef extends UntypedSchemaDef>(
tables: SchemaDef['tables']
): NamespacedQueryBuilder<SchemaDef> {
const result: Record<string, unknown> = Object.create(null);
const namespaces: Record<string, Record<string, unknown>> = Object.create(
null
);

for (const table of Object.values(tables) as UntypedTableDef[]) {
const dotIdx = table.sourceName.indexOf('.');
if (dotIdx === -1) {
result[table.accessorName] = createTableRefFromDef(table as any);
} else {
const ns = table.sourceName.slice(0, dotIdx);
const key = table.sourceName.slice(dotIdx + 1);
(namespaces[ns] ??= Object.create(null))[key] = createTableRefFromDef(
table as any
);
}
}

for (const [ns, nsTables] of Object.entries(namespaces)) {
result[ns] = Object.freeze(nsTables);
}

return Object.freeze(result) as unknown as NamespacedQueryBuilder<SchemaDef>;
}

function createRowExpr<TableDef extends TypedTableDef>(
tableDef: TableDef
): RowExpr<TableDef> {
Expand Down Expand Up @@ -879,7 +936,10 @@ function literalValueToSql(value: unknown): string {
}

function quoteIdentifier(name: string): string {
return `"${name.replace(/"/g, '""')}"`;
return name
.split('.')
.map(part => `"${part.replace(/"/g, '""')}"`)
.join('.');
}

function isLiteralExpr<Value>(
Expand Down
7 changes: 7 additions & 0 deletions crates/bindings-typescript/src/lib/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ export interface JwtClaims {
readonly fullPayload: JsonObject;
}

export type AliasViews<SchemaDef extends UntypedSchemaDef> = SchemaDef extends {
namespaces: infer NS extends Record<string, UntypedSchemaDef>;
}
? { readonly [K in keyof NS]: ReducerCtx<NS[K]> }
: {};

/**
* Reducer context parametrized by the inferred Schema
*/
Expand All @@ -113,4 +119,5 @@ export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
newUuidV4(): Uuid;
newUuidV7(): Uuid;
random: Random;
as: AliasViews<SchemaDef>;
}>;
13 changes: 13 additions & 0 deletions crates/bindings-typescript/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from './algebraic_type';
import type {
CaseConversionPolicy,
RawSubmoduleV10,
RawModuleDefV10,
RawModuleDefV10Section,
RawScopedTypeNameV10,
Expand Down Expand Up @@ -42,6 +43,7 @@ export type TableNamesOf<S extends UntypedSchemaDef> = Values<
*/
export type UntypedSchemaDef = {
tables: Record<string, UntypedTableDef>;
namespaces?: Record<string, UntypedSchemaDef>;
};

/**
Expand Down Expand Up @@ -202,6 +204,7 @@ export class ModuleContext {
explicitNames: {
entries: [],
},
submodules: [],
};

get moduleDef(): ModuleDef {
Expand Down Expand Up @@ -266,9 +269,19 @@ export class ModuleContext {
value: module.caseConversionPolicy,
}
);
push(
module.submodules && {
tag: 'Submodules',
value: module.submodules,
}
);
return { sections };
}

addSubmodule(submodule: RawSubmoduleV10) {
this.#moduleDef.submodules.push(submodule);
}

/**
* Set the case conversion policy for this module.
* Called by the settings mechanism.
Expand Down
11 changes: 8 additions & 3 deletions crates/bindings-typescript/src/sdk/db_connection_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
} from './message_types.ts';
import type { ReducerEvent } from './reducer_event.ts';
import { type UntypedRemoteModule } from './spacetime_module.ts';
import { makeQueryBuilder } from '../lib/query';
import { makeFromBuilder, type NamespacedQueryBuilder } from '../lib/query';
import {
type TableCache,
type Operation,
Expand All @@ -57,6 +57,7 @@ import type {
} from './reducers.ts';
import type { ClientDbView } from './db_view.ts';
import type { RowType, UntypedTableDef } from '../lib/table.ts';
import type { UntypedSchemaDef } from '../lib/schema';
import type { ProceduresView } from './procedures.ts';
import type { Values } from '../lib/type_util.ts';
import type { TransactionUpdate } from './client_api/types.ts';
Expand Down Expand Up @@ -484,8 +485,12 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
return new SubscriptionBuilderImpl(this);
};

getTablesMap(): any {
return makeQueryBuilder({ tables: this.#remoteModule.tables } as any);
getFromBuilder<
SchemaDef extends UntypedSchemaDef,
>(): NamespacedQueryBuilder<SchemaDef> {
return makeFromBuilder<SchemaDef>(
this.#remoteModule.tables as SchemaDef['tables']
);
}

registerSubscription(
Expand Down
20 changes: 15 additions & 5 deletions crates/bindings-typescript/src/sdk/subscription_builder_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import type {
} from './event_context';
import { EventEmitter } from './event_emitter';
import type { UntypedRemoteModule } from './spacetime_module';
import { isRowTypedQuery, toSql, type RowTypedQuery } from '../lib/query';
import type { Values } from '../lib/type_util';
import {
isRowTypedQuery,
toSql,
type NamespacedQueryBuilder,
type RowTypedQuery,
} from '../lib/query';
import type { UntypedSchemaDef } from '../lib/schema';

export class SubscriptionBuilderImpl<RemoteModule extends UntypedRemoteModule> {
#onApplied?: (ctx: SubscriptionEventContextInterface<RemoteModule>) => void =
Expand Down Expand Up @@ -86,9 +91,14 @@ export class SubscriptionBuilderImpl<RemoteModule extends UntypedRemoteModule> {
subscribe(
query_sql: Array<string | RowTypedQuery<any, any>>
): SubscriptionHandleImpl<RemoteModule>;
/**
* @param queryFn - Receives the query builder for all tables (root and namespaced), e.g.
* `tables => tables.players.build()` or, for a submodule table,
* `tables => tables.inventory.items.build()`. May return multiple queries as an array.
*/
subscribe(
queryFn: (
tables: Values<RemoteModule['tables']>
tables: NamespacedQueryBuilder<RemoteModule & UntypedSchemaDef>
) => RowTypedQuery<any, any> | RowTypedQuery<any, any>[]
): SubscriptionHandleImpl<RemoteModule>;
subscribe(
Expand All @@ -100,8 +110,8 @@ export class SubscriptionBuilderImpl<RemoteModule extends UntypedRemoteModule> {
): SubscriptionHandleImpl<RemoteModule> {
let queries: Array<string | RowTypedQuery<any, any>>;
if (typeof query_sql === 'function') {
const tablesMap = this.db.getTablesMap?.();
const result = query_sql(tablesMap);
const tables = this.db.getFromBuilder<RemoteModule & UntypedSchemaDef>();
const result = query_sql(tables);
queries = Array.isArray(result) ? result : [result];
} else {
queries = Array.isArray(query_sql) ? query_sql : [query_sql];
Expand Down
12 changes: 10 additions & 2 deletions crates/bindings-typescript/src/server/db_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export type ReadonlyDbView<SchemaDef extends UntypedSchemaDef> = {
readonly [Tbl in Values<
SchemaDef['tables']
> as Tbl['accessorName']]: ReadonlyTable<Tbl>;
};
} & (SchemaDef extends {
namespaces: infer NS extends Record<string, UntypedSchemaDef>;
}
? { readonly [K in keyof NS]: ReadonlyDbView<NS[K]> }
: {});

/**
* A type representing the database view, mapping table names to their corresponding Table handles.
Expand All @@ -18,4 +22,8 @@ export type DbView<SchemaDef extends UntypedSchemaDef> = {
readonly [Tbl in Values<
SchemaDef['tables']
> as Tbl['accessorName']]: Table<Tbl>;
};
} & (SchemaDef extends {
namespaces: infer NS extends Record<string, UntypedSchemaDef>;
}
? { readonly [K in keyof NS]: DbView<NS[K]> }
: {});
Loading
Loading