Skip to content
Draft
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
9 changes: 8 additions & 1 deletion crates/bindings-typescript/src/lib/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DbView } from '../server/db_view';
import type { Random } from '../server/rng';
import type { ConnectionId } from './connection_id';
import type { Identity } from './identity';
import { type UntypedSchemaDef } from './schema';
import { type MountsOf, type UntypedSchemaDef } from './schema';
import { type Timestamp } from './timestamp';
import {
ColumnBuilder,
Expand Down Expand Up @@ -80,6 +80,12 @@ export interface JsonObject {
[key: string]: JsonValue;
}

export type ReducerCtxMounts<SchemaDef extends UntypedSchemaDef> = {
readonly [Alias in keyof MountsOf<SchemaDef> & string]: ReducerCtx<
MountsOf<SchemaDef>[Alias]
>;
};

/**
* Auth Claims extracted from the payload of a JWT token
*/
Expand Down Expand Up @@ -109,6 +115,7 @@ export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
timestamp: Timestamp;
connectionId: ConnectionId | null;
db: DbView<SchemaDef>;
as: ReducerCtxMounts<SchemaDef>;
senderAuth: AuthCtx;
newUuidV4(): Uuid;
newUuidV7(): Uuid;
Expand Down
10 changes: 9 additions & 1 deletion crates/bindings-typescript/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,17 @@ export type TableNamesOf<S extends UntypedSchemaDef> = Values<
S['tables']
>['accessorName'];

export type MountsOf<S extends UntypedSchemaDef> = Extract<
NonNullable<S['mounts']>,
Record<string, UntypedSchemaDef>
>;

/**
* An untyped representation of the database schema.
*/
export type UntypedSchemaDef = {
tables: Record<string, UntypedTableDef>;
mounts?: Record<string, UntypedSchemaDef>;
};

/**
Expand All @@ -52,6 +58,7 @@ export interface TablesToSchema<T extends Record<string, UntypedTableSchema>>
tables: {
readonly [AccName in keyof T & string]: TableToSchema<AccName, T[AccName]>;
};
mounts?: {};
}

export interface TableToSchema<
Expand Down Expand Up @@ -90,6 +97,7 @@ export function tablesToSchema<

return {
tables: tableDefs as TablesToSchema<T>['tables'],
mounts: {},
};
}

Expand Down Expand Up @@ -149,7 +157,7 @@ export function tableToSchema<
// For client,`schama.tableName` will always be there as canonical name.
// For module, if explicit name is not provided via `name`, accessor name will
// be used, it is stored as alias in database, hence works in query builder.
sourceName: schema.tableName || accName,
sourceName: tableDef.sourceName,
accessorName: accName,
columns: schema.rowType.row, // typed as T[i]['rowType']['row'] under TablesToSchema<T>
rowType: schema.rowSpacetimeType,
Expand Down
10 changes: 9 additions & 1 deletion crates/bindings-typescript/src/server/db_view.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UntypedSchemaDef } from '../lib/schema';
import type { MountsOf, UntypedSchemaDef } from '../lib/schema';
import type { ReadonlyTable, Table } from '../lib/table';
import type { Values } from '../lib/type_util';

Expand All @@ -9,6 +9,10 @@ export type ReadonlyDbView<SchemaDef extends UntypedSchemaDef> = {
readonly [Tbl in Values<
SchemaDef['tables']
> as Tbl['accessorName']]: ReadonlyTable<Tbl>;
} & {
readonly [Alias in keyof MountsOf<SchemaDef> & string]: ReadonlyDbView<
MountsOf<SchemaDef>[Alias]
>;
};

/**
Expand All @@ -18,4 +22,8 @@ export type DbView<SchemaDef extends UntypedSchemaDef> = {
readonly [Tbl in Values<
SchemaDef['tables']
> as Tbl['accessorName']]: Table<Tbl>;
} & {
readonly [Alias in keyof MountsOf<SchemaDef> & string]: DbView<
MountsOf<SchemaDef>[Alias]
>;
};
18 changes: 13 additions & 5 deletions crates/bindings-typescript/src/server/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { makeRandom, type Random } from './rng';
import { callUserFunction, ReducerCtxImpl, sys } from './runtime';
import {
exportContext,
moduleExportKind,
registerExport,
type ModuleExport,
type SchemaInner,
Expand Down Expand Up @@ -52,6 +53,7 @@ export function makeProcedureExport<
const procedureExport: ProcedureExport<S, Params, Ret> = (...args) =>
fn(...args);
procedureExport[exportContext] = ctx;
procedureExport[moduleExportKind] = 'procedure';
procedureExport[registerExport] = (ctx, exportName) => {
registerProcedure(ctx, name ?? exportName, params, ret, fn);
ctx.functionExports.set(
Expand Down Expand Up @@ -160,7 +162,8 @@ export function callProcedure(
connectionId: ConnectionId | null,
timestamp: Timestamp,
argsBuf: Uint8Array,
dbView: () => DbView<any>
dbView: () => DbView<any>,
asView: () => ReducerCtx<UntypedSchemaDef>['as']
): Uint8Array {
const { fn, deserializeArgs, serializeReturn, returnTypeBaseSize } =
moduleCtx.procedures[id];
Expand All @@ -170,7 +173,8 @@ export function callProcedure(
sender,
timestamp,
connectionId,
dbView
dbView,
asView
);

const ret = callUserFunction(fn, ctx, args);
Expand All @@ -187,14 +191,17 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
#uuidCounter: { value: 0 } | undefined;
#random: Random | undefined;
#dbView: () => DbView<any>;
#asView: () => ReducerCtx<UntypedSchemaDef>['as'];

constructor(
readonly sender: Identity,
readonly timestamp: Timestamp,
readonly connectionId: ConnectionId | null,
dbView: () => DbView<any>
dbView: () => DbView<any>,
asView: () => ReducerCtx<UntypedSchemaDef>['as']
) {
this.#dbView = dbView;
this.#asView = asView;
}

get databaseIdentity() {
Expand Down Expand Up @@ -222,8 +229,9 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
this.sender,
new Timestamp(timestamp),
this.connectionId,
this.#dbView()
);
this.#dbView(),
this.#asView()
) as unknown as TransactionCtx<S>;
return body(ctx);
} catch (e) {
sys.procedure_abort_mut_tx();
Expand Down
24 changes: 23 additions & 1 deletion crates/bindings-typescript/src/server/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RowBuilder, type RowObj } from '../lib/type_builders';
import { toPascalCase } from '../lib/util';
import {
exportContext,
moduleExportKind,
registerExport,
type ModuleExport,
type SchemaInner,
Expand All @@ -21,6 +22,15 @@ export interface ReducerOpts {
name: string;
}

export const reducerExportInfo = Symbol('SpacetimeDB.reducerExportInfo');

export type ReducerExportInfo = {
params: RowObj | RowBuilder<RowObj>;
fn: Reducer<any, any>;
opts?: ReducerOpts;
lifecycle?: Lifecycle;
};

export function makeReducerExport<
S extends UntypedSchemaDef,
Params extends ParamsObj,
Expand All @@ -31,8 +41,20 @@ export function makeReducerExport<
fn: Reducer<any, any>,
lifecycle?: Lifecycle
): ReducerExport<S, Params> {
const reducerExport: ReducerExport<S, Params> = (...args) => fn(...args);
const reducerExport: ReducerExport<S, Params> = ((...args) =>
(fn as any)(...args)) as ReducerExport<S, Params>;
reducerExport[exportContext] = ctx;
reducerExport[moduleExportKind] = 'reducer';
(
reducerExport as ReducerExport<S, Params> & {
[reducerExportInfo]: ReducerExportInfo;
}
)[reducerExportInfo] = {
params,
fn,
opts,
lifecycle,
};
reducerExport[registerExport] = (ctx, exportName) => {
registerReducer(ctx, exportName, params, fn, opts, lifecycle);
ctx.functionExports.set(
Expand Down
90 changes: 73 additions & 17 deletions crates/bindings-typescript/src/server/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,21 @@ export const ReducerCtxImpl = class ReducerCtx<
timestamp: Timestamp;
connectionId: ConnectionId | null;
db: DbView<SchemaDef>;
as: IReducerCtx<SchemaDef>['as'];

constructor(
sender: Identity,
timestamp: Timestamp,
connectionId: ConnectionId | null,
dbView: DbView<any>
dbView: DbView<any>,
asView: IReducerCtx<SchemaDef>['as']
) {
Object.seal(this);
this.sender = sender;
this.timestamp = timestamp;
this.connectionId = connectionId;
this.db = dbView;
this.db = dbView as DbView<SchemaDef>;
this.as = asView;
}

/** Reset the `ReducerCtx` to be used for a new transaction */
Expand All @@ -221,6 +224,20 @@ export const ReducerCtxImpl = class ReducerCtx<
me.#senderAuth = undefined;
}

