Skip to content

Commit edfc0fd

Browse files
committed
wip
1 parent ed2a18c commit edfc0fd

4 files changed

Lines changed: 143 additions & 0 deletions

File tree

crates/bindings-typescript/src/sdk/db_connection_impl.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import type {
5555
import type { ClientDbView } from './db_view.ts';
5656
import type { UntypedTableDef } from '../lib/table.ts';
5757
import { toCamelCase, toPascalCase } from '../lib/util.ts';
58+
import type { ProceduresView } from './procedures.ts';
5859

5960
export {
6061
DbConnectionBuilder,
@@ -137,13 +138,20 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
137138
*/
138139
setReducerFlags: SetReducerFlags<RemoteModule>;
139140

141+
/**
142+
* The accessor field to access the reducers in the database and associated
143+
* callback functions.
144+
*/
145+
procedures: ProceduresView<RemoteModule>;
146+
140147
/**
141148
* The `ConnectionId` of the connection to to the database.
142149
*/
143150
connectionId: ConnectionId = ConnectionId.random();
144151

145152
// These fields are meant to be strictly private.
146153
#queryId = 0;
154+
#requestId = 0;
147155
#emitter: EventEmitter<ConnectionEvent>;
148156
#reducerEmitter: EventEmitter<string, ReducerEventCallback<RemoteModule>> =
149157
new EventEmitter();
@@ -198,6 +206,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
198206
this.db = this.#makeDbView(remoteModule);
199207
this.reducers = this.#makeReducers(remoteModule);
200208
this.setReducerFlags = this.#makeSetReducerFlags(remoteModule);
209+
this.procedures = this.#makeProcedures(remoteModule);
201210

202211
this.wsPromise = createWSFn({
203212
url,
@@ -237,6 +246,8 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
237246
return queryId;
238247
};
239248

249+
#getNextRequestId = () => this.#requestId++;
250+
240251
#makeDbView(def: RemoteModule): ClientDbView<RemoteModule> {
241252
const view = Object.create(null) as ClientDbView<RemoteModule>;
242253

@@ -310,6 +321,45 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
310321
return out;
311322
}
312323

324+
#makeProcedures(def: RemoteModule): ProceduresView<RemoteModule> {
325+
const out: Record<string, unknown> = {};
326+
327+
for (const reducer of def.reducers) {
328+
const key = toCamelCase(reducer.name);
329+
330+
(out as any)[key] = (params: InferTypeOfRow<typeof reducer.params>) => {
331+
this.callReducerWithParams(
332+
reducer.name,
333+
reducer.paramsType,
334+
params,
335+
flags
336+
);
337+
};
338+
339+
const onReducerEventKey = `on${toPascalCase(reducer.name)}`;
340+
(out as any)[onReducerEventKey] = (
341+
callback: ReducerEventCallback<
342+
RemoteModule,
343+
InferTypeOfRow<typeof reducer.params>
344+
>
345+
) => {
346+
this.onReducer(reducer.name, callback);
347+
};
348+
349+
const offReducerEventKey = `removeOn${toPascalCase(reducer.name)}`;
350+
(out as any)[offReducerEventKey] = (
351+
callback: ReducerEventCallback<
352+
RemoteModule,
353+
InferTypeOfRow<typeof reducer.params>
354+
>
355+
) => {
356+
this.offReducer(reducer.name, callback);
357+
};
358+
}
359+
360+
return out as ReducersView<RemoteModule>;
361+
}
362+
313363
#makeEventContext(
314364
event: Event<
315365
ReducerEventInfo<
@@ -916,6 +966,43 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
916966
this.callReducer(reducerName, argsBuffer, flags);
917967
}
918968

969+
/**
970+
* Call a reducer on your SpacetimeDB module.
971+
*
972+
* @param procedureName The name of the reducer to call
973+
* @param argsBuffer The arguments to pass to the reducer
974+
*/
975+
callProcedure(procedureName: string, argsBuffer: Uint8Array): void {
976+
const message = ClientMessage.CallProcedure({
977+
procedure: procedureName,
978+
args: argsBuffer,
979+
// The TypeScript SDK doesn't currently track `request_id`s,
980+
// so always use 0.
981+
requestId: 0,
982+
// reserved for future use - 0 is the only valid value
983+
flags: 0,
984+
});
985+
this.#sendMessage(message);
986+
}
987+
988+
/**
989+
* Call a reducer on your SpacetimeDB module with typed arguments.
990+
* @param reducerSchema The schema of the reducer to call
991+
* @param callReducerFlags The flags for the reducer call
992+
* @param params The arguments to pass to the reducer
993+
*/
994+
callProcedureWithParams(
995+
procedureName: string,
996+
paramsType: ProductType,
997+
params: object,
998+
flags: CallReducerFlags
999+
) {
1000+
const writer = new BinaryWriter(1024);
1001+
ProductType.serializeValue(writer, paramsType, params);
1002+
const argsBuffer = writer.getBuffer();
1003+
this.callProcedure(procedureName, argsBuffer, flags);
1004+
}
1005+
9191006
/**
9201007
* Close the current connection.
9211008
*

crates/bindings-typescript/src/sdk/event_context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export interface ReducerEventContextInterface<
3030
>;
3131
}
3232

33+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
34+
export interface ProcedureEventContextInterface<
35+
RemoteModule extends UntypedRemoteModule,
36+
> extends DbContext<RemoteModule> {
37+
/** No event is provided */
38+
}
39+
3340
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
3441
export interface SubscriptionEventContextInterface<
3542
RemoteModule extends UntypedRemoteModule,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { AlgebraicType, ProductType } from '../lib/algebraic_type';
2+
import type { ParamsObj } from '../lib/reducers';
3+
import type { CoerceRow } from '../lib/table';
4+
import type { Infer, InferTypeOfRow, TypeBuilder } from '../lib/type_builders';
5+
import type { CamelCase } from '../lib/type_util';
6+
import type { ProcedureEventContextInterface } from './event_context';
7+
import type { UntypedRemoteModule } from './spacetime_module';
8+
9+
export type ProcedureEventPromise<
10+
RemoteModule extends UntypedRemoteModule,
11+
ProcedureReturn,
12+
> = Promise<
13+
[ctx: ProcedureEventContextInterface<RemoteModule>, result: ProcedureReturn]
14+
>;
15+
16+
// Utility: detect 'any'
17+
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
18+
19+
// Loose shape that allows all three families even when key names are unknown
20+
type ProceduresViewLoose = {
21+
// call: camelCase(name)
22+
[k: string]: (params: any) => ProcedureEventPromise<any, any>;
23+
};
24+
25+
export type ProceduresView<RemoteModule> = IfAny<
26+
RemoteModule,
27+
ProceduresViewLoose,
28+
RemoteModule extends UntypedRemoteModule
29+
? // x: camelCase(name)
30+
{
31+
[K in RemoteModule['procedures'][number] as CamelCase<
32+
K['accessorName']
33+
>]: (
34+
params: InferTypeOfRow<K['params']>
35+
) => ProcedureEventPromise<RemoteModule, Infer<K['returnType']>>;
36+
}
37+
: never
38+
>;
39+
40+
export type UntypedProcedureDef = {
41+
name: string;
42+
accessorName: string;
43+
params: CoerceRow<ParamsObj>;
44+
paramsType: ProductType;
45+
returnType: TypeBuilder<any, any>;
46+
};

crates/bindings-typescript/src/sdk/reducers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
ReducerEventContextInterface,
1010
SubscriptionEventContextInterface,
1111
} from './event_context';
12+
import type { UntypedProcedureDef } from './procedures';
1213

1314
export type ReducerEventCallback<
1415
RemoteModule extends UntypedRemoteModule,
@@ -81,8 +82,10 @@ export type UntypedReducerDef = {
8182
paramsType: ProductType;
8283
};
8384

85+
// TODO: rename to FunctionsDef
8486
export type UntypedReducersDef = {
8587
reducers: readonly UntypedReducerDef[];
88+
procedures: readonly UntypedProcedureDef[];
8689
};
8790

8891
export type SetReducerFlags<R extends UntypedReducersDef> = {

0 commit comments

Comments
 (0)