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.

61 changes: 60 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,25 @@ export type QueryBuilder<SchemaDef extends UntypedSchemaDef> = {
> as Tbl['accessorName']]: TableRef<Tbl> & From<Tbl>;
} & {};

/**
* The type of `q.from` in an `addQuery` 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 `q.from.<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 SubscriptionFromBuilder<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 +355,42 @@ export function makeQueryBuilder<SchemaDef extends UntypedSchemaDef>(
return Object.freeze(qb) as QueryBuilder<SchemaDef>;
}

/**
* Builds the `q.from` object for use in `addQuery` callbacks.
*
* 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']
): SubscriptionFromBuilder<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 SubscriptionFromBuilder<SchemaDef>;
}

function createRowExpr<TableDef extends TypedTableDef>(
tableDef: TableDef
): RowExpr<TableDef> {
Expand Down Expand Up @@ -879,7 +935,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
2 changes: 1 addition & 1 deletion crates/bindings-typescript/src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function toPascalCase(s: string): string {
*/
export function toCamelCase<T extends string>(s: T): CamelCase<T> {
const str = s
.replace(/[-_]+/g, '_') // collapse runs to a single separator (no backtracking issue)
.replace(/[-_]+/g, '_')
.replace(/_([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
return (str.charAt(0).toLowerCase() + str.slice(1)) as CamelCase<T>;
}
Expand Down
22 changes: 19 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,11 @@ 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,
makeQueryBuilder,
type SubscriptionFromBuilder,
} from '../lib/query';
import {
type TableCache,
type Operation,
Expand All @@ -57,6 +61,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 @@ -488,6 +493,14 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
return makeQueryBuilder({ tables: this.#remoteModule.tables } as any);
}

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

registerSubscription(
handle: SubscriptionHandleImpl<RemoteModule>,
handleEmitter: EventEmitter<
Expand Down Expand Up @@ -534,8 +547,10 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
const rows: Operation[] = [];

const deserializeRow = this.#rowDeserializers[tableName];
const { primaryKeyColName, primaryKeyColType } =
this.#rowIdMetadata[tableName];
if (!deserializeRow) return [];
const rowIdInfo = this.#rowIdMetadata[tableName];
if (!rowIdInfo) return [];
const { primaryKeyColName, primaryKeyColType } = rowIdInfo;
let previousOffset = 0;
while (reader.remaining > 0) {
const row = deserializeRow(reader);
Expand Down Expand Up @@ -824,6 +839,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
// Get table information for the table being updated
const tableName = tableUpdate.tableName;
const tableDef = this.#sourceNameToTableDef[tableName];
if (!tableDef) continue;
const table = this.clientCache.getOrCreateTable(tableDef);
const newCallbacks = table.applyOperations(
tableUpdate.operations as Operation<
Expand Down
Loading
Loading