From 2c0c7efc8dfd2da04d495d4c741b9cac066a89c0 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Wed, 24 Jun 2026 09:04:50 -0400
Subject: [PATCH] Single SDK reference: always use the official customer skills
(typescript-server/client SKILL.md). Remove focused/fork alternate refs and
STDB_SDK_REF switch
---
tools/llm-sequential-upgrade/DEVELOP.md | 2 +-
.../backends/spacetime-sdk-focused.md | 164 -----------
.../backends/spacetime-sdk-rules.md | 258 ------------------
tools/llm-sequential-upgrade/run.sh | 31 +--
4 files changed, 13 insertions(+), 442 deletions(-)
delete mode 100644 tools/llm-sequential-upgrade/backends/spacetime-sdk-focused.md
delete mode 100644 tools/llm-sequential-upgrade/backends/spacetime-sdk-rules.md
diff --git a/tools/llm-sequential-upgrade/DEVELOP.md b/tools/llm-sequential-upgrade/DEVELOP.md
index e9e19db970..18251388bb 100644
--- a/tools/llm-sequential-upgrade/DEVELOP.md
+++ b/tools/llm-sequential-upgrade/DEVELOP.md
@@ -269,8 +269,8 @@ llm-sequential-upgrade/
parse-telemetry.mjs # Telemetry → COST_REPORT.md
backends/
spacetime.md # SpacetimeDB-specific phases
- spacetime-sdk-rules.md # SpacetimeDB SDK patterns
spacetime-templates.md # Code templates
+ # SDK reference = the official skills/typescript-{server,client}/SKILL.md
postgres.md # PostgreSQL-specific phases
mongodb.md # MongoDB-specific phases
test-plans/
diff --git a/tools/llm-sequential-upgrade/backends/spacetime-sdk-focused.md b/tools/llm-sequential-upgrade/backends/spacetime-sdk-focused.md
deleted file mode 100644
index fc0402bee4..0000000000
--- a/tools/llm-sequential-upgrade/backends/spacetime-sdk-focused.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# SpacetimeDB TypeScript SDK Reference (focused)
-
-Lean, chat-app-focused SDK reference for the SpacetimeDB **2.x** TypeScript SDK —
-parity in scope/prescriptiveness with the PostgreSQL/MongoDB backend files. Covers only
-what this app needs; omits SDK features the app doesn't use.
-
-## Imports
-
-```typescript
-import { schema, table, t } from 'spacetimedb/server';
-import { SenderError } from 'spacetimedb/server';
-import { ScheduleAt } from 'spacetimedb'; // scheduled tables only
-```
-
-## Tables
-
-`table(OPTIONS, COLUMNS)` — two arguments. `name` is snake_case.
-
-```typescript
-const user = table(
- { name: 'user', public: true },
- { identity: t.identity().primaryKey(), name: t.string(), online: t.bool() }
-);
-```
-
-Options: `name` (snake_case), `public: true`, `scheduled: (): any => reducerRef`, `indexes: [...]`.
-`ctx.db` accessors use the **camelCase** form of the table's `name`.
-
-## Column Types
-
-`t.u64()`/`t.i64()` → bigint (use `0n` literals) · `t.u32()`/`t.i32()`/`t.f64()` → number ·
-`t.bool()` · `t.string()` · `t.identity()` → Identity · `t.timestamp()` → Timestamp ·
-`t.scheduleAt()` → ScheduleAt · optional: `t.option(t.string())`
-
-Modifiers: `.primaryKey()` `.autoInc()` `.unique()` `.index('btree')`
-
-## Indexes
-
-```typescript
-// single-column inline (preferred):
-authorId: t.u64().index('btree'), // → ctx.db.post.authorId.filter(authorId)
-// multi-column (named):
-indexes: [{ accessor: 'by_room_user', algorithm: 'btree', columns: ['roomId', 'userIdentity'] }]
-// → ctx.db.draft.by_room_user.filter([roomId, identity])
-```
-
-## Schema Export
-
-```typescript
-const spacetimedb = schema({ user, room, message }); // ONE object, not spread args
-export default spacetimedb;
-```
-
-## Reducers
-
-Export name becomes the reducer name.
-
-```typescript
-export const sendMessage = spacetimedb.reducer(
- { roomId: t.u64(), text: t.string() },
- (ctx, { roomId, text }) => {
- ctx.db.message.insert({ id: 0n, roomId, sender: ctx.sender, text, sentAt: ctx.timestamp });
- }
-);
-// no arguments — just the callback:
-export const reset = spacetimedb.reducer((ctx) => { ... });
-```
-
-## DB Operations
-
-```typescript
-ctx.db.message.insert({ id: 0n, ... }); // insert (0n for autoInc PK)
-ctx.db.message.id.find(msgId); // by PK → row | null
-ctx.db.user.identity.find(ctx.sender); // by unique column
-[...ctx.db.message.roomId.filter(roomId)]; // filter → spread to Array
-[...ctx.db.message.iter()]; // all rows → Array
-ctx.db.message.id.update({ ...existing, text }); // update (spread + override)
-ctx.db.message.id.delete(msgId);
-```
-
-`iter()`/`filter()` return iterators — spread to Array for `.sort()`/`.map()`/`.filter()`.
-
-## 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) => { ... });
-```
-
-## Reducer Context — identity, time, randomness
-
-Inside a reducer, get sender / time / randomness **only** from `ctx`.
-**Standard-library clocks and random sources (`Date.now()`, `Math.random()`) are NOT available
-in modules** — use `ctx` instead.
-
-```typescript
-ctx.sender // caller Identity
-if (!row.owner.equals(ctx.sender)) throw new SenderError('unauthorized');
-ctx.timestamp // deterministic server time
-ctx.db.message.insert({ ..., createdAt: ctx.timestamp });
-ctx.random(); // [0.0, 1.0)
-ctx.random.integerInRange(1, 6); // inclusive
-// Client: Timestamp → Date
-new Date(Number(row.createdAt.microsSinceUnixEpoch / 1000n));
-```
-
-## Scheduled Tables (timers)
-
-```typescript
-const tickTimer = table(
- { name: 'tick_timer', scheduled: (): any => tick }, // (): any => breaks circular dep
- { scheduledId: t.u64().primaryKey().autoInc(), scheduledAt: t.scheduleAt() }
-);
-export const tick = spacetimedb.reducer(
- { timer: tickTimer.rowType },
- (ctx, { timer }) => { /* timer row auto-deleted after this runs */ }
-);
-// one-shot: ScheduleAt.time(ctx.timestamp.microsSinceUnixEpoch + delayMicros)
-// repeating: ScheduleAt.interval(60_000_000n)
-```
-
-## React Client — main.tsx
-
-```typescript
-import { SpacetimeDBProvider } from 'spacetimedb/react';
-import { DbConnection } from './module_bindings';
-import { MODULE_NAME, SPACETIMEDB_URI } from './config';
-
-const connectionBuilder = useMemo(() =>
- DbConnection.builder()
- .withUri(SPACETIMEDB_URI)
- .withDatabaseName(MODULE_NAME)
- .withToken(localStorage.getItem('auth_token') || undefined),
- []);
-//
-```
-
-## React Client — App.tsx
-
-```typescript
-import { useTable, useSpacetimeDB } from 'spacetimedb/react';
-import { DbConnection, tables } from './module_bindings';
-
-const { isActive, identity: myIdentity, token, getConnection } = useSpacetimeDB();
-const conn = getConnection() as DbConnection | null;
-
-useEffect(() => { if (token) localStorage.setItem('auth_token', token); }, [token]);
-
-useEffect(() => { // subscribe once connected
- if (!conn || !isActive) return;
- conn.subscriptionBuilder()
- .onApplied(() => setReady(true))
- .subscribe([tables.user, tables.message]); // typed tables (raw SQL strings also accepted)
-}, [conn, isActive]);
-
-const [users] = useTable(tables.user); // reactive rows; returns [rows, isReady]
-const [messages] = useTable(tables.message);
-
-conn?.reducers.sendMessage({ roomId, text }); // call reducers with object args
-const isMe = row.owner.toHexString() === myIdentity?.toHexString();
-```
diff --git a/tools/llm-sequential-upgrade/backends/spacetime-sdk-rules.md b/tools/llm-sequential-upgrade/backends/spacetime-sdk-rules.md
deleted file mode 100644
index 337af9269a..0000000000
--- a/tools/llm-sequential-upgrade/backends/spacetime-sdk-rules.md
+++ /dev/null
@@ -1,258 +0,0 @@
-# SpacetimeDB TypeScript SDK Reference
-
-## Imports
-
-```typescript
-import { schema, table, t } from 'spacetimedb/server';
-import { SenderError } from 'spacetimedb/server';
-import { ScheduleAt } from 'spacetimedb'; // for scheduled tables only
-```
-
-## Tables
-
-`table(OPTIONS, COLUMNS)` — 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, required), `public: true`, `event: true`, `scheduled: (): any => reducerRef`, `indexes: [...]`
-
-`ctx.db` accessors use the JS variable name (camelCase), not the SQL name.
-
-## Column Types
-
-| 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.timestamp()` | Timestamp | |
-| `t.scheduleAt()` | ScheduleAt | |
-
-Modifiers: `.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:
-
-```typescript
-// Inline (preferred):
-authorId: t.u64().index('btree'),
-// Access: ctx.db.post.authorId.filter(authorId);
-
-// Multi-column (named):
-indexes: [{ accessor: 'by_cat_sev', algorithm: 'btree', columns: ['category', 'severity'] }]
-```
-
-## Schema Export
-
-```typescript
-const spacetimedb = schema({ entity, record }); // ONE object, not spread args
-export default spacetimedb;
-```
-
-## Reducers
-
-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) => { ... });
-```
-
-## DB Operations
-
-```typescript
-ctx.db.entity.insert({ id: 0n, name: 'Sample' }); // Insert (0n for autoInc)
-ctx.db.entity.id.find(entityId); // Find by PK → row | null
-ctx.db.entity.identity.find(ctx.sender); // Find by unique column
-[...ctx.db.item.authorId.filter(authorId)]; // Filter → spread to Array
-[...ctx.db.entity.iter()]; // All rows → Array
-ctx.db.entity.id.update({ ...existing, name: newName }); // Update (spread + override)
-ctx.db.entity.id.delete(entityId); // 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:
-
-```typescript
-export const init = spacetimedb.init((ctx) => { ... });
-export const onConnect = spacetimedb.clientConnected((ctx) => { ... });
-export const onDisconnect = spacetimedb.clientDisconnected((ctx) => { ... });
-```
-
-## Authentication & Timestamps
-
-```typescript
-// Auth: ctx.sender is the caller's Identity
-if (!row.owner.equals(ctx.sender)) throw new SenderError('unauthorized');
-
-// Server timestamps
-ctx.db.item.insert({ id: 0n, createdAt: ctx.timestamp });
-
-// Client: Timestamp → Date
-new Date(Number(row.createdAt.microsSinceUnixEpoch / 1000n));
-```
-
-## Scheduled Tables
-
-```typescript
-const tickTimer = table({
- name: 'tick_timer',
- scheduled: (): any => tick, // (): any => breaks circular dep
-}, {
- scheduledId: t.u64().primaryKey().autoInc(),
- scheduledAt: t.scheduleAt(),
-});
-
-export const tick = spacetimedb.reducer(
- { timer: tickTimer.rowType },
- (ctx, { timer }) => { /* timer row auto-deleted after this runs */ }
-);
-
-// One-time: ScheduleAt.time(ctx.timestamp.microsSinceUnixEpoch + delayMicros)
-// Repeating: ScheduleAt.interval(60_000_000n)
-```
-
-## React Client
-
-### main.tsx — SpacetimeDBProvider is required
-
-```typescript
-import React, { useMemo } from 'react';
-import ReactDOM from 'react-dom/client';
-import { SpacetimeDBProvider } from 'spacetimedb/react';
-import { DbConnection } from './module_bindings';
-import { MODULE_NAME, SPACETIMEDB_URI } from './config';
-import App from './App';
-
-function Root() {
- const connectionBuilder = useMemo(() =>
- DbConnection.builder()
- .withUri(SPACETIMEDB_URI)
- .withDatabaseName(MODULE_NAME)
- .withToken(localStorage.getItem('auth_token') || undefined),
- []
- );
- return (
-
-
-
- );
-}
-
-ReactDOM.createRoot(document.getElementById('root')!).render();
-```
-
-### App.tsx patterns
-
-```typescript
-import { useTable, useSpacetimeDB } from 'spacetimedb/react';
-import { DbConnection, tables } from './module_bindings';
-
-function App() {
- const { isActive, identity: myIdentity, token, getConnection } = useSpacetimeDB();
- const conn = getConnection() as DbConnection | null;
-
- // Save auth token
- useEffect(() => { if (token) localStorage.setItem('auth_token', token); }, [token]);
-
- // Subscribe when connected
- useEffect(() => {
- if (!conn || !isActive) return;
- conn.subscriptionBuilder()
- .onApplied(() => setSubscribed(true))
- .subscribe(['SELECT * FROM entity', 'SELECT * FROM record']);
- }, [conn, isActive]);
-
- // Reactive data
- const [entities] = useTable(tables.entity);
- const [records] = useTable(tables.record);
-
- // Call reducers with object syntax
- conn?.reducers.addRecord({ data });
-
- // Compare identities
- const isMe = row.owner.toHexString() === myIdentity?.toHexString();
-}
-```
-
-## Complete Example
-
-```typescript
-// schema.ts
-import { schema, table, t } from 'spacetimedb/server';
-
-const entity = table({ name: 'entity', public: true }, {
- identity: t.identity().primaryKey(),
- name: t.string(),
- active: t.bool(),
-});
-
-const record = table({ name: 'record', public: true }, {
- id: t.u64().primaryKey().autoInc(),
- owner: t.identity(),
- value: t.u32(),
- createdAt: t.timestamp(),
-});
-
-const spacetimedb = schema({ entity, record });
-export default spacetimedb;
-```
-
-```typescript
-// index.ts
-import spacetimedb from './schema';
-import { t, SenderError } from 'spacetimedb/server';
-export { default } from './schema';
-
-export const onConnect = spacetimedb.clientConnected((ctx) => {
- const existing = ctx.db.entity.identity.find(ctx.sender);
- if (existing) ctx.db.entity.identity.update({ ...existing, active: true });
-});
-
-export const onDisconnect = spacetimedb.clientDisconnected((ctx) => {
- const existing = ctx.db.entity.identity.find(ctx.sender);
- if (existing) ctx.db.entity.identity.update({ ...existing, active: false });
-});
-
-export const createEntity = spacetimedb.reducer(
- { name: t.string() },
- (ctx, { name }) => {
- if (ctx.db.entity.identity.find(ctx.sender)) throw new SenderError('already exists');
- ctx.db.entity.insert({ identity: ctx.sender, name, active: true });
- }
-);
-
-export const addRecord = spacetimedb.reducer(
- { value: t.u32() },
- (ctx, { value }) => {
- if (!ctx.db.entity.identity.find(ctx.sender)) throw new SenderError('not found');
- ctx.db.record.insert({ id: 0n, owner: ctx.sender, value, createdAt: ctx.timestamp });
- }
-);
-```
diff --git a/tools/llm-sequential-upgrade/run.sh b/tools/llm-sequential-upgrade/run.sh
index b05e878652..349f4fc7ba 100644
--- a/tools/llm-sequential-upgrade/run.sh
+++ b/tools/llm-sequential-upgrade/run.sh
@@ -481,7 +481,8 @@ snapshot_inputs() {
# Backend specs (only relevant backend)
cp "$SCRIPT_DIR/backends/$BACKEND.md" "$INPUTS_DIR/backends/" 2>/dev/null || true
if [[ "$BACKEND" == "spacetime" ]]; then
- cp "$SCRIPT_DIR/backends/spacetime-sdk-rules.md" "$INPUTS_DIR/backends/" 2>/dev/null || true
+ cp "$SCRIPT_DIR/../../skills/typescript-server/SKILL.md" "$INPUTS_DIR/backends/typescript-server-SKILL.md" 2>/dev/null || true
+ cp "$SCRIPT_DIR/../../skills/typescript-client/SKILL.md" "$INPUTS_DIR/backends/typescript-client-SKILL.md" 2>/dev/null || true
cp "$SCRIPT_DIR/backends/spacetime-templates.md" "$INPUTS_DIR/backends/" 2>/dev/null || true
fi
@@ -750,7 +751,11 @@ if [[ -z "$FIX_MODE" && -z "$UPGRADE_MODE" ]]; then
elif [[ "$RULES" == "standard" ]]; then
case "$BACKEND" in
spacetime)
- cat "$SCRIPT_DIR/backends/spacetime-sdk-rules.md" > "$APP_DIR/CLAUDE.md"
+ _strip='NR==1 && /^---$/ {fm=1; next} fm && /^---$/ {fm=0; next} !fm {print}'
+ { awk "$_strip" "$SCRIPT_DIR/../../skills/typescript-server/SKILL.md"
+ echo ""; echo "---"; echo ""
+ awk "$_strip" "$SCRIPT_DIR/../../skills/typescript-client/SKILL.md"
+ } > "$APP_DIR/CLAUDE.md"
;;
mongodb)
echo "# MongoDB Backend" > "$APP_DIR/CLAUDE.md"
@@ -771,31 +776,19 @@ if [[ -z "$FIX_MODE" && -z "$UPGRADE_MODE" ]]; then
esac
echo "Assembled standard CLAUDE.md (rules=$RULES)"
else
- # guided (default) — phases + SDK reference + templates.
- # SDK reference is selectable via STDB_SDK_REF: focused (default) | skills | fork
+ # guided (default) — phases + the official customer SDK skills + templates.
if [[ "$BACKEND" == "spacetime" ]]; then
_strip_fm() { awk 'NR==1 && /^---$/ {fm=1; next} fm && /^---$/ {fm=0; next} !fm {print}' "$1"; }
- _sdk_ref="${STDB_SDK_REF:-focused}"
{
cat "$SCRIPT_DIR/backends/spacetime.md"
echo ""; echo "---"; echo ""
- case "$_sdk_ref" in
- skills)
- _strip_fm "$SCRIPT_DIR/../../skills/typescript-server/SKILL.md"
- echo ""; echo "---"; echo ""
- _strip_fm "$SCRIPT_DIR/../../skills/typescript-client/SKILL.md"
- ;;
- fork)
- cat "$SCRIPT_DIR/backends/spacetime-sdk-rules.md"
- ;;
- *) # focused (default) — lean reference, parity in scope with mongo/pg backend files
- cat "$SCRIPT_DIR/backends/spacetime-sdk-focused.md"
- ;;
- esac
+ _strip_fm "$SCRIPT_DIR/../../skills/typescript-server/SKILL.md"
+ echo ""; echo "---"; echo ""
+ _strip_fm "$SCRIPT_DIR/../../skills/typescript-client/SKILL.md"
echo ""; echo "---"; echo ""
cat "$SCRIPT_DIR/backends/spacetime-templates.md"
} > "$APP_DIR/CLAUDE.md"
- echo "Assembled guided CLAUDE.md from spacetime.md + SDK ref [$_sdk_ref] + templates"
+ echo "Assembled guided CLAUDE.md from spacetime.md + official skills + templates"
else
cp "$SCRIPT_DIR/backends/$BACKEND.md" "$APP_DIR/CLAUDE.md"
echo "Copied backends/$BACKEND.md → app CLAUDE.md"