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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ rayon-core = "1.11.0"
regex = "1"
reqwest = { version = "0.12", features = ["stream", "json"] }
rolldown = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-beta.42" }
rolldown_common = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-beta.42" }
rolldown_utils = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-beta.42" }
ron = "0.8"
rusqlite = { version = "0.29.0", features = ["bundled", "column_decltype"] }
Expand Down
3 changes: 1 addition & 2 deletions crates/bindings-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"author": "Clockwork Labs",
"type": "module",
"sideEffects": [
"./src/server/polyfills.ts",
"./src/server/register_hooks.ts"
"./src/server/polyfills.ts"
],
"scripts": {
"build:js": "tsup",
Expand Down
1 change: 0 additions & 1 deletion crates/bindings-typescript/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ export { type Uuid } from '../lib/uuid';
export { type Random } from './rng';

import './polyfills'; // Ensure polyfills are loaded
import './register_hooks'; // Ensure module hooks are registered
53 changes: 46 additions & 7 deletions crates/bindings-typescript/src/server/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,41 @@ import {
import { bsatnBaseSize } from '../lib/util';
import { Uuid } from '../lib/uuid';
import { httpClient, type HttpClient } from './http_internal';
import type { DbView } from './db_view';
import { makeRandom, type Random } from './rng';
import { callUserFunction, ReducerCtxImpl, sys } from './runtime';
import type { SchemaInner } from './schema';
import {
exportContext,
registerExport,
type ModuleExport,
type SchemaInner,
} from './schema';

export type ProcedureExport<
S extends UntypedSchemaDef,
Params extends ParamsObj,
Ret extends TypeBuilder<any, any>,
> = ProcedureFn<S, Params, Ret> & ModuleExport;

export function makeProcedureExport<
S extends UntypedSchemaDef,
Params extends ParamsObj,
Ret extends TypeBuilder<any, any>,
>(
ctx: SchemaInner,
name: string | undefined,
params: Params,
ret: Ret,
fn: ProcedureFn<S, Params, Ret>
): ProcedureExport<S, Params, Ret> {
const procedureExport: ProcedureExport<S, Params, Ret> = (...args) =>
fn(...args);
procedureExport[exportContext] = ctx;
procedureExport[registerExport] = (ctx, exportName) => {
registerProcedure(ctx, name ?? exportName, params, ret, fn);
};
return procedureExport;
}

export type ProcedureFn<
S extends UntypedSchemaDef,
Expand All @@ -45,7 +77,7 @@ export interface ProcedureCtx<S extends UntypedSchemaDef> {
export interface TransactionCtx<S extends UntypedSchemaDef>
extends ReducerCtx<S> {}

export function procedure<
function registerProcedure<
S extends UntypedSchemaDef,
Params extends ParamsObj,
Ret extends TypeBuilder<any, any>,
Expand Down Expand Up @@ -99,7 +131,8 @@ export function callProcedure(
sender: Identity,
connectionId: ConnectionId | null,
timestamp: Timestamp,
argsBuf: Uint8Array
argsBuf: Uint8Array,
dbView: DbView<any>
): Uint8Array {
const { fn, deserializeArgs, serializeReturn, returnTypeBaseSize } =
moduleCtx.procedures[id];
Expand All @@ -108,7 +141,8 @@ export function callProcedure(
const ctx: ProcedureCtx<UntypedSchemaDef> = new ProcedureCtxImpl(
sender,
timestamp,
connectionId
connectionId,
dbView
);

const ret = callUserFunction(fn, ctx, args);
Expand All @@ -124,12 +158,16 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
#identity: Identity | undefined;
#uuidCounter: { value: 0 } | undefined;
#random: Random | undefined;
#dbView: DbView<any>;

constructor(
readonly sender: Identity,
readonly timestamp: Timestamp,
readonly connectionId: ConnectionId | null
) {}
readonly connectionId: ConnectionId | null,
dbView: DbView<any>
) {
this.#dbView = dbView;
}

get identity() {
return (this.#identity ??= new Identity(sys.identity()));
Expand All @@ -151,7 +189,8 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
const ctx: TransactionCtx<UntypedSchemaDef> = new ReducerCtxImpl(
this.sender,
new Timestamp(timestamp),
this.connectionId
this.connectionId,
this.#dbView
);
return body(ctx);
} catch (e) {
Expand Down
166 changes: 33 additions & 133 deletions crates/bindings-typescript/src/server/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import Lifecycle from '../lib/autogen/lifecycle_type';
import type RawReducerDefV9 from '../lib/autogen/raw_reducer_def_v_9_type';
import type {
ParamsAsObject,
ParamsObj,
Reducer,
ReducerCtx,
} from '../lib/reducers';
import type { ParamsObj, Reducer } from '../lib/reducers';
import { type UntypedSchemaDef } from '../lib/schema';
import {
ColumnBuilder,
RowBuilder,
type Infer,
type RowObj,
type TypeBuilder,
} from '../lib/type_builders';
import { RowBuilder, type Infer, type RowObj } from '../lib/type_builders';
import { toPascalCase } from '../lib/util';
import type { SchemaInner } from './schema';
import {
exportContext,
registerExport,
type ModuleExport,
type SchemaInner,
} from './schema';

export interface ReducerExport<
S extends UntypedSchemaDef,
Params extends ParamsObj,
> extends Reducer<S, Params>,
ModuleExport {}

export function makeReducerExport<
S extends UntypedSchemaDef,
Params extends ParamsObj,
>(
ctx: SchemaInner,
name: string | undefined,
params: RowObj | RowBuilder<RowObj>,
fn: Reducer<any, any>,
lifecycle?: Infer<typeof RawReducerDefV9>['lifecycle']
): ReducerExport<S, Params> {
const reducerExport: ReducerExport<S, Params> = (...args) => fn(...args);
reducerExport[exportContext] = ctx;
reducerExport[registerExport] = (ctx, exportName) => {
registerReducer(ctx, name ?? exportName, params, fn, lifecycle);
};
return reducerExport;
}

/**
* internal: pushReducer() helper used by reducer() and lifecycle wrappers
Expand All @@ -25,7 +42,7 @@ import type { SchemaInner } from './schema';
* @param fn - The reducer function.
* @param lifecycle - Optional lifecycle hooks for the reducer.
*/
export function pushReducer(
export function registerReducer(
ctx: SchemaInner,
name: string,
params: RowObj | RowBuilder<RowObj>,
Expand Down Expand Up @@ -61,120 +78,3 @@ export function pushReducer(
}

export type Reducers = Reducer<any, any>[];

/**
* Defines a SpacetimeDB reducer function.
*
* Reducers are the primary way to modify the state of your SpacetimeDB application.
* They are atomic, meaning that either all operations within a reducer succeed,
* or none of them do.
*
* @template S - The inferred schema type of the SpacetimeDB module.
* @template Params - The type of the parameters object expected by the reducer.
*
* @param {string} name - The name of the reducer. This name will be used to call the reducer from clients.
* @param {Params} params - An object defining the parameters that the reducer accepts.
* Each key-value pair represents a parameter name and its corresponding
* {@link TypeBuilder} or {@link ColumnBuilder}.
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
*
* @example
* ```typescript
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
* reducer(
* 'create_user',
* {
* username: t.string(),
* email: t.string(),
* },
* (ctx, { username, email }) => {
* // Access the 'user' table from the database view in the context
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
* }
* );
* ```
*/
export function reducer<S extends UntypedSchemaDef, Params extends ParamsObj>(
ctx: SchemaInner,
name: string,
params: Params,
fn: Reducer<S, Params>
): void {
pushReducer(ctx, name, params, fn);
}

/**
* Registers an initialization reducer that runs when the SpacetimeDB module is published
* for the first time.
* This function is useful to set up any initial state of your database that is guaranteed
* to run only once, and before any other reducers or client connections.
* @template S - The inferred schema type of the SpacetimeDB module.
* @template Params - The type of the parameters object expected by the initialization reducer.
*
* @param params - The parameters object defining the expected input for the initialization reducer.
* @param fn - The initialization reducer function.
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
*/
export function init<S extends UntypedSchemaDef, Params extends ParamsObj>(
ctx: SchemaInner,
name: string,
params: Params,
fn: Reducer<S, Params>
): void {
pushReducer(ctx, name, params, fn, Lifecycle.Init);
}

/**
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
* This function allows you to define custom logic that should execute
* whenever a new client establishes a connection.
* @template S - The inferred schema type of the SpacetimeDB module.
* @template Params - The type of the parameters object expected by the connection reducer.
* @param params - The parameters object defining the expected input for the connection reducer.
* @param fn - The connection reducer function itself.
*/
export function clientConnected<
S extends UntypedSchemaDef,
Params extends ParamsObj,
>(
ctx: SchemaInner,
name: string,
params: Params,
fn: Reducer<S, Params>
): void {
pushReducer(ctx, name, params, fn, Lifecycle.OnConnect);
}

/**
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
* This function allows you to define custom logic that should execute
* whenever a client disconnects.
*
* @template S - The inferred schema type of the SpacetimeDB module.
* @template Params - The type of the parameters object expected by the disconnection reducer.
* @param params - The parameters object defining the expected input for the disconnection reducer.
* @param fn - The disconnection reducer function itself.
* @example
* ```typescript
* spacetime.clientDisconnected(
* { reason: t.string() },
* (ctx, { reason }) => {
* console.log(`Client ${ctx.connection_id} disconnected: ${reason}`);
* }
* );
* ```
*/
export function clientDisconnected<
S extends UntypedSchemaDef,
Params extends ParamsObj,
>(
ctx: SchemaInner,
name: string,
params: Params,
fn: Reducer<S, Params>
): void {
pushReducer(ctx, name, params, fn, Lifecycle.OnDisconnect);
}
4 changes: 0 additions & 4 deletions crates/bindings-typescript/src/server/register_hooks.ts

This file was deleted.

Loading
Loading