| name | deepspace |
| version | 0.1.2 |
| description | Use when building or maintaining real-time collaborative apps with the DeepSpace SDK on Cloudflare Workers — scaffolding new apps, adding features, debugging a `worker.ts` that imports from `deepspace` / `deepspace/worker` or uses `RecordRoom`, `__DO_MANIFEST__`, or `npx deepspace`. Also use when the user mentions DeepSpace or app.space, or asks for anything involving real-time sync, multiplayer state, live cursors / presence, whiteboards or canvases, collaborative text editing (Yjs), channel-based chat, per-role permissions (RBAC), Durable Object rooms, Stripe-backed subscriptions / paywalls / one-time products / tips / refunds, or one-package deploy to `.app.space` — even if they don't name DeepSpace explicitly.
|
Build real-time collaborative apps on Cloudflare Workers in one package: SQLite-backed Durable Objects, RBAC, WebSocket subscriptions, Better Auth. Scaffolds with sensible defaults — generouted file-based routing, shadcn-style primitives, Vite + Tailwind v4. Apps deploy to <name>.app.space.
Quickstart — the development lifecycle
CLI commands, in order. Each step is rerunnable. dev and test rewrite only the 9 SDK-managed keys in .dev.vars (auth + worker URLs + owner JWT + per-app identity token + debug flag); anything else you add — third-party tokens, custom flags — is preserved verbatim across runs and ships to prod as secret_text bindings on deploy. See "Login, test, deploy" for the contract.
npm create deepspace@latest <app-name>
cd <app-name>
npx deepspace login
npx deepspace dev
npx deepspace dev --port 5180
npx deepspace dev --prod
npx deepspace kill
npx deepspace kill --port 5180
npx deepspace kill --all
npx deepspace test-accounts list
npx deepspace test-accounts create --email <…@deepspace.test> --password <p> --name <n>
npx deepspace test
npx deepspace test e2e
npx deepspace test unit
npx deepspace screenshot http://localhost:5173/ out.png
npx deepspace screenshot http://localhost:5173/ out.png --full-page --wait-for-timeout 500
npx deepspace add --list
npx deepspace add --info <name>
npx deepspace add <feature>
npx deepspace integrations list
npx deepspace integrations info openai/chat-completion
npx deepspace invoke openai/chat-completion --body '{...}'
npx deepspace invoke openai/chat-completion --body-file -
npx deepspace deploy
npx deepspace domain search <query>
npx deepspace domain buy <domain>
npx deepspace domain list
npx deepspace domain attach <domain> --app <name>
npx deepspace library publish [--name "<title>"] [--description "<short>"] [--category <cat>]
npx deepspace library unpublish <handle>
npx deepspace managed-repos list
npx deepspace managed-repos create <app-name>
npx deepspace managed-repos token <repo-id>
npx deepspace managed-repos delete <repo-id> --yes
Login state is shared across all apps on the machine. One deepspace login covers dev, test-accounts, and deploy for any app. Probe login state with npx deepspace whoami (--json for agents); don't stat ~/.deepspace/session — that's a CLI implementation detail. Re-login only when whoami reports not-signed-in or the session has expired. See "Login, test, deploy" below for the full contract.
Two imports
import { RecordProvider, RecordScope, useQuery, useMutations, useAuth } from 'deepspace'
import { RecordRoom, verifyJwt, CHANNELS_SCHEMA } from 'deepspace/worker'
Two more entry points exist for narrower use cases: 'deepspace/server' for worker-side helpers backed by the platform (payments helpers + captureScreenshot — see references/payments.md / references/sdk-reference.md), and 'deepspace/testing' for the Playwright multi-user fixture (see references/testing.md).
Project layout
The scaffold generates a Vite + Cloudflare-Worker app. Files you'll touch most often:
| Path | Purpose |
|---|
worker.ts | Hono app worker; __DO_MANIFEST__ declares 6 DO classes (AppRecordRoom, AppYjsRoom, AppCanvasRoom, AppPresenceRoom, AppCronRoom, AppJobRoom). AI chat routes live in src/ai/chat-routes.ts (registered via registerAiChatRoutes(app, resolveAuth)) — edit there, not here. Edit worker.ts itself when adding new DO classes / WebSocket routes, customizing AppRecordRoom options, adding custom HTTP routes, or wiring cross-app proxies. |
wrangler.toml | Cloudflare config. name becomes the <name>.app.space subdomain — must match ^[a-z0-9](?:-?[a-z0-9])+$ (2-63 chars, lowercase). dev and deploy fail-fast on a non-canonical name now (was silent canonicalization in earlier SDKs) — fix the field rather than ignoring the error. run_worker_first = ["/api/*", "/ws/*", "/internal/*", "/v1/*", "/_deepspace/*"] — /v1/* is included so OpenAI-compatible routes mounted in worker.ts resolve before the SPA fallback; /_deepspace/* is the same-origin browser proxy the subscriptions / charges hooks call (don't strip it). Apps can append their own prefixes (/oauth/*, /preview/*, /.well-known/*); the CLI strips the reserved set and forwards the rest. Declare custom Cloudflare bindings here (vectorize / r2 / kv / d1 / queue / ai / browser_rendering / hyperdrive / analytics_engine); use "auto" as the id to auto-provision. → references/bindings.md. |
src/pages/_app.tsx | Provider stack: ToastProvider → DeepSpaceAuthProvider → AuthBoot → RecordProvider → RecordScope. Extend, don't replace. |
src/pages/ | File-based routes via generouted. (protected)/ is the gated route group. |
src/schemas.ts + src/schemas/ | Collection schemas. Ships usersSchema + settingsSchema. |
src/themes.ts + src/themes.css | 15 theme presets; active one set on <html data-theme> in index.html. |
src/styles.css | Tailwind v4 entry; @theme block holds the slate baseline. |
src/nav.ts | Top-nav entries — add new pages here so Navigation.tsx picks them up. |
src/constants.ts | APP_NAME, SCOPE_ID = "app:${APP_NAME}", role re-exports. |
src/actions/index.ts | Server-action handlers. |
src/cron.ts | Scheduled tasks for AppCronRoom. |
src/jobs.ts | Background-job handlers for AppJobRoom (durable work outliving the HTTP response). |
src/integrations.ts | Per-integration billing config (developer vs user). |
src/ai/tools.ts | System prompt + tool allowlist for /api/ai/chat. Tools are not read-only by default — the scaffold ships records.create / records.update / records.delete alongside reads. Per-collection RBAC at the DO is the actual security boundary; trim the allowlist if you want a stricter assistant. |
src/ai/chat-routes.ts | Hono handlers for the 4 AI chat endpoints (POST /api/ai/chats, PATCH /api/ai/chats/:id, DELETE /api/ai/chats/:id, POST /api/ai/chat — the streaming turn). Edit to switch model/provider, change context-window compaction, or extend the tool surface. |
tests/ | Playwright smoke.spec.ts / api.spec.ts / collab.spec.ts + playwright.config.ts. |
Build a new app
Steps run in dependency order. Each links its deep-dive reference; load that reference only when you reach the step.
- Scaffold — see Quickstart.
- Schemas — define collections with
name, columns, permissions. Add to src/schemas/, register in src/schemas.ts alongside usersSchema (required — uses USERS_COLUMNS from deepspace/worker; useUser / useUsers / auth depend on a 'users' collection with this exact shape, so never rename or remove it). The scaffold also ships settingsSchema as a starter admin key/value store — keep, customize, or drop freely; no SDK feature depends on it. For messaging, also add CHANNELS_SCHEMA / MESSAGES_SCHEMA / REACTIONS_SCHEMA from deepspace/worker. → references/schemas.md
- Cross-app data sharing? — if the app needs to read/write
workspace:*, dir:*, or conv:* scopes shared across DeepSpace apps (e.g., the email-handle workspace, cross-app inbox), the scaffolded /ws/:roomId handler routes everything to the local DO and won't see shared data. Add the PLATFORM_WORKER proxy edit. → references/architecture.md § Cross-app shared scopes. Skip otherwise.
- Auth model — pick public, gated, or mixed (the default; gated routes drop into
src/pages/(protected)/). → references/auth.md
- Theme — pick a preset on
<html data-theme="..."> in index.html, update <title>/favicon. Don't ship the default slate. → references/uiux.md §2
- Pages and features — pages in
src/pages/. Ready-made features ship inside the deepspace SDK (node_modules/deepspace/features/); install with npx deepspace add <feature> (use --list to enumerate). → references/uiux.md for UI primitives.
- Tests — read
references/testing.md and apply the Step 8 checklist before extending smoke.spec.ts / api.spec.ts / collab.spec.ts. Multi-user features need a 2-user Playwright spec, not just tsc or unit tests.
- Deploy —
npx deepspace deploy. Pre-deploy checklist: home replaced, theme picked, browser-default primitives removed, toasts wired to mutations. → references/uiux.md §5
For maintenance work on an existing app, jump straight to the relevant reference.
Frontend hooks
The three core auth + data hooks. useAuth is universal (every page that has a sign-in state uses it); useQuery / useMutations show up the moment a page reads or writes a collection. For everything else (messaging, directory, R2, Yjs, presence, canvas, cron monitor, jobs, theme, env), see references/sdk-reference.md.
const { records, status } = useQuery<Item>('items', { where: { status: 'published' }, orderBy: 'createdAt' })
const { create, put, remove } = useMutations<Item>('items')
const { isSignedIn, isLoaded } = useAuth()
Each record is an envelope — { recordId, data: T, createdBy, createdAt, updatedAt }. User fields live under .data (r.data.title, never r.title). Pass r.recordId to put / remove.
put accepts Partial<T> — server merges into the existing row ({...existing, ...patch}), so send only the fields you're changing (put(id, { completed: true }), not the whole spread). create still requires the full T.
For anything beyond these three (messaging, presence, R2, Yjs, canvas, game rooms, cron, jobs, AI chat, integrations, theming, RBAC), read references/sdk-reference.md first; only then drop into node_modules/deepspace/dist/index.d.ts (frontend) or node_modules/deepspace/dist/worker.d.ts (worker) for exact type signatures. Do not guess hook names or argument shapes.
Worker-side extensions
Seven independent surfaces. Load only what you need:
- Server actions (privileged writes that bypass caller RBAC) →
references/server-actions.md
- AI chat (streamed Claude / OpenAI / Cerebras with multi-turn tool use, persistent chat history, context-window compaction) →
references/ai-chat.md
- Cron (scheduled tasks via
AppCronRoom + useCronMonitor) → references/cron.md
- Jobs (durable background work via
AppJobRoom + useJobs — anything that needs to outlive the HTTP response: AI generation, exports, renders) → references/jobs.md
- Payments (Stripe-backed subscriptions, paywalls, one-time products, tips, refunds, cancellation — never hand-roll Stripe; use
useSubscription / useCheckout / requireSubscription) → references/payments.md
- Game rooms (turn/tick game loops, lobby, host-advanced phases,
useGameRoom + GameRoom DO) → references/sdk-reference.md § Real-time collab + § Game rooms
- Custom bindings & metering (Vectorize, AI, R2, KV, D1, Queues, Hyperdrive;
"auto" autoprovisioning; runMigrations for D1; meterAi / meterVectorize per-tenant rollup via the auto-attached USAGE_EVENTS AE dataset) → references/bindings.md
Skip all of these for apps that only need client hooks and integration.post(...).
Integrations
Call external APIs through the api-worker proxy:
import { integration } from 'deepspace'
const result = await integration.post('openweathermap/geocoding', { q: city })
Endpoint names are two segments: <integration>/<endpoint>. Don't guess — names like geocode-city or weather-forecast aren't real and return 404 at runtime. Verify with npx deepspace integrations list / info <ep> (the agent-friendly catalog + schema source — no auth, no app dir required).
Default billing is owner-pays, so auth-gate any UI that calls integration.post(...) to keep anonymous bots from billing the owner. → references/integrations.md for billing modes, the discovery CLI, testing rules, and the response-envelope gotchas.
Login, test, deploy
Login (npx deepspace login)
npx deepspace whoami is the canonical login-state probe (add --json from agents). It refreshes the JWT in the same call path that dev / test / deploy use — if whoami succeeds, those will too. On failure: stderr Not logged in. Run `deepspace login`.`, exit 1.
dev / test / deploy themselves require a valid session at ~/.deepspace/session; if absent, they exit with Not logged in. Run `deepspace login` first.` Four hard rules:
- Pause and tell the user. Login opens a browser tab (GitHub/Google OAuth) on their machine and polls up to 10 minutes. They need to be at the keyboard. There is no agent-runnable bypass — never ask the user for their password.
- Run interactive login without an artificial time bound. Do not wrap in
timeout N, sleep N && kill, or any cutoff — those terminate OAuth before completion and leave no session. (timeout isn't installed on macOS by default; don't reach for it.) Run in foreground or a true background process.
- After login completes, verify with
npx deepspace whoami before retrying dev / test / deploy. Re-running them while login is still polling produces the same error — that's expected order, not a bug.
- Never copy
.dev.vars from a sibling app. APP_OWNER_JWT is minted against that app's wrangler name; borrowing causes silent auth mismatches.
Test (npx deepspace test)
Tests are the primary way to verify code changes. The scaffolded specs (smoke.spec.ts / api.spec.ts / collab.spec.ts) are starting points — extend them per the Step 8 checklist in references/testing.md. The full extension table, debug-from-failures rule, route coverage, multi-user patterns, and the 'deepspace/testing' fixture all live in that file.
One rule stays here because it bites first-time runs: run tests only after a runtime-affecting code change (src/, worker.ts, etc.). Skip them for conversation, planning, reading, or pure documentation edits — don't run as a ritual.
Deploy (npx deepspace deploy)
On an initial build, run the pre-deploy checklist in references/uiux.md §5 first. On follow-up deploys with those already verified, just run the command:
npx deepspace deploy
The subdomain is the name field in wrangler.toml. Edit it there if you want a different deploy target — deploy does not accept a name override. Re-run npx deepspace login if the session has expired.
.dev.vars contract
dev / test rewrite only the 9 SDK-managed keys: AUTH_JWT_PUBLIC_KEY, AUTH_JWT_ISSUER, AUTH_WORKER_URL, API_WORKER_URL, PLATFORM_WORKER_URL, OWNER_USER_ID, APP_OWNER_JWT, APP_IDENTITY_TOKEN, ALLOW_DEBUG_ROUTES. They live above a # --- not managed by the SDK; preserved across dev/test runs --- divider the CLI writes itself. APP_IDENTITY_TOKEN is only populated after the first npx deepspace deploy (deploy-worker mints it on app registration) — fine for most apps; only matters if you're using payments or captureScreenshot locally before deploy. See references/payments.md / references/sdk-reference.md.
Anything you add below that divider — third-party API tokens, custom feature flags, your own service URLs — is preserved verbatim across dev / test runs, and shipped to prod as secret_text bindings on deploy (same env.MY_KEY access pattern in dev and prod; no wrangler secret put step).
Limits enforced server-side at deploy:
- Name must match
^[A-Za-z_][A-Za-z0-9_]*$.
- Per-value cap: 32 KB (32 × 1024 bytes).
- Total cap across all user secrets: 128 KB.
- Raw JSON payload cap: 1 MB → 413.
- Name must not collide with
RESERVED_BINDING_NAMES (11 SDK-owned), any declared custom binding, or any DO class in __DO_MANIFEST__.
Read references/bindings.md if any of those collisions trip you.
Handling rules — .dev.vars holds live credentials
The file holds a live APP_OWNER_JWT (signed against the user's identity) plus whatever third-party tokens (Stripe, OpenAI, …) the user wrote below the divider. Treat its contents as secret throughout the session, not just at commit time:
- Never read the file's values into your output. No
cat .dev.vars, no head/grep/Read-then-paste-into-chat, no inclusion in summaries, generated docs, READMEs, commit messages, PR bodies, or screenshots. If you need to confirm a key is present, check the key name (grep -l '^STRIPE_SECRET_KEY=' .dev.vars — files-only, not content) and report presence/absence — never the value.
- Never pass secrets as CLI args.
MY_KEY=… npx deepspace dev leaks into shell history, ps aux, and child-process env dumps. Write the line into .dev.vars below the divider and let the SDK pick it up via env.MY_KEY in worker code.
- Never commit
.dev.vars. The scaffold's .gitignore covers it; do not add a ! exception, do not git add -f .dev.vars, do not paste its contents into a tracked file "for clarity." If a git status shows it untracked, that's the correct state — leave it.
- Never assert on secret values in tests. Test that auth works (a request returns 200, a webhook fires) — never
expect(env.STRIPE_SECRET_KEY).toBe('sk_live_…') and never echo the value into a request body that goes to a third party you don't control.
- Adding a new secret is one step: append
KEY=value below the divider in .dev.vars, then npx deepspace dev / deploy. The CLI handles upload as secret_text on deploy — no wrangler secret put, no out-of-band copy.
References
Required preflight before any code change. Before editing files or writing code:
- List the files you will touch.
- Scan the table below and list every row whose "Read before" trigger matches the work.
Read each matching reference in this turn, before the first edit — not after, not "if needed later."
- If your list under (2) is empty, stop and re-scan the table; for any non-trivial DeepSpace work at least one row applies.
Each reference also declares its own "Load when …" trigger as its first line — that gate is authoritative; if it says load, load it before touching the matching surface.
| Reference | Read before |
|---|
references/sdk-reference.md | Reaching for any hook, type, or export beyond useQuery / useMutations / useAuth. Especially required for multiplayer / game rooms, presence, Yjs, canvas, R2, messaging. Open this before node_modules/deepspace/dist/*.d.ts. |
references/schemas.md | Defining a collection, picking a permission rule, debugging "why can't this user see/edit X," wiring visibilityField / collaboratorsField. |
references/auth.md | Choosing the auth model (public / gated / mixed), adding <AuthGate>, customizing the sign-in fallback. |
references/architecture.md | Editing worker.ts, adding cross-app shared scopes (workspace:* / dir:* / conv:*), wiring platformWorkerFetch, understanding the WebSocket / /api/* identity-strip security model. |
references/server-actions.md | Adding privileged writes that bypass the caller's RBAC. |
references/ai-chat.md | Adding a streamed chat UI with tool use over the app's records. |
references/cron.md | Adding scheduled tasks, building the admin cron monitor, testing cron via trigger. |
references/jobs.md | Adding background-job handlers (AI generation, exports, renders), choosing client vs server enqueue, handling progress / cancellation / multi-tick checkpoints. |
references/bindings.md | Declaring custom Cloudflare bindings (Vectorize / R2 / KV / D1 / Queues / AI / Browser / Hyperdrive / AE), "auto" autoprovisioning, D1 bootstrap with runMigrations, per-tenant cost rollup via meterAi / meterVectorize / meterUsage. |
references/integrations.md | Calling external APIs (LLMs, search, media, social, finance, etc.). |
references/payments.md | Anything involving money, Stripe, billing, paywalls, subscriptions, pricing pages, "Upgrade" buttons, Pro / premium tiers, gating features behind a plan, one-time products, tips, donations, free trials, refunds, or cancellation. Never hand-roll Stripe in a DeepSpace app — declare in src/subscriptions.ts / src/products.ts and use useSubscription / useCheckout / requireSubscription. |
references/domain.md | Buying / attaching / managing a custom domain (deepspace domain CLI). Skip for apps that are happy on <name>.app.space. |
references/integrations/livekit.md | Adding audio/video rooms — token mint, billing model, room lifecycle. |
references/integrations/google-oauth.md | Calling Gmail / Calendar / Drive / Contacts — per-user billing, scope step-up, requiresOAuth retry, Playwright page.route() mocks. |
references/uiux.md | Working on theme, home page, primitives, interaction polish, or "feels generic" feedback. Trigger especially when about to use <select> / window.confirm / window.alert / window.prompt. |
references/testing.md | Writing or extending specs, applying the Step 8 checklist, building multi-user flows, route coverage, debugging flaky tests. |
references/landing-design.md | Building marketing / landing / splash pages, addressing "feels AI-generated" feedback, customizing the scaffolded landing feature. |
Gotchas
Cross-cutting traps that don't have a natural reference home. Domain-specific gotchas live in their topical reference (auth, schemas, integrations, architecture, testing, bindings).
- Scaffold's local UI primitives shadow the SDK —
_app.tsx wraps the tree in ToastProvider from src/components/ui/, not from deepspace. Importing useToast (or any locally-shadowed primitive) from deepspace throws useToast must be used within ToastProvider at runtime — the React contexts don't match. Import from ../components/ui, not from deepspace. Full explanation in references/uiux.md § "Critical import rule."
- Page files belong in
src/pages/ — generouted scans only this directory. Putting pages in src/features/<name>/ results in 404s even if nav links exist.
- Don't scaffold-then-move to pair a directory with a different subdomain. If you need directory
foo-site with subdomain foo-main, scaffold into foo-site and edit wrangler.toml's name to foo-main. Scaffolding as foo-main then moving the files lands in the same state with extra failure surface (broken git init, stray .wrangler/ paths). See references/architecture.md § App-name rules.
- Never put identity in WebSocket URLs or
/api/* headers. The starter wsRoute strips userId / userName / userEmail / userImageUrl / role query params on every upgrade and re-applies them only from a verified JWT. The platform worker does the same on /api/* (overwrites X-User-Id from JWT, strips X-App-Action). Caller identity is always the JWT subject — there is no client-side override. Three valid WS states: no token → anonymous (DO assigns anon-<uuid>); invalid token → 401; valid token → JWT identity. (Cross-app scopes workspace:* / dir:* / conv:* have no anonymous path — auth is required.)
- Port 5173 may be held by a parallel session —
tests/playwright.config.ts ships with reuseExistingServer: true, so a sibling session's Vite on 5173 is picked up and your tests run against its app. Do not kill a parallel session's processes. For your own leaked workerd/wrangler (crashed terminal, IDE close), use npx deepspace kill (add --all to sweep every workerd/wrangler/vite on the box). For genuine parallel work, use --port 5174 (or 5175, 5176, …) and edit tests/playwright.config.ts so webServer.port and use.baseURL both match before running tests.