static resetTree(
me: InstanceType<typeof this>,
sender: Identity,
timestamp: Timestamp,
connectionId: ConnectionId | null
) {
this.reset(me, sender, timestamp, connectionId);
for (const childCtx of Object.values(me.as) as InstanceType<
typeof this
>[]) {
this.resetTree(childCtx, sender, timestamp, connectionId);
}
}

get databaseIdentity() {
return (this.#identity ??= new Identity(sys.identity()));
}
Expand Down Expand Up @@ -290,22 +307,16 @@ class ModuleHooksImpl implements ModuleHooks {
}

get #dbView() {
return (this.#dbView_ ??= freeze(
Object.fromEntries(
Object.values(this.#schema.schemaType.tables).map(table => [
table.accessorName,
makeTableView(this.#schema.typespace, table.tableDef),
])
)
return (this.#dbView_ ??= makeDbView(
this.#schema.typespace,
this.#schema.schemaType
));
}

get #reducerCtx() {
return (this.#reducerCtx_ ??= new ReducerCtxImpl(
Identity.zero(),
Timestamp.UNIX_EPOCH,
null,
this.#dbView
return (this.#reducerCtx_ ??= makeReducerCtx(
this.#dbView,
this.#schema.schemaType
));
}

Expand Down Expand Up @@ -339,13 +350,13 @@ class ModuleHooksImpl implements ModuleHooks {
const args = deserializeArgs(BINARY_READER);
const senderIdentity = new Identity(sender);
const ctx = this.#reducerCtx;
ReducerCtxImpl.reset(
ReducerCtxImpl.resetTree(
ctx,
senderIdentity,
new Timestamp(timestamp),
ConnectionId.nullIfZero(new ConnectionId(connId))
);
callUserFunction(moduleCtx.reducers[reducerId], ctx, args);
callUserFunction(moduleCtx.reducers[reducerId] as any, ctx as any, args);
}

__call_view__(
Expand Down Expand Up @@ -415,14 +426,59 @@ class ModuleHooksImpl implements ModuleHooks {
ConnectionId.nullIfZero(new ConnectionId(connection_id)),
new Timestamp(timestamp),
args,
() => this.#dbView
() => this.#dbView,
() => this.#reducerCtx.as
);
}
}

const BINARY_WRITER = new BinaryWriter(0);
const BINARY_READER = new BinaryReader(new Uint8Array());

function makeDbView(
typespace: Typespace,
schemaDef: UntypedSchemaDef
): DbView<any> {
return freeze(
Object.assign(
Object.create(null),
Object.fromEntries(
Object.values(schemaDef.tables).map(table => [
table.accessorName,
makeTableView(typespace, table.tableDef),
])
),
Object.fromEntries(
Object.entries(schemaDef.mounts ?? {}).map(([alias, mountedSchema]) => [
alias,
makeDbView(typespace, mountedSchema),
])
)
)
) as DbView<any>;
}

function makeReducerCtx(
dbView: DbView<any>,
schemaDef: UntypedSchemaDef
): InstanceType<typeof ReducerCtxImpl> {
const asView = freeze(
Object.fromEntries(
Object.keys(schemaDef.mounts ?? {}).map(alias => [
alias,
makeReducerCtx(dbView[alias], (schemaDef.mounts ?? {})[alias]),
])
)
);
return new ReducerCtxImpl(
Identity.zero(),
Timestamp.UNIX_EPOCH,
null,
dbView,
asView
);
}

function makeTableView(
typespace: Typespace,
table: RawTableDefV10
Expand Down
Loading
Loading