一键导入
spacetimedb-typescript
Build TypeScript clients for SpacetimeDB. Use when connecting to SpacetimeDB from web apps, Node.js, Deno, Bun, or other JavaScript runtimes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Build TypeScript clients for SpacetimeDB. Use when connecting to SpacetimeDB from web apps, Node.js, Deno, Bun, or other JavaScript runtimes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
SpacetimeDB CLI reference for initializing projects, building modules, publishing databases, querying data, and managing servers
Understand SpacetimeDB architecture and core concepts. Use when learning SpacetimeDB or making architectural decisions.
| name | spacetimedb-typescript |
| description | Build TypeScript clients for SpacetimeDB. Use when connecting to SpacetimeDB from web apps, Node.js, Deno, Bun, or other JavaScript runtimes. |
| license | Apache-2.0 |
| metadata | {"author":"clockworklabs","version":"2.0"} |
Build real-time TypeScript clients that connect directly to SpacetimeDB modules. The SDK provides type-safe database access, automatic synchronization, and reactive updates for web apps, Node.js, Deno, Bun, and other JavaScript runtimes.
These APIs DO NOT EXIST. LLMs frequently hallucinate them.
// WRONG PACKAGE — does not exist
import { SpacetimeDBClient } from "@clockworklabs/spacetimedb-sdk";
// WRONG — these methods don't exist
SpacetimeDBClient.connect(...);
SpacetimeDBClient.call("reducer_name", [...]);
connection.call("reducer_name", [arg1, arg2]);
// WRONG — positional reducer arguments
conn.reducers.doSomething("value"); // WRONG!
// WRONG — old 1.0 patterns
spacetimedb.reducer('reducer_name', params, fn); // Use export const name = spacetimedb.reducer(params, fn)
schema(myTable); // Use schema({ myTable })
schema(t1, t2, t3); // Use schema({ t1, t2, t3 })
scheduled: 'run_cleanup' // Use scheduled: () => run_cleanup
.withModuleName('db') // Use .withDatabaseName('db') (2.0)
setReducerFlags.x('NoSuccessNotify') // Removed in 2.0
// CORRECT IMPORTS
import { DbConnection, tables } from './module_bindings'; // Generated!
import { SpacetimeDBProvider, useTable } from 'spacetimedb/react';
import { Identity } from 'spacetimedb';
// CORRECT REDUCER CALLS — object syntax, not positional!
conn.reducers.doSomething({ value: 'test' });
conn.reducers.updateItem({ itemId: 1n, newValue: 42 });
// CORRECT DATA ACCESS — useTable returns [rows, isReady]
const [items, isReady] = useTable(tables.item);
useItems(), useData() — use useTable(tables.tableName)spacetimedb, spacetimedb/react, ./module_bindings| Wrong | Right | Error |
|---|---|---|
Missing package.json | Create package.json | "could not detect language" |
Missing tsconfig.json | Create tsconfig.json | "TsconfigNotFound" |
Entrypoint not at src/index.ts | Use src/index.ts | Module won't bundle |
indexes in COLUMNS (2nd arg) | indexes in OPTIONS (1st arg) of table() | "reading 'tag'" error |
Index without algorithm | algorithm: 'btree' | "reading 'tag'" error |
filter({ ownerId }) | filter(ownerId) | "does not exist in type 'Range'" |
.filter() on unique column | .find() on unique column | TypeError |
insert({ ...without id }) | insert({ id: 0n, ... }) | "Property 'id' is missing" |
const id = table.insert(...) | const row = table.insert(...) | .insert() returns ROW, not ID |
.unique() + explicit index | Just use .unique() | "name is used for multiple entities" |
| Import spacetimedb from index.ts | Import from schema.ts | "Cannot access before initialization" |
Incorrect multi-column .filter() range shape | Match index prefix/tuple shape | Empty results or range/type errors |
.iter() in views | Use index lookups only | Views can't scan tables |
ctx.db in procedures | ctx.withTx(tx => tx.db...) | Procedures need explicit transactions |
| Wrong | Right | Error |
|---|---|---|
Inline connectionBuilder | useMemo(() => ..., []) | Reconnects every render |
const rows = useTable(table) | const [rows, isReady] = useTable(table) | Tuple destructuring |
| Optimistic UI updates | Let subscriptions drive state | Desync issues |
<SpacetimeDBProvider builder={...}> | connectionBuilder={...} | Wrong prop name |
schema({ table }) — use a single tables object; optional module settings are allowed as a second argumentexport const name = spacetimedb.reducer(params, fn); never reducer('name', ...){ param: 'value' } not positional argsDbConnection from ./module_bindings — not from spacetimedbspacetime generatetable()0n, 1n, not 0, 1.iter() causes severe performance issuesctx.withTx() — ctx.db doesn't exist in procedures{ tag: 'variant', value: payload } not { variant: payload }.withDatabaseName() — not .withModuleName() (2.0)npm install spacetimedb
For Node.js environments without native fetch/WebSocket support, install undici.
spacetime generate --lang typescript --out-dir ./src/module_bindings --module-path ./server
import { DbConnection } from './module_bindings';
const connection = DbConnection.builder()
.withUri('ws://localhost:3000')
.withDatabaseName('my_database')
.withToken(localStorage.getItem('spacetimedb_token') ?? undefined)
.onConnect((conn, identity, token) => {
// identity: your unique Identity for this database
console.log('Connected as:', identity.toHexString());
// Save token for reconnection (preserves identity across sessions)
localStorage.setItem('spacetimedb_token', token);
conn.subscriptionBuilder()
.onApplied(() => console.log('Cache ready'))
.subscribe('SELECT * FROM player');
})
.onDisconnect((ctx) => console.log('Disconnected'))
.onConnectError((ctx, error) => console.error('Connection failed:', error))
.build();
// Basic subscription
connection.subscriptionBuilder()
.onApplied((ctx) => console.log('Cache ready'))
.subscribe('SELECT * FROM player');
// Multiple queries
connection.subscriptionBuilder()
.subscribe(['SELECT * FROM player', 'SELECT * FROM game_state']);
// Subscribe to all tables (development only — cannot mix with Subscribe)
connection.subscriptionBuilder().subscribeToAllTables();
// Subscription handle for later unsubscribe
const handle = connection.subscriptionBuilder()
.onApplied(() => console.log('Subscribed'))
.subscribe('SELECT * FROM player');
handle.unsubscribeThen(() => console.log('Unsubscribed'));
for (const player of connection.db.player.iter()) { console.log(player.name); }
const players = Array.from(connection.db.player.iter());
const count = connection.db.player.count();
const player = connection.db.player.id.find(42n);
connection.db.player.onInsert((ctx, player) => console.log('New:', player.name));
connection.db.player.onDelete((ctx, player) => console.log('Left:', player.name));
connection.db.player.onUpdate((ctx, old, new_) => console.log(`${old.score} -> ${new_.score}`));
CRITICAL: Use object syntax, not positional arguments.
connection.reducers.createPlayer({ name: 'Alice', location: { x: 0, y: 0 } });
export const do_something = spacetimedb.reducer(...)conn.reducers.doSomething({ ... })identity and token are provided in the onConnect callback (see Client Connection above)identity.toHexString() for display or logging.withToken() for anonymous connection — server assigns a new identityonConnectConnection-level errors (.onConnectError, .onDisconnect) are shown in the Client Connection example above.
// Subscription error
connection.subscriptionBuilder()
.onApplied(() => console.log('Subscribed'))
.onError((ctx) => console.error('Subscription error:', ctx.event))
.subscribe('SELECT * FROM player');
import { schema, table, t } from 'spacetimedb/server';
export const Task = table({
name: 'task',
public: true,
indexes: [{ name: 'task_owner_id', algorithm: 'btree', columns: ['ownerId'] }]
}, {
id: t.u64().primaryKey().autoInc(),
ownerId: t.identity(),
title: t.string(),
createdAt: t.timestamp(),
});
t.identity() // User identity
t.u64() // Unsigned 64-bit integer (use for IDs)
t.string() // Text
t.bool() // Boolean
t.timestamp() // Timestamp
t.scheduleAt() // For scheduled tables only
t.object('Name', {}) // Product types (nested objects)
t.enum('Name', {}) // Sum types (tagged unions)
t.string().optional() // Nullable
BigInt syntax: All
u64/i64fields use0n,1n, not0,1.
const spacetimedb = schema({ Task, Player });
export default spacetimedb;
Name comes from the export — NOT from a string argument.
import spacetimedb from './schema';
import { t, SenderError } from 'spacetimedb/server';
export const create_task = spacetimedb.reducer(
{ title: t.string() },
(ctx, { title }) => {
if (!title) throw new SenderError('title required');
ctx.db.task.insert({ id: 0n, ownerId: ctx.sender, title, createdAt: ctx.timestamp });
}
);
const existing = ctx.db.task.id.find(taskId);
if (!existing) throw new SenderError('Task not found');
ctx.db.task.id.update({ ...existing, title: newTitle, updatedAt: ctx.timestamp });
spacetimedb.clientConnected((ctx) => { /* ctx.sender is the connecting identity */ });
spacetimedb.clientDisconnected((ctx) => { /* clean up */ });
Reducer callbacks are removed in 2.0. Use event tables + onInsert instead.
export const DamageEvent = table(
{ name: 'damage_event', public: true, event: true },
{ target: t.identity(), amount: t.u32() }
);
export const deal_damage = spacetimedb.reducer(
{ target: t.identity(), amount: t.u32() },
(ctx, { target, amount }) => {
ctx.db.damageEvent.insert({ target, amount });
}
);
Client subscribes and uses onInsert:
conn.db.damageEvent.onInsert((ctx, evt) => {
playDamageAnimation(evt.target, evt.amount);
});
Event tables must be subscribed explicitly — they are excluded from subscribeToAllTables().
// ViewContext — has ctx.sender, result varies per user
spacetimedb.view({ name: 'my_items', public: true }, t.array(Item.rowType), (ctx) => {
return [...ctx.db.item.by_owner.filter(ctx.sender)];
});
// AnonymousViewContext — no ctx.sender, same result for everyone (better perf)
spacetimedb.anonymousView({ name: 'leaderboard', public: true }, t.array(Player.rowType), (ctx) => {
return ctx.from.player.where(p => p.score.gt(1000));
});
Views can only use index lookups — .iter() is NOT allowed.
export const CleanupJob = table({
name: 'cleanup_job',
scheduled: () => run_cleanup // function returning the exported reducer
}, {
scheduledId: t.u64().primaryKey().autoInc(),
scheduledAt: t.scheduleAt(),
targetId: t.u64(),
});
export const run_cleanup = spacetimedb.reducer(
{ arg: CleanupJob.rowType },
(ctx, { arg }) => { /* arg.scheduledId, arg.targetId available */ }
);
// Schedule a job
import { ScheduleAt } from 'spacetimedb';
ctx.db.cleanupJob.insert({
scheduledId: 0n,
scheduledAt: ScheduleAt.time(ctx.timestamp.microsSinceUnixEpoch + 60_000_000n),
targetId: someId
});
// ScheduleAt is a tagged union on the client
// { tag: 'Time', value: Timestamp } or { tag: 'Interval', value: TimeDuration }
const schedule = row.scheduledAt;
if (schedule.tag === 'Time') {
const date = new Date(Number(schedule.value.microsSinceUnixEpoch / 1000n));
}
ctx.db.item.insert({ id: 0n, createdAt: ctx.timestamp });
const future = ctx.timestamp.microsSinceUnixEpoch + 300_000_000n;
// Timestamps are objects with BigInt, not numbers
const date = new Date(Number(row.createdAt.microsSinceUnixEpoch / 1000n));
export const fetch_data = spacetimedb.procedure(
{ url: t.string() }, t.string(),
(ctx, { url }) => {
const response = ctx.http.fetch(url);
ctx.withTx(tx => { tx.db.myTable.insert({ id: 0n, content: response.text() }); });
return response.text();
}
);
Procedures don't have ctx.db — use ctx.withTx(tx => tx.db...).
import { useMemo } from 'react';
import { SpacetimeDBProvider, useTable } from 'spacetimedb/react';
import { DbConnection, tables } from './module_bindings';
function Root() {
const connectionBuilder = useMemo(() =>
DbConnection.builder()
.withUri('ws://localhost:3000')
.withDatabaseName('my_game')
.withToken(localStorage.getItem('auth_token') || undefined)
.onConnect((conn, identity, token) => {
localStorage.setItem('auth_token', token);
conn.subscriptionBuilder().subscribe(tables.player);
}),
[]
);
return (
<SpacetimeDBProvider connectionBuilder={connectionBuilder}>
<App />
</SpacetimeDBProvider>
);
}
function PlayerList() {
const [players, isReady] = useTable(tables.player);
if (!isReady) return <div>Loading...</div>;
return <ul>{players.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
backend/spacetimedb/)src/schema.ts -> Tables, export spacetimedb
src/index.ts -> Reducers, lifecycle, import schema
package.json -> { "type": "module", "dependencies": { "spacetimedb": "^2.0.0" } }
tsconfig.json -> Standard config
client/)src/module_bindings/ -> Generated (spacetime generate)
src/main.tsx -> Provider, connection setup
src/App.tsx -> UI components
spacetime start
spacetime publish <module-name> --module-path <backend-dir>
spacetime publish <module-name> --clear-database -y --module-path <backend-dir>
spacetime generate --lang typescript --out-dir <client>/src/module_bindings --module-path <backend-dir>
spacetime logs <module-name>