Skip to main content

typescript-server

SpacetimeDB TypeScript server module SDK reference. Use when writing tables, reducers, or module logic in TypeScript.

Quellinformationen

Repository
clockworklabs/SpacetimeDB
Letzte Quellaktivität
4. November 2026 um 18:46
Erkannte Sprache von SKILL.md
Englisch
Sterne
25.531
Forks
1.117

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
typescript-server
description
SpacetimeDB TypeScript server module SDK reference. Use when writing tables, reducers, or module logic in TypeScript.
license
Apache-2.0
metadata
{"author":"clockworklabs","version":"2.0","role":"server","language":"typescript","cursor_globs":"**/*.ts","cursor_always_apply":true}
# SpacetimeDB TypeScript SDK Reference ## Module Structure Tables are built with `table()`, bound with `schema()`, and exported as default. Reducers and lifecycle hooks are `export const`: ```typescript import { schema, table, t } from 'spacetimedb/server'; const scoreRecord = table( { name: 'score_record', public: true }, { id: t.u64().primaryKey().autoInc(), owner: t.identity(), value: t.u32(), } ); const spacetimedb = schema({ scoreRecord }); // ONE object, not spread args export default spacetimedb; export const addRecord = spacetimedb.reducer( { value: t.u32() }, (ctx, { value }) => { ctx.db.scoreRecord.insert({ id: 0n, owner: ctx.sender, value }); } ); ``` `schema({...})` accepts table definitions and submodule namespace registrations. Row and object builders used as reducer arguments or view return types are not schema entries. Environment declarations go in the second argument: `schema(tables, { env: { ... } })`. Export the default schema and every reducer, lifecycle hook, view, procedure, HTTP export, and visibility filter to register. Root-entry named exports must be registered SpacetimeDB values: keep ordinary root helpers/constants unexported. A submodule library may export ordinary helpers for its consumer to call, because submodule discovery ignores non-module exports; do not re-export those helpers from the root entry. ## Imports Schema builders and module exports come from `spacetimedb/server`. Runtime value classes such as `ScheduleAt`, `Timestamp`, and `ConnectionId` come from the root `spacetimedb` package; `Range` comes from `spacetimedb/server`: ```typescript import { schema, table, t, SenderError, type InferSchema, type ReducerCtx, } from 'spacetimedb/server'; import { ConnectionId, ScheduleAt, Timestamp } from 'spacetimedb'; import { Range } from 'spacetimedb/server'; ``` ## Tables `table(OPTIONS, COLUMNS)` takes two arguments. The `name` field MUST be snake_case: ```typescript const entity = table( { name: 'entity', public: true }, { identity: t.identity().primaryKey(), name: t.string(), active: t.bool(), } ); ``` Options: `name` (snake_case, recommended), `public: true`, `event: true`, `indexes: [...]` `ctx.db` accessors are the keys passed to `schema({...})`, verbatim: `schema({ scoreRecord })` -> `ctx.db.scoreRecord`. Keep TypeScript identifiers and accessors camelCase. Use explicit `name: 'snake_case'` strings when you need a canonical database name that differs from the TypeScript identifier. ## Column Types Every column is a `t` builder value: | Builder | JS type | Notes | |---------|---------|-------| | `t.u8()` / `t.u16()` / `t.u32()` | number | | | `t.i8()` / `t.i16()` / `t.i32()` | number | | | `t.u64()` | bigint | Use `0n` literals | | `t.i64()` | bigint | Use `0n` literals | | `t.u128()` / `t.i128()` / `t.u256()` / `t.i256()` | bigint | | | `t.f64()` / `t.f32()` | number | | | `t.bool()` | boolean | | | `t.string()` | string | | | `t.identity()` | Identity | | | `t.connectionId()` | ConnectionId | | | `t.timestamp()` | Timestamp | | | `t.timeDuration()` | TimeDuration | | | `t.scheduleAt()` | ScheduleAt | | Modifiers: `.primaryKey()`, `.autoInc()`, `.unique()`, `.index('btree')`, `.default(value)`. Use `.default(value)` only for a newly appended migration-safe field. Do not put defaults on primary-key, unique, or auto-increment columns. Optional columns: `nickname: t.option(t.string())` Schema builders describe the database's wire types; they are not TypeScript type names. For example, a `t.u16()` value is a TypeScript `number`, not a value cast to a type named `u16`. ## Indexes Use inline `.index('btree')` when a single-column index does not need a named accessor. Use an `indexes` entry when the accessor is named explicitly or the index spans multiple columns. Every `indexes` entry requires `columns`; do not also add `.index('btree')` to the same column. ```typescript // Inline (preferred for single-column): authorId: t.u64().index('btree'), // Access: ctx.db.post.authorId.filter(authorId); // Multi-column (named): indexes: [{ accessor: 'byGroupUser', algorithm: 'btree', columns: ['groupId', 'userId'] }] // Access: ctx.db.membership.byGroupUser.filter([groupId, userId]); ``` Prefer a multi-column index over filtering by one column and looping. Filter takes an array in index column order; a prefix scan passes the leading value bare: `filter(groupId)`. The published module's **entry file must export the schema as default**. If you split tables (`schema.ts`) from reducers/lifecycle (`index.ts`), re-export it from the entry: ```typescript // index.ts export { default } from './schema'; // re-export the schema for the module entry ``` ## Reducers Reducers are created with `spacetimedb.reducer(...)`; the export name becomes the reducer name: ```typescript export const createEntity = spacetimedb.reducer( { name: t.string(), age: t.i32() }, (ctx, { name, age }) => { ctx.db.entity.insert({ identity: ctx.sender, name, age, active: true }); } ); // No arguments, just the callback: export const doReset = spacetimedb.reducer((ctx) => { ... }); ``` Reducer args accept any column type, including arrays of custom types: `{ splits: t.array(Split) }`. Do not pass JSON strings for structured data. ## DB Operations ```typescript ctx.db.scoreRecord.insert({ id: 0n, owner: ctx.sender, value: 1 }); // Insert (0n for autoInc) ctx.db.scoreRecord.id.find(recordId); // Find by PK → row | null ctx.db.entity.identity.find(ctx.sender); // Find by unique column [...ctx.db.post.authorId.filter(authorId)]; // Filter → spread to Array [...ctx.db.entity.iter()]; // All rows → Array ctx.db.scoreRecord.id.update({ ...existing, value: 2 }); // Update (spread + override) ctx.db.scoreRecord.id.delete(recordId); // Delete by PK ``` Insert through the table accessor (`ctx.db.scoreRecord.insert(...)`). Primary-key, unique, and index accessors support lookup or mutation of existing rows, but do not have `insert(...)`. `insert(...)` returns the inserted row, including database-assigned auto-increment fields. The accessor for a primary key or index is the declared column name. For example, a primary key named `eventId` is accessed as `ctx.db.event.eventId`, not `ctx.db.event.id`. The schema value registers module exports but does not expose database rows. Pass a context into any helper that needs `ctx.db`. Note: `iter()` and `filter()` return iterators. Spread to Array for `.sort()`, `.filter()`, `.map()`. ## Lifecycle Hooks MUST be `export const`. Bare calls are silently ignored: ```typescript export const init = spacetimedb.init((ctx) => { ... }); export const onConnect = spacetimedb.clientConnected((ctx) => { ... }); export const onDisconnect = spacetimedb.clientDisconnected((ctx) => { ... }); ``` `ctx.connectionId` is `ConnectionId | null`, including in lifecycle contexts. Guard it before passing it to a helper or using it as a table key. ## Reducer Context API `ctx` is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules. Let exported callbacks infer their context type. In helpers, use `ReducerCtx<InferSchema<typeof spacetimedb>>`; do not annotate a context as `any`, because that erases table row types and can make `bigint` expressions infer as `number`. ```typescript type Ctx = ReducerCtx<InferSchema<typeof spacetimedb>>; function findRecord(ctx: Ctx, id: bigint) { return ctx.db.scoreRecord.id.find(id); } ``` ```typescript // Auth: ctx.sender is the caller's Identity if (!row.owner.equals(ctx.sender)) throw new SenderError('unauthorized'); // ctx.connectionId: the per-connection id, NULLABLE (ConnectionId | null) — null-check before use. // One Identity can hold several connections (multiple tabs/devices). if (ctx.connectionId) { /* ... */ } // Server timestamp (deterministic per reducer call) ctx.db.item.insert({ id: 0n, createdAt: ctx.timestamp }); // Deterministic RNG const f: number = ctx.random(); // [0.0, 1.0) const roll: number = ctx.random.integerInRange(1, 6); // safe JS number bounds/result, inclusive const storedRoll: bigint = BigInt(roll); // convert the result for an i64/u64 column const bytes: Uint8Array = ctx.random.fill(new Uint8Array(16)); // 2.13+: in-place shuffle; copy first to preserve the original array. const shuffled = ctx.random.shuffle([...cards]); // Client: Timestamp → Date new Date(Number(row.createdAt.microsSinceUnixEpoch / 1000n)); ``` Do not construct `Identity` values from strings (e.g. `'hex' as Identity`): serialization fails and kills the module. Identities come from `ctx.sender` or `t.identity()` columns. Construct a `ConnectionId` from its numeric representation with `new ConnectionId(value)` after importing `ConnectionId` from `spacetimedb`. Construct exact timestamps with `new Timestamp(micros)` after importing `Timestamp` from `spacetimedb`. Inclusive index ranges use `Range` from `spacetimedb/server`: ```typescript import { Timestamp } from 'spacetimedb'; import { Range } from 'spacetimedb/server'; ctx.db.shipment.deliverBy.filter(new Range( { tag: 'included', value: new Timestamp(1_000n) }, { tag: 'included', value: new Timestamp(2_000n) }, )); ``` ## Scheduled Tables The reducer or procedure referenced by a table's `scheduled` option must be exported. ```typescript import { ScheduleAt } from 'spacetimedb'; // ScheduleAt comes from the root package const tickTimer = table({ name: 'tick_timer', }, { scheduledId: t.u64().primaryKey().autoInc(), scheduledAt: t.scheduleAt(), }); export const tick = spacetimedb.reducer( { onSchedule: tickTimer }, { timer: tickTimer.rowType }, (ctx, { timer }) => { /* a one-time row is deleted after this runs; an interval row stays */ } ); // `onSchedule` also works for scheduled procedures whose return type is `t.unit()`. // Legacy table-side scheduling, `scheduled: (): any => tick`, still works but is not // recommended for new code because it forces a forward reference. // One-time: ScheduleAt.time(ctx.timestamp.microsSinceUnixEpoch + delayMicros) // Repeating: ScheduleAt.interval(60_000_000n) // Read time back from a scheduleAt value (tagged union): // const micros = at.tag === 'time' ? at.value : at.value.microsSinceUnixEpoch; // bigint ``` To let only the scheduler run a callback, reject calls where `!ctx.sender.isEqual(ctx.databaseIdentity)`. ## Submodules Import the library's full module-namespace object, not just its default schema: ```typescript import * as authLib from 'auth_lib'; const spacetimedb = schema({ scoreRecord, myAuth: { name: 'auth_data', module: authLib }, }); export default spacetimedb; ``` `myAuth` is the accessor (`ctx.db.myAuth.<table>`); `auth_data` is the canonical namespace used in SQL and wire names. Without an explicit `name`, the root's case policy derives it (`myAuth` → `my_auth` by default). Pin `name` when renaming an accessor: changing the canonical namespace drops its tables and their data. The library keeps its own flat `ctx.db` accessors. To call a library function directly, pass `ctx.as.myAuth`; inside `withTx`, use `tx.as.myAuth`. Nested TypeScript submodules are supported. Lifecycle reducers and nonempty environment declarations belong in the root. Submodule routers are ignored; register delegated handlers on the root router. Composition is not cross-language. Official reusable libraries include `@spacetimedb/cron`, `@spacetimedb/retry`, `@spacetimedb/rate-limit`, and `@spacetimedb/crypto`. Their registration shapes differ: `@spacetimedb/rate-limit/submodule` is mountable, cron/retry expose factories, and crypto exports helpers. Consult the chosen package's README/exports rather than assuming every package root is a mountable module. ## Environment Variables ```typescript const spacetimedb = schema({ scoreRecord }, { env: { API_KEY: t.string(), MODE: t.enum('Mode', ['development', 'production']), LOG_LEVEL: t.string().optional(), }, }); // In a root reducer/procedure/view/handler: // ctx.env.API_KEY: string; ctx.env.LOG_LEVEL: string | undefined
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen