Skip to content
Development
Skill

/typescript-server

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

From plugin
spacetimedb
25k22 skills1 MCP
Install
$ npx -y skills add clockworklabs/spacetimedb --skill typescript-server --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/typescript-server

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

typescript-server.SKILL.md
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`:

import { schema, table, t } from 'spacetimedb/server';

const score_record = table(
  { name: 'score_record', public: true },
  {
    id: t.u64().primaryKey().autoInc(),
    owner: t.identity(),
    value: t.u32(),
  }
);

const spacetimedb = schema({ score_record });  // ONE object, not spread args
export default spacetimedb;

export const addRecord = spacetimedb.reducer(
  { value: t.u32() },
  (ctx, { value }) => {
    ctx.db.score_record.insert({ id: 0n, owner: ctx.sender, value });
  }
);

Imports

`spacetimedb/server` is the only import path for server modules:

import { schema, table, t } from 'spacetimedb/server';
import { SenderError } from 'spacetimedb/server';
import { ScheduleAt } from 'spacetimedb';        // for scheduled tables only

Tables

`table(OPTIONS, COLUMNS)` takes two arguments. The `name` field MUST be snake_case:

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`, `scheduled: (): any => reducerRef`, `indexes: [...]`

`ctx.db` accessors are the keys passed to `schema({...})`, verbatim: `schema({ score_record })` → `ctx.db.score_record`. Use snake_case keys matching the table `name`. Client codegen converts case; server `ctx.db` does not.

Column Types

Every column is a `t` builder value:

| Builder | JS type | Notes | |---------|---------|-------| | `t.u64()` | bigint | Use `0n` literals | | `t.i64()` | bigint | Use `0n` literals | | `t.u32()` / `t.i32()` | number | | | `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 (complete set): `.primaryKey()`, `.autoInc()`, `.unique()`, `.index('btree')`

Optional columns: `nickname: t.option(t.string())`

Indexes

Prefer inline `.index('btree')` for single-column. Use named indexes only for multi-column:

// Inline (preferred for single-column):
authorId: t.u64().index('btree'),
// Access: ctx.db.post.authorId.filter(authorId);

// Multi-column (named):
indexes: [{ accessor: 'by_group_user', algorithm: 'btree', columns: ['groupId', 'userId'] }]
// Access: ctx.db.membership.by_group_user.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:

// 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:

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

ctx.db.score_record.insert({ id: 0n, owner: ctx.sender, value: 1 });  // Insert (0n for autoInc)
ctx.db.score_record.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.score_record.id.update({ ...existing, value: 2 });  // Update (spread + override)
ctx.db.score_record.id.delete(recordId);                   // Delete by PK

Note: `iter()` and `filter()` return iterators. Spread to Array for `.sort()`, `.filter()`, `.map()`.

Lifecycle Hooks

MUST be `export const`. Bare calls are silently ignored:

export const init = spacetimedb.init((ctx) => { ... });
export const onConnect = spacetimedb.clientConnected((ctx) => { ... });
export const onDisconnect = spacetimedb.clientDisconnected((ctx) => { ... });

Reducer Context API

`ctx` is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules. In helpers, type it as `ReducerCtx<InferSchema<typeof spacetimedb>>`.

// 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);    // inclusive
const bytes: Uint8Array = ctx.random.fill(new Uint8Array(1
Read more
Ships withspacetimedb

Development at the speed of light

Get the whole plugin
Stats
24,990
Stars
1,035
Forks
Active
Maintenance
Rust
Language
1h ago
Last commit
3y ago
Created

Repo: clockworklabs/spacetimedb