deepspace 0.23.2 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/utils/cors.ts","../src/server/utils/plan-quotas.ts","../src/server/utils/registry-client.ts","../src/server/utils/tools.ts","../src/shared/app-files.ts","../src/server/utils/r2-storage-quota.ts","../src/server/utils/scoped-r2-files.ts","../src/server/utils/proxies.ts","../src/server/utils/cron.ts","../src/server/utils/app-role.ts","../src/server/utils/ai.ts","../src/server/utils/agent.ts","../src/shared/ai-models.ts","../src/server/utils/composio-tools.ts","../src/server/utils/app-identity.ts","../src/server/utils/screenshot.ts","../src/server/utils/chat-context.ts","../src/shared/protocol/constants.ts","../src/server/utils/chat-history.ts","../src/server/utils/metering.ts","../src/server/utils/d1-migrations.ts","../src/shared/room-identity-headers.ts","../src/server/rooms/base-room.ts","../src/server/schemas/registry.ts","../src/server/rooms/collection-table-migration.ts","../src/server/handlers/yjs.ts","../src/shared/protocol/yjs.ts","../src/shared/protocol/messages.ts","../src/server/handlers/records.ts","../src/server/handlers/subscriptions.ts","../src/server/handlers/users.ts","../src/server/handlers/tools-api.ts","../src/server/handlers/debug-api.ts","../src/server/rooms/record-room.ts","../src/server/rooms/yjs-room.ts","../src/server/rooms/canvas-room.ts","../src/shared/roles.ts","../src/server/rooms/presence-room.ts","../src/server/rooms/cron-schedule.ts","../src/server/rooms/cron-room.ts","../src/server/rooms/job-room.ts","../src/server/rooms/do-manifest.ts","../src/server/rooms/binding-manifest.ts","../src/server/rooms/do-migration.ts","../src/server/rooms/app-name.ts","../src/server/schemas/ai-chat.ts","../src/server/schemas/messaging.ts","../src/server/schemas/conversation.ts","../src/server/schemas/directory.ts","../src/server/schemas/workspace.ts","../src/server/schemas/shared-do.ts","../src/server/auth/jwtVerifier.ts","../src/server/auth/utils.ts","../src/server/auth/internalAuth.ts","../src/server/auth/testAccounts.ts","../src/server/auth/betterAuth.ts","../src/shared/subscription.ts","../src/server/subscription.ts","../src/server/refunds.ts"],"sourcesContent":["/**\n * Wildcard-aware CORS origin matching for the platform workers.\n *\n * Hono's array form of `cors({ origin: [...] })` is an exact `includes()` — it\n * never expands `*`. Every worker that listed `https://*.app.space` or\n * `http://localhost:*` in an array had a dead entry: the wildcard rows matched\n * nothing, silently narrowing the effective CORS surface to the literal\n * entries. This helper is the single implementation both the api and deploy\n * workers pass to Hono's function-form `origin`, so the pattern semantics\n * can't drift between them.\n *\n * `*` stands for exactly one host label or a port: it never crosses a dot or\n * a slash, so `https://*.app.space` matches `https://x.app.space` but neither\n * `https://a.b.app.space` nor `https://x.app.space.evil.com`.\n */\nexport function matchOrigin(origin: string, patterns: string[]): string | null {\n if (!origin) return null\n const matches = patterns.some((pattern) => {\n const rx = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\\\\\*/g, '[^./]+')\n return new RegExp(`^${rx}$`).test(origin)\n })\n return matches ? origin : null\n}\n\n/**\n * The platform's standard browser-origin allow-list, derived from the\n * environment's apexes so a second constellation (staging) trusts its own\n * hosts and only its own. Both values are explicit deployment-plane config.\n * Dedupes for environments that serve both roles from one apex.\n */\nexport function platformCorsPatterns(env: { APP_DOMAIN: string; PLATFORM_DOMAIN: string }): string[] {\n const app = env.APP_DOMAIN\n const platform = env.PLATFORM_DOMAIN\n return [\n ...new Set([`https://${platform}`, `https://*.${platform}`, `https://*.${app}`]),\n 'http://localhost:*',\n ]\n}\n","/**\n * Deployed-app quota defaults — the ONE copy.\n *\n * Two workers use these numbers for different jobs: the deploy worker\n * ENFORCES them at registration time, the api worker DISPLAYS them via\n * GET /api/plan-limits. They used to be independent literal sets (plus the\n * same numbers again in six wrangler [vars] blocks as overrides) and had\n * already diverged once — the display copy was missing the admin tier\n * entirely. The env vars remain the per-plane override mechanism; only the\n * fallbacks live here.\n */\nexport const DEPLOYED_APPS_DEFAULTS = {\n free: 1,\n starter: 4,\n premium: 10,\n admin: 100,\n} as const\n\n/** Parse a quota env override; anything non-integer/negative = fallback. */\nexport function parseQuotaLimit(raw: string | undefined, fallback: number): number {\n if (!raw) return fallback\n const n = Number(raw)\n return Number.isInteger(n) && n >= 0 ? n : fallback\n}\n","/**\n * The ONE registry client. Every platform worker reaches the app-registry\n * Durable Object (hosted by the deploy worker, singleton `v1:global`) through\n * a cross-script REGISTRY binding — and before this module, each of the five\n * workers hand-rolled its own copy of the same mechanics: the instance name\n * appeared in 6 places, the `https://registry/` URL in 6, `APP_ID_RE` in 5,\n * and `RegistryClientError` + `registryErrorJson` existed byte-identical in\n * two workers. Copies that must agree and nothing enforcing it.\n *\n * This module owns the MECHANICS: instance name, stub lookup, the wire call,\n * the error type, and the standard route mapping. Each worker still declares\n * its own typed convenience wrapper listing only the actions it uses — that\n * subset is genuinely per-worker; the plumbing is not.\n */\n\n/** The singleton registry instance — one DO holds every app/route/collaborator. */\nexport const REGISTRY_INSTANCE = 'v1:global'\n\n/** Public app ids have one canonical ULID-minted shape. */\nexport const APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/\nexport const STRICT_APP_ID_RE = APP_ID_RE\n\n/** Internal physical locators survive identity migration and are not public ids. */\nexport const RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —\n * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */\nexport function mintUlid(now = Date.now()): string {\n let ts = ''\n let t = now\n for (let i = 0; i < 10; i++) {\n ts = CROCKFORD[t % 32] + ts\n t = Math.floor(t / 32)\n }\n const rand = new Uint8Array(10)\n crypto.getRandomValues(rand)\n let rs = ''\n let acc = 0\n let bits = 0\n for (const byte of rand) {\n acc = (acc << 8) | byte\n bits += 8\n while (bits >= 5) {\n bits -= 5\n rs += CROCKFORD[(acc >> bits) & 31]\n }\n }\n return `${ts}${rs}`.slice(0, 26)\n}\n\n/** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the\n * deploy worker's authenticated `POST /api/apps/mint`, which registers the id\n * to its caller in the same step — an id with no owner never exists. */\nexport function mintAppId(now = Date.now()): string {\n return `app_${mintUlid(now)}`\n}\n\nexport class RegistryClientError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n ) {\n super(message)\n this.name = 'RegistryClientError'\n }\n}\n\nexport function registryStub(ns: DurableObjectNamespace): DurableObjectStub {\n return ns.get(ns.idFromName(REGISTRY_INSTANCE))\n}\n\n/**\n * One registry action over the wire. Non-2xx answers throw\n * {@link RegistryClientError} carrying the DO's structured `code` (or\n * `'error'` for a transport-shaped body) — callers distinguish a registry-\n * REPORTED miss (`code === 'not_found'`) from an outage by that code, never\n * by treating every throw as \"not found\".\n */\nexport async function callRegistry<T>(\n stub: DurableObjectStub,\n body: { action: string } & Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch('https://registry/', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n const json = (await res.json().catch(() => ({}))) as { error?: string; code?: string }\n if (!res.ok) {\n throw new RegistryClientError(\n json.error ?? `Registry error (${res.status})`,\n json.code ?? 'error',\n res.status,\n )\n }\n return json as T\n}\n\n/** Standard mapping of a registry failure onto a route response. */\nexport function registryErrorJson(\n c: { json: (body: unknown, status?: number) => Response },\n err: unknown,\n): Response {\n if (err instanceof RegistryClientError) {\n return c.json({ error: err.message, code: err.code }, err.status as 400)\n }\n throw err\n}\n","/**\n * Tools API Types and Definitions\n * \n * MCP-like interface for agent tool calls.\n * Built-in tools for records, schemas, users, and Yjs documents.\n */\n\nexport interface ToolSchema {\n name: string\n description: string\n params: Record<string, {\n type: 'string' | 'number' | 'boolean' | 'object' | 'array'\n description: string\n required?: boolean\n default?: unknown\n }>\n}\n\n/**\n * Discriminated union so callers don't have to guard on `error` being\n * defined when `success` is false — TS enforces the invariant.\n */\nexport type ToolResult =\n | { success: true; data?: unknown }\n | { success: false; error: string }\n\n/**\n * Page size the assistant defaults to for `records.query` when it omits\n * `limit`. Keeps a model-issued unbounded scan from blowing the tool-result\n * byte cap and gives the model a usable first page. Raise `limit` to page\n * through more (still subject to the cap; see\n * `DEFAULT_CONTEXT_CONFIG.toolResultCap` / `capToolResultSize`, which truncates\n * oversized pages gracefully rather than dropping them).\n *\n * Applied by the AI tool layer via `applyAiToolDefaults`, never by the shared\n * tools-api dispatch, so internal record readers (chat history, cron, app\n * `actions.query`) stay unbounded.\n */\nexport const DEFAULT_QUERY_LIMIT = 50\n\n/**\n * Page size `records.deleteWhere` uses when the caller omits `limit`, and the\n * ceiling it clamps any larger `limit` to.\n *\n * The tool exists so a cascade costs one subrequest per *page* instead of one\n * per row (see `deleteChatCascade`); the ceiling keeps a single call's SQL and\n * delete broadcasts bounded, and callers page by repeating the call until it\n * reports fewer deletions than the page size.\n */\nexport const DEFAULT_DELETE_WHERE_LIMIT = 100\nexport const MAX_DELETE_WHERE_LIMIT = 500\n\n/**\n * Fill in assistant-only parameter defaults for a built-in tool call.\n *\n * Applied by the AI tool layer (`buildTools`) before a model-issued tool call\n * is dispatched, so the default only affects calls the assistant makes.\n * `records.query` doubles as the SDK's general record-read primitive (chat\n * history, cron, app `actions.query`); those callers reach the tools dispatch\n * directly and must return every row, so this default must not live in the\n * dispatch itself. Pure and non-mutating.\n */\nexport function applyAiToolDefaults(\n toolName: string,\n params: Record<string, unknown>,\n): Record<string, unknown> {\n if (toolName === 'records.query' && params.limit === undefined) {\n return { ...params, limit: DEFAULT_QUERY_LIMIT }\n }\n return params\n}\n\n/**\n * Built-in tool definitions for record, schema, user, and Yjs operations.\n */\nexport const BUILT_IN_TOOLS: ToolSchema[] = [\n // Record tools (structured collections with RBAC)\n {\n name: 'records.query',\n description:\n 'Query records from a collection with optional filtering, ordering, and pagination. ' +\n `Returns at most \\`limit\\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: ` +\n 'an oversized page is truncated to the leading records that fit and flagged with ' +\n '`{ truncated, returned, total }`, so narrow with a `where` filter (or page via `orderBy` + `limit`) ' +\n 'to see the rest.',\n params: {\n collection: { type: 'string', description: 'Collection name to query', required: true },\n where: { type: 'object', description: 'Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column (an unknown key is refused, not ignored)', required: false },\n orderBy: { type: 'string', description: 'Field to order by (or \"createdAt\"/\"updatedAt\")', required: false },\n orderDir: { type: 'string', description: 'Order direction: \"asc\" or \"desc\" (default: \"desc\")', required: false },\n limit: { type: 'number', description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false },\n }\n },\n {\n name: 'records.get',\n description: 'Get a single record by ID from a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to retrieve', required: true },\n }\n },\n {\n name: 'records.create',\n description: 'Create a new record in a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n data: { type: 'object', description: 'Record data matching the collection schema', required: true },\n recordId: { type: 'string', description: 'Custom record ID (auto-generated if omitted)', required: false },\n }\n },\n {\n name: 'records.update',\n description: 'Update an existing record in a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to update', required: true },\n data: { type: 'object', description: 'Fields to update (merged with existing data)', required: true },\n }\n },\n {\n name: 'records.delete',\n description: 'Delete a record from a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to delete', required: true },\n }\n },\n {\n name: 'records.deleteWhere',\n description:\n 'Delete every record matching a filter, in one bounded batch. ' +\n `Deletes at most \\`limit\\` records (default ${DEFAULT_DELETE_WHERE_LIMIT}, max ${MAX_DELETE_WHERE_LIMIT}) and returns ` +\n '`{ deleted }`; repeat the same call until `deleted` is less than the limit to drain a larger set. ' +\n 'A non-empty `where` whose every key names a real field (`recordId`, `createdBy`, or a schema column) is required — ' +\n 'an unknown key is refused rather than ignored, so this tool never deletes a whole collection. ' +\n 'Every matched record goes through the same delete permission check as `records.delete`; ' +\n 'if any of them is denied, nothing in the batch is deleted.',\n params: {\n collection: { type: 'string', description: 'Collection name (must have a schema)', required: true },\n where: { type: 'object', description: 'Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column', required: true },\n limit: { type: 'number', description: `Maximum number of records to delete in this call (default: ${DEFAULT_DELETE_WHERE_LIMIT}, max: ${MAX_DELETE_WHERE_LIMIT})`, required: false },\n }\n },\n // Schema tools\n {\n name: 'schema.list',\n description: 'List all registered collection schemas with their fields and permissions',\n params: {}\n },\n {\n name: 'schema.describe',\n description: 'Get the full schema for a specific collection including field types and role permissions',\n params: {\n collection: { type: 'string', description: 'Collection name to describe', required: true },\n }\n },\n // User tools\n {\n name: 'user.current',\n description: 'Get the current user\\'s profile (ID, name, email, role)',\n params: {}\n },\n {\n name: 'user.list',\n description: 'List all users in this room',\n params: {}\n },\n // Yjs tools (collaborative document access)\n {\n name: 'yjs.list',\n description: 'List all Yjs documents stored in this room, showing collection, recordId, fieldName, and last update time',\n params: {}\n },\n {\n name: 'yjs.getText',\n description: 'Read text content from a Yjs document (for fields using useYjsText)',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID', required: true },\n fieldName: { type: 'string', description: 'Field name (e.g., \"content\")', required: true },\n }\n },\n {\n name: 'yjs.setText',\n description: 'Write text content to a Yjs document (replaces existing text, broadcasts to connected clients)',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID', required: true },\n fieldName: { type: 'string', description: 'Field name (e.g., \"content\")', required: true },\n text: { type: 'string', description: 'The text content to set', required: true },\n }\n },\n]\n","/**\n * App-files limits, chunk planning, and failure reading — shared by every\n * caller of the files API: the browser hook (`client/storage/useR2Files.ts`),\n * the CLI (`cli/lib/app-files-api.ts`), the server handler\n * (`server/utils/scoped-r2-files.ts`), and the docs that quote them.\n *\n * There are three numbers here and they answer three different questions.\n * Conflating them is what produced the old 25 MiB per-file ceiling: that was\n * never a product decision, it was the size of one HTTP request leaking out as\n * a rule about files. A request is bounded by physics — Cloudflare rejects an\n * oversized body before any worker runs, and the worker's isolate is ~128 MiB.\n * A FILE is not, once it travels as a sequence of bounded requests.\n *\n * The other half of this module is failure reading. An upload refused at the\n * edge answers with a Cloudflare HTML page, which every caller used to feed\n * straight into `JSON.parse` and report as \"Unexpected token '<' … is not\n * valid JSON\". `describeFilesFailure` exists to make that impossible.\n */\n\n/**\n * The largest single file the app-files API accepts.\n *\n * Reachable only through the multipart transport: a file above\n * {@link UPLOAD_PART_BYTES} is uploaded as a sequence of parts, so no request\n * and no isolate ever holds more than one part. Enforced by the shared handler\n * (`server/utils/scoped-r2-files.ts`) twice — once at init against the\n * declared total, so an impossible upload fails before a byte moves, and again\n * at complete against the assembled object, because the declared total is\n * client-supplied. Callers check it locally too, but only to fail early;\n * skipping the client check cannot widen what is accepted.\n */\nexport const MAX_APP_FILE_BYTES = 1024 * 1024 * 1024\n\n/**\n * The most any ONE request body may carry — a whole small file on the\n * single-request path, or one part on the multipart path.\n *\n * This is the transport's real physical bound, and the only thing 25 MiB ever\n * meant. Above it the worker risks exhausting its isolate holding the body;\n * well above it (~100 MB) Cloudflare refuses the request before the worker\n * runs at all, which is why callers must read failures as text first.\n */\nexport const MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024\n\n/**\n * The part size clients chunk at, and the threshold above which they must.\n *\n * Below the {@link MAX_UPLOAD_REQUEST_BYTES} bound with headroom for the\n * request envelope, and comfortably inside R2's own part rules: parts must be\n * at least 5 MiB and every non-final part must be exactly the same size\n * (R2 errors 10011 / 10048). The server advertises this at init — a client\n * chunks at the size it is told, not at this constant, so the number can move\n * without stranding older callers mid-upload.\n */\nexport const UPLOAD_PART_BYTES = 20 * 1024 * 1024\n\n/**\n * The highest part number the server accepts.\n *\n * The server derives the only valid part number and byte length from the\n * declared total and {@link UPLOAD_PART_BYTES}. The maximum part count is\n * therefore the exact number needed to reach the file ceiling.\n *\n * R2 allows 10,000 parts, so this is far inside what the API permits.\n */\nexport const MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES)\n\n/**\n * The largest base64 payload the single-request JSON shape can carry.\n *\n * Derived from the ENCODED size, because that is what the request body is:\n * base64 spends 4 characters per 3 bytes, so a payload measured only by its\n * decoded size can sit under {@link MAX_UPLOAD_REQUEST_BYTES} and still\n * produce a body a third larger than the server will accept — a client check\n * that passes everything the server then refuses. Roughly 18.75 MiB.\n */\nexport const MAX_BASE64_UPLOAD_BYTES = Math.floor((MAX_UPLOAD_REQUEST_BYTES * 3) / 4)\n\n/** Byte ranges for a chunked upload: `[start, end)`, `end` exclusive. */\nexport interface UploadPart {\n partNumber: number\n start: number\n end: number\n}\n\n/**\n * Split a file into the parts the multipart transport expects.\n *\n * Every part is exactly `partSize` except the last, which is what R2 requires\n * of non-final parts. Both chunking clients plan with this, so \"which bytes\n * are part N\" is answered in one place.\n */\nexport function planUploadParts(totalBytes: number): UploadPart[] {\n const parts: UploadPart[] = []\n for (let start = 0; start < totalBytes; start += UPLOAD_PART_BYTES) {\n parts.push({\n partNumber: parts.length + 1,\n start,\n end: Math.min(start + UPLOAD_PART_BYTES, totalBytes),\n })\n }\n return parts\n}\n\nconst GIB = 1024 * 1024 * 1024\n\n/**\n * The customer-visible storage schedule, in bytes, PER ACCOUNT — the total an\n * owner's uploaded files may occupy across every app they own, not a budget\n * each app gets separately. This is the number the billing page advertises.\n *\n * Admitted by the shared files handler (`server/utils/scoped-r2-files.ts`),\n * which sums the owner's apps rather than the one being written to.\n *\n * It deliberately does NOT govern the repo store — see\n * `REPO_STORE_LIMIT_BYTES` below, and docs/platform/r2-storage.md.\n */\nexport const ACCOUNT_STORAGE_LIMIT_BYTES = {\n test: 0,\n free: 1 * GIB,\n starter: 10 * GIB,\n premium: 30 * GIB,\n admin: 100 * GIB,\n} as const\n\n/** The account storage limit for a billing tier; unknown or missing tiers get free. */\nexport function storageLimitForTier(tier: string | null | undefined): number {\n if (tier && Object.hasOwn(ACCOUNT_STORAGE_LIMIT_BYTES, tier)) {\n return ACCOUNT_STORAGE_LIMIT_BYTES[tier as keyof typeof ACCOUNT_STORAGE_LIMIT_BYTES]\n }\n return ACCOUNT_STORAGE_LIMIT_BYTES.free\n}\n\n/**\n * The repo store's budget, per app, flat across every tier.\n *\n * Git packs, retained rollback bundles, and stored deploy assets are platform\n * bookkeeping, not something a customer buys — so this is a working ceiling,\n * not an entitlement, and it is deliberately small. An app that cannot fit its\n * SOURCE plus a few rollback bundles in this has something in the wrong place:\n * user uploads belong in the app-files allocation above, which is what the\n * account schedule sizes.\n *\n * Being flat is the point. Tying it to the tier table meant every raise to the\n * customer-facing number silently multiplied platform-side storage per app,\n * and made a push depend on a billing lookup that could refuse it.\n */\nexport const REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024\n\n/** The one storage-quota refusal sentence. `usedBytes` is net of any object\n * the upload replaces, so an upsert is charged only for its growth. */\nexport function storageQuotaMessage(\n incomingBytes: number,\n usedBytes: number,\n limitBytes: number,\n): string {\n const free = Math.max(0, limitBytes - usedBytes)\n return (\n `This upload needs ${formatBytes(incomingBytes)} but the app's file storage has ` +\n `${formatBytes(free)} free (${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used). ` +\n `Delete files with \\`deepspace app files rm <key>\\`, or upgrade the owner's plan.`\n )\n}\n\n/**\n * The largest single file a deploy may ship as a static asset.\n *\n * Cloudflare Static Assets refuses anything larger, so this is a hard edge of\n * the platform rather than a policy dial — which is why the CLI may check it\n * locally. It lives here so the deploy worker and the CLI cannot drift; the\n * per-DEPLOY total is deliberately NOT here, because it is env-configurable\n * per environment and only the server can know it.\n */\nexport const MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024\n\n/** MIME types that must download rather than render in the app's origin. */\nexport const DANGEROUS_MIME_TYPES = new Set<string>([\n 'text/html',\n 'application/xhtml+xml',\n 'image/svg+xml',\n 'application/xml',\n 'text/xml',\n 'application/javascript',\n 'text/javascript',\n 'application/ecmascript',\n 'text/ecmascript',\n])\n\nexport function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`\n if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`\n return `${bytes} B`\n}\n\n/** Encode an R2 key as URL path segments while preserving its hierarchy. */\nexport function encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/')\n}\n\n/** The one oversize sentence, for whichever caller notices first. */\nexport function oversizeMessage(bytes: number, limit = MAX_APP_FILE_BYTES): string {\n return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(limit)} per file.`\n}\n\n/**\n * The one over-the-request-bound sentence. Distinct from\n * {@link oversizeMessage} because it names a fixable mistake: the file is fine,\n * the caller just sent it in one piece instead of in parts.\n */\nexport function oversizeRequestMessage(bytes: number): string {\n return (\n `This request carries ${formatBytes(bytes)}; one request may carry ` +\n `${formatBytes(MAX_UPLOAD_REQUEST_BYTES)}. Files above ` +\n `${formatBytes(UPLOAD_PART_BYTES)} upload in parts.`\n )\n}\n\n/** A part is bounded tighter than a request; say which number was crossed. */\nexport function oversizePartMessage(bytes: number): string {\n return (\n `This part carries ${formatBytes(bytes)}; a part may carry ` +\n `${formatBytes(UPLOAD_PART_BYTES)} — the size the upload advertised.`\n )\n}\n\n/**\n * Turn a failed files-API response into a sentence worth showing.\n *\n * `body` is the raw response text. A JSON `{ error }` is the app's own\n * refusal and is passed through; anything else came from the edge (an HTML\n * error page, an empty body, a proxy) and is described by its status instead\n * of being echoed, because echoing it is what produced the JSON-parse noise.\n */\nexport function describeFilesFailure(status: number, body: string): string {\n const trimmed = body.trim()\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed) as { error?: string }\n if (parsed.error) return parsed.error\n } catch {\n // Not the app's JSON after all — fall through to the status description.\n }\n }\n if (status === 413) {\n return (\n `The upload is too large. Files are limited to ${formatBytes(MAX_APP_FILE_BYTES)}, ` +\n `and one request may carry ${formatBytes(MAX_UPLOAD_REQUEST_BYTES)} — a larger request ` +\n `is rejected by the network before the app receives it.`\n )\n }\n if (status === 411) return 'The upload did not declare its length.'\n if (status === 401 || status === 403) return 'Not authorized to write these files.'\n if (status === 415) return 'That file type is not accepted.'\n if (status >= 500) return `The files service failed (HTTP ${status}). Retry shortly.`\n return `Upload failed (HTTP ${status}).`\n}\n","/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { storageQuotaMessage } from '../../shared/app-files'\n\nconst CORS_HEADERS = { 'Access-Control-Allow-Origin': '*' } as const\nconst QUOTA_SUMMARY_VERSION = 1\nconst MAX_QUOTA_SCAN_OBJECTS = 500_000\nconst MAX_QUOTA_SCAN_LISTS = 900\nconst QUOTA_REPAIR_COOLDOWN_MS = 60_000\nconst PENDING_MARKER_MAX_AGE_MS = 60 * 60_000\nconst EMPTY_MULTIPART_MARKER_MAX_AGE_MS = 60 * 60_000\nconst ACTIVE_MULTIPART_MARKER_MAX_AGE_MS = 8 * 24 * 60 * 60_000\n\n/**\n * Everything the files handler needs to enforce one account allocation.\n *\n * `quotaKey` is required because an exact scan followed by an uncoordinated\n * write cannot enforce a limit under concurrency. Callers that do not want a\n * quota omit `storage`; callers that do want one must name its serialized\n * summary.\n */\nexport interface StorageAdmission {\n /** The app prefix receiving this request's write. */\n prefix: string\n /** Internal R2 key for the account's ETag-serialized usage summary. */\n quotaKey: string\n /** Resolve the account's limit. `null` fails writes closed. */\n limitBytes: () => Promise<number | null>\n /** Resolve the other app prefixes in the account. `null` fails closed. */\n siblingPrefixes?: () => Promise<string[] | null>\n}\n\ninterface StorageReservation {\n markerKey: string | null\n}\n\ninterface QuotaMarkerSpec {\n key: string\n body?: string\n metadata: Record<string, string>\n}\n\nexport interface MultipartQuotaReservation {\n key: string\n etag: string\n reservationId: string\n uploadId: string\n objectKey: string\n uploadKey: string\n admitted: boolean\n declaredBytes: number\n reservedBytes: number\n}\n\ninterface QuotaSummary {\n version: typeof QUOTA_SUMMARY_VERSION\n prefixes: string[]\n usedBytes: number\n measuredAt: number\n dirty: boolean\n}\n\ninterface QuotaSnapshot {\n etag: string | null\n summary: QuotaSummary | null\n}\n\ninterface QuotaScanBudget {\n lists: number\n objects: number\n staleKeys: string[]\n}\n\ninterface MultipartMarkerState {\n version: 1\n reservationId: string\n uploadId: string\n objectKey: string\n uploadKey: string\n admitted: boolean\n declaredBytes: number\n}\n\nfunction fail(\n status: number,\n error: string,\n code?: string,\n extra?: Record<string, unknown>,\n): Response {\n return Response.json(\n { error, ...(code ? { code } : {}), ...extra },\n { status, headers: CORS_HEADERS },\n )\n}\n\nexport function quotaUnavailable(message: string): Response {\n return fail(503, message, 'storage_limit_unavailable')\n}\n\nfunction quotaKeySegment(value: string): string {\n return encodeURIComponent(value)\n}\n\nfunction markerPrefix(storage: StorageAdmission, kind: 'pending' | 'multipart'): string {\n return `__deepspace/quota/${kind}/${quotaKeySegment(storage.quotaKey)}/`\n}\n\nfunction pendingMarkerPrefix(storage: StorageAdmission): string {\n return markerPrefix(storage, 'pending')\n}\n\nfunction multipartMarkerPrefix(storage: StorageAdmission): string {\n return markerPrefix(storage, 'multipart')\n}\n\nexport function multipartMarkerKey(storage: StorageAdmission, reservationId: string): string {\n return `${multipartMarkerPrefix(storage)}${quotaKeySegment(reservationId)}`\n}\n\nfunction accountPrefixesEqual(left: string[], right: string[]): boolean {\n return left.length === right.length && left.every((prefix, index) => prefix === right[index])\n}\n\nasync function allocationPrefixes(storage: StorageAdmission): Promise<string[] | null> {\n const siblings = storage.siblingPrefixes ? await storage.siblingPrefixes() : []\n if (siblings === null) return null\n return [...new Set([storage.prefix, ...siblings])].sort()\n}\n\nfunction countScan(budget: QuotaScanBudget, count: number): void {\n budget.lists++\n budget.objects += count\n if (budget.lists > MAX_QUOTA_SCAN_LISTS || budget.objects > MAX_QUOTA_SCAN_OBJECTS) {\n throw new Error('quota scan work limit exceeded')\n }\n}\n\n/**\n * Measure stored bytes under one app prefix.\n *\n * The account has one pair of marker scans rather than one pair per app, so an\n * empty historical identity costs a single bounded list call. Object count has\n * its own larger bound because tiny objects are independently amplifiable.\n */\nasync function prefixUsage(\n bucket: R2Bucket,\n prefix: string,\n budget: QuotaScanBudget,\n): Promise<number> {\n let total = 0\n let cursor: string | undefined\n do {\n const page = await bucket.list({ prefix, cursor, limit: 1000 })\n countScan(budget, page.objects.length)\n for (const object of page.objects) total += object.size\n cursor = page.truncated ? page.cursor : undefined\n } while (cursor !== undefined)\n return total\n}\n\nfunction markerBytes(object: R2Object): number {\n const raw = object.customMetadata?.reservedBytes\n const bytes = raw === undefined ? NaN : Number(raw)\n if (!Number.isSafeInteger(bytes) || bytes < 0) throw new Error('invalid quota marker')\n return bytes\n}\n\nfunction markerMaxAge(object: R2Object, fallback: number): number {\n if (object.customMetadata?.quotaKind !== 'multipart') return fallback\n return object.customMetadata.active === 'true'\n ? ACTIVE_MULTIPART_MARKER_MAX_AGE_MS\n : EMPTY_MULTIPART_MARKER_MAX_AGE_MS\n}\n\nasync function markerUsage(\n bucket: R2Bucket,\n prefix: string,\n maxAgeMs: number,\n budget: QuotaScanBudget,\n excludedKey?: string,\n): Promise<number> {\n let total = 0\n let cursor: string | undefined\n const now = Date.now()\n do {\n const options = {\n prefix,\n cursor,\n limit: 1000,\n include: ['customMetadata'],\n } as R2ListOptions & { include: ['customMetadata'] }\n const page = await bucket.list(options)\n countScan(budget, page.objects.length)\n for (const object of page.objects) {\n if (now - object.uploaded.getTime() > markerMaxAge(object, maxAgeMs)) {\n if (budget.staleKeys.length < 1000) budget.staleKeys.push(object.key)\n } else if (object.key !== excludedKey) {\n total += markerBytes(object)\n }\n }\n cursor = page.truncated ? page.cursor : undefined\n } while (cursor !== undefined)\n return total\n}\n\nasync function measuredAllocationUsage(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n excludedMarkerKey?: string,\n): Promise<number> {\n const budget: QuotaScanBudget = { lists: 0, objects: 0, staleKeys: [] }\n let total = 0\n // Sequential by design: an account with many historical identities must not\n // turn one request into hundreds of concurrent R2 operations.\n for (const prefix of prefixes) total += await prefixUsage(bucket, prefix, budget)\n total += await markerUsage(\n bucket,\n pendingMarkerPrefix(storage),\n PENDING_MARKER_MAX_AGE_MS,\n budget,\n excludedMarkerKey,\n )\n total += await markerUsage(\n bucket,\n multipartMarkerPrefix(storage),\n ACTIVE_MULTIPART_MARKER_MAX_AGE_MS,\n budget,\n excludedMarkerKey,\n )\n if (budget.staleKeys.length > 0) {\n await bucket.delete(budget.staleKeys).catch((error) => {\n console.error('[files:quota] stale marker cleanup:', error)\n })\n }\n return total\n}\n\nfunction isQuotaSummary(value: unknown): value is QuotaSummary {\n if (!value || typeof value !== 'object') return false\n const summary = value as Partial<QuotaSummary>\n return (\n summary.version === QUOTA_SUMMARY_VERSION &&\n Array.isArray(summary.prefixes) &&\n summary.prefixes.every((prefix) => typeof prefix === 'string') &&\n Number.isSafeInteger(summary.usedBytes) &&\n (summary.usedBytes ?? -1) >= 0 &&\n Number.isSafeInteger(summary.measuredAt) &&\n (summary.measuredAt ?? -1) >= 0 &&\n typeof summary.dirty === 'boolean'\n )\n}\n\nasync function readQuotaSummary(bucket: R2Bucket, key: string): Promise<QuotaSnapshot> {\n const object = await bucket.get(key)\n if (!object) return { etag: null, summary: null }\n try {\n const parsed = await object.json<unknown>()\n return { etag: object.etag, summary: isQuotaSummary(parsed) ? parsed : null }\n } catch {\n return { etag: object.etag, summary: null }\n }\n}\n\nasync function writeQuotaSummary(\n bucket: R2Bucket,\n key: string,\n previousEtag: string | null,\n summary: QuotaSummary,\n): Promise<QuotaSnapshot | null> {\n const onlyIf = previousEtag\n ? { etagMatches: previousEtag }\n : new Headers({ 'If-None-Match': '*' })\n const object = await bucket.put(key, JSON.stringify(summary), {\n onlyIf,\n httpMetadata: { contentType: 'application/json' },\n })\n return object ? { etag: object.etag, summary } : null\n}\n\nasync function ensureQuotaSummary(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n forceMeasure = false,\n excludedMarkerKey?: string,\n): Promise<QuotaSnapshot | null> {\n for (let attempt = 0; attempt < 4; attempt++) {\n const current = await readQuotaSummary(bucket, storage.quotaKey)\n if (\n !forceMeasure &&\n current.summary &&\n accountPrefixesEqual(current.summary.prefixes, prefixes)\n ) {\n return current\n }\n const usedBytes = await measuredAllocationUsage(bucket, storage, prefixes, excludedMarkerKey)\n const repaired = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n version: QUOTA_SUMMARY_VERSION,\n prefixes,\n usedBytes,\n measuredAt: Date.now(),\n dirty: false,\n })\n if (repaired) return repaired\n }\n return null\n}\n\nexport async function markQuotaDirty(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n): Promise<void> {\n if (!storage) return\n try {\n for (let attempt = 0; attempt < 4; attempt++) {\n const current = await readQuotaSummary(bucket, storage.quotaKey)\n if (!current.summary || current.summary.dirty) return\n const written = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n ...current.summary,\n dirty: true,\n })\n if (written) return\n }\n console.error('[files:quota] could not mark summary reclaimable after concurrent writes')\n } catch (error) {\n console.error('[files:quota] could not mark summary reclaimable:', error)\n }\n}\n\nasync function neutralizeMarker(bucket: R2Bucket, markerKey: string): Promise<boolean> {\n for (let attempt = 0; attempt < 4; attempt++) {\n const marker = await bucket.get(markerKey)\n if (!marker) return true\n if (markerBytes(marker) === 0) return true\n // Preserve multipart recovery state if the following delete keeps failing;\n // a completion retry can then neutralize/delete the same marker again.\n const body = await marker.arrayBuffer()\n const object = await bucket.put(markerKey, body, {\n onlyIf: { etagMatches: marker.etag },\n customMetadata: {\n ...marker.customMetadata,\n reservedBytes: '0',\n releasedAt: String(Date.now()),\n },\n })\n if (object) return true\n }\n return false\n}\n\n/**\n * Make a reservation stop counting before attempting its best-effort delete.\n * A failed delete therefore leaves a zero-byte cleanup record, not days of\n * phantom quota. `summaryMayBeStale` is false only when admission refused\n * before its counter reservation landed.\n */\nexport async function releaseQuotaMarker(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n markerKey: string | null,\n summaryMayBeStale: boolean,\n): Promise<boolean> {\n try {\n if (markerKey && !(await neutralizeMarker(bucket, markerKey))) {\n console.error('[files:quota] could not neutralize reservation marker')\n return false\n }\n if (summaryMayBeStale) await markQuotaDirty(bucket, storage)\n if (markerKey) {\n for (let attempt = 0; attempt < 4; attempt++) {\n try {\n await bucket.delete(markerKey)\n break\n } catch (error) {\n if (attempt === 3) throw error\n }\n }\n }\n return true\n } catch (error) {\n console.error('[files:quota] reservation cleanup:', error)\n return false\n }\n}\n\nfunction parseMultipartMarker(value: unknown): MultipartMarkerState | null {\n if (!value || typeof value !== 'object') return null\n const marker = value as Partial<MultipartMarkerState>\n if (\n marker.version !== 1 ||\n typeof marker.reservationId !== 'string' ||\n !marker.reservationId ||\n typeof marker.uploadId !== 'string' ||\n !marker.uploadId ||\n typeof marker.objectKey !== 'string' ||\n !marker.objectKey ||\n typeof marker.uploadKey !== 'string' ||\n !marker.uploadKey ||\n typeof marker.admitted !== 'boolean' ||\n !Number.isSafeInteger(marker.declaredBytes) ||\n (marker.declaredBytes ?? -1) < 0\n ) {\n return null\n }\n return marker as MultipartMarkerState\n}\n\nfunction multipartMarkerBody(reservation: MultipartQuotaReservation): string {\n return JSON.stringify({\n version: 1,\n reservationId: reservation.reservationId,\n uploadId: reservation.uploadId,\n objectKey: reservation.objectKey,\n uploadKey: reservation.uploadKey,\n admitted: reservation.admitted,\n declaredBytes: reservation.declaredBytes,\n } satisfies MultipartMarkerState)\n}\n\nexport async function readMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n): Promise<MultipartQuotaReservation | null | 'invalid'> {\n if (!storage) return null\n const key = multipartMarkerKey(storage, reservationId)\n let object: R2ObjectBody | null\n try {\n object = await bucket.get(key)\n } catch (error) {\n console.error('[files:quota] reservation read:', error)\n return 'invalid'\n }\n if (!object) return null\n let marker: MultipartMarkerState | null = null\n try {\n marker = parseMultipartMarker(await object.json<unknown>())\n } catch {\n return 'invalid'\n }\n if (!marker || marker.reservationId !== reservationId) return 'invalid'\n let reservedBytes: number\n try {\n reservedBytes = markerBytes(object)\n } catch {\n return 'invalid'\n }\n return {\n key,\n etag: object.etag,\n reservationId,\n uploadId: marker.uploadId,\n objectKey: marker.objectKey,\n uploadKey: marker.uploadKey,\n admitted: marker.admitted,\n declaredBytes: marker.declaredBytes,\n reservedBytes,\n }\n}\n\n/** Refresh an active multipart marker without carrying a per-part ledger. */\nexport async function refreshMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n uploadId: string,\n objectKey: string,\n): Promise<MultipartQuotaReservation | null | 'invalid' | 'busy'> {\n if (!storage) return null\n for (let attempt = 0; attempt < 4; attempt++) {\n const reservation = await readMultipartReservation(bucket, storage, reservationId)\n if (reservation === null || reservation === 'invalid') return reservation ?? 'invalid'\n if (\n !reservation.admitted ||\n reservation.uploadId !== uploadId ||\n reservation.objectKey !== objectKey\n ) {\n return 'invalid'\n }\n const object = await bucket.put(reservation.key, multipartMarkerBody(reservation), {\n onlyIf: { etagMatches: reservation.etag },\n customMetadata: {\n quotaKind: 'multipart',\n reservedBytes: String(reservation.reservedBytes),\n active: 'true',\n },\n })\n if (object) return { ...reservation, etag: object.etag }\n }\n return 'busy'\n}\n\nexport async function activateMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n): Promise<MultipartQuotaReservation | null> {\n if (!storage) return null\n for (let attempt = 0; attempt < 4; attempt++) {\n const reservation = await readMultipartReservation(bucket, storage, reservationId)\n if (!reservation || reservation === 'invalid') return null\n if (reservation.admitted) return reservation\n const next = { ...reservation, admitted: true }\n const object = await bucket.put(reservation.key, multipartMarkerBody(next), {\n onlyIf: { etagMatches: reservation.etag },\n customMetadata: {\n quotaKind: 'multipart',\n reservedBytes: String(reservation.reservedBytes),\n active: 'false',\n },\n })\n if (object) {\n return { ...next, etag: object.etag }\n }\n }\n return null\n}\n\nfunction quotaExceeded(\n incomingBytes: number,\n summaryBytes: number,\n replacedBytes: number,\n limitBytes: number,\n): Response {\n const usedBytes = Math.max(0, summaryBytes - replacedBytes)\n return fail(\n 409,\n storageQuotaMessage(incomingBytes, usedBytes, limitBytes),\n 'storage_quota_exceeded',\n { usedBytes, limitBytes },\n )\n}\n\nasync function reserveQuotaSummary(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n initial: QuotaSnapshot,\n chargeBytes: number,\n incomingBytes: number,\n replacedBytes: number,\n limitBytes: number,\n markerKey: string | null,\n): Promise<Response | null> {\n if (chargeBytes === 0) return null\n let current = initial\n let repaired = false\n for (let attempt = 0; attempt < 8; attempt++) {\n if (!current.summary || !accountPrefixesEqual(current.summary.prefixes, prefixes)) {\n const ensured = await ensureQuotaSummary(\n bucket,\n storage,\n prefixes,\n false,\n markerKey ?? undefined,\n )\n if (!ensured) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n current = ensured\n }\n const summary = current.summary\n if (!summary) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n if (summary.usedBytes + chargeBytes > limitBytes) {\n const recentlyMeasured = Date.now() - summary.measuredAt < QUOTA_REPAIR_COOLDOWN_MS\n if (repaired || (!summary.dirty && recentlyMeasured)) {\n return quotaExceeded(incomingBytes, summary.usedBytes, replacedBytes, limitBytes)\n }\n const refreshed = await ensureQuotaSummary(\n bucket,\n storage,\n prefixes,\n true,\n markerKey ?? undefined,\n )\n if (!refreshed?.summary) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n current = refreshed\n repaired = true\n continue\n }\n const written = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n ...summary,\n usedBytes: summary.usedBytes + chargeBytes,\n })\n if (written) return null\n current = await readQuotaSummary(bucket, storage.quotaKey)\n }\n return quotaUnavailable('Storage is busy with other uploads. Retry shortly.')\n}\n\nexport async function admitStorage(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n incomingBytes: number,\n replacedBytes: number,\n marker?: QuotaMarkerSpec,\n): Promise<StorageReservation | Response> {\n if (!storage) return { markerKey: null }\n let markerKey: string | null = null\n try {\n const limit = await storage.limitBytes()\n if (limit === null) {\n return quotaUnavailable(\n 'The storage limit for this app could not be verified; nothing was uploaded. Retry shortly.',\n )\n }\n const prefixes = await allocationPrefixes(storage)\n if (prefixes === null) {\n return quotaUnavailable(\n 'The apps in this storage allocation could not be enumerated; nothing was uploaded. Retry shortly.',\n )\n }\n const initial = await ensureQuotaSummary(bucket, storage, prefixes)\n if (!initial) {\n return quotaUnavailable(\n 'The storage already used by this account could not be measured; nothing was uploaded. Retry shortly.',\n )\n }\n\n const chargeBytes = Math.max(0, incomingBytes - replacedBytes)\n const effectiveMarker =\n marker ??\n (chargeBytes > 0\n ? {\n key: `${pendingMarkerPrefix(storage)}${crypto.randomUUID()}`,\n metadata: { quotaKind: 'pending' },\n }\n : null)\n if (effectiveMarker) {\n const object = await bucket.put(effectiveMarker.key, effectiveMarker.body ?? '', {\n onlyIf: new Headers({ 'If-None-Match': '*' }),\n customMetadata: {\n ...effectiveMarker.metadata,\n reservedBytes: String(chargeBytes),\n },\n })\n if (!object) {\n return fail(\n 409,\n 'An upload with this request id is already in progress.',\n 'reservation_conflict',\n )\n }\n markerKey = effectiveMarker.key\n }\n\n const refusal = await reserveQuotaSummary(\n bucket,\n storage,\n prefixes,\n initial,\n chargeBytes,\n incomingBytes,\n replacedBytes,\n limit,\n effectiveMarker?.key ?? null,\n )\n if (refusal) {\n await releaseQuotaMarker(bucket, storage, effectiveMarker?.key ?? null, false)\n return refusal\n }\n return { markerKey: effectiveMarker?.key ?? null }\n } catch (error) {\n console.error('[files:quota] admission:', error)\n await releaseQuotaMarker(bucket, storage, markerKey, true)\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n}\n\nexport async function storageUsage(\n bucket: R2Bucket,\n storage: StorageAdmission,\n): Promise<number | null> {\n try {\n const prefixes = await allocationPrefixes(storage)\n if (prefixes === null) return null\n const current = await ensureQuotaSummary(bucket, storage, prefixes)\n return current?.summary?.usedBytes ?? null\n } catch (error) {\n console.error('[files:quota] usage:', error)\n return null\n }\n}\n","/**\n * Shared Scoped R2 Files Handler\n *\n * Provides a secure, prefix-scoped R2 files API that enforces:\n * 1. All R2 keys are validated against the resolved prefix (no bypass)\n * 2. Path traversal (`..`, `.`) is rejected\n * 3. Mutations (upload/delete) require authentication by default\n *\n * Each worker provides a `resolvePrefix` callback for its scoping rules.\n * The security invariants are enforced here once — not per-worker.\n *\n * Routes:\n * POST /api/files/upload → upload one file in one request\n * POST /api/files/multipart → begin a chunked upload\n * PUT /api/files/multipart/part → send one part\n * POST /api/files/multipart/complete → assemble the parts\n * DELETE /api/files/multipart → abandon a chunked upload\n * GET /api/files → list (prefix + optional user prefix)\n * GET /api/files/:key → download (validated against prefix)\n * DELETE /api/files/:key → delete (validated against prefix)\n *\n * ## One transport discipline\n *\n * Both upload paths obey the rule the deploy asset transport established\n * (`docs/platform/deploy-asset-transport.md`): nothing here\n * ever holds more than one bounded request in memory. The single-request path\n * exists so a small file still costs one round trip; above\n * {@link UPLOAD_PART_BYTES} a file travels as parts, each streamed into R2\n * through a `FixedLengthStream` with a required `Content-Length`. That is why\n * a 1 GiB file is admissible in a ~128 MiB isolate.\n *\n * Validation is identical on both paths and on both mounts, because it lives\n * here: the prefix scoping, the traversal check, the MIME refusals, and both\n * size bounds. A hand-rolled request that skips every client guard reaches\n * exactly these checks.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport {\n ACCOUNT_STORAGE_LIMIT_BYTES,\n DANGEROUS_MIME_TYPES,\n MAX_APP_FILE_BYTES,\n MAX_DEPLOY_ASSET_FILE_BYTES,\n MAX_UPLOAD_PARTS,\n MAX_UPLOAD_REQUEST_BYTES,\n UPLOAD_PART_BYTES,\n encodeKeyPath,\n formatBytes,\n oversizeMessage,\n oversizePartMessage,\n oversizeRequestMessage,\n storageLimitForTier,\n storageQuotaMessage,\n} from '../../shared/app-files'\nimport {\n activateMultipartReservation,\n admitStorage,\n markQuotaDirty,\n multipartMarkerKey,\n quotaUnavailable,\n readMultipartReservation,\n refreshMultipartReservation,\n releaseQuotaMarker,\n storageUsage,\n} from './r2-storage-quota'\nimport type { MultipartQuotaReservation, StorageAdmission } from './r2-storage-quota'\n\nexport type { StorageAdmission } from './r2-storage-quota'\n\n// The limits this handler enforces, re-exported so workers and their tests\n// cite the constants rather than copies of the numbers — two copies of one\n// limit is how the CLI and the workers drift. `formatBytes` rides along for\n// the same reason.\nexport {\n ACCOUNT_STORAGE_LIMIT_BYTES,\n MAX_APP_FILE_BYTES,\n MAX_DEPLOY_ASSET_FILE_BYTES,\n MAX_UPLOAD_REQUEST_BYTES,\n UPLOAD_PART_BYTES,\n MAX_UPLOAD_PARTS,\n formatBytes,\n storageLimitForTier,\n storageQuotaMessage,\n}\n\nconst CORS_HEADERS = { 'Access-Control-Allow-Origin': '*' } as const\n\n/** Every refusal in this file, in one shape. */\nfunction fail(\n status: number,\n error: string,\n code?: string,\n extra?: Record<string, unknown>,\n): Response {\n return Response.json(\n { error, ...(code ? { code } : {}), ...extra },\n { status, headers: CORS_HEADERS },\n )\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ScopeContext {\n userId: string | null\n url: URL\n}\n\nexport type PrefixResult =\n | { prefix: string; excludedPrefixes?: readonly string[]; error?: undefined }\n | { prefix?: undefined; error: string }\n\nexport interface ScopedR2Config {\n /**\n * Resolve the R2 key prefix for the given scope.\n * Called with the `?scope=` query param value (default: 'self').\n */\n resolvePrefix: (scope: string, ctx: ScopeContext) => PrefixResult\n\n /**\n * Require a non-null userId for upload and delete.\n * @default true\n */\n requireAuthForMutations?: boolean\n}\n\n/**\n * The storage-quota contract for one allocation, supplied per request by the\n * mount. The handler is the enforcer; the mount only knows whose limit\n * applies.\n */\nexport interface ScopedR2Auth {\n userId: string | null\n /** Absent = no quota on this mount (e.g. an app's own bucket). */\n storage?: StorageAdmission\n /** Account-wide quota details are private to an authorized owner surface. */\n includeStorageUsage?: boolean\n}\n\nexport type ScopedR2Handler = (\n request: Request,\n url: URL,\n bucket: R2Bucket,\n auth: ScopedR2Auth,\n) => Promise<Response>\n\n// ============================================================================\n// Path Security\n// ============================================================================\n\n/**\n * Build a safe Content-Disposition header value from an uploader-supplied\n * filename. The legacy `filename=` parameter is double-quoted, so a name\n * containing `\"` or CR/LF can break out of the value and inject arbitrary\n * response headers (response-splitting). We strip those characters for the\n * legacy parameter and use RFC 5987 `filename*` with percent-encoding for\n * the canonical, UTF-8-safe form that modern browsers prefer.\n */\nfunction contentDisposition(originalName: string, disposition: 'inline' | 'attachment'): string {\n // The legacy `filename=` value must be ASCII as well as header-safe; Fetch\n // implementations may reject raw Unicode header bytes. `filename*` carries\n // the exact UTF-8 name for modern clients.\n const safe = originalName.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, '_') || 'download'\n // encodeURIComponent leaves five characters that RFC 5987 does not admit\n // in an attr-char value, so escape those too.\n const encoded = encodeURIComponent(originalName).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n return `${disposition}; filename=\"${safe}\"; filename*=UTF-8''${encoded}`\n}\n\n/**\n * Reject path traversal attempts.\n * Returns null if the path contains `..` or `.` segments.\n */\nfunction sanitizeSubpath(raw: string): string | null {\n if (!raw) return ''\n const segments = raw.split('/')\n if (segments.some((s) => s === '..' || s === '.')) return null\n return raw\n}\n\n/**\n * Refuse a request body past {@link MAX_UPLOAD_REQUEST_BYTES}.\n *\n * This is where the transport's physical bound is real. Clients check it too,\n * but only to fail early — a hand-rolled request that skips them reaches here,\n * and without this the worker would buffer the whole body into a ~128 MiB\n * isolate. Callers check Content-Length BEFORE reading the body, then the\n * decoded size, which catches a body that lied about (or omitted) its length.\n * The 8 KiB slack leaves room for multipart headers around a maximum body.\n */\nfunction overRequestBound(bytes: number, slack = 0): Response | null {\n if (bytes <= MAX_UPLOAD_REQUEST_BYTES + slack) return null\n return fail(413, oversizeRequestMessage(bytes), 'too_large', {\n maxBytes: MAX_UPLOAD_REQUEST_BYTES,\n partSize: UPLOAD_PART_BYTES,\n })\n}\n\n/** Refuse a FILE past {@link MAX_APP_FILE_BYTES} — the product ceiling. */\nfunction overCeiling(bytes: number): Response | null {\n if (bytes <= MAX_APP_FILE_BYTES) return null\n return fail(413, oversizeMessage(bytes), 'too_large', { maxBytes: MAX_APP_FILE_BYTES })\n}\n\n/**\n * Refuse a part past {@link UPLOAD_PART_BYTES}.\n *\n * Tighter than the request bound on purpose. Parts must be uniform, so a\n * client sending a bigger one has ignored what init told it — and this is the\n * number {@link MAX_UPLOAD_PARTS} is computed against, so admitting a larger\n * part would silently raise the real ceiling above the advertised one.\n */\nfunction overPartBound(bytes: number): Response | null {\n if (bytes <= UPLOAD_PART_BYTES) return null\n return fail(413, oversizePartMessage(bytes), 'too_large', { maxBytes: UPLOAD_PART_BYTES })\n}\n\n/**\n * The length a request declares, or the refusal for one that declares none.\n *\n * `Number(null)` is 0, so reading the header without testing for its ABSENCE\n * treats a chunked body as an empty one — and it then passes every size bound\n * before anything has been read. That is not theoretical: a chunked control\n * body sailed past the 64 KiB cap into an unbounded `request.json()`, and a\n * chunked part reached `FixedLengthStream(0)` and failed as \"declared 0 bytes\n * but its body did not carry them\", blaming the client for the server's own\n * missing check. One helper, so both paths ask the question the same way.\n */\nfunction declaredLength(request: Request, what: string): number | Response {\n const raw = request.headers.get('content-length')\n const declared = raw === null ? NaN : Number(raw)\n if (!Number.isSafeInteger(declared) || declared < 0) {\n return fail(\n 411,\n `${what} must declare a Content-Length. A chunked or length-less body is refused: ` +\n `R2 cannot size a write it has no length for.`,\n 'length_required',\n )\n }\n return declared\n}\n\n/**\n * Validate that a key falls within the expected prefix.\n *\n * This is the core security check: the resolved prefix is derived from\n * authenticated context (userId, widgetId, appName), so ensuring the\n * requested key starts with the prefix prevents cross-scope access.\n */\nfunction isKeyWithinScope(\n key: string,\n prefix: string,\n excludedPrefixes: readonly string[],\n): boolean {\n return key.startsWith(prefix) && !excludedPrefixes.some((excluded) => key.startsWith(excluded))\n}\n\n/**\n * The key a caller can address within its own scope: the stored key minus the\n * resolved prefix. Clients never learn the prefix (it is derived from their\n * authenticated context), so every response that names an absolute key also\n * carries this spelling — the one that works with `list(prefix)` filters and\n * `?key=` upserts. The inverse of {@link anchorKey}, and the only place the\n * slice happens.\n */\nfunction scopeRelativeKey(key: string, prefix: string): string {\n return key.slice(prefix.length)\n}\n\n/**\n * Anchor a caller-supplied key under the resolved prefix.\n *\n * The one place a client gets to name a location. Used by the single-request\n * upsert (`?key=`) and by every multipart call (`?uploadKey=`), so a key can\n * never address another scope on either path — and a stolen `uploadId` is\n * inert, because the key it must be resumed with is rebuilt from the caller's\n * own authenticated prefix.\n */\nfunction anchorKey(\n raw: string,\n prefix: string,\n excludedPrefixes: readonly string[],\n): { key: string } | Response {\n const safe = sanitizeSubpath(raw)\n if (safe === null) return fail(400, 'Invalid key: traversal not allowed', 'invalid_key')\n if (!safe) return fail(400, 'Invalid key: empty', 'invalid_key')\n const key = `${prefix}${safe}`\n return isKeyWithinScope(key, prefix, excludedPrefixes)\n ? { key }\n : fail(403, 'Access denied: key belongs to a private scope', 'forbidden')\n}\n\n/**\n * The handler's own verbs: subpaths that name an operation rather than a key.\n *\n * Exported because the owner mount has to know them. That mount anchors a\n * caller's key under the app's prefix before dispatching here, and a verb must\n * arrive un-prefixed — so both sides read this one table and cannot drift.\n */\nconst VERB_ROUTES = new Set([\n 'POST upload',\n 'POST multipart',\n 'DELETE multipart',\n 'PUT multipart/part',\n 'POST multipart/complete',\n])\n\nexport function isFilesVerbPath(method: string, subpath: string): boolean {\n return VERB_ROUTES.has(`${method} ${subpath}`)\n}\n\n// ============================================================================\n// MIME Security\n// ============================================================================\n\n// The refused set lives in shared/app-files.ts so the CLI and the browser\n// hook check the same list before sending; this handler stays the enforcer.\n/**\n * Normalize the stored media type. Active content is valid downloadable data;\n * the download boundary forces it to an attachment so it cannot execute in\n * the app's origin.\n */\nfunction resolveMimeType(raw: string): string {\n const mimeType = raw.split(';')[0].trim().toLowerCase() || 'application/octet-stream'\n return mimeType\n}\n\n/**\n * R2 names its refusals with a numeric code at the end of the message\n * (`\"uploadPart: The specified multipart upload does not exist. (10024)\"`).\n * Reading it is what lets a client be told which mistake it made instead of\n * being handed a 500.\n */\nfunction r2ErrorCode(error: unknown): number | null {\n const message = error instanceof Error ? error.message : String(error)\n const match = /\\((\\d{4,6})\\)\\s*$/.exec(message.trim())\n return match ? Number(match[1]) : null\n}\n\n/**\n * Turn an R2 multipart failure into an honest status and sentence.\n *\n * The codes are R2's documented ones (`r2/api/error-codes`). Note that the\n * local emulator is not faithful for every one of them — it answers 10001\n * (\"internal error\") where production R2 answers 10024 on `complete` for an\n * unknown session — so this maps what R2 documents, and the tests assert\n * against the operations the emulator does reproduce.\n */\nfunction multipartFailure(error: unknown): Response {\n switch (r2ErrorCode(error)) {\n case 10024: // NoSuchUpload\n return fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n )\n case 10011: // EntityTooSmall\n case 10025: // InvalidPart — a named part was never uploaded\n case 10048: // InvalidPart — non-final parts must all be the same size\n return fail(\n 400,\n 'The parts do not assemble: every part except the last must be exactly the size the ' +\n 'upload advertised, and each must be uploaded before it is named.',\n 'bad_parts',\n )\n case 10012: // MetadataTooLarge\n case 10020: // InvalidObjectName\n return fail(400, 'The upload names a key or metadata R2 will not accept.', 'invalid_key')\n default: {\n const message = error instanceof Error ? error.message : String(error)\n console.error('[files:multipart] R2 error:', message)\n return fail(\n 503,\n 'The files service could not complete that upload. Retry shortly.',\n 'r2_error',\n )\n }\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create a scoped R2 files handler.\n *\n * Security guarantees:\n * - Download/delete keys are validated to start with the resolved prefix\n * - Path traversal (`..`) is rejected at the entry point\n * - Mutations require a non-null userId by default\n *\n * @returns A handler function: `(request, url, bucket, auth) => Promise<Response>`\n */\nexport function createScopedR2Handler(config: ScopedR2Config): ScopedR2Handler {\n const requireAuthForMutations = config.requireAuthForMutations ?? true\n\n return async (request, url, bucket, auth) => {\n // url.pathname preserves percent-encoding (e.g. spaces → %20), but R2\n // keys are stored with raw characters. Decode so the lookup matches.\n let subpathRaw: string\n try {\n subpathRaw = decodeURIComponent(url.pathname.replace('/api/files', '').replace(/^\\//, ''))\n } catch {\n return Response.json(\n { error: 'Invalid path encoding' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Sanitize — reject traversal attempts\n const subpath = sanitizeSubpath(subpathRaw)\n if (subpath === null) {\n return Response.json(\n { error: 'Invalid path: traversal not allowed' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Every operation defaults to per-user storage. Public app-scope reads\n // must say `?scope=app`; upload/list responses preserve that selector in\n // their URLs. This is load-bearing because user keys are nested below the\n // app prefix — treating every keyed GET as app scope exposed known private\n // keys anonymously.\n const scope = url.searchParams.get('scope') || 'self'\n if (scope !== 'self' && scope !== 'app') {\n return Response.json({ error: 'Invalid scope' }, { status: 400, headers: CORS_HEADERS })\n }\n const result = config.resolvePrefix(scope, { userId: auth.userId, url })\n\n if (!result.prefix) {\n const errorMsg = result.error ?? 'Invalid scope'\n const status = errorMsg.toLowerCase().includes('auth') ? 401 : 400\n return Response.json({ error: errorMsg }, { status, headers: CORS_HEADERS })\n }\n\n const prefix: string = result.prefix\n const excludedPrefixes = result.excludedPrefixes ?? []\n\n // ── Upload: one request, or a chunked session ───────────────────────\n if (isFilesVerbPath(request.method, subpath)) {\n if (requireAuthForMutations && !auth.userId) {\n // Nothing past this point reads the body, so tell the client rather\n // than leaving the runtime to tear the connection down mid-upload.\n await request.body?.cancel().catch(() => undefined)\n return fail(401, 'Authentication required', 'unauthorized')\n }\n return subpath === 'upload'\n ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope)\n : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath)\n }\n\n // ── List ────────────────────────────────────────────────────────────\n if (!subpath && request.method === 'GET') {\n return handleList(bucket, prefix, excludedPrefixes, url, scope, auth)\n }\n\n // ── Download ────────────────────────────────────────────────────────\n if (subpath && request.method === 'GET') {\n if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {\n return Response.json(\n { error: 'Access denied: key outside scope' },\n { status: 403, headers: CORS_HEADERS },\n )\n }\n return handleDownload(request, bucket, subpath, scope)\n }\n\n // ── Delete ──────────────────────────────────────────────────────────\n if (subpath && request.method === 'DELETE') {\n if (requireAuthForMutations && !auth.userId) {\n return Response.json(\n { error: 'Authentication required' },\n { status: 401, headers: CORS_HEADERS },\n )\n }\n if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {\n return Response.json(\n { error: 'Access denied: key outside scope' },\n { status: 403, headers: CORS_HEADERS },\n )\n }\n // R2 deletes are idempotent, so a typo'd key used to report exactly what\n // a real one did. Report which happened — but keep the 200: this is a\n // shipped API and deployed apps branch on `res.ok`, so turning an\n // idempotent DELETE into a 404 would break them. `deleted` carries the\n // fact; the CLI is where it becomes a visible refusal.\n const existed = (await bucket.head(subpath)) !== null\n if (existed) {\n await bucket.delete(subpath)\n // The high-water summary never decrements speculatively. Marking it\n // reclaimable lets the next would-be refusal repair immediately.\n await markQuotaDirty(bucket, auth.storage)\n }\n return Response.json(\n { success: true, deleted: existed ? subpath : null, existed },\n { headers: CORS_HEADERS },\n )\n }\n\n return Response.json({ error: 'Method not allowed' }, { status: 405, headers: CORS_HEADERS })\n }\n}\n\n// ============================================================================\n// Operation Handlers\n// ============================================================================\n\nasync function handleUpload(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n): Promise<Response> {\n try {\n // Before anything reads the body.\n const declaredBytes = declaredLength(request, 'File uploads')\n if (declaredBytes instanceof Response) return declaredBytes\n const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024)\n if (tooLargeRequest) return tooLargeRequest\n\n const contentType = request.headers.get('content-type') || ''\n let fileData: ArrayBuffer\n let fileName: string\n let rawMimeType: string\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData()\n const file = formData.get('file') as File | null\n if (!file) return fail(400, 'No file provided', 'bad_request')\n const tooBig = overRequestBound(file.size)\n if (tooBig) return tooBig\n fileData = await file.arrayBuffer()\n fileName = formData.get('name')?.toString() || file.name\n rawMimeType = file.type || 'application/octet-stream'\n } else {\n const body = (await request.json()) as { data: string; name: string; mimeType?: string }\n if (!body.data || !body.name) return fail(400, 'Missing data or name', 'bad_request')\n const base64Data = body.data.replace(/^data:[^;]+;base64,/, '')\n // Check the DECODED size before allocating it: base64 carries 3 bytes\n // per 4 characters, so a body inside the Content-Length guard can still\n // decode past the bound.\n const tooBig = overRequestBound(Math.floor((base64Data.length * 3) / 4))\n if (tooBig) return tooBig\n fileData = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)).buffer\n fileName = body.name\n rawMimeType = body.mimeType || 'application/octet-stream'\n }\n\n const mimeType = resolveMimeType(rawMimeType)\n\n const located = locateUpload(url, prefix, excludedPrefixes, fileName)\n if (located instanceof Response) return located\n\n // Only an explicit `?key=` can replace something; generated keys are fresh.\n const replacesKey = url.searchParams.get('key') === null ? null : located.key\n const replaced = replacesKey === null ? null : await bucket.head(replacesKey)\n const replacedBytes = replaced?.size ?? 0\n const admission = await admitStorage(bucket, auth.storage, fileData.byteLength, replacedBytes)\n if (admission instanceof Response) return admission\n\n try {\n const written = await bucket.put(located.key, fileData, {\n // Replacement credit is valid only while the exact object that earned\n // it still exists. A concurrent delete or overwrite loses this CAS and\n // retries from a fresh head instead of landing uncounted bytes.\n ...(replaced ? { onlyIf: { etagMatches: replaced.etag } } : {}),\n httpMetadata: { contentType: mimeType },\n customMetadata: fileMetadata(fileName, auth.userId),\n })\n if (!written) {\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n return fail(\n 409,\n 'The file changed while this upload was being admitted. Retry the upload.',\n 'write_conflict',\n )\n }\n } catch (error) {\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n throw error\n }\n await releaseQuotaMarker(\n bucket,\n auth.storage,\n admission.markerKey,\n admission.markerKey !== null || fileData.byteLength < replacedBytes,\n )\n return storedFileJson(located.key, prefix, fileName, url, scope)\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Upload failed'\n console.error('[handleUpload] Error:', msg, err instanceof Error ? err.stack : '')\n return fail(500, msg, 'upload_failed')\n }\n}\n\n/**\n * Where an upload lands.\n *\n * Deterministic key mode: `?key=<subkey>` overrides the timestamped-filename\n * behavior so callers can upsert at a known location. Without it a unique key\n * is generated per upload, so two uploads of the same name don't clobber each\n * other. Both paths use it, so \"which key\" is decided once.\n */\nfunction locateUpload(\n url: URL,\n prefix: string,\n excludedPrefixes: readonly string[],\n fileName: string,\n generatedId?: string,\n): { key: string; subkey: string } | Response {\n const requested = url.searchParams.get('key')\n const subkey =\n requested ??\n (generatedId\n ? `${generatedId}-${fileName}`\n : `${Date.now()}-${Math.random().toString(36).slice(2, 11)}-${fileName}`)\n const anchored = anchorKey(subkey, prefix, excludedPrefixes)\n return anchored instanceof Response ? anchored : { key: anchored.key, subkey }\n}\n\nfunction fileMetadata(\n fileName: string,\n userId: string | null,\n reservationId?: string,\n declaredBytes?: number,\n): Record<string, string> {\n return {\n originalName: fileName,\n ...(userId ? { uploadedBy: userId } : {}),\n ...(reservationId ? { reservationId } : {}),\n ...(declaredBytes === undefined ? {} : { declaredBytes: String(declaredBytes) }),\n uploadedAt: new Date().toISOString(),\n }\n}\n\n/** The one success shape for a stored file, whichever path stored it. */\nfunction storedFileJson(\n key: string,\n prefix: string,\n name: string,\n url: URL,\n scope: string,\n): Response {\n const fileUrl = new URL(`/api/files/${encodeKeyPath(key)}`, url.origin)\n fileUrl.searchParams.set('scope', scope)\n return Response.json(\n {\n success: true,\n key,\n relativeKey: scopeRelativeKey(key, prefix),\n url: fileUrl.toString(),\n name,\n },\n { headers: CORS_HEADERS },\n )\n}\n\n// ============================================================================\n// Multipart: the transport for anything above one request\n// ============================================================================\n\n/** Control bodies are metadata. 52 parts serialize to ~3 KiB. */\nconst MAX_CONTROL_BODY_BYTES = 64 * 1024\n\n/**\n * Read a small JSON control body.\n *\n * A `Content-Length` is required: every real client sends one for a JSON body,\n * and without it there is nothing to bound the read against before it happens.\n */\nasync function readControlJson(request: Request): Promise<Record<string, unknown> | Response> {\n const declared = declaredLength(request, 'This request')\n if (declared instanceof Response) return declared\n if (declared > MAX_CONTROL_BODY_BYTES) {\n return fail(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, 'too_large')\n }\n let parsed: unknown\n try {\n parsed = await request.json()\n } catch {\n return fail(400, 'Body must be JSON', 'bad_request')\n }\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : fail(400, 'Body must be a JSON object', 'bad_request')\n}\n\nconst REQUEST_ID_RE = /^[A-Za-z0-9_-]{16,80}$/\n\ninterface MultipartSessionRef {\n key: string\n uploadKey: string\n uploadId: string\n reservationId: string\n}\n\n/** Resolve the session a part/complete/abort call names. */\nfunction resolveSession(\n url: URL,\n prefix: string,\n excludedPrefixes: readonly string[],\n): MultipartSessionRef | Response {\n const uploadKey = url.searchParams.get('uploadKey')\n const uploadId = url.searchParams.get('uploadId')\n const reservationId = url.searchParams.get('reservationId')\n if (!uploadKey || !uploadId || !reservationId) {\n return fail(400, 'uploadKey, uploadId, and reservationId are required', 'bad_request')\n }\n if (!REQUEST_ID_RE.test(reservationId)) {\n return fail(400, 'reservationId is invalid', 'bad_request')\n }\n const anchored = anchorKey(uploadKey, prefix, excludedPrefixes)\n return anchored instanceof Response\n ? anchored\n : { key: anchored.key, uploadKey, uploadId, reservationId }\n}\n\nfunction reservationMatches(\n reservation: MultipartQuotaReservation,\n session: MultipartSessionRef,\n): boolean {\n return (\n reservation.admitted &&\n reservation.uploadId === session.uploadId &&\n reservation.objectKey === session.key &&\n reservation.uploadKey === session.uploadKey\n )\n}\n\nfunction multipartSessionJson(\n uploadId: string,\n uploadKey: string,\n reservationId: string,\n): Response {\n return Response.json(\n {\n uploadId,\n uploadKey,\n reservationId,\n partSize: UPLOAD_PART_BYTES,\n maxParts: MAX_UPLOAD_PARTS,\n maxBytes: MAX_APP_FILE_BYTES,\n },\n { headers: CORS_HEADERS },\n )\n}\n\nfunction multipartPartCount(declaredBytes: number): number {\n return Math.ceil(declaredBytes / UPLOAD_PART_BYTES)\n}\n\nfunction expectedMultipartPartBytes(declaredBytes: number, partNumber: number): number | null {\n const partCount = multipartPartCount(declaredBytes)\n if (partNumber < 1 || partNumber > partCount) return null\n return partNumber === partCount\n ? declaredBytes - (partCount - 1) * UPLOAD_PART_BYTES\n : UPLOAD_PART_BYTES\n}\n\nfunction multipartLayoutError(declaredBytes: number): Response {\n return fail(\n 400,\n `This upload must use ${multipartPartCount(declaredBytes)} sequential part(s) at the advertised ${formatBytes(UPLOAD_PART_BYTES)} size, except for the final remainder.`,\n 'bad_part_layout',\n { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) },\n )\n}\n\n/**\n * Stream one part into R2, exactly as the deploy asset transport streams an\n * object: the declared length travels with the bytes through a\n * `FixedLengthStream`, so a truncated or overlong body fails the write instead\n * of landing a part that lies about its size. Only one chunk is ever resident.\n */\nasync function streamPart(\n upload: R2MultipartUpload,\n partNumber: number,\n body: ReadableStream<Uint8Array>,\n expectedBytes: number,\n): Promise<\n { ok: true; part: R2UploadedPart } | { ok: false; lengthMismatch: boolean; error: unknown }\n> {\n let observedBytes = 0\n const sized = new FixedLengthStream(expectedBytes)\n const pumped = body\n .pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n observedBytes += chunk.byteLength\n controller.enqueue(chunk)\n },\n }),\n )\n .pipeTo(sized.writable)\n\n const [uploaded, drained] = await Promise.allSettled([\n upload.uploadPart(partNumber, sized.readable),\n pumped,\n ])\n if (uploaded.status === 'rejected' || drained.status === 'rejected') {\n const reason =\n uploaded.status === 'rejected' ? uploaded.reason : (drained as PromiseRejectedResult).reason\n return { ok: false, lengthMismatch: observedBytes !== expectedBytes, error: reason }\n }\n return { ok: true, part: uploaded.value }\n}\n\nasync function handleMultipart(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n subpath: string,\n): Promise<Response> {\n if (subpath === 'multipart' && request.method === 'POST') {\n return multipartInit(request, url, bucket, prefix, excludedPrefixes, auth)\n }\n if (subpath === 'multipart/part') {\n return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth)\n }\n if (subpath === 'multipart/complete') {\n return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope)\n }\n return multipartAbort(url, bucket, prefix, excludedPrefixes, auth)\n}\n\n/**\n * Begin a chunked upload.\n *\n * Everything checkable is checked here, before a byte of content moves: the\n * declared total against the ceiling, the media type, and the key. The reply\n * carries the part size the client must chunk at — R2 requires every non-final\n * part to be exactly the same size, so the server owning that number is what\n * keeps a client from assembling an upload R2 will refuse.\n */\nasync function multipartInit(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const body = await readControlJson(request)\n if (body instanceof Response) return body\n\n const size = body.size\n if (!Number.isSafeInteger(size) || (size as number) <= 0) {\n return fail(400, 'size must be a positive integer number of bytes', 'bad_request')\n }\n const tooBig = overCeiling(size as number)\n if (tooBig) return tooBig\n\n const name = typeof body.name === 'string' ? body.name.trim() : ''\n if (!name) return fail(400, 'name is required', 'bad_request')\n const requestId = typeof body.requestId === 'string' ? body.requestId : ''\n if (!REQUEST_ID_RE.test(requestId)) {\n return fail(\n 400,\n 'requestId must be 16–80 URL-safe characters and must be reused when retrying init.',\n 'bad_request',\n )\n }\n\n const mimeType = resolveMimeType(typeof body.mimeType === 'string' ? body.mimeType : '')\n\n // A caller-generated request id makes the server-generated key stable across\n // an init retry whose first response was lost.\n const located = locateUpload(url, prefix, excludedPrefixes, name, requestId)\n if (located instanceof Response) return located\n\n // A completed request id is terminal. Reusing it could make a failed\n // completion of a new R2 session look like the old object's successful\n // completion, stranding the new parts without their reservation.\n const completed = await bucket.head(located.key)\n if (completed?.customMetadata?.reservationId === requestId) {\n return fail(\n 409,\n 'requestId already completed an upload; start the new upload with a fresh requestId.',\n 'request_id_completed',\n )\n }\n\n const existing = await readMultipartReservation(bucket, auth.storage, requestId)\n if (existing === 'invalid') {\n return quotaUnavailable('This upload reservation could not be read. Retry shortly.')\n }\n if (existing) {\n const sameRequest =\n existing.objectKey === located.key &&\n existing.uploadKey === located.subkey &&\n existing.declaredBytes === size\n return sameRequest && existing.admitted\n ? multipartSessionJson(existing.uploadId, existing.uploadKey, requestId)\n : sameRequest\n ? quotaUnavailable('This upload is still being initialized. Retry shortly.')\n : fail(\n 409,\n 'requestId already belongs to a different multipart upload.',\n 'request_id_conflict',\n )\n }\n\n let upload: R2MultipartUpload\n try {\n upload = await bucket.createMultipartUpload(located.key, {\n httpMetadata: { contentType: mimeType },\n customMetadata: fileMetadata(name, auth.userId, requestId, size as number),\n })\n } catch (error) {\n return multipartFailure(error)\n }\n\n const marker = auth.storage\n ? {\n key: multipartMarkerKey(auth.storage, requestId),\n body: JSON.stringify({\n version: 1,\n reservationId: requestId,\n uploadId: upload.uploadId,\n objectKey: located.key,\n uploadKey: located.subkey,\n admitted: false,\n declaredBytes: size,\n }),\n metadata: {\n quotaKind: 'multipart',\n active: 'false',\n },\n }\n : undefined\n // Small multipart calls remain part of the plain HTTP contract. Charge at\n // least one R2 minimum part so one-byte sessions cannot create an unbounded\n // number of week-long uploads inside a byte quota.\n const reservedBytes = Math.max(size as number, 5 * 1024 * 1024)\n const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker)\n if (admission instanceof Response) {\n await upload.abort().catch(() => undefined)\n if (admission.status === 409 && auth.storage) {\n const winner = await readMultipartReservation(bucket, auth.storage, requestId)\n if (\n winner &&\n winner !== 'invalid' &&\n winner.objectKey === located.key &&\n winner.uploadKey === located.subkey &&\n winner.declaredBytes === size &&\n winner.admitted\n ) {\n return multipartSessionJson(winner.uploadId, winner.uploadKey, requestId)\n }\n }\n return admission\n }\n if (auth.storage && !(await activateMultipartReservation(bucket, auth.storage, requestId))) {\n await upload.abort().catch(() => undefined)\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n return quotaUnavailable('This upload reservation could not be finalized. Retry shortly.')\n }\n return multipartSessionJson(upload.uploadId, located.subkey, requestId)\n}\n\nasync function abandonMultipart(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n auth: ScopedR2Auth,\n reservation: MultipartQuotaReservation | null,\n): Promise<'aborted' | 'absent' | 'failed'> {\n let result: 'aborted' | 'absent' = 'aborted'\n try {\n await bucket.resumeMultipartUpload(session.key, session.uploadId).abort()\n } catch (error) {\n if (r2ErrorCode(error) !== 10024) {\n console.error('[files:multipart] abort:', error instanceof Error ? error.message : error)\n return 'failed'\n }\n result = 'absent'\n }\n if (reservation) {\n if (!(await releaseQuotaMarker(bucket, auth.storage, reservation.key, true))) return 'failed'\n }\n return result\n}\n\n/** Send one part. The body is streamed; it is never held whole. */\nasync function multipartPart(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const refuse = async (response: Response): Promise<Response> => {\n await request.body?.cancel().catch(() => undefined)\n return response\n }\n\n const declaredBytes = declaredLength(request, 'Part uploads')\n if (declaredBytes instanceof Response) return refuse(declaredBytes)\n const tooBig = overPartBound(declaredBytes)\n if (tooBig) return refuse(tooBig)\n\n const partNumber = Number(url.searchParams.get('partNumber'))\n if (!Number.isSafeInteger(partNumber) || partNumber < 1 || partNumber > MAX_UPLOAD_PARTS) {\n return refuse(\n fail(400, `partNumber must be a whole number from 1 to ${MAX_UPLOAD_PARTS}`, 'bad_request'),\n )\n }\n\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return refuse(session)\n if (!request.body) return fail(400, 'A part body is required', 'bad_request')\n\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return refuse(quotaUnavailable('This upload reservation could not be read. Retry shortly.'))\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return refuse(\n fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n ),\n )\n }\n if (auth.storage && !reservation) {\n return refuse(\n fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n ),\n )\n }\n\n if (reservation) {\n const expectedBytes = expectedMultipartPartBytes(reservation.declaredBytes, partNumber)\n if (expectedBytes === null || declaredBytes !== expectedBytes) {\n return refuse(multipartLayoutError(reservation.declaredBytes))\n }\n }\n\n // Refresh the session lease before R2 accepts the stream. The declared total\n // determines every valid part number and length, so no per-part ledger is\n // needed: retries are identical by construction.\n const refreshed = await refreshMultipartReservation(\n bucket,\n auth.storage,\n session.reservationId,\n session.uploadId,\n session.key,\n )\n if (auth.storage && (refreshed === null || refreshed === 'invalid')) {\n return refuse(quotaUnavailable('This upload reservation is missing or invalid.'))\n }\n if (refreshed === 'busy') {\n return refuse(quotaUnavailable('This upload reservation is busy. Retry the part shortly.'))\n }\n\n const upload = bucket.resumeMultipartUpload(session.key, session.uploadId)\n const stored = await streamPart(upload, partNumber, request.body, declaredBytes)\n if (!stored.ok) {\n if (r2ErrorCode(stored.error) === 10024 && reservation) {\n await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n }\n return stored.lengthMismatch\n ? fail(\n 400,\n `Part ${partNumber} declared ${declaredBytes} bytes but its body did not carry them`,\n 'bad_request',\n )\n : multipartFailure(stored.error)\n }\n return Response.json(\n { partNumber: stored.part.partNumber, etag: stored.part.etag },\n { headers: CORS_HEADERS },\n )\n}\n\nasync function completedReservationObject(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n): Promise<R2Object | null> {\n const object = await bucket.head(session.key)\n return object?.customMetadata?.reservationId === session.reservationId ? object : null\n}\n\nasync function finishCompletedMultipart(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n reservation: MultipartQuotaReservation | null,\n object: R2Object,\n auth: ScopedR2Auth,\n url: URL,\n scope: string,\n prefix: string,\n): Promise<Response> {\n const declaredMetadata = Number(object.customMetadata?.declaredBytes)\n const declaredBytes =\n reservation?.declaredBytes ??\n (Number.isSafeInteger(declaredMetadata) && declaredMetadata >= 0\n ? declaredMetadata\n : MAX_APP_FILE_BYTES)\n const refusal =\n overCeiling(object.size) ??\n (object.size > declaredBytes\n ? fail(\n 400,\n `The assembled upload exceeded its declared ${declaredBytes}-byte size and was not kept.`,\n 'declared_size_exceeded',\n )\n : null)\n if (refusal) {\n let remaining: R2Object | null\n try {\n const current = await bucket.head(session.key)\n if (current?.etag === object.etag) await bucket.delete(session.key)\n remaining = await bucket.head(session.key)\n } catch (error) {\n console.error('[files:multipart] invalid object cleanup:', error)\n return quotaUnavailable('The invalid assembled file could not be removed. Retry completion.')\n }\n if (remaining?.etag === object.etag) {\n return quotaUnavailable('The invalid assembled file could not be removed. Retry completion.')\n }\n if (reservation) {\n const cleaned = await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n if (!cleaned) return quotaUnavailable('The upload was removed, but cleanup must be retried.')\n }\n return refusal\n }\n if (reservation) {\n const cleaned = await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n if (!cleaned) {\n return quotaUnavailable('The file was stored, but its reservation cleanup must be retried.')\n }\n }\n const name = object.customMetadata?.originalName ?? session.uploadKey.split('/').pop() ?? 'file'\n return storedFileJson(session.key, prefix, name, url, scope)\n}\n\n/**\n * Assemble the parts. Completion is idempotent: if R2 committed the object but\n * the response or marker cleanup was interrupted, a retry recognizes the\n * reservation id stored on the object and finishes cleanup.\n */\nasync function multipartComplete(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n): Promise<Response> {\n const body = await readControlJson(request)\n if (body instanceof Response) return body\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return session\n\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return quotaUnavailable('This upload reservation could not be read. Retry shortly.')\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n )\n }\n if (auth.storage && !reservation) {\n const completed = await completedReservationObject(bucket, session)\n if (completed) {\n return finishCompletedMultipart(bucket, session, null, completed, auth, url, scope, prefix)\n }\n await abandonMultipart(bucket, session, auth, null)\n return quotaUnavailable('This upload reservation is missing or does not match.')\n }\n\n const raw = body.parts\n if (!Array.isArray(raw) || raw.length === 0) {\n return fail(400, 'parts must be a non-empty array', 'bad_request')\n }\n if (raw.length > MAX_UPLOAD_PARTS) {\n return fail(400, `An upload may have at most ${MAX_UPLOAD_PARTS} parts`, 'bad_request')\n }\n if (reservation && raw.length !== multipartPartCount(reservation.declaredBytes)) {\n return multipartLayoutError(reservation.declaredBytes)\n }\n const parts: R2UploadedPart[] = []\n for (let index = 0; index < raw.length; index++) {\n const { partNumber, etag } = (raw[index] ?? {}) as Record<string, unknown>\n if (\n !Number.isSafeInteger(partNumber) ||\n (partNumber as number) < 1 ||\n (partNumber as number) > MAX_UPLOAD_PARTS\n ) {\n return fail(\n 400,\n `parts[${index}].partNumber must be a whole number from 1 to ${MAX_UPLOAD_PARTS}`,\n 'bad_request',\n )\n }\n if (reservation && partNumber !== index + 1) {\n return multipartLayoutError(reservation.declaredBytes)\n }\n if (typeof etag !== 'string' || !etag) {\n return fail(400, `parts[${index}].etag must be the string uploadPart returned`, 'bad_request')\n }\n parts.push({ partNumber: partNumber as number, etag })\n }\n\n let object: R2Object\n try {\n object = await bucket.resumeMultipartUpload(session.key, session.uploadId).complete(parts)\n } catch (error) {\n const completed = await completedReservationObject(bucket, session)\n if (completed) {\n return finishCompletedMultipart(\n bucket,\n session,\n reservation,\n completed,\n auth,\n url,\n scope,\n prefix,\n )\n }\n if (r2ErrorCode(error) === 10024 && reservation) {\n await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n }\n return multipartFailure(error)\n }\n\n return finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope, prefix)\n}\n\n/**\n * Abandon a chunked upload, releasing whatever parts landed.\n *\n * Unconditionally idempotent. The postcondition a caller needs is \"this\n * session is not holding anything of mine\", and a session R2 cannot find is\n * already in that state; one it cannot reach is reaped within 7 days anyway\n * (uncompleted uploads expire). There is no action a caller would take on a\n * failed abort that differs from what it does on a successful one — both\n * clients call this on the failure path and then re-raise the real error.\n * `NoSuchUpload` is already the desired postcondition and still releases the\n * quota marker.\n */\nasync function multipartAbort(\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return session\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return Response.json({ success: true, aborted: false }, { headers: CORS_HEADERS })\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return Response.json({ success: true, aborted: false }, { headers: CORS_HEADERS })\n }\n const result = await abandonMultipart(bucket, session, auth, reservation || null)\n return Response.json({ success: true, aborted: result === 'aborted' }, { headers: CORS_HEADERS })\n}\n\nasync function handleList(\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n url: URL,\n scope: string,\n auth: ScopedR2Auth,\n): Promise<Response> {\n const userPrefix = url.searchParams.get('prefix') || ''\n const listPrefix = `${prefix}${userPrefix}`\n const requestedLimit = Number(url.searchParams.get('limit') ?? 100)\n const limit = Number.isSafeInteger(requestedLimit)\n ? Math.min(1000, Math.max(1, requestedLimit))\n : 100\n const requestCursor = url.searchParams.get('cursor')\n if (excludedPrefixes.some((excluded) => listPrefix.startsWith(excluded))) {\n return Response.json({ files: [], truncated: false }, { headers: CORS_HEADERS })\n }\n\n // The default workers-types entry intentionally models the oldest runtime,\n // while every DeepSpace worker uses a post-2022 compatibility date where R2\n // honors `include`. Keep that compatibility boundary local to these calls.\n const withMetadata = (options: R2ListOptions) =>\n ({ ...options, include: ['customMetadata'] }) as R2ListOptions & {\n include: ['customMetadata']\n }\n const listed = await bucket.list(\n withMetadata({\n prefix: listPrefix,\n limit,\n ...(requestCursor ? { cursor: requestCursor } : {}),\n }),\n )\n const visible = listed.objects.filter((object) =>\n isKeyWithinScope(object.key, prefix, excludedPrefixes),\n )\n const seen = new Set(visible.map((object) => object.key))\n // The frontier is whichever list call the response's truncation state\n // comes from — the initial page, or the last jump past an excluded\n // subtree below.\n let frontier: R2Objects = listed\n // If a private subtree occupied the first page, jump past it instead of\n // walking private keys or hiding public keys that sort after it. Only a\n // truncated listing has anything left to jump to: from an exhausted page the\n // jump would rewind to keys earlier cursor pages already returned.\n // The frontier advances across iterations: a second excluded subtree must\n // jump from where the previous jump ended, not from the original page's\n // tail — restarting from the stale tail would rewind startAfter and\n // re-return keys an earlier jump already delivered.\n let frontierKey = listed.objects.at(-1)?.key\n for (const excluded of excludedPrefixes\n .filter((candidate) => candidate.startsWith(listPrefix))\n .sort()) {\n if (!frontier.truncated || visible.length >= limit) break\n const excludedEnd = `${excluded}\\u{10ffff}`\n const after = await bucket.list(\n withMetadata({\n prefix: listPrefix,\n startAfter: frontierKey && frontierKey > excludedEnd ? frontierKey : excludedEnd,\n limit: limit - visible.length,\n }),\n )\n for (const object of after.objects) {\n if (!seen.has(object.key) && isKeyWithinScope(object.key, prefix, excludedPrefixes)) {\n seen.add(object.key)\n visible.push(object)\n }\n }\n frontierKey = after.objects.at(-1)?.key ?? frontierKey\n frontier = after\n }\n const files = visible.slice(0, limit).map((obj) => {\n const fileUrl = new URL(`/api/files/${encodeKeyPath(obj.key)}`, url.origin)\n fileUrl.searchParams.set('scope', scope)\n return {\n key: obj.key,\n relativeKey: scopeRelativeKey(obj.key, prefix),\n size: obj.size,\n uploaded: obj.uploaded.toISOString(),\n url: fileUrl.toString(),\n ...obj.customMetadata,\n }\n })\n // Account totals belong only on the explicitly authorized owner surface.\n // Public app listings do no registry, billing, or quota-repair work.\n let storage: { usedBytes: number; limitBytes?: number } | undefined\n if (auth.storage && auth.includeStorageUsage) {\n const usedBytes = await storageUsage(bucket, auth.storage)\n if (usedBytes !== null) storage = { usedBytes }\n const limitBytes = await auth.storage.limitBytes()\n if (storage && limitBytes !== null) storage.limitBytes = limitBytes\n }\n // The cursor rides with `truncated`: it continues the frontier list call,\n // so `?cursor=` resumes exactly there.\n return Response.json(\n {\n files,\n truncated: frontier.truncated,\n ...(frontier.truncated ? { cursor: frontier.cursor } : {}),\n ...(storage ? { storage } : {}),\n },\n { headers: CORS_HEADERS },\n )\n}\n\nasync function handleDownload(\n request: Request,\n bucket: R2Bucket,\n key: string,\n scope: 'self' | 'app',\n): Promise<Response> {\n const object = await bucket.get(key)\n if (!object) {\n return Response.json({ error: 'File not found' }, { status: 404, headers: CORS_HEADERS })\n }\n const headers = new Headers()\n const storedType = resolveMimeType(object.httpMetadata?.contentType ?? '')\n const mustDownload = DANGEROUS_MIME_TYPES.has(storedType)\n headers.set('Content-Type', mustDownload ? 'application/octet-stream' : storedType)\n headers.set('X-Content-Type-Options', 'nosniff')\n headers.set('ETag', object.httpEtag)\n headers.set(\n 'Cache-Control',\n scope === 'self' ? 'private, no-store' : 'public, max-age=0, must-revalidate',\n )\n headers.set('Access-Control-Allow-Origin', '*')\n const originalName = object.customMetadata?.originalName ?? key.split('/').pop() ?? 'download'\n headers.set(\n 'Content-Disposition',\n contentDisposition(originalName, mustDownload ? 'attachment' : 'inline'),\n )\n if (mustDownload) headers.set('Content-Security-Policy', \"sandbox; default-src 'none'\")\n if (scope === 'app' && request.headers.get('if-none-match') === object.httpEtag) {\n return new Response(null, { status: 304, headers })\n }\n return new Response(object.body, { headers })\n}\n","/**\n * Upstream worker proxy helpers.\n *\n * App workers reach the platform's other workers (api, platform, auth) through\n * one of two transports:\n *\n * 1. **Service binding** (`env.API_WORKER` / `env.PLATFORM_WORKER`) — the\n * preferred path in production. Configured via wrangler `[[services]]`.\n * Cross-worker calls over plain `*.workers.dev` URLs return Cloudflare\n * error 1042 in production, so the binding is the only working path\n * in deployed apps.\n *\n * 2. **HTTPS URL** (`env.API_WORKER_URL` / `env.PLATFORM_WORKER_URL`) — the\n * fallback used in local development. `deepspace dev start` writes these\n * into `.dev.vars`, which `wrangler dev` exposes as env vars. Service\n * bindings don't work cross-process under `wrangler dev` for SDK apps,\n * so the URL is the only working path in dev.\n *\n * The auth-worker has no service binding even in production — its responses\n * carry `Set-Cookie` headers we want preserved verbatim, which we get for\n * free over plain HTTPS. So `authWorkerFetch` is URL-only; the helper exists\n * for surface consistency, not to switch transports.\n *\n * Each helper:\n * - Prefers the binding if present, falls back to the URL otherwise.\n * - Throws an actionable Error if neither is configured. No silent 502s.\n * - Forwards `init` (method/headers/body) verbatim to the upstream worker.\n *\n * History: a previous in-tree helper folded the binding/URL fallback into\n * the AI module's `resolveTransport`. Inline call sites in the starter\n * template (integrations, files, debug) used `c.env.X.fetch(...)` directly,\n * which broke `npx deepspace dev start` for any app calling those routes — the\n * binding is undefined locally, so the fetch threw. These helpers\n * standardize on the same shape `resolveTransport` had, so every upstream\n * call works in both dev and prod.\n */\n\n/**\n * Env shape required by `apiWorkerFetch`. App workers should extend this\n * (the starter template does) so the helper can be called with `c.env`.\n */\nexport interface ApiWorkerEnv {\n /** Cloudflare service binding for the api-worker. Preferred. */\n API_WORKER?: Fetcher\n /** HTTPS URL for the api-worker. Used when the binding is absent (dev). */\n API_WORKER_URL?: string\n}\n\n/** Env shape required by `platformWorkerFetch`. */\nexport interface PlatformWorkerEnv {\n /** Cloudflare service binding for the platform-worker. Preferred. */\n PLATFORM_WORKER?: Fetcher\n /** HTTPS URL for the platform-worker. Used when the binding is absent. */\n PLATFORM_WORKER_URL?: string\n}\n\n/** Env shape required by `authWorkerFetch`. URL-only. */\nexport interface AuthWorkerEnv {\n /** HTTPS URL for the auth-worker. Always required. */\n AUTH_WORKER_URL?: string\n}\n\ntype Transport = { kind: 'binding'; fetcher: Fetcher } | { kind: 'url'; baseUrl: string }\n\n/**\n * Resolve the api-worker transport. Exported for the AI helper, which\n * needs the URL form to rewrite an internal `https://api-worker.internal`\n * placeholder before calling the provider SDK.\n */\nexport function resolveApiTransport(env: ApiWorkerEnv): Transport {\n if (env.API_WORKER) return { kind: 'binding', fetcher: env.API_WORKER }\n if (env.API_WORKER_URL) {\n return { kind: 'url', baseUrl: env.API_WORKER_URL.replace(/\\/$/, '') }\n }\n throw new Error(\n 'apiWorkerFetch: neither env.API_WORKER nor env.API_WORKER_URL is set. ' +\n 'Add a [[services]] binding in wrangler.toml for production, or let ' +\n '`deepspace dev start` write API_WORKER_URL into .dev.vars for local development.',\n )\n}\n\nfunction resolvePlatformTransport(env: PlatformWorkerEnv): Transport {\n if (env.PLATFORM_WORKER) return { kind: 'binding', fetcher: env.PLATFORM_WORKER }\n if (env.PLATFORM_WORKER_URL) {\n return { kind: 'url', baseUrl: env.PLATFORM_WORKER_URL.replace(/\\/$/, '') }\n }\n throw new Error(\n 'platformWorkerFetch: neither env.PLATFORM_WORKER nor env.PLATFORM_WORKER_URL ' +\n 'is set. Add a [[services]] binding in wrangler.toml for production, or let ' +\n '`deepspace dev start` write PLATFORM_WORKER_URL into .dev.vars for local development.',\n )\n}\n\n/**\n * Build the URL/Request to send over a resolved transport.\n *\n * `path` may be a full URL (e.g. `https://api-worker/api/integrations`,\n * which inline call sites used historically) or a path-only string\n * (`/api/integrations`). Either way we extract `pathname + search` and\n * route it through the chosen transport.\n *\n * - binding: keep an internal placeholder host. The Fetcher dispatches\n * by binding, not host, so the host is purely cosmetic.\n * - url: prefix with `transport.baseUrl`.\n */\nfunction buildTargetUrl(transport: Transport, path: string, internalHost: string): string {\n let pathAndSearch: string\n if (path.startsWith('http://') || path.startsWith('https://')) {\n const u = new URL(path)\n pathAndSearch = u.pathname + u.search\n } else if (path.startsWith('/')) {\n pathAndSearch = path\n } else {\n pathAndSearch = '/' + path\n }\n\n if (transport.kind === 'binding') {\n return `https://${internalHost}${pathAndSearch}`\n }\n return `${transport.baseUrl}${pathAndSearch}`\n}\n\n/**\n * Fetch the api-worker. Prefers the `API_WORKER` service binding, falls\n * back to `API_WORKER_URL` over HTTPS.\n *\n * `path` is treated as path-only — any host in a passed-in URL is\n * stripped and replaced. This matches how `c.env.API_WORKER.fetch(...)`\n * already worked in the starter template (the host was always a\n * placeholder like `api-worker`).\n */\nexport function apiWorkerFetch(\n env: ApiWorkerEnv,\n path: string,\n init?: RequestInit,\n): Promise<Response> {\n const transport = resolveApiTransport(env)\n const url = buildTargetUrl(transport, path, 'api-worker')\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, init as RequestInit)\n }\n return fetch(url, init)\n}\n\n/**\n * Fetch the platform-worker. Prefers the `PLATFORM_WORKER` service binding,\n * falls back to `PLATFORM_WORKER_URL` over HTTPS.\n *\n * Accepts a `Request` instance directly so callers can hand off\n * `c.req.raw`-derived requests with their original method/headers/body\n * intact. (`/api/files/*` does this — it forwards the caller's body\n * stream verbatim.)\n */\nexport function platformWorkerFetch(\n env: PlatformWorkerEnv,\n pathOrRequest: string | Request,\n init?: RequestInit,\n): Promise<Response> {\n const transport = resolvePlatformTransport(env)\n\n if (typeof pathOrRequest === 'string') {\n const url = buildTargetUrl(transport, pathOrRequest, 'platform-worker')\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, init as RequestInit)\n }\n return fetch(url, init)\n }\n\n // Request instance: rewrite the URL through the chosen transport,\n // preserve method/headers/body from the original Request.\n const req = pathOrRequest\n const url = buildTargetUrl(transport, req.url, 'platform-worker')\n const rewritten = new Request(url, req)\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(rewritten)\n }\n return fetch(rewritten)\n}\n\n/**\n * Fetch the auth-worker over HTTPS. URL-only — there is no auth-worker\n * service binding, by design (we want plain-HTTP cookie semantics).\n *\n * Kept as a helper for surface symmetry with `apiWorkerFetch` /\n * `platformWorkerFetch`. Throws if `AUTH_WORKER_URL` is unset.\n */\nexport function authWorkerFetch(\n env: AuthWorkerEnv,\n path: string,\n init?: RequestInit,\n): Promise<Response> {\n if (!env.AUTH_WORKER_URL) {\n throw new Error(\n 'authWorkerFetch: env.AUTH_WORKER_URL is not set. `deepspace dev start` should ' +\n 'write this into .dev.vars; for production, set it as a wrangler var or secret.',\n )\n }\n const baseUrl = env.AUTH_WORKER_URL.replace(/\\/$/, '')\n\n let pathAndSearch: string\n if (path.startsWith('http://') || path.startsWith('https://')) {\n const u = new URL(path)\n pathAndSearch = u.pathname + u.search\n } else if (path.startsWith('/')) {\n pathAndSearch = path\n } else {\n pathAndSearch = '/' + path\n }\n\n return fetch(`${baseUrl}${pathAndSearch}`, init)\n}\n","/**\n * Cron System — Server-Side Scheduled Tasks\n *\n * Provides CronContext for miniapp cron handlers and buildCronContext\n * to construct it from worker environment bindings.\n *\n * CronContext gives handlers access to:\n * - records: Query/create/update/delete via RecordRoom tools API\n * - integrations: Call platform integration endpoints, billed to the app owner\n * - ownerUserId: The app owner's user ID\n */\n\nimport { apiWorkerFetch, type ApiWorkerEnv } from './proxies'\n\n/** Context passed to cron handler functions */\nexport interface CronContext {\n /** RecordRoom data access (queries the DO directly via tools API) */\n records: {\n query(\n collection: string,\n opts?: { where?: Record<string, unknown>; limit?: number },\n ): Promise<unknown[]>\n create(collection: string, data: Record<string, unknown>): Promise<unknown>\n update(collection: string, recordId: string, data: Record<string, unknown>): Promise<unknown>\n delete(collection: string, recordId: string): Promise<unknown>\n }\n /**\n * Call a platform integration endpoint (e.g. \"openai/chat-completion\",\n * \"fal/run-model\"). Billed to the app owner using the same\n * APP_OWNER_JWT the rest of the SDK uses for server-side billed calls.\n */\n integrations: {\n call(endpoint: string, params?: Record<string, unknown>): Promise<unknown>\n }\n /** App owner's user ID */\n ownerUserId: string\n}\n\n/** Environment bindings needed by buildCronContext */\ninterface CronEnv extends ApiWorkerEnv {\n RECORD_ROOMS: DurableObjectNamespace\n /**\n * Long-lived app-owner JWT minted at deploy time. Required for\n * `integrations.call()` so the call is billed to the app owner.\n * Optional here so apps that don't use integrations from cron can omit\n * it without a type error; missing-at-call-time throws a clear message.\n */\n APP_OWNER_JWT?: string\n}\n\n/**\n * Build a CronContext from worker environment bindings.\n *\n * @param env - Worker environment with RECORD_ROOMS DO namespace, APP_OWNER_JWT, and an api-worker transport (API_WORKER binding or API_WORKER_URL)\n * @param ownerUserId - App owner's user ID (for RBAC and billing)\n * @param roomId - RecordRoom ID (defaults to 'default')\n */\nexport function buildCronContext(\n env: CronEnv,\n ownerUserId: string,\n roomId = 'default',\n): CronContext {\n // Get the RecordRoom DO stub for direct internal calls\n const roomIdObj = env.RECORD_ROOMS.idFromName(roomId)\n const room = env.RECORD_ROOMS.get(roomIdObj)\n\n /** Execute a tool call against the RecordRoom's tools API */\n async function executeTool(tool: string, params: Record<string, unknown>): Promise<unknown> {\n const response = await room.fetch(\n new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': ownerUserId,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({ tool, params }),\n }),\n )\n const result = (await response.json()) as { success: boolean; data?: unknown; error?: string }\n if (!result.success) {\n throw new Error(`RecordRoom tool ${tool} failed: ${result.error || 'Unknown error'}`)\n }\n return result.data\n }\n\n const records: CronContext['records'] = {\n async query(collection, opts) {\n const params: Record<string, unknown> = { collection }\n if (opts?.where) params.where = opts.where\n if (opts?.limit) params.limit = opts.limit\n const data = await executeTool('records.query', params)\n const records = (data as { records?: unknown[] } | null | undefined)?.records\n return records ?? []\n },\n async create(collection, data) {\n return executeTool('records.create', { collection, data })\n },\n async update(collection, recordId, data) {\n return executeTool('records.update', { collection, recordId, data })\n },\n async delete(collection, recordId) {\n return executeTool('records.delete', { collection, recordId })\n },\n }\n\n const integrations: CronContext['integrations'] = {\n async call(endpoint, params = {}) {\n if (!env.APP_OWNER_JWT) {\n throw new Error(\n 'integrations.call requires env.APP_OWNER_JWT. Deployed apps receive ' +\n 'this automatically; in dev, `deepspace dev start` writes it into .dev.vars.',\n )\n }\n // The api-worker's /api/integrations route is path-shaped as\n // /:name/:endpoint. Pass the same \"name/endpoint\" string the\n // catalog uses (e.g. \"openai/chat-completion\").\n const response = await apiWorkerFetch(env, `/api/integrations/${endpoint}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${env.APP_OWNER_JWT}`,\n },\n body: JSON.stringify(params),\n })\n // Read as text first so a non-JSON error body (e.g. a 502 from an\n // upstream gateway) still yields a useful error message instead of\n // surfacing a JSON parse failure.\n const text = await response.text()\n let result: { success?: boolean; data?: unknown; error?: string; message?: string } = {}\n try {\n result = text ? JSON.parse(text) : {}\n } catch {\n // fall through with empty `result`; the !response.ok branch below\n // will throw using the raw text as the detail.\n }\n if (!response.ok || !result.success) {\n const detail = result.message || result.error || text || `HTTP ${response.status}`\n throw new Error(`Integration call ${endpoint} failed: ${detail}`)\n }\n return result.data\n },\n }\n\n return { records, integrations, ownerUserId }\n}\n","import type { Role } from '../../shared/roles'\n\ninterface AppRoleEnv {\n RECORD_ROOMS: DurableObjectNamespace\n DEEPSPACE_APP_ID: string\n OWNER_USER_ID: string\n}\n\n/** Resolve a user's current role from the app's canonical users collection. */\nexport async function resolveAppRole(env: AppRoleEnv, userId: string): Promise<Role> {\n if (userId === env.OWNER_USER_ID) return 'admin'\n\n const stub = env.RECORD_ROOMS.get(env.RECORD_ROOMS.idFromName(`app:${env.DEEPSPACE_APP_ID}`))\n try {\n const res = await stub.fetch(\n new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': env.OWNER_USER_ID,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({\n tool: 'records.get',\n params: { collection: 'users', recordId: userId },\n }),\n }),\n )\n const json = (await res.json()) as {\n success?: boolean\n data?: { record?: { data?: { role?: unknown } } }\n }\n const role = json.data?.record?.data?.role\n return json.success && (role === 'admin' || role === 'member') ? role : 'viewer'\n } catch {\n return 'viewer'\n }\n}\n","/**\n * AI provider helpers — create Vercel AI SDK providers that route through\n * the DeepSpace API worker proxy for per-user billing.\n *\n * Supported providers: anthropic, openai, cerebras.\n *\n * The API worker can be reached in two ways:\n * - Service binding `env.API_WORKER` (Cloudflare Fetcher) — preferred in\n * production if the app has declared the binding in wrangler.toml.\n * - HTTPS URL `env.API_WORKER_URL` — used in local dev and in production\n * for apps that don't declare the binding. `deepspace dev start` writes this\n * into `.dev.vars` automatically.\n *\n * Auth is automatic by default:\n * - For server-side autonomous calls (cron, DO alarms, background agents),\n * the helper reads the long-lived `env.APP_OWNER_JWT` minted at deploy\n * time (or by `deepspace dev start` in local development) and uses it for the\n * proxy auth header. The owner is billed automatically via the JWT sub.\n * - For user-initiated calls (e.g. an `/api/ai/chat` route handling a\n * browser request), pass `options.authToken` explicitly with the user's\n * own JWT so the call is billed to the user.\n *\n * Usage:\n *\n * // Server-side autonomous — no auth config needed\n * import { createDeepSpaceAI } from 'deepspace/worker'\n * const cerebras = createDeepSpaceAI(env, 'cerebras')\n * const result = await generateText({ model: cerebras('llama-3.3-70b'), ... })\n *\n * // User-initiated (inside a request handler)\n * const jwt = c.req.header('Authorization')!.slice(7)\n * const anthropic = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport type { DeepSpaceAIProvider } from '../../shared/ai-models'\nimport { resolveApiTransport, type ApiWorkerEnv } from './proxies'\nimport type { LanguageModel } from 'ai'\n\n/**\n * Model factory: `(modelId) => LanguageModel`. The explicit return type\n * keeps tsup's DTS build from leaking unportable `.pnpm/@ai-sdk+provider/...`\n * paths into the published `dist/index.d.ts`.\n */\nexport type DeepSpaceModelFactory = (modelId: string) => LanguageModel\n\nexport interface DeepSpaceAIEnv extends ApiWorkerEnv {\n /**\n * Long-lived owner-scoped JWT minted at deploy time (or by `deepspace dev start`).\n * Used as the default proxy auth token when `options.authToken` is absent.\n * Bills the app owner.\n */\n APP_OWNER_JWT?: string\n}\n\nexport interface DeepSpaceAIOptions {\n /**\n * Explicit auth token for this call. Use this for user-initiated flows\n * where the caller's own JWT should be billed. If omitted, the helper\n * falls back to `env.APP_OWNER_JWT` (bills the app owner).\n *\n * Billing is always against the JWT subject — to bill a different user,\n * pass a JWT whose subject is that user. The proxy does not accept any\n * client-supplied billing override.\n */\n authToken?: string\n}\n\nconst ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096\n\nfunction withDefaultAnthropicMaxTokens(body: RequestInit['body']): RequestInit['body'] {\n if (typeof body !== 'string') return body\n\n try {\n const parsed = JSON.parse(body) as Record<string, unknown>\n const hasExplicitLimit =\n typeof parsed.max_tokens === 'number' ||\n typeof parsed.max_completion_tokens === 'number' ||\n typeof parsed.max_output_tokens === 'number'\n\n if (hasExplicitLimit) return body\n\n return JSON.stringify({\n ...parsed,\n max_tokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,\n })\n } catch {\n return body\n }\n}\n\n/**\n * Build an AI SDK provider that routes through the DeepSpace API worker.\n *\n * Resolves the transport (service binding or URL) and the auth token\n * (explicit or `env.APP_OWNER_JWT`) automatically. Throws a clear error if\n * either is unconfigured.\n */\nexport function createDeepSpaceAI(\n env: DeepSpaceAIEnv,\n provider: DeepSpaceAIProvider,\n options: DeepSpaceAIOptions = {},\n): DeepSpaceModelFactory {\n const transport = resolveApiTransport(env)\n const authToken = options.authToken ?? env.APP_OWNER_JWT\n if (!authToken) {\n throw new Error(\n 'createDeepSpaceAI: no auth token available. Either pass `options.authToken` ' +\n 'explicitly (for user-initiated calls), or ensure `env.APP_OWNER_JWT` is set ' +\n '(injected at deploy time or by `deepspace dev start`).',\n )\n }\n\n const proxyFetch: typeof globalThis.fetch = (input, init) => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.toString()\n : (input as Request).url\n\n const headers = new Headers(init?.headers)\n // Strip the AI SDK's provider-auth headers (Authorization for OpenAI/\n // Cerebras, x-api-key for Anthropic). They carry our placeholder\n // apiKey value 'platform-managed' which would collide with the proxy's\n // JWT auth below and fail verifyJwt.\n headers.delete('authorization')\n headers.delete('x-api-key')\n headers.set('X-Auth-Token', authToken)\n\n const body = provider === 'anthropic' ? withDefaultAnthropicMaxTokens(init?.body) : init?.body\n if (body !== init?.body) {\n // The request body length changed, so the runtime must recompute it.\n headers.delete('content-length')\n }\n const nextInit = { ...init, headers, body }\n\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, nextInit as RequestInit)\n }\n\n // URL transport: rewrite the internal host to the real API worker URL.\n const original = new URL(url)\n const rewritten = `${transport.baseUrl}${original.pathname}${original.search}`\n return fetch(rewritten, nextInit)\n }\n\n // Every provider's SDK expects baseURL to end in /v1 — it then appends\n // the provider-specific suffix (/messages for Anthropic, /chat/completions\n // for OpenAI/Groq/Cerebras).\n const baseURL = `https://api-worker.internal/api/proxy/${provider}/v1`\n\n switch (provider) {\n case 'anthropic':\n // Anthropic always returns usage in both streaming and non-streaming\n // responses, so no extra config is needed.\n return createAnthropic({ baseURL, apiKey: 'platform-managed', fetch: proxyFetch })\n\n case 'openai': {\n // Pin to `.chat` — v5's default `openai(modelId)` returns a Responses\n // API model (POST /responses); our proxy speaks /chat/completions.\n const op = createOpenAI({ baseURL, apiKey: 'platform-managed', fetch: proxyFetch })\n return op.chat\n }\n\n case 'cerebras':\n // `createCerebras` doesn't expose `includeUsage`, so use the underlying\n // openai-compatible primitive directly. Same wire format Cerebras's own\n // wrapper produces, but with `include_usage` flipped on so streaming\n // responses carry token counts.\n return createOpenAICompatible({\n name: 'cerebras',\n baseURL,\n apiKey: 'platform-managed',\n fetch: proxyFetch,\n includeUsage: true,\n })\n\n default: {\n const _exhaustive: never = provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n","import {\n stepCountIs,\n streamText,\n type PrepareStepFunction,\n type StreamTextResult,\n type ToolSet,\n} from 'ai'\nimport {\n resolveDeepSpaceAgentModel,\n type DeepSpaceAgentProfileId,\n type ResolvedDeepSpaceAgentModel,\n} from '../../shared/ai-models'\nimport { createDeepSpaceAI, type DeepSpaceAIEnv } from './ai'\n\ntype StreamTextOptions<TOOLS extends ToolSet> = Parameters<typeof streamText<TOOLS>>[0]\n\nexport type DeepSpaceAgentStreamOptions<TOOLS extends ToolSet> = Omit<\n StreamTextOptions<TOOLS>,\n 'model' | 'providerOptions' | 'stopWhen'\n> & {\n profile: DeepSpaceAgentProfileId\n modelId?: unknown\n authToken?: string\n}\n\nexport interface DeepSpaceAgentStream<TOOLS extends ToolSet> {\n selection: ResolvedDeepSpaceAgentModel\n result: StreamTextResult<TOOLS, never>\n}\n\nexport class DeepSpaceAgentModelError extends Error {\n readonly code = 'unsupported_agent_model'\n\n constructor(readonly modelId: unknown, readonly profile: DeepSpaceAgentProfileId) {\n super(\n `Model ${typeof modelId === 'string' ? JSON.stringify(modelId) : String(modelId)} ` +\n `is not compatible with the ${profile} agent profile`,\n )\n this.name = 'DeepSpaceAgentModelError'\n }\n}\n\nexport class DeepSpaceAgentProfileError extends Error {\n readonly code = 'agent_profile_violation'\n\n constructor(readonly profile: DeepSpaceAgentProfileId, readonly toolName: string) {\n super(`The ${profile} agent profile does not allow tool ${JSON.stringify(toolName)}`)\n this.name = 'DeepSpaceAgentProfileError'\n }\n}\n\n/** The sole provider/model/tool-loop implementation for every DeepSpace agent surface. */\nexport function streamDeepSpaceAgent<TOOLS extends ToolSet>(\n env: DeepSpaceAIEnv,\n options: DeepSpaceAgentStreamOptions<TOOLS>,\n): DeepSpaceAgentStream<TOOLS> {\n const { profile, modelId, authToken, ...streamOptions } = options\n const selection = resolveDeepSpaceAgentModel(modelId, profile)\n if (!selection) throw new DeepSpaceAgentModelError(modelId, profile)\n if (selection.profile.allowedTools !== 'application-defined') {\n const allowed = selection.profile.allowedTools as readonly string[]\n for (const toolName of Object.keys(streamOptions.tools ?? {})) {\n if (!allowed.includes(toolName)) {\n throw new DeepSpaceAgentProfileError(profile, toolName)\n }\n }\n }\n\n const provider = createDeepSpaceAI(env, selection.provider, authToken ? { authToken } : {})\n const { prepareStep, tools, ...remainingStreamOptions } = streamOptions\n const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls)\n const result = streamText<TOOLS>({\n ...remainingStreamOptions,\n ...(boundedTools ? { tools: boundedTools } : {}),\n model: provider(selection.modelId),\n stopWhen: stepCountIs(selection.profile.maxSteps),\n prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),\n ...(selection.provider === 'openai'\n ? { providerOptions: { openai: { reasoningEffort: 'none' as const } } }\n : {}),\n } as StreamTextOptions<TOOLS>)\n\n return { selection, result }\n}\n\nfunction enforceToolCallLimit<TOOLS extends ToolSet>(\n prepareStep: PrepareStepFunction<TOOLS> | undefined,\n maxToolCalls: number | undefined,\n): PrepareStepFunction<TOOLS> | undefined {\n if (maxToolCalls === undefined) return prepareStep\n return async (context) => {\n const prepared = await prepareStep?.(context)\n const completedToolCalls = context.steps.reduce(\n (count, step) => count + step.toolCalls.length,\n 0,\n )\n if (completedToolCalls < maxToolCalls) return prepared\n return { ...prepared, activeTools: [], toolChoice: 'none' }\n }\n}\n\nfunction limitToolExecutions<TOOLS extends ToolSet>(\n tools: TOOLS | undefined,\n maxToolCalls: number | undefined,\n): TOOLS | undefined {\n if (!tools || maxToolCalls === undefined) return tools\n let executions = 0\n return Object.fromEntries(\n Object.entries(tools).map(([name, definition]) => {\n if (typeof definition.execute !== 'function') return [name, definition]\n const execute = definition.execute\n return [\n name,\n {\n ...definition,\n execute: (...args: Parameters<typeof execute>) => {\n if (executions >= maxToolCalls) {\n return Promise.resolve({\n error: `The agent tool-call limit of ${maxToolCalls} has been reached`,\n })\n }\n executions++\n return execute(...args)\n },\n },\n ]\n }),\n ) as TOOLS\n}\n\nexport interface DeepSpaceAgentDiagnosticContext {\n profile?: DeepSpaceAgentProfileId\n provider?: string\n modelId?: string\n}\n\n/** Preserve safe structured provider diagnostics in bounded Worker logs. */\nexport function deepSpaceAgentErrorSummary(\n error: unknown,\n context: DeepSpaceAgentDiagnosticContext = {},\n): string {\n const details: string[] = []\n const contextFields = [\n context.profile ? `profile=${context.profile}` : undefined,\n context.provider ? `provider=${safeLogToken(context.provider)}` : undefined,\n context.modelId ? `model=${safeLogToken(context.modelId)}` : undefined,\n ].filter((part): part is string => Boolean(part))\n if (contextFields.length > 0) details.push(contextFields.join(' '))\n const pending: unknown[] = [error]\n const visited = new Set<unknown>()\n while (pending.length > 0 && details.length < 8) {\n const current = pending.shift()\n if (current == null || visited.has(current)) continue\n visited.add(current)\n if (typeof current === 'object') {\n const record = current as Record<string, unknown>\n const parts = [\n typeof record.name === 'string'\n ? `name=${safeLogToken(record.name)}`\n : current instanceof Error\n ? `name=${safeLogToken(current.name)}`\n : undefined,\n safeErrorMessage(current, record),\n typeof record.statusCode === 'number' ? `status=${record.statusCode}` : undefined,\n typeof record.code === 'string' ? `code=${safeLogToken(record.code)}` : undefined,\n typeof record.requestId === 'string'\n ? `requestId=${safeLogToken(record.requestId)}`\n : undefined,\n ...safeProviderResponseMetadata(record.responseBody),\n ].filter((part): part is string => Boolean(part))\n details.push(parts.join(' ') || Object.prototype.toString.call(current))\n if (record.lastError != null) pending.push(record.lastError)\n if (record.cause != null) pending.push(record.cause)\n if (Array.isArray(record.errors)) pending.push(...record.errors)\n continue\n }\n details.push(String(current))\n }\n return details.join(' <- ') || 'Unknown error'\n}\n\nfunction safeErrorMessage(current: object, record: Record<string, unknown>): string | undefined {\n if (record.responseBody !== undefined) return undefined\n const message = current instanceof Error ? current.message : record.message\n if (typeof message !== 'string' || !message.trim()) return undefined\n return `message=${safeLogText(message)}`\n}\n\nfunction safeProviderResponseMetadata(responseBody: unknown): string[] {\n if (typeof responseBody !== 'string') return []\n try {\n const parsed = JSON.parse(responseBody) as Record<string, unknown>\n const error =\n parsed.error && typeof parsed.error === 'object'\n ? (parsed.error as Record<string, unknown>)\n : undefined\n const code =\n typeof error?.code === 'string'\n ? error.code\n : typeof error?.type === 'string'\n ? error.type\n : undefined\n const requestId = typeof parsed.request_id === 'string' ? parsed.request_id : undefined\n return [\n code ? `upstreamCode=${safeLogToken(code)}` : undefined,\n requestId ? `upstreamRequestId=${safeLogToken(requestId)}` : undefined,\n ].filter((part): part is string => Boolean(part))\n } catch {\n return []\n }\n}\n\nfunction safeLogToken(value: string): string {\n return value.replace(/[^a-zA-Z0-9._:/-]/g, '_').slice(0, 120)\n}\n\nfunction safeLogText(value: string): string {\n const withoutControlCharacters = Array.from(value, (character) => {\n const codePoint = character.codePointAt(0) ?? 0\n return codePoint <= 31 || codePoint === 127 ? ' ' : character\n }).join('')\n\n return withoutControlCharacters\n .replace(/\\bBearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')\n .replace(\n /\\b(api[_ -]?key|authorization|password|secret|token)\\b\\s*[:=]\\s*\\S+/gi,\n '$1=[redacted]',\n )\n .trim()\n .slice(0, 300)\n}\n","/**\n * Canonical DeepSpace model catalog and agent policy.\n *\n * Provider catalogs are discovery inputs, not a safe runtime allowlist: a\n * model is promoted here only after its tool loop, transport, pricing, and\n * streaming contract have been verified end to end through the DeepSpace\n * proxy. Every client picker and server agent profile consumes this module.\n */\n\nexport type DeepSpaceAIProvider = 'anthropic' | 'openai' | 'cerebras'\nexport type DeepSpaceAgentProfileId = 'application' | 'documentation'\nexport type DeepSpaceAgentSupport = 'multi-step' | 'single-step' | 'none'\n\nexport interface DeepSpaceAIModel {\n id: string\n label: string\n provider: DeepSpaceAIProvider\n providerLabel: string\n family: string\n /** Whether the model is eligible for DeepSpace's server-owned agent loop. */\n agentSupport: DeepSpaceAgentSupport\n /** Profiles in which the model has passed the complete runtime contract. */\n agentProfiles: readonly DeepSpaceAgentProfileId[]\n /** Provider transport currently used by the DeepSpace proxy adapter. */\n transport: 'messages' | 'chat-completions'\n recommendation: 'frontier' | 'balanced' | 'fast' | 'available' | 'limited'\n note?: string\n}\n\nexport interface DeepSpaceAgentProfile {\n id: DeepSpaceAgentProfileId\n defaultModel: string\n maxSteps: number\n maxToolCalls?: number\n allowedTools: 'application-defined' | readonly string[]\n}\n\n/**\n * Versioned provenance for the provider catalogs used during the latest\n * promotion review. Keeping this in the shipped artifact makes selection\n * policy auditable without allowing a mutable upstream list to change a\n * deployed app underneath a commit.\n */\nexport const DEEPSPACE_MODEL_CATALOG_PROVENANCE = {\n version: '2026-08-04',\n verifiedAt: '2026-08-04',\n sources: {\n anthropic: 'https://platform.claude.com/docs/en/api/beta/models/list',\n openai: 'https://developers.openai.com/api/docs/models/all',\n cerebras: 'https://inference-docs.cerebras.ai/models/overview',\n },\n} as const\n\nconst MULTI_STEP_AGENT_PROFILES = ['application', 'documentation'] as const\n\ntype MultiStepModelInput = Pick<DeepSpaceAIModel, 'id' | 'label' | 'family' | 'recommendation'>\ntype MultiStepProvider = Pick<DeepSpaceAIModel, 'provider' | 'providerLabel' | 'transport'>\n\nfunction createMultiStepModelFactory<const Provider extends MultiStepProvider>(provider: Provider) {\n return <const Model extends MultiStepModelInput>(model: Model) => ({\n ...model,\n ...provider,\n agentSupport: 'multi-step' as const,\n agentProfiles: MULTI_STEP_AGENT_PROFILES,\n })\n}\n\nconst anthropicModel = createMultiStepModelFactory({\n provider: 'anthropic',\n providerLabel: 'Anthropic',\n transport: 'messages',\n})\n\nconst openAIModel = createMultiStepModelFactory({\n provider: 'openai',\n providerLabel: 'OpenAI',\n transport: 'chat-completions',\n})\n\nexport const DEEPSPACE_AI_MODELS = [\n anthropicModel({\n id: 'claude-fable-5',\n label: 'Claude Fable 5',\n family: 'Claude 5',\n recommendation: 'frontier',\n }),\n anthropicModel({\n id: 'claude-opus-5',\n label: 'Claude Opus 5',\n family: 'Claude 5',\n recommendation: 'frontier',\n }),\n anthropicModel({\n id: 'claude-sonnet-5',\n label: 'Claude Sonnet 5',\n family: 'Claude 5',\n recommendation: 'balanced',\n }),\n anthropicModel({\n id: 'claude-haiku-4-5',\n label: 'Claude Haiku 4.5',\n family: 'Claude 4.5',\n recommendation: 'fast',\n }),\n openAIModel({\n id: 'gpt-5.6-sol',\n label: 'GPT-5.6 Sol',\n family: 'GPT-5.6',\n recommendation: 'frontier',\n }),\n openAIModel({\n id: 'gpt-5.6-terra',\n label: 'GPT-5.6 Terra',\n family: 'GPT-5.6',\n recommendation: 'balanced',\n }),\n openAIModel({\n id: 'gpt-5.6-luna',\n label: 'GPT-5.6 Luna',\n family: 'GPT-5.6',\n recommendation: 'fast',\n }),\n {\n id: 'gpt-oss-120b',\n label: 'GPT-OSS 120B',\n provider: 'cerebras',\n providerLabel: 'Cerebras',\n family: 'GPT-OSS',\n agentSupport: 'single-step',\n agentProfiles: [],\n transport: 'chat-completions',\n recommendation: 'limited',\n note: 'Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation.',\n },\n] as const satisfies readonly DeepSpaceAIModel[]\n\nexport const DEEPSPACE_AI_DEFAULTS = {\n agent: 'claude-sonnet-5',\n directGeneration: 'claude-sonnet-5',\n summarization: 'claude-haiku-4-5',\n} as const satisfies Record<string, DeepSpaceAIModelId>\n\nexport const DEEPSPACE_AGENT_PROFILES = {\n application: {\n id: 'application',\n defaultModel: DEEPSPACE_AI_DEFAULTS.agent,\n maxSteps: 21,\n maxToolCalls: 20,\n allowedTools: 'application-defined',\n },\n documentation: {\n id: 'documentation',\n defaultModel: DEEPSPACE_AI_DEFAULTS.agent,\n // Permit up to twenty completed read-only tools plus one final synthesis\n // step. The runner disables tools once the tool-call budget is exhausted.\n maxSteps: 21,\n maxToolCalls: 20,\n allowedTools: ['documentation_search', 'documentation_read'],\n },\n} as const satisfies Record<DeepSpaceAgentProfileId, DeepSpaceAgentProfile>\n\nexport type DeepSpaceAIModelId = (typeof DEEPSPACE_AI_MODELS)[number]['id']\n\nexport function getDeepSpaceAIModel(modelId: unknown): DeepSpaceAIModel | null {\n if (typeof modelId !== 'string') return null\n return DEEPSPACE_AI_MODELS.find((model) => model.id === modelId) ?? null\n}\n\nexport function listDeepSpaceAgentModels(\n profileId: DeepSpaceAgentProfileId = 'application',\n): readonly DeepSpaceAIModel[] {\n const defaultModel = DEEPSPACE_AGENT_PROFILES[profileId].defaultModel\n return DEEPSPACE_AI_MODELS.filter((model) =>\n (model.agentProfiles as readonly DeepSpaceAgentProfileId[]).includes(profileId),\n ).sort((left, right) => Number(right.id === defaultModel) - Number(left.id === defaultModel))\n}\n\nexport interface ResolvedDeepSpaceAgentModel {\n modelId: string\n provider: DeepSpaceAIProvider\n model: DeepSpaceAIModel\n profile: DeepSpaceAgentProfile\n}\n\nexport function resolveDeepSpaceAgentModel(\n modelId: unknown,\n profileId: DeepSpaceAgentProfileId = 'application',\n): ResolvedDeepSpaceAgentModel | null {\n const profile = DEEPSPACE_AGENT_PROFILES[profileId]\n const resolvedId = modelId === undefined ? profile.defaultModel : modelId\n const model = getDeepSpaceAIModel(resolvedId)\n if (!model || !model.agentProfiles.includes(profileId)) return null\n return { modelId: model.id, provider: model.provider, model, profile }\n}\n","/**\n * Composio tools for the Vercel AI SDK.\n *\n * Turns the platform's Composio integration into ready-to-use Vercel AI SDK\n * tools, so an agent (or the DeepSpace AI chat) can take actions on a user's\n * connected apps. Each returned tool's `execute()` calls our own\n * `composio/execute-tool` endpoint through the api-worker proxy, so our auth\n * and per-user billing apply. This is the managed-proxy equivalent of\n * Composio's `@composio/vercel` provider, which talks straight to Composio.\n *\n * `authToken` must be the END USER's JWT: Composio resolves the user's\n * connected accounts by the JWT subject, and that user is billed.\n *\n * Usage (inside a request handler, e.g. /api/ai/chat):\n *\n * import { composioTools, createDeepSpaceAI } from 'deepspace/worker'\n * const jwt = c.req.header('Authorization')!.slice(7)\n * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })\n * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })\n * const result = streamText({ model: ai('claude-sonnet-5'), prompt, tools })\n *\n * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.\n */\n\nimport { tool, jsonSchema, type ToolSet } from 'ai'\nimport { apiWorkerFetch, type ApiWorkerEnv } from './proxies'\n\nexport interface ComposioToolsOptions {\n /** End user's JWT. Required: the tools run as this user and bill them. */\n authToken: string\n /** Restrict to one toolkit (e.g. 'gmail', 'github'). */\n toolkit?: string\n /** Explicit tool slugs to expose (e.g. ['GMAIL_SEND_EMAIL']). */\n tools?: string[]\n /** Natural-language search to pick relevant tools. */\n search?: string\n /** Cap how many tools are exposed (default 20). Keep small: the LLM sees them all. */\n limit?: number\n}\n\ninterface ComposioToolItem {\n slug: string\n name?: string\n description?: string\n input_parameters?: Record<string, unknown>\n}\n\nconst EMPTY_SCHEMA = { type: 'object', properties: {} }\n\n/** POST a Composio endpoint through the api-worker proxy and parse the\n * `{ success, data }` envelope. Text-first so a non-JSON gateway error still\n * yields a useful detail string instead of a parse failure. Never throws. */\nasync function requestComposio(\n env: ApiWorkerEnv,\n endpoint: string,\n params: Record<string, unknown>,\n authToken: string,\n): Promise<{ ok: boolean; success: boolean; data: unknown; detail: string }> {\n const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${authToken}`,\n },\n body: JSON.stringify(params),\n })\n const text = await res.text()\n let parsed: { success?: boolean; data?: unknown; error?: string; message?: string } = {}\n try {\n parsed = text ? JSON.parse(text) : {}\n } catch {\n // non-JSON body; detail falls back to the raw text below\n }\n const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`\n return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail }\n}\n\n/** Composio slugs are already valid tool-name chars; sanitize + cap at 64 only\n * as a guard (Anthropic rejects longer names). The real slug is always used\n * for execution regardless of the key. */\nfunction toolKey(slug: string): string {\n const key = slug.replace(/[^a-zA-Z0-9_-]/g, '_')\n return key.length <= 64 ? key : key.slice(0, 64)\n}\n\n/**\n * Fetch Composio tools for the given scope and return them as a Vercel AI SDK\n * `ToolSet`. One `list-tools` call provides each tool's JSON Schema; each tool\n * executes via `composio/execute-tool` as the authenticated user.\n */\nexport async function composioTools(\n env: ApiWorkerEnv,\n options: ComposioToolsOptions,\n): Promise<ToolSet> {\n const { authToken } = options\n if (!authToken) {\n throw new Error(\n 'composioTools: options.authToken is required. Pass the end user\\'s JWT so the ' +\n 'tools run as that user (Composio resolves their connected accounts by it) and ' +\n 'the calls are billed to them.',\n )\n }\n\n const list = await requestComposio(\n env,\n 'list-tools',\n {\n toolkit: options.toolkit,\n toolSlugs: options.tools?.length ? options.tools.join(',') : undefined,\n search: options.search,\n limit: options.limit ?? 20,\n },\n authToken,\n )\n if (!list.ok || !list.success) {\n throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`)\n }\n\n const listItems = (list.data as { items?: unknown } | undefined)?.items\n const items: ComposioToolItem[] = Array.isArray(listItems) ? (listItems as ComposioToolItem[]) : []\n const tools: ToolSet = {}\n\n for (const item of items) {\n if (!item?.slug) continue\n const slug = item.slug\n const schema = (item.input_parameters ?? EMPTY_SCHEMA) as Parameters<typeof jsonSchema>[0]\n\n tools[toolKey(slug)] = tool({\n description: item.description ?? item.name ?? slug,\n inputSchema: jsonSchema(schema),\n execute: async (args: Record<string, unknown>) => {\n const r = await requestComposio(\n env,\n 'execute-tool',\n { slug, arguments: args ?? {} },\n authToken,\n )\n if (!r.ok || !r.success) {\n // Surface a readable error to the model instead of throwing, so the\n // agent loop can recover or explain to the user.\n return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` }\n }\n const data = r.data\n if (data && typeof data === 'object' && (data as { requiresConnection?: boolean }).requiresConnection) {\n const toolkit = (data as { toolkit?: string }).toolkit\n return {\n requiresConnection: true,\n toolkit,\n message: `The user has not connected ${toolkit ?? 'this app'}. Ask them to connect it before using this tool.`,\n }\n }\n return data\n },\n })\n }\n\n return tools\n}\n","/**\n * App-identity headers for platform calls (api-worker, platform-worker).\n *\n * The pair is `x-app-identity-token` (HMAC(PLATFORM_IDENTITY_SECRET, appId),\n * minted at deploy time and injected as a binding) + `x-app-id`. Pre-first-\n * deploy the token binding is ABSENT — `deepspace dev start` can only fetch it once\n * the app is in the deploy registry. Attaching headers built from an undefined\n * binding sends the literal string \"undefined\", which upstream identity\n * verification rejects as a *tampered* token (401 invalid) instead of the\n * truthful \"missing app identity\" — so this helper fails closed by attaching\n * nothing when the token is unset. The starter's worker.ts proxies follow the\n * same policy (see reassertAppIdentity there).\n */\n\nexport interface AppIdentityEnv {\n /** The app's immutable id (wrangler.toml [vars] DEEPSPACE_APP_ID). */\n DEEPSPACE_APP_ID: string\n /** Absent until the app's first deploy injects it. */\n APP_IDENTITY_TOKEN?: string\n}\n\n/**\n * Append the app's identity pair to `headers`, fail-closed: no token → no\n * identity headers, so the upstream answers \"missing app identity\" rather\n * than rejecting a garbage token.\n */\nexport function appendAppIdentity(headers: Headers, env: AppIdentityEnv): void {\n // Sanitize first so forwarded request headers can never smuggle identity —\n // same policy as the starter worker's reassertAppIdentity.\n headers.delete('x-app-identity-token')\n headers.delete('x-app-id')\n if (!env.APP_IDENTITY_TOKEN) return\n headers.set('x-app-identity-token', env.APP_IDENTITY_TOKEN)\n headers.set('x-app-id', env.DEEPSPACE_APP_ID)\n}\n","/**\n * captureScreenshot — call platform-worker /internal/screenshot.\n *\n * Apps don't ship CF Browser Rendering bindings or puppeteer in their\n * own bundle. The platform holds the binding; consumers call this\n * helper to get PNG bytes for a URL.\n *\n * The platform enforces HTTPS, a DeepSpace host allowlist, a per-app rate\n * limit, and one fixed viewport-only capture profile. Returns `null` on any\n * non-2xx — callers should treat that as \"no preview available\".\n *\n * Auth is the same HMAC-of-appId pattern `/internal/files` uses:\n * x-app-identity-token = hmac(PLATFORM_IDENTITY_SECRET, DEEPSPACE_APP_ID)\n * x-app-id = DEEPSPACE_APP_ID\n *\n * Apps already have both as bindings (APP_IDENTITY_TOKEN + DEEPSPACE_APP_ID),\n * so this helper is a thin wrapper — no extra secrets to manage.\n */\nimport { platformWorkerFetch, type PlatformWorkerEnv } from './proxies'\nimport { appendAppIdentity } from './app-identity'\n\nexport interface ScreenshotEnv extends PlatformWorkerEnv {\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n}\n\nexport interface ScreenshotResult {\n /** PNG bytes. */\n body: ArrayBuffer\n /** `image/png`. */\n contentType: string\n}\n\n/**\n * Capture a screenshot of `url` and return the PNG bytes.\n *\n * Returns null on capture failure (target unreachable, timeout, BR\n * binding misconfigured platform-side). The platform logs a generic failure\n * without target details; callers should surface their own fallback UX.\n */\nexport async function captureScreenshot(\n env: ScreenshotEnv,\n url: string,\n): Promise<ScreenshotResult | null> {\n const headers = new Headers({ 'content-type': 'application/json' })\n appendAppIdentity(headers, env)\n\n const res = await platformWorkerFetch(\n env,\n new Request('https://platform-internal/internal/screenshot', {\n method: 'POST',\n headers,\n body: JSON.stringify({ url }),\n }),\n )\n\n if (!res.ok) {\n console.warn(`[captureScreenshot] platform returned ${res.status}`)\n return null\n }\n\n return {\n body: await res.arrayBuffer(),\n contentType: res.headers.get('content-type') || 'image/png',\n }\n}\n","/**\n * Chat context pipeline — keeps the per-request payload to the LLM bounded.\n *\n * `prepareMessagesWithCompaction` runs before `streamText`: truncate old tool\n * results, apply a cached summary if available, otherwise summarize the older\n * half of history when over budget. Falls back to a sliding window if\n * summarization fails. `capToolResultSize` caps individual tool calls.\n */\n\nimport { generateText, type ModelMessage } from 'ai'\nimport { createDeepSpaceAI, type DeepSpaceAIEnv } from './ai'\n\nexport interface ChatTurn {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n}\n\nexport type Summarizer = (messages: ChatTurn[]) => Promise<string>\n\nexport interface ChatContextConfig {\n contextBudget: number\n toolResultCap: number\n keepRecentToolResults: number\n minKept: number\n}\n\n// contextBudget: 240_000 chars ≈ 60-80K tokens of history.\n// Safe for 200K+ context models (Claude Sonnet/Opus, GPT-4.1/4o).\n// If your app uses a 128K-context model, lower to ~120_000.\n// If your app uses a 32K-context model (some open-weight Cerebras models),\n// lower to ~40_000.\nexport const DEFAULT_CONTEXT_CONFIG: ChatContextConfig = {\n contextBudget: 240_000,\n toolResultCap: 30_000,\n keepRecentToolResults: 5,\n minKept: 10,\n}\n\nfunction sizeOf(m: ChatTurn): number {\n return (m.content ?? '').length + (m.parts ? JSON.stringify(m.parts).length : 0)\n}\n\nexport function totalChars(messages: ChatTurn[]): number {\n return messages.reduce((acc, m) => acc + sizeOf(m), 0)\n}\n\n/**\n * Replace older tool-result payloads with a small marker. Keeps the last\n * `keepRecent` tool results intact. Errors (`success: false`) are preserved —\n * they're small and the agent needs them for reasoning.\n */\nexport function truncateOldToolResults(messages: ChatTurn[], keepRecent: number): ChatTurn[] {\n const assistantIdxs: number[] = []\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === 'assistant') assistantIdxs.push(i)\n }\n const protectedStart =\n assistantIdxs.length > keepRecent ? assistantIdxs[assistantIdxs.length - keepRecent] : 0\n\n return messages.map((msg, idx) => {\n if (msg.role !== 'assistant' || idx >= protectedStart) return msg\n if (!Array.isArray(msg.parts) || msg.parts.length === 0) return msg\n const newParts = msg.parts.map((p) => {\n if (!p || typeof p !== 'object') return p\n const anyP = p as Record<string, unknown>\n if (anyP.type !== 'tool-invocation') return p\n const inv = anyP.toolInvocation as Record<string, unknown> | undefined\n if (!inv || inv.state !== 'result') return p\n const result = inv.result as Record<string, unknown> | undefined\n if (result && result.success === false) return p\n return {\n ...anyP,\n toolInvocation: {\n ...inv,\n result: {\n _truncated: true,\n note: 'Result from an earlier turn omitted to save context. Call this tool again if you need the data.',\n },\n },\n }\n })\n return { ...msg, parts: newParts }\n })\n}\n\n/**\n * Drop oldest messages until total character count is under `charCap`,\n * never going below `minKept` messages. System messages (e.g. compaction\n * summaries) are pinned — dropping them would discard the most condensed\n * context first.\n */\nexport function applySlidingWindow(\n messages: ChatTurn[],\n charCap: number,\n minKept: number,\n): ChatTurn[] {\n let total = totalChars(messages)\n if (total <= charCap) return messages\n const out = [...messages]\n while (out.length > minKept && total > charCap) {\n const idx = out.findIndex((m) => m.role !== 'system')\n if (idx < 0) break\n const dropped = out.splice(idx, 1)[0]\n total -= sizeOf(dropped)\n }\n return out\n}\n\ntype ResultObj = Record<string, unknown>\n\n// Paths (root → array) we know how to trim, in priority order. Covers the\n// built-in tool-result shapes: `{ records: [...] }`, `{ data: { records } }`,\n// and a bare `{ data: [...] }`.\nconst TRIMMABLE_ARRAY_PATHS: string[][] = [\n ['records'],\n ['data', 'records'],\n ['items'],\n ['data', 'items'],\n ['data'],\n]\n\nfunction getPath(root: unknown, path: string[]): unknown {\n let cur: unknown = root\n for (const key of path) {\n if (!cur || typeof cur !== 'object') return undefined\n cur = (cur as ResultObj)[key]\n }\n return cur\n}\n\n// Shallow-clone along `path`, set the leaf to `value`, and merge `flags` into\n// the object that directly holds the leaf. Non-mutating — the original result\n// is untouched.\nfunction setPath(root: unknown, path: string[], value: unknown, flags: ResultObj): unknown {\n const obj = root && typeof root === 'object' ? (root as ResultObj) : {}\n const [head, ...tail] = path\n if (tail.length === 0) {\n return { ...obj, [head]: value, ...flags }\n }\n return { ...obj, [head]: setPath(obj[head], tail, value, flags) }\n}\n\n// Find the first trimmable array in a tool result, so an oversized payload can\n// be degraded to a usable prefix instead of discarded. Returns undefined when\n// the payload carries no array we know how to shorten.\nfunction locateTrimmableArray(result: ResultObj): { path: string[]; items: unknown[] } | undefined {\n for (const path of TRIMMABLE_ARRAY_PATHS) {\n const arr = getPath(result, path)\n if (Array.isArray(arr)) return { path, items: arr }\n }\n // Generic fallbacks: first array prop at the top level, then one level into `data`.\n for (const [key, value] of Object.entries(result)) {\n if (Array.isArray(value)) return { path: [key], items: value }\n }\n const data = result.data\n if (data && typeof data === 'object') {\n for (const [key, value] of Object.entries(data as ResultObj)) {\n if (Array.isArray(value)) return { path: ['data', key], items: value }\n }\n }\n return undefined\n}\n\n/**\n * Keep an individual tool result under `byteCap`.\n *\n * If the payload carries a list of items (e.g. a `records.query` result), it is\n * degraded gracefully: as many leading items as fit under the cap are returned,\n * the `success: true` shape is preserved, and `{ truncated, returned, total }`\n * flags are merged in next to the array so callers can still use the partial\n * data and paginate for the rest.\n *\n * Only when there is no array to trim (or even an empty list still overflows\n * because of oversized sibling fields) does it fall back to replacing the\n * result with an error + small preview telling the agent to narrow its query.\n */\nexport function capToolResultSize(result: unknown, byteCap: number): unknown {\n let serialized: string | undefined\n try {\n serialized = JSON.stringify(result)\n } catch {\n return { success: false, error: 'Tool result could not be serialized.' }\n }\n // JSON.stringify returns `undefined` (not a string) for `undefined`,\n // bare functions, and symbols. Pass those through — caller decides what\n // to do with non-serializable shapes; this function's job is only to\n // cap oversized payloads.\n if (typeof serialized !== 'string') return result\n if (serialized.length <= byteCap) return result\n\n if (result && typeof result === 'object') {\n const located = locateTrimmableArray(result as ResultObj)\n if (located) {\n const { path, items } = located\n const total = items.length\n const build = (k: number) =>\n setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total })\n const fits = (k: number): boolean => {\n try {\n return JSON.stringify(build(k)).length <= byteCap\n } catch {\n return false\n }\n }\n // Only degrade if an empty list actually fits — otherwise the bloat is in\n // sibling fields, not the array, and trimming it can't help.\n if (fits(0)) {\n // Binary search for the largest prefix that still fits under the cap.\n let lo = 0\n let hi = total\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (fits(mid)) lo = mid\n else hi = mid - 1\n }\n // Return a partial page only if at least one item fits. When not even a\n // single record fits (one oversized record), fall through to the error\n // below so the caller gets actionable guidance instead of an empty list\n // that reads like \"no results\".\n if (lo > 0) return build(lo)\n }\n }\n }\n\n return {\n success: false,\n truncated: true,\n error:\n `Tool result exceeded ${byteCap} bytes (was ${serialized.length}). ` +\n `Retry with a narrower query (e.g. add a \\`where\\` filter, reduce \\`limit\\`, ` +\n `or call records.get for a single record).`,\n preview: serialized.slice(0, 2_000),\n }\n}\n\n/**\n * Convert persisted ChatTurns into AI SDK ModelMessages.\n *\n * Persisted assistant rows store `parts` in UI shape (text + tool-invocation,\n * each invocation carrying its own `result`). When fed back to the LLM, the\n * shape MUST match the original multi-step flow: an assistant message\n * containing a `tool_use` block must end with that block, the IMMEDIATELY\n * NEXT message must be a tool/user message containing the matching\n * `tool_result`, and any text the model produced AFTER seeing the tool\n * result belongs in a SEPARATE assistant message after the tool message.\n *\n * Anthropic specifically rejects an assistant message of the form\n * `[text, tool_use, text]` — the trailing text breaks its `tool_use` →\n * `tool_result` pairing check. So we walk the parts in order and split at\n * each tool-invocation boundary, emitting a fresh assistant + tool pair per\n * tool call, and a final trailing assistant message for any post-tool text.\n *\n * Tool-invocation entries with `state: 'call'` (no result — typically an\n * interrupted stream) are dropped on both sides.\n */\nexport function turnsToCoreMessages(turns: ChatTurn[]): ModelMessage[] {\n const out: ModelMessage[] = []\n for (const t of turns) {\n if (t.role === 'system') {\n out.push({ role: 'system', content: t.content })\n continue\n }\n if (t.role === 'user') {\n out.push({ role: 'user', content: t.content })\n continue\n }\n\n if (!Array.isArray(t.parts) || t.parts.length === 0) {\n if (t.content) out.push({ role: 'assistant', content: t.content })\n continue\n }\n\n // Persisted ChatTurn parts use a flat `args`/`result` shape; the SDK\n // wants v5's `input` and `output: { type:'json', value }` wrapper. The\n // `json` tag is the right variant for JSON-serializable tool results.\n type AssistantPart =\n | { type: 'text'; text: string }\n | { type: 'tool-call'; toolCallId: string; toolName: string; input: unknown }\n type ToolResultEntry = {\n type: 'tool-result'\n toolCallId: string\n toolName: string\n output: { type: 'json'; value: unknown }\n }\n\n // Pending text segment + the tool call it terminates with (if any).\n let pending: AssistantPart[] = []\n let emittedAny = false\n\n const flushAssistant = () => {\n if (pending.length === 0) return\n out.push({ role: 'assistant', content: pending } as ModelMessage)\n pending = []\n emittedAny = true\n }\n\n for (const p of t.parts) {\n if (!p || typeof p !== 'object') continue\n const anyP = p as Record<string, unknown>\n if (anyP.type === 'text' && typeof anyP.text === 'string' && anyP.text) {\n pending.push({ type: 'text', text: anyP.text })\n continue\n }\n if (anyP.type !== 'tool-invocation') continue\n const inv = anyP.toolInvocation as Record<string, unknown> | undefined\n const toolCallId = typeof anyP.toolCallId === 'string' ? anyP.toolCallId : undefined\n const toolName = typeof inv?.toolName === 'string' ? (inv.toolName as string) : undefined\n if (!toolCallId || !toolName) continue\n // Drop calls without a paired result — AI SDK errors on unanswered calls.\n if (inv?.state !== 'result') continue\n\n // Close out the current assistant segment with the tool_use block, then\n // emit it followed by a tool message holding the matching tool_result.\n pending.push({ type: 'tool-call', toolCallId, toolName, input: inv?.args })\n flushAssistant()\n const tr: ToolResultEntry = {\n type: 'tool-result',\n toolCallId,\n toolName,\n output: { type: 'json', value: inv.result as unknown },\n }\n out.push({ role: 'tool', content: [tr] } as ModelMessage)\n }\n\n // Trailing text after the last tool call (or the whole turn if no tools).\n flushAssistant()\n\n // Fallback: turn had only orphan tool-calls (all state:'call'); use plain\n // content text so we don't drop the message entirely.\n if (!emittedAny && t.content) {\n out.push({ role: 'assistant', content: t.content })\n }\n }\n\n return out\n}\n\n/**\n * Convert AI SDK response messages into our persisted UI shape (text +\n * tool-invocation parts), pairing each assistant tool-call with its tool-\n * result from the following tool message. Order is chronological.\n *\n * Inverse of `turnsToCoreMessages`: takes the v5 `ModelMessage[]` returned\n * from `streamText`'s `onFinish` and produces the flat `parts` array we\n * persist on `ai-messages` rows. Reads `c.input` / `c.output` (v5 wire\n * names) and unwraps `output`'s tagged-union via `unwrapToolOutput`.\n */\nexport function buildUiParts(responseMessages: ModelMessage[]): unknown[] {\n const toolResults = new Map<string, unknown>()\n for (const msg of responseMessages) {\n if (msg.role !== 'tool' || !Array.isArray(msg.content)) continue\n for (const c of msg.content) {\n if (c.type === 'tool-result') {\n toolResults.set(c.toolCallId, unwrapToolOutput(c.output))\n }\n }\n }\n const parts: unknown[] = []\n for (const msg of responseMessages) {\n if (msg.role !== 'assistant') continue\n if (typeof msg.content === 'string') {\n if (msg.content) parts.push({ type: 'text', text: msg.content })\n continue\n }\n if (!Array.isArray(msg.content)) continue\n for (const c of msg.content) {\n if (c.type === 'text' && typeof c.text === 'string' && c.text) {\n parts.push({ type: 'text', text: c.text })\n } else if (c.type === 'tool-call') {\n const result = toolResults.get(c.toolCallId)\n if (result === undefined) {\n // onFinish only fires on a successful stream, so every tool-call\n // should have a paired tool-result. If one is missing (provider\n // quirk, mid-step failure), drop the half-state invocation —\n // turnsToCoreMessages would drop it on the next turn anyway.\n continue\n }\n parts.push({\n type: 'tool-invocation',\n toolCallId: c.toolCallId,\n toolInvocation: {\n toolName: c.toolName,\n state: 'result',\n args: c.input,\n result,\n },\n })\n }\n }\n }\n return parts\n}\n\n/**\n * Unwrap v5's tagged tool-result `output` to the flat shape we persist.\n * Errors get remapped to `{ success: false, error }` because\n * `truncateOldToolResults` preserves entries with that shape across turns —\n * without the remap, error context would get truncated like a normal result.\n */\nexport function unwrapToolOutput(output: unknown): unknown {\n if (!output || typeof output !== 'object') return output\n const o = output as Record<string, unknown>\n const tag = typeof o.type === 'string' ? o.type : null\n if (tag === 'json' || tag === 'text') {\n return 'value' in o ? o.value : output\n }\n if (tag === 'error-text' || tag === 'error-json') {\n return { success: false, error: 'value' in o ? o.value : 'Tool execution failed' }\n }\n return output\n}\n\nfunction buildSummaryMessage(text: string, throughId: string): ChatTurn {\n return {\n id: `summary-${throughId}`,\n role: 'system',\n content: `Earlier conversation summary:\\n${text}`,\n }\n}\n\n/**\n * Pre-stream pipeline with compaction.\n *\n * 1. Truncate old tool results.\n * 2. If a cached summary covers a known message id, replace prior turns with it.\n * 3. If still over budget, summarize the older half of `working` and return a\n * `newSummary` for persistence — runs even after cached-summary application\n * so a long-running chat can re-summarize on subsequent turns.\n * 4. On summarizer error or missing ids, fall back to a sliding window\n * (which preserves system messages — see `applySlidingWindow`).\n */\nexport async function prepareMessagesWithCompaction(\n messages: ChatTurn[],\n config: ChatContextConfig,\n options: {\n summarizer: Summarizer\n cachedSummary?: { text: string; throughId: string }\n },\n): Promise<{ messages: ChatTurn[]; newSummary?: { text: string; throughId: string } }> {\n const truncated = truncateOldToolResults(messages, config.keepRecentToolResults)\n\n let working = truncated\n if (options.cachedSummary) {\n const idx = truncated.findIndex((m) => m.id === options.cachedSummary!.throughId)\n if (idx >= 0) {\n working = [\n buildSummaryMessage(options.cachedSummary.text, options.cachedSummary.throughId),\n ...truncated.slice(idx + 1),\n ]\n }\n }\n\n if (totalChars(working) <= config.contextBudget) {\n return { messages: working }\n }\n\n // Over budget: summarize the older half of `working`. Runs even when a\n // cached summary was applied — the summarizer prompt rolls prior summaries\n // forward, so the fresh summary subsumes the old one.\n const half = Math.max(1, Math.floor(working.length / 2))\n const older = working.slice(0, half)\n const newer = working.slice(half)\n // Pick the last REAL message id in `older` as the anchor. A previously\n // applied cached summary leaves a synthetic `summary-...` id at index 0;\n // anchoring on it would round-trip through history (which has no such id)\n // and force a re-summarize from scratch every turn — a billing leak.\n let throughId: string | undefined\n for (let i = older.length - 1; i >= 0; i--) {\n const id = older[i].id\n if (id && !id.startsWith('summary-')) {\n throughId = id\n break\n }\n }\n if (throughId) {\n try {\n const text = await options.summarizer(older)\n return {\n messages: [buildSummaryMessage(text, throughId), ...newer],\n newSummary: { text, throughId },\n }\n } catch (err) {\n console.error('prepareMessagesWithCompaction: summarizer failed, falling back to sliding window', err)\n }\n } else {\n console.warn('prepareMessagesWithCompaction: messages lack ids; cannot anchor a summary, falling back to sliding window')\n }\n\n return {\n messages: applySlidingWindow(working, config.contextBudget, config.minKept),\n }\n}\n\nconst SUMMARIZER_SYSTEM_PROMPT = `You are compressing a conversation between a user and an AI assistant into a\nconcise memory summary. The summary will replace the original messages in the\nconversation history; the assistant will read it on subsequent turns to\nmaintain continuity.\n\nPreserve in the summary:\n- User preferences and constraints the user stated (coding style, tone,\n skill level, goals).\n- Decisions made and rejected alternatives (\"we chose X over Y because Z\").\n- Current task state (what's in progress, what's been completed).\n- Any facts the user might reasonably reference later.\n\nIf the messages below contain a prior summary, incorporate its content into\nyour new summary — do not drop information from earlier summaries.\n\nKeep the summary under 2000 tokens. Write as a factual note to future-you,\nnot as a narrative. Use terse bullet points where possible.`\n\nfunction formatTranscript(messages: ChatTurn[]): string {\n return messages\n .map((m) => {\n const label = m.role === 'user' ? 'User' : m.role === 'assistant' ? 'Assistant' : 'System'\n const tools = Array.isArray(m.parts)\n ? m.parts\n .filter((p) => (p as { type?: string } | null)?.type === 'tool-invocation')\n .map((p) => {\n const name = (p as { toolInvocation?: { toolName?: string } }).toolInvocation?.toolName\n return ` [tool: ${name ?? 'unknown'}]`\n })\n .join('\\n')\n : ''\n return `${label}: ${m.content}${tools ? '\\n' + tools : ''}`\n })\n .join('\\n\\n')\n}\n\n/**\n * Build a default summarizer backed by Claude Haiku.\n *\n * Billing: defaults to the app owner via `APP_OWNER_JWT` — summarization is\n * usually infrastructure, not user work. Pass `{ authToken }` to bill a\n * specific user (e.g. the caller's JWT) instead.\n */\nexport function makeDefaultSummarizer(\n env: DeepSpaceAIEnv,\n options: { authToken?: string } = {},\n): Summarizer {\n return async (messages) => {\n const anthropic = createDeepSpaceAI(env, 'anthropic', { authToken: options.authToken })\n const { text } = await generateText({\n // Stable alias — provider bug-fix snapshots roll forward without code change.\n model: anthropic('claude-haiku-4-5'),\n // 2500 gives the model room to finish a sentence after hitting the\n // \"under 2000 tokens\" soft limit in the prompt.\n maxOutputTokens: 2500,\n system: SUMMARIZER_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: formatTranscript(messages) }],\n })\n return text\n }\n}\n","/**\n * Wire protocol constants.\n *\n * The JSON WebSocket protocol uses dotted string identifiers (e.g.\n * `\"records.query\"`) as the `type` discriminator. All message types are\n * grouped under a single `MSG` object so imports stay tidy:\n *\n * import { MSG, dispatch, clientBuild } from 'deepspace'\n *\n * dispatch<ServerMessage>(raw, {\n * })\n *\n * Each key's value is the on-wire string — grep-friendly, self-\n * documenting, and infinite per namespace. Adding a new message is one\n * line here plus one arm in the discriminated union in `./messages.ts`.\n *\n * Yjs binary protocol constants (`MSG_YJS_SYNC`, `MSG_YJS_AWARENESS`) are\n * intentionally kept numeric and separate from `MSG` — they ride a\n * binary WebSocket frame format and aren't part of the JSON dispatcher.\n */\n\nexport const MSG = {\n // ---- Records / CRUD --------------------------------------------------\n SUBSCRIBE: 'core.subscribe',\n UNSUBSCRIBE: 'core.unsubscribe',\n QUERY_RESULT: 'core.query_result',\n RECORD_CHANGE: 'core.record_change',\n PUT: 'core.put',\n DELETE: 'core.delete',\n ERROR: 'core.error',\n\n // ---- Users -----------------------------------------------------------\n USER_INFO: 'user.info',\n USER_LIST: 'user.list',\n SET_ROLE: 'user.set_role',\n USER_UPDATE: 'user.update',\n\n // ---- Auth signal -----------------------------------------------------\n // Server → client one-shot on connect from rooms that enforce role-based\n // writes (Canvas, Game, Cron). Tells the client whether this connection\n // can write so its hook can disable local write APIs and the UI can\n // render controls accordingly. Without this, a viewer's click would\n // round-trip to the server and silently come back as ERROR.\n AUTH: 'auth',\n\n // ---- Yjs JSON control frames (binary sync lives in ./yjs.ts) ---------\n YJS_JOIN: 'yjs.join',\n YJS_LEAVE: 'yjs.leave',\n\n // ---- Records room misc -----------------------------------------------\n ACK: 'records.ack',\n LIST_SCHEMAS: 'records.list_schemas',\n RESUBSCRIBE: 'records.resubscribe',\n\n\n // ---- CanvasRoom ------------------------------------------------------\n CANVAS_SHAPES: 'canvas.shapes',\n CANVAS_ADD: 'canvas.add',\n CANVAS_MOVE: 'canvas.move',\n CANVAS_RESIZE: 'canvas.resize',\n CANVAS_DELETE: 'canvas.delete',\n CANVAS_UPDATE: 'canvas.update',\n CANVAS_VIEWPORT: 'canvas.viewport',\n CANVAS_UNDO: 'canvas.undo',\n CANVAS_REDO: 'canvas.redo',\n\n // ---- CronRoom --------------------------------------------------------\n CRON_TASKS: 'cron.tasks',\n CRON_HISTORY: 'cron.history',\n CRON_TRIGGER: 'cron.trigger',\n CRON_PAUSE: 'cron.pause',\n CRON_RESUME: 'cron.resume',\n CRON_STATUS: 'cron.status',\n // Per-mutation receipt, sent only to the socket whose trigger/pause/\n // resume carried a `requestId` — frames without one stay fire-and-forget.\n CRON_ACK: 'cron.ack',\n\n // ---- JobRoom ---------------------------------------------------------\n // Background-job queue with durable, alarm-driven execution. See\n // server/rooms/job-room.ts. Clients enqueue and subscribe to updates;\n // the room broadcasts JOB_UPDATE for every status transition.\n JOB_ENQUEUE: 'job.enqueue',\n JOB_CANCEL: 'job.cancel',\n JOB_RETRY: 'job.retry',\n JOB_UPDATE: 'job.update',\n\n // ---- PresenceRoom ----------------------------------------------------\n PRESENCE_SYNC: 'presence.sync',\n PRESENCE_JOIN: 'presence.join',\n PRESENCE_LEAVE: 'presence.leave',\n PRESENCE_UPDATE: 'presence.update',\n\n // ---- Gateway multiplexing --------------------------------------------\n GW_SCOPE_CONNECT: 'gateway.scope_connect',\n GW_SCOPE_DISCONNECT: 'gateway.scope_disconnect',\n GW_SCOPE_ERROR: 'gateway.scope_error',\n GW_TOKEN_REFRESH: 'gateway.token_refresh',\n GW_USER_UPDATE: 'gateway.user_update',\n} as const\n\n/**\n * Type of any message type constant — the union of every string literal\n * stored in `MSG`. Useful when declaring functions that accept \"any known\n * message type\" without enumerating all 54 strings by hand.\n */\nexport type MsgType = (typeof MSG)[keyof typeof MSG]\n\n// ---------------------------------------------------------------------------\n// Yjs binary protocol (NOT part of MSG — see header comment)\n// ---------------------------------------------------------------------------\n\n/** Outer envelope id for binary-framed yjs sync messages. Varuint-encoded. */\nexport const MSG_YJS_SYNC = 22\n/** Outer envelope id for binary-framed yjs awareness messages. Varuint-encoded. */\nexport const MSG_YJS_AWARENESS = 23\n\n// ---------------------------------------------------------------------------\n// Role names — not wire messages, just the string values the auth layer uses\n// as role identifiers.\n// ---------------------------------------------------------------------------\n\n/** Role assigned to unauthenticated WebSocket connections */\nexport const ROLE_ANONYMOUS = 'viewer'\n/** Prefix `base-room` mints anonymous socket ids with. */\nexport const ANONYMOUS_USER_ID_PREFIX = 'anon-'\n/** The one test for an anonymous socket identity — `ROLE_ANONYMOUS` cannot be\n * it, since authenticated users can legitimately hold the same role name. */\nexport function isAnonymousUserId(userId: string): boolean {\n return userId.startsWith(ANONYMOUS_USER_ID_PREFIX)\n}\n/** Default role for newly registered authenticated users */\nexport const ROLE_DEFAULT = 'member'\n/** Admin role */\nexport const ROLE_ADMIN = 'admin'\n\n// ---------------------------------------------------------------------------\n// Error strings that cross a room boundary and are matched by the caller\n// ---------------------------------------------------------------------------\n\n/**\n * Reported when a record id resolves to nothing.\n *\n * This crosses the DO boundary as a response body and callers branch on it,\n * so it is a contract rather than prose. `getChat` in particular treats it as\n * \"no such chat\" and returns null — the same answer it gives for a chat owned\n * by someone else, which is what stops chat ids being enumerable. If a room\n * reworded this independently, a miss would start throwing while a cross-user\n * hit kept returning null, and that difference is an existence oracle.\n */\nexport const RECORD_NOT_FOUND = 'Record not found'\n","/**\n * Chat history helpers — wrap RecordRoom's tools API for ai-chats / ai-messages.\n *\n * Trust model: every helper here runs through `executeToolAsApp`, which sends\n * `X-App-Action: 'true'` and therefore **bypasses RecordRoom's per-record\n * RBAC entirely**. The `userId` argument these helpers take is the identity\n * they act *as* — it is NOT an authorization boundary, and RecordRoom will\n * not check it. The worker is the only trust boundary.\n *\n * `getChat` is that boundary for this module: it compares the stored\n * `userId` against the caller and returns null on a mismatch, so the\n * worker's `/api/ai/chat`, `PATCH /api/ai/chats/:id`, and\n * `DELETE /api/ai/chats/:id` routes 404 when a row is missing *or* owned by\n * someone else. Every write helper (`updateChat`, `appendMessage`,\n * `deleteChatCascade`) is reachable only behind that precheck; a new caller\n * that skips it is writing across users, so route new consumers through\n * `getChat` first.\n *\n * `updateChat` and `appendMessage` re-run that precheck themselves and report\n * `false` instead of writing when the chat is gone: `records.update` /\n * `records.create` are upserts, so a write racing the user's delete would\n * resurrect the chat as a ghost row or orphan messages under a chat that no\n * longer exists. The guard lives in the helpers, not the routes, so every copy\n * of the scaffolded chat routes gets it.\n *\n * The tools API returns records as `{ recordId, data, createdAt, updatedAt }`\n * envelopes; helpers below flatten them into ChatRow / ChatMessageRow.\n */\n\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\n\nexport type ChatRow = {\n recordId: string\n userId: string\n title: string\n model?: string\n compactedSummary?: string\n compactedThroughId?: string\n createdAt: string\n updatedAt: string\n}\n\nexport type ChatMessageRow = {\n recordId: string\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n createdAt: string\n}\n\ntype RecordEnvelope<T> = {\n recordId: string\n data: T\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype ChatColumns = {\n userId: string\n title?: string\n model?: string\n compactedSummary?: string\n compactedThroughId?: string\n}\n\ntype MessageColumns = {\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n}\n\ntype ToolResponse<T> = { success: boolean; data?: T; error?: string }\n\nasync function executeToolAsApp<T>(\n stub: DurableObjectStub,\n userId: string,\n tool: string,\n params: Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch(new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': userId,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({ tool, params }),\n }))\n const body = await res.json() as ToolResponse<T>\n if (!body.success) {\n throw new Error(`chat-history ${tool} failed: ${body.error ?? 'unknown error'}`)\n }\n return body.data as T\n}\n\nfunction toChatRow(env: RecordEnvelope<ChatColumns>): ChatRow {\n return {\n recordId: env.recordId,\n userId: env.data.userId,\n title: env.data.title ?? '',\n model: env.data.model,\n compactedSummary: env.data.compactedSummary,\n compactedThroughId: env.data.compactedThroughId,\n createdAt: env.createdAt,\n updatedAt: env.updatedAt,\n }\n}\n\nfunction toMessageRow(env: RecordEnvelope<MessageColumns>): ChatMessageRow {\n return {\n recordId: env.recordId,\n chatId: env.data.chatId,\n userId: env.data.userId,\n role: env.data.role,\n content: env.data.content ?? '',\n parts: env.data.parts,\n createdAt: env.createdAt,\n }\n}\n\n/**\n * Fetch a chat the caller owns, or null.\n *\n * This is the module's authorization boundary. `records.get` runs with\n * `X-App-Action`, so RecordRoom applies no per-record RBAC and will happily\n * return another user's row — the ownership comparison below is the only\n * thing standing between a caller and someone else's chat. A miss and a\n * cross-user hit deliberately look identical to the caller (both null, both\n * 404 at the route) so chat ids stay unenumerable.\n */\nexport async function getChat(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<ChatRow | null> {\n try {\n const result = await executeToolAsApp<{ record: RecordEnvelope<ChatColumns> }>(stub, userId, 'records.get', {\n collection: 'ai-chats',\n recordId: chatId,\n })\n if (!result.record) return null\n const chat = toChatRow(result.record)\n return chat.userId === userId ? chat : null\n } catch (err) {\n // Shared constant, not a loose string: a miss and a cross-user hit must\n // keep producing the same null. If a room reworded this on its own, a\n // miss would throw while a foreign chat still returned null, and that\n // asymmetry is exactly the existence oracle the ownership check removes.\n if (err instanceof Error && err.message.includes(RECORD_NOT_FOUND)) return null\n throw err\n }\n}\n\nexport async function createChat(\n stub: DurableObjectStub,\n userId: string,\n opts: { title?: string; model?: string } = {},\n): Promise<ChatRow> {\n const data: Record<string, unknown> = { userId }\n if (opts.title !== undefined) data.title = opts.title\n if (opts.model !== undefined) data.model = opts.model\n const result = await executeToolAsApp<{ record: RecordEnvelope<ChatColumns> }>(stub, userId, 'records.create', {\n collection: 'ai-chats',\n data,\n })\n return toChatRow(result.record)\n}\n\n/**\n * Patch a chat the caller owns. Returns true if the row was written, false if\n * the chat no longer exists (or never belonged to the caller).\n *\n * The `getChat` precheck is not redundant with the route's own: `records.update`\n * is an upsert on the DO — same code path as create — so an unguarded patch of\n * a chat the user deleted mid-stream *recreates* it as a title-less ghost whose\n * messages are already cascaded away. Writing only over a row that still exists\n * is the one place that can be prevented for every caller, so the guard lives\n * here rather than at each call site. A tiny TOCTOU window remains between the\n * read and the write (closing it needs a compare-and-set on the DO); a delete\n * landing inside that window is the same rare race as before, not the routine\n * \"user deleted the chat while the stream ran\" case this closes.\n */\nexport async function updateChat(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n patch: Partial<Pick<ChatRow, 'title' | 'model' | 'compactedSummary' | 'compactedThroughId'>>,\n): Promise<boolean> {\n if (!(await getChat(stub, chatId, userId))) return false\n await executeToolAsApp(stub, userId, 'records.update', {\n collection: 'ai-chats',\n recordId: chatId,\n data: patch,\n })\n return true\n}\n\n/**\n * Messages removed per `records.deleteWhere` call. One subrequest per page,\n * so a chat of any length costs `ceil(messages / PAGE) + 1` subrequests\n * instead of one per message — the old shape ran out of the Workers\n * subrequest budget mid-cascade and orphaned the remainder.\n */\nconst MESSAGE_DELETE_PAGE = 200\n\nexport async function deleteChatCascade(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<void> {\n let messagesError: unknown = null\n try {\n // Each page really removes its rows, so the remaining set strictly shrinks\n // and a short page is the last one.\n let deleted = MESSAGE_DELETE_PAGE\n while (deleted === MESSAGE_DELETE_PAGE) {\n const page = await executeToolAsApp<{ deleted: number }>(stub, userId, 'records.deleteWhere', {\n collection: 'ai-messages',\n where: { chatId },\n limit: MESSAGE_DELETE_PAGE,\n })\n deleted = page.deleted\n }\n } catch (err) {\n messagesError = err\n }\n\n // Always attempt the chat row delete so the row disappears from listings,\n // even if some message rows got orphaned. If it fails too, its error is the\n // one that surfaces — a chat still sitting in the user's list is the visible\n // failure, orphaned messages are not.\n await executeToolAsApp(stub, userId, 'records.delete', {\n collection: 'ai-chats',\n recordId: chatId,\n })\n\n if (messagesError) throw messagesError\n}\n\nexport async function loadMessages(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<ChatMessageRow[]> {\n // Filter by userId in addition to chatId — defense in depth against any\n // future change that lets a row land in this collection without going\n // through the worker (which already verifies chat ownership).\n const result = await executeToolAsApp<{ records: Array<RecordEnvelope<MessageColumns>> }>(stub, userId, 'records.query', {\n collection: 'ai-messages',\n where: { chatId, userId },\n orderBy: 'createdAt',\n orderDir: 'asc',\n })\n return result.records.map(toMessageRow)\n}\n\n/**\n * Append one message to a chat the caller owns. Returns true if the row was\n * written, false if the chat is gone (or never belonged to the caller).\n *\n * Same write-after-delete guard as `updateChat`, for the same reason: a turn\n * that finishes after the user deleted the chat would otherwise write messages\n * whose parent row no longer exists — invisible in every listing and never\n * cascaded again.\n */\nexport async function appendMessage(\n stub: DurableObjectStub,\n msg: {\n id: string\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n },\n): Promise<boolean> {\n if (!(await getChat(stub, msg.chatId, msg.userId))) return false\n const data: Record<string, unknown> = {\n chatId: msg.chatId,\n userId: msg.userId,\n role: msg.role,\n content: msg.content,\n }\n if (msg.parts !== undefined) data.parts = msg.parts\n await executeToolAsApp(stub, msg.userId, 'records.create', {\n collection: 'ai-messages',\n recordId: msg.id,\n data,\n })\n return true\n}\n","/**\n * Per-binding usage metering — record Vectorize / Workers AI / etc. costs\n * to the auto-attached `USAGE_EVENTS` Analytics Engine dataset.\n *\n * Why: the platform's tail-worker captures per-invocation compute (CPU + wall\n * time + script name) but it can't see which model an AI call hit, how many\n * tokens it embedded, or how many vectors a Vectorize query scanned. Without\n * those signals there's no way to surface per-tenant binding cost on the\n * billing dashboard.\n *\n * The deploy-worker auto-attaches a `USAGE_EVENTS` AE binding to every app\n * (dataset: `deepspace_binding_usage`). Apps don't need to declare it. They\n * just call `meterAi(...)` / `meterVectorize(...)` / `meterUsage(...)` after\n * each call and the dashboard rolls it up by `ownerUserId`.\n *\n * Schema written:\n * indexes: [ownerUserId]\n * blobs: [appName, kind, model_or_index, op]\n * doubles: [units, count]\n *\n * Use:\n * await meterAi(env, '@cf/qwen/qwen3-embedding-0.6b', { inputChars: 5000 })\n * await meterVectorize(env, 'unison-candidates', 'query', { vectors: 1000 })\n * await meterUsage(env, 'custom-thing', { units: 1 })\n */\n/// <reference types=\"@cloudflare/workers-types\" />\n\ninterface MeteringEnv {\n USAGE_EVENTS?: AnalyticsEngineDataset\n OWNER_USER_ID?: string\n DEEPSPACE_RESOURCE_ID?: string\n APP_NAME?: string\n}\n\n/**\n * Generic event recorder. Returns `false` if the binding isn't present\n * (dev / not yet deployed) or if AnalyticsEngine throws — metering must\n * never break the calling code path.\n */\nexport function meterUsage(\n env: MeteringEnv,\n kind: string,\n fields: { id?: string; op?: string; units?: number; count?: number } = {},\n): boolean {\n const ds = env.USAGE_EVENTS\n if (!ds) return false\n try {\n ds.writeDataPoint({\n indexes: [env.OWNER_USER_ID ?? 'unknown'],\n blobs: [\n env.DEEPSPACE_RESOURCE_ID ?? env.APP_NAME ?? 'unknown',\n kind,\n fields.id ?? '',\n fields.op ?? '',\n ],\n doubles: [fields.units ?? 0, fields.count ?? 0],\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Record a Workers AI call.\n *\n * Cloudflare prices input and output tokens at different rates for LLMs\n * (output is typically more expensive); embedding models bill input only.\n * Emits up to two events per call so the dashboard rollup can group by\n * `op` and apply the right per-token rate:\n *\n * op='input' units=inputChars\n * op='output' units=outputChars\n *\n * For a pure embedding call (outputChars=0), only the input event fires.\n * Pass `inputChars` and `outputChars` raw — the rough chars-to-token\n * conversion happens at price time using `COST_RATES.ai.embedInputPerChar`.\n *\n * Note: only embedding-input has an authoritative rate today. LLM-output\n * pricing varies wildly per model so `priceBindingUsageEvent` returns 0\n * for `op='output'` until per-model rates are wired. The events are still\n * recorded so the dashboard can show that the calls happened.\n */\nexport function meterAi(\n env: MeteringEnv,\n model: string,\n fields: { inputChars?: number; outputChars?: number; calls?: number } = {},\n): boolean {\n const calls = fields.calls ?? 1\n const inChars = fields.inputChars ?? 0\n const outChars = fields.outputChars ?? 0\n let ok = true\n if (inChars > 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'input', units: inChars, count: calls }) && ok\n }\n if (outChars > 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'output', units: outChars, count: calls }) && ok\n }\n // No tokens reported: still record the call so we can see the model was\n // invoked in dashboards. Useful for failed/empty responses.\n if (inChars === 0 && outChars === 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'call', units: 0, count: calls }) && ok\n }\n return ok\n}\n\n/**\n * Record a Vectorize operation.\n *\n * Cloudflare's published model (https://developers.cloudflare.com/vectorize/platform/pricing/):\n *\n * \"If you have 10,000 vectors with 384-dimensions in an index, and make\n * 100 queries against that index, your total queried vector dimensions\n * would sum to 3.878 million ((10000 + 100) * 384).\"\n *\n * So query billing is *additive* — `(stored + queries) * dims` summed\n * across the call, not per-query-multiplied-by-stored. Translating to a\n * per-call meter:\n *\n * op='query': units = (vectors + storedCount) * dims\n * Without `storedCount` we significantly undercount: a single\n * query against a 100K-vector index produces ~100K queried\n * dims, not just `dims`.\n * op='upsert': CF doesn't bill upserts directly; the chargeable delta is\n * the change to stored-vector-month. `units = vectors * dims`\n * approximates the per-call storage delta.\n * op='delete' / 'getByIds': recorded for observability; no direct cost.\n *\n * Edge case: querying an empty index gives `(1 + 0) * dims = dims`, which\n * matches CF's formula (the `+ queries` term is always added, even at 0\n * stored). If CF later changes that and an empty-index query bills 0,\n * adjust here — `metering` is the single place to update the math.\n */\nexport function meterVectorize(\n env: MeteringEnv,\n indexName: string,\n op: 'query' | 'upsert' | 'delete' | 'getByIds',\n fields: { vectors?: number; dims?: number; storedCount?: number } = {},\n): boolean {\n const vectors = fields.vectors ?? 0\n const dims = fields.dims ?? 0\n const stored = fields.storedCount ?? 0\n const units = op === 'query' ? (vectors + stored) * dims : vectors * dims\n return meterUsage(env, 'vectorize', { id: indexName, op, units, count: vectors })\n}\n\n// ---- Cost estimates (USD) — see Cloudflare pricing as of 2026-05 ----\n\n/** English avg; replace with a real tokenizer if accuracy matters. */\nconst CHARS_PER_TOKEN = 4\n\n/** $0.012 per 1M input tokens (bge-m3 + qwen3-embedding-0.6b tier). */\nconst AI_EMBED_USD_PER_M_TOKENS = 0.012\n\n/** $0.01 per 1M queried dimensions. */\nconst VECTORIZE_QUERIED_USD_PER_M_DIMS = 0.01\n\n/** $0.05 per 100M stored dimensions per month. */\nconst VECTORIZE_STORED_USD_PER_100M_DIMS = 0.05\n\n/**\n * Per-`units` USD multipliers, matched to the (`kind`, `op`) the meter\n * helpers above record. Dashboard rollup can multiply\n *\n * SUM(_sample_interval * doubles[1]) -- units\n *\n * by these to surface $-figures without re-querying CF's billing API.\n */\nexport const COST_RATES = {\n ai: {\n /**\n * USD per character of *embedding input* (bge-m3 / qwen3-embedding tier).\n * Renamed from `perChar` to make explicit that this rate does NOT\n * apply to LLM-generation output — see `priceBindingUsageEvent`.\n */\n embedInputPerChar: AI_EMBED_USD_PER_M_TOKENS / 1_000_000 / CHARS_PER_TOKEN,\n },\n vectorize: {\n /** USD per queried dimension (per query, per stored vector compared). */\n queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1_000_000,\n /** USD per stored dimension per month. */\n storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 100_000_000,\n },\n} as const\n\n/**\n * Price a single rolled-up `deepspace_binding_usage` row. Lives next to\n * `COST_RATES` so the (kind, op) → rate mapping stays paired with the schema\n * the meter helpers write.\n *\n * Returns 0 for combinations without an authoritative per-unit rate; the row\n * still surfaces in dashboards for observability. Notable zeros:\n * - `ai/output`: LLM-output prices vary per model and `meterAi` doesn't\n * carry a model-family signal. Pricing it at the embedding rate would\n * silently under-bill chat-LLM use.\n * - `vectorize.storedPerDimPerMonth`: events are per-call deltas, not\n * monthly snapshots, so a windowed SUM isn't meaningful here.\n */\nexport function priceBindingUsageEvent(kind: string, op: string, units: number): number {\n if (kind === 'ai' && op === 'input') {\n return units * COST_RATES.ai.embedInputPerChar\n }\n if (kind === 'vectorize' && op === 'query') {\n return units * COST_RATES.vectorize.queriedPerDim\n }\n return 0\n}\n","/**\n * Lightweight D1 schema bootstrapping for apps that use auto-provisioned\n * `[[d1_databases]]` bindings.\n *\n * The auto-provisioner gives apps an empty D1; the app needs to create its\n * own tables before using them. This helper runs ordered SQL fragments and\n * tracks which have applied via a `_dpc_migrations` meta-table so re-running\n * is a no-op:\n *\n * ```ts\n * import { runMigrations } from 'deepspace/worker'\n *\n * await runMigrations(env.CARDS_DB, [\n * `CREATE TABLE cards (id INTEGER PRIMARY KEY, json TEXT NOT NULL);`,\n * `CREATE INDEX idx_cards_updated ON cards(updated_at);`,\n * ])\n * ```\n *\n * **SQL formatting:** statements may span multiple lines freely. The runner\n * splits each migration string on `;` and runs each fragment via\n * `prepare().run()`. That sidesteps `db.exec()`'s newline-or-semicolon quirks\n * (it's optimized for migration files, not inline strings) and gets you\n * predictable single-statement semantics. Trailing `;` after the last\n * statement is fine; semicolons inside string literals are not (we use a\n * naive split, since DDL almost never has them).\n *\n * Each entry in the array is one migration. The runner records the index of\n * each successfully-applied migration in `_dpc_migrations`; subsequent calls\n * skip rows already recorded. Adding a new migration means appending to the\n * array; never reorder or delete entries.\n *\n * Why a meta-table instead of `PRAGMA user_version`: D1's SQLite authorizer\n * rejects PRAGMA writes with `SQLITE_AUTH`, even though the same statements\n * work in raw SQLite. A real table works on any D1 database and stays a\n * trivial bootstrap (one CREATE TABLE IF NOT EXISTS).\n *\n * Concurrency: D1 serializes statements per database, but two simultaneous\n * `runMigrations` callers could race the same migration index. The duplicate\n * INSERT collides on the primary key and the second caller sees the failure\n * — but the migration itself uses `IF NOT EXISTS` so the schema is correct\n * either way. Apps invoke this at startup which is single-threaded per\n * worker isolate; the cross-isolate race is rare and self-healing.\n *\n * This is the simplest possible migration story (option 1 in\n * docs/proposals/binding-auto-provisioning.md). Apps that outgrow it can\n * adopt CF's `wrangler d1 migrations apply` directly without breaking the\n * helper.\n */\n\nconst META_TABLE = '_dpc_migrations'\n// Single-statement DDL — runs through prepare().run(), not exec(). exec()\n// is documented as newline-separated statements for migration files; the\n// binding's behavior on a single statement with a trailing ';' has been\n// flaky in practice and prepare()/run() is the right shape anyway.\nconst META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`\n\nexport interface RunMigrationsResult {\n /** Version before this run started. Equals the count of migrations already applied. */\n fromVersion: number\n /** Version after migrations applied. Equals fromVersion if nothing ran. */\n toVersion: number\n /** Number of migrations applied this call. */\n applied: number\n}\n\n/**\n * Apply ordered SQL migrations to a D1 database. Idempotent: the next call\n * with the same array is a no-op until the array grows.\n *\n * Throws on any individual migration failure. The migrations meta-row is\n * only inserted after a migration succeeds, so a partial failure leaves a\n * recoverable state — fix the SQL, redeploy, and the failed migration runs\n * on next startup.\n */\nexport async function runMigrations(\n db: D1Database,\n migrations: readonly string[],\n): Promise<RunMigrationsResult> {\n // Bootstrap the meta-table. Single statement → prepare().run().\n await db.prepare(META_BOOTSTRAP_SQL).run()\n\n const row = await db\n .prepare(`SELECT COUNT(*) AS n FROM ${META_TABLE}`)\n .first<{ n: number }>()\n const fromVersion = row?.n ?? 0\n\n if (fromVersion >= migrations.length) {\n return { fromVersion, toVersion: fromVersion, applied: 0 }\n }\n\n let current = fromVersion\n for (let i = fromVersion; i < migrations.length; i++) {\n const statements = splitStatements(migrations[i])\n for (const stmt of statements) {\n try {\n await db.prepare(stmt).run()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`Migration ${i} failed (db at version=${current}): ${msg}`)\n }\n }\n // Record the applied migration. PK collision (duplicate idx from a\n // concurrent run) surfaces as an error and we abort — but the schema\n // is already in place, so the next attempt sees fromVersion advance\n // and skips it.\n await db\n .prepare(`INSERT INTO ${META_TABLE} (idx, applied_at) VALUES (?, ?)`)\n .bind(i, new Date().toISOString())\n .run()\n current = i + 1\n }\n\n return { fromVersion, toVersion: current, applied: current - fromVersion }\n}\n\n/**\n * Naive `;` split: good enough for DDL where the only semicolons are\n * statement terminators. Strings with embedded `;` inside literals would\n * break this — accept that tradeoff because real migrations rarely contain\n * them and the alternative is shipping a SQL parser.\n */\nfunction splitStatements(sql: string): string[] {\n return sql\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n}\n","/**\n * Identity claims cross from an app Worker to its private room Durable Object\n * in headers. Header values are ByteStrings, while verified profile claims may\n * contain arbitrary Unicode or control characters, so carry each value as\n * UTF-8 base64url rather than writing the claim directly.\n */\n\nconst BASE64URL = /^[A-Za-z0-9_-]*$/\nconst IDENTITY_QUERY_PARAMS = ['userId', 'userName', 'userEmail', 'userImageUrl', 'role'] as const\nconst IDENTITY_HEADERS = [\n 'x-user-id',\n 'x-user-name',\n 'x-user-email',\n 'x-user-image-url',\n 'x-user-role',\n] as const\n\ninterface RoomAuth {\n userId: string\n claims: { name?: string; email?: string; image?: string }\n}\n\nexport function encodeRoomIdentityHeader(value: string): string {\n const bytes = new TextEncoder().encode(value)\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nexport function decodeRoomIdentityHeader(value: string | null): string | undefined {\n if (value === null || !BASE64URL.test(value) || value.length % 4 === 1) return undefined\n const base64 = value\n .replaceAll('-', '+')\n .replaceAll('_', '/')\n .padEnd(value.length + ((4 - (value.length % 4)) % 4), '=')\n try {\n const binary = atob(base64)\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n } catch {\n return undefined\n }\n}\n\n/** Strip public identity inputs and forward only verified room identity. */\nexport function authenticatedRoomRequest(\n request: Request,\n auth: RoomAuth | null,\n identity?: { role?: string },\n): Request {\n const url = new URL(request.url)\n url.searchParams.delete('token')\n for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key)\n\n const forwarded = new Request(url.toString(), request)\n for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header)\n if (!auth) return forwarded\n\n forwarded.headers.set('x-user-id', encodeRoomIdentityHeader(auth.userId))\n if (auth.claims.name) {\n forwarded.headers.set('x-user-name', encodeRoomIdentityHeader(auth.claims.name))\n }\n if (auth.claims.email) {\n forwarded.headers.set('x-user-email', encodeRoomIdentityHeader(auth.claims.email))\n }\n if (auth.claims.image) {\n forwarded.headers.set('x-user-image-url', encodeRoomIdentityHeader(auth.claims.image))\n }\n if (identity?.role) {\n forwarded.headers.set('x-user-role', encodeRoomIdentityHeader(identity.role))\n }\n return forwarded\n}\n","/**\n * BaseRoom — Abstract base class for all DeepSpace Durable Objects.\n *\n * Provides:\n * - WebSocket upgrade with Cloudflare hibernation API\n * - Connection tracking (WebSocket -> UserAttachment)\n * - Auth: parse JWT-verified user info from internal request headers\n * - Presence: connected users list, awareness on connect/disconnect\n * - Message routing: JSON parse -> dispatch by `type` field, binary hook\n * - Raw SQLite access via this.sql\n * - Broadcast helpers: broadcast(), sendTo()\n * - HTTP fetch handler with WebSocket upgrade detection\n * - Internal control-plane endpoint: POST /internal/disconnect-sockets\n * (force every client to reconnect and resync after out-of-band writes)\n *\n * Subclasses implement lifecycle hooks:\n * onConnect, onMessage, onBinaryMessage, onDisconnect, onRequest, onAlarm\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport { ANONYMOUS_USER_ID_PREFIX } from '../../shared/protocol/constants'\nimport { decodeRoomIdentityHeader } from '../../shared/room-identity-headers'\n\n// ============================================================================\n// User Attachment (survives DO hibernation via WebSocket serialization)\n// ============================================================================\n\nexport interface UserAttachment {\n userId: string\n userName: string\n userEmail: string\n userImageUrl?: string\n /** Subclass-specific data serialized alongside user info */\n [key: string]: unknown\n}\n\n/** Build the hibernation attachment from app-worker-verified headers only. */\nexport function connectionAttachmentFromRequest(request: Request): UserAttachment {\n return {\n userId:\n decodeRoomIdentityHeader(request.headers.get('x-user-id')) ||\n `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,\n userName: decodeRoomIdentityHeader(request.headers.get('x-user-name')) || 'Anonymous',\n userEmail: decodeRoomIdentityHeader(request.headers.get('x-user-email')) || '',\n userImageUrl: decodeRoomIdentityHeader(request.headers.get('x-user-image-url')),\n role: decodeRoomIdentityHeader(request.headers.get('x-user-role')),\n }\n}\n\n// ============================================================================\n// BaseRoom\n// ============================================================================\n\nexport abstract class BaseRoom<E = Record<string, unknown>> {\n protected state: DurableObjectState\n protected env: E\n protected sql: SqlStorage\n\n constructor(state: DurableObjectState, env: unknown) {\n this.state = state\n this.env = (env ?? {}) as E\n this.sql = state.storage.sql\n\n this.state.setWebSocketAutoResponse(new WebSocketRequestResponsePair('ping', 'pong'))\n }\n\n // ==========================================================================\n // HTTP Entry Point\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n // Internal control-plane endpoints. These are reachable ONLY via a DO stub\n // fetch from the app worker's own server-side code (same trust model as the\n // `/api/tools/execute` surface) — the app worker's public `/ws/:roomId`\n // proxy forwards only `/ws/*` paths, so a browser can never reach\n // `/internal/*` through it. Do NOT add a public app-worker route that\n // forwards arbitrary paths to a room DO.\n const internal = await this.handleInternalRequest(request, url)\n if (internal) return internal\n\n // WebSocket upgrade\n if (request.headers.get('Upgrade') === 'websocket') {\n return this.handleWebSocketUpgrade(request, url)\n }\n\n // Delegate non-WS requests to subclass\n if (this.onRequest) {\n return this.onRequest(request)\n }\n\n return new Response('Not Found', { status: 404 })\n }\n\n // ==========================================================================\n // Internal Control-Plane Endpoints\n // ==========================================================================\n\n /**\n * Handle built-in `/internal/*` control-plane routes shared by every room\n * type. Returns a `Response` if the request was an internal route, or `null`\n * to let the caller continue normal dispatch.\n *\n * Currently:\n * - `POST /internal/disconnect-sockets` — close every live WebSocket so\n * clients reconnect and resync. Optional JSON body `{ code?, reason? }`\n * overrides the defaults (1012 / 'state-refresh'). Responds with\n * `{ success: true, closed: <n> }`.\n *\n * See the security note on `fetch()`: this path is only reachable via DO\n * stub fetch from the app worker, never from the public internet.\n */\n protected async handleInternalRequest(request: Request, url: URL): Promise<Response | null> {\n if (request.method === 'POST' && url.pathname === '/internal/disconnect-sockets') {\n let options: { code?: number; reason?: string } | undefined\n try {\n const text = await request.text()\n if (text) options = JSON.parse(text) as { code?: number; reason?: string }\n } catch {\n // Malformed/empty body → fall back to defaults.\n }\n const closed = this.disconnectAllSockets(options)\n return Response.json({ success: true, closed })\n }\n\n return null\n }\n\n // ==========================================================================\n // WebSocket Connection\n // ==========================================================================\n\n private async handleWebSocketUpgrade(request: Request, _url: URL): Promise<Response> {\n const pair = new WebSocketPair()\n const [client, server] = Object.values(pair)\n\n this.state.acceptWebSocket(server)\n\n const attachment = connectionAttachmentFromRequest(request)\n\n // Let subclass augment the attachment and perform setup\n const augmented = (await this.onConnect(server, attachment)) ?? attachment\n server.serializeAttachment(augmented)\n\n return new Response(null, { status: 101, webSocket: client })\n }\n\n // ==========================================================================\n // Hibernation API Handlers\n // ==========================================================================\n\n async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void> {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (!attachment) return\n\n // Binary messages\n if (message instanceof ArrayBuffer) {\n if (this.onBinaryMessage) {\n try {\n await this.onBinaryMessage(ws, attachment, message)\n } catch (e) {\n console.error(`[${this.constructor.name}] Binary message error:`, e)\n }\n }\n return\n }\n\n // JSON messages\n try {\n const msg = JSON.parse(message)\n await this.onMessage(ws, attachment, msg)\n } catch (e) {\n console.error(`[${this.constructor.name}] Message error:`, e)\n }\n }\n\n async webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void> {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (attachment) {\n try {\n await this.onDisconnect(ws, attachment)\n } catch {\n /* best-effort */\n }\n }\n }\n\n async webSocketError(_ws: WebSocket, error: unknown): Promise<void> {\n console.error(`[${this.constructor.name}] webSocketError:`, error)\n }\n\n async alarm(): Promise<void> {\n if (this.onAlarm) {\n await this.onAlarm()\n }\n }\n\n // ==========================================================================\n // Lifecycle Hooks (subclasses override)\n // ==========================================================================\n\n /**\n * Called when a new WebSocket connects (after auth parsing).\n * Return an augmented attachment to serialize on the WebSocket,\n * or void to use the default attachment.\n */\n protected onConnect(\n _ws: WebSocket,\n _user: UserAttachment,\n ): UserAttachment | void | Promise<UserAttachment | void> {\n // Default: no-op\n }\n\n /**\n * Called for each parsed JSON message.\n */\n protected abstract onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): void | Promise<void>\n\n /**\n * Called for binary messages (Yjs, custom protocols).\n */\n protected onBinaryMessage?(\n ws: WebSocket,\n user: UserAttachment,\n data: ArrayBuffer,\n ): void | Promise<void>\n\n /**\n * Called when a WebSocket disconnects.\n */\n protected onDisconnect(_ws: WebSocket, _user: UserAttachment): void | Promise<void> {\n // Default: no-op\n }\n\n /**\n * Called for HTTP requests that are NOT WebSocket upgrades.\n */\n protected onRequest?(request: Request): Response | Promise<Response>\n\n /**\n * Called on DO alarm.\n */\n protected onAlarm?(): void | Promise<void>\n\n // ==========================================================================\n // Utilities\n // ==========================================================================\n\n /**\n * Get all connected WebSockets.\n */\n protected getWebSockets(): WebSocket[] {\n return this.state.getWebSockets()\n }\n\n /**\n * Force every connected client to reconnect and resync by closing all live\n * WebSockets. Returns the number of sockets closed.\n *\n * Use this after an **out-of-band, server-side write** to the room's records\n * — an admin import route, a migration script, a cron job, or a server\n * action — that connected clients have no way to learn about. Without it, a\n * browser tab holding stale in-memory state keeps operating on (and may\n * autosave over) data that changed underneath it. Closing the socket makes\n * the client SDK reconnect and pull fresh query results.\n *\n * The default close code is 1012 (\"service restart\") with reason\n * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect\n * trigger (it does not special-case clean/1000 closes), so on reconnect it\n * re-subscribes every active query and receives fresh `QUERY_RESULT`s —\n * `useQuery` consumers see the new data without re-subscribing.\n *\n * Each close is guarded so one already-closing socket can't abort the sweep.\n *\n * @param options.code WebSocket close code (default 1012).\n * @param options.reason WebSocket close reason (default 'state-refresh').\n * @returns the number of sockets that were closed.\n */\n disconnectAllSockets(options?: { code?: number; reason?: string }): number {\n const code = options?.code ?? 1012\n const reason = options?.reason ?? 'state-refresh'\n let closed = 0\n for (const ws of this.state.getWebSockets()) {\n try {\n ws.close(code, reason)\n closed++\n } catch {\n // Socket already closing/closed — skip it, keep sweeping the rest.\n }\n }\n return closed\n }\n\n /**\n * Get the user attachment for a WebSocket.\n */\n protected getAttachment(ws: WebSocket): UserAttachment | null {\n return ws.deserializeAttachment() as UserAttachment | null\n }\n\n /**\n * Get all currently connected users.\n */\n protected getConnectedUsers(): UserAttachment[] {\n const users: UserAttachment[] = []\n for (const ws of this.state.getWebSockets()) {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (attachment) users.push(attachment)\n }\n return users\n }\n\n /**\n * Send a JSON message to a specific WebSocket.\n *\n * Typed as `ServerMessage` so every room's outbound traffic is\n * compile-checked against the wire protocol contract. Passing\n * `{ type: 'whatever', payload: {...} }` with a non-matching arm fails\n * to compile — that's the whole point of the typed layer. Apps that\n * need to send an app-specific message should override `sendTo` in\n * their subclass with a widened union (`ServerMessage | MyAppMessage`).\n */\n protected sendTo(ws: WebSocket, message: ServerMessage): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n } catch {\n // Ignore send errors on dead sockets\n }\n }\n\n /**\n * Send binary data to a specific WebSocket.\n */\n protected sendBinaryTo(ws: WebSocket, data: Uint8Array | ArrayBuffer): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n // Ignore send errors on dead sockets\n }\n }\n\n /**\n * Broadcast a JSON message to all connected WebSockets.\n * Optionally exclude a specific WebSocket (e.g. the sender).\n *\n * See `sendTo` for the reasoning behind typing as `ServerMessage`.\n */\n protected broadcast(message: ServerMessage, exclude?: WebSocket): void {\n const encoded = JSON.stringify(message)\n for (const ws of this.state.getWebSockets()) {\n if (ws === exclude) continue\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(encoded)\n }\n } catch {\n /* dead socket */\n }\n }\n }\n\n /**\n * Broadcast binary data to all connected WebSockets.\n */\n protected broadcastBinary(data: Uint8Array | ArrayBuffer, exclude?: WebSocket): void {\n for (const ws of this.state.getWebSockets()) {\n if (ws === exclude) continue\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n /* dead socket */\n }\n }\n }\n}\n","/**\n * Collection Schema Definitions & Validation\n *\n * All collections use typed SQL columns. No document-mode / fields-based storage.\n */\n\nimport type {\n CollectionSchema,\n ColumnDefinition,\n ColumnInterpretation,\n PermissionLevel,\n RolePermissions,\n} from '../../shared/types'\n\nexport type {\n CollectionSchema,\n ColumnDefinition,\n ColumnInterpretation,\n PermissionLevel,\n RolePermissions,\n} from '../../shared/types'\n\n// ============================================================================\n// Column Definitions\n// ============================================================================\n\nexport interface ResolvedColumn {\n id: string\n name: string\n storage: 'number' | 'text'\n interpretation: ColumnInterpretation\n expression?: string\n readonly: boolean\n userBound?: boolean\n immutable?: boolean\n required?: boolean\n default?: unknown\n timestampTrigger?: { field: string; value?: unknown }\n}\n\n// ============================================================================\n// Column Helpers\n// ============================================================================\n\nexport function collectionTableName(name: string): string {\n return `c_${name.replace(/[^a-zA-Z0-9_]/g, '_')}`\n}\n\nexport function columnId(name: string): string {\n return `col_${name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`\n}\n\nexport function resolveColumn(col: ColumnDefinition): ResolvedColumn {\n const interp: ColumnInterpretation =\n typeof col.interpretation === 'string'\n ? ({ kind: col.interpretation } as ColumnInterpretation)\n : col.interpretation\n return {\n id: col.id ?? columnId(col.name),\n name: col.name,\n storage: col.storage,\n interpretation: interp,\n expression: col.expression,\n readonly: !!col.expression,\n userBound: col.userBound,\n immutable: col.immutable,\n required: col.required,\n default: col.default,\n timestampTrigger: col.timestampTrigger,\n }\n}\n\nexport function rowToData(\n row: Record<string, unknown>,\n columns: ResolvedColumn[],\n): Record<string, unknown> {\n const data: Record<string, unknown> = {}\n for (const col of columns) {\n if (row[col.id] !== undefined && row[col.id] !== null) {\n let val = row[col.id]\n if (col.interpretation.kind === 'json' && typeof val === 'string') {\n try {\n val = JSON.parse(val as string)\n } catch {\n /* keep as string */\n }\n }\n data[col.name] = val\n }\n }\n return data\n}\n\nexport function dataToColumnValues(\n data: Record<string, unknown>,\n columns: ResolvedColumn[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n for (const col of columns) {\n if (col.expression || col.readonly) continue\n // Record mutations use public schema names only. Accepting physical column\n // ids here lets a second key bypass name-based RBAC/userBound checks.\n const val = data[col.name]\n if (val !== undefined) {\n values[col.id] = coerceValue(val, col.storage, col.interpretation)\n }\n }\n return values\n}\n\nexport function coerceValue(\n value: unknown,\n storage: 'number' | 'text',\n interpretation: ColumnInterpretation,\n): unknown {\n if (value === null || value === undefined || value === '') return null\n\n if (storage === 'text') {\n if (interpretation.kind === 'json') {\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value)\n } catch {\n return null\n }\n }\n return typeof value === 'string' ? value : String(value)\n }\n\n if (typeof value === 'number') return isFinite(value) ? value : null\n if (typeof value === 'boolean') return value ? 1 : 0\n if (typeof value !== 'string') return String(value)\n\n const s = value.trim()\n\n if (interpretation.kind === 'boolean') {\n const lower = s.toLowerCase()\n if (lower === 'true' || lower === '1' || lower === 'yes') return 1\n if (lower === 'false' || lower === '0' || lower === 'no') return 0\n return s\n }\n\n if (interpretation.kind === 'percent') {\n const m = s.match(/^(-?\\d+(?:\\.\\d+)?)\\s*%$/)\n if (m) return parseFloat(m[1]) / 100\n }\n\n if (interpretation.kind === 'currency') {\n const cleaned = s.replace(/[^0-9.-]/g, '')\n const n = parseFloat(cleaned)\n if (!isNaN(n)) return n\n return s\n }\n\n if (interpretation.kind === 'date' || interpretation.kind === 'datetime') {\n const d = new Date(s)\n if (!isNaN(d.getTime())) return Math.floor(d.getTime() / 1000)\n return s\n }\n\n const cleaned = s.replace(/[$€£¥,\\s]/g, '')\n const n = parseFloat(cleaned)\n if (!isNaN(n) && isFinite(n)) return n\n\n return s\n}\n\nexport function buildTableSelect(collectionName: string, columns: ResolvedColumn[]): string {\n const tbl = collectionTableName(collectionName)\n const parts: string[] = ['_row_id', '_created_by', '_created_at', '_updated_at']\n for (const col of columns) {\n if (col.expression) {\n parts.push(`(${col.expression}) AS \"${col.id}\"`)\n } else {\n parts.push(`\"${col.id}\"`)\n }\n }\n return `SELECT ${parts.join(', ')} FROM \"${tbl}\"`\n}\n\nexport interface User {\n id: string\n email: string\n name: string\n imageUrl?: string\n role: string\n createdAt: string\n lastSeenAt: string\n}\n\nexport interface StoredRecord {\n collection: string\n recordId: string\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\n// ============================================================================\n// Permission Checking\n// ============================================================================\n\nexport interface PermissionContext {\n isTeamMember: (teamId: string, userId: string) => boolean\n}\n\nexport const noopPermissionContext: PermissionContext = {\n isTeamMember: () => false,\n}\n\nexport function getRolePermissions(schema: CollectionSchema, role: string): RolePermissions {\n if (schema.permissions[role]) return schema.permissions[role]\n if (schema.permissions['*']) return schema.permissions['*']\n return { read: false, create: false, update: false, delete: false }\n}\n\nexport function isOwner(\n schema: CollectionSchema,\n record: { data: Record<string, unknown>; createdBy: string },\n userId: string,\n): boolean {\n if (schema.ownerField) return record.data[schema.ownerField] === userId\n return record.createdBy === userId\n}\n\nfunction isCollaborator(\n schema: CollectionSchema,\n record: { data: Record<string, unknown> },\n userId: string,\n): boolean {\n if (!schema.collaboratorsField) return false\n const collab = record.data[schema.collaboratorsField]\n if (typeof collab === 'string') {\n try {\n return (JSON.parse(collab) as string[]).includes(userId)\n } catch {\n return false\n }\n }\n if (Array.isArray(collab)) return collab.includes(userId)\n return false\n}\n\nfunction passesVisibilityCheck(\n schema: CollectionSchema,\n record: { data: Record<string, unknown> },\n): boolean {\n if (!schema.visibilityField) return false\n if (typeof schema.visibilityField === 'string') {\n return record.data[schema.visibilityField] === 'public'\n }\n return record.data[schema.visibilityField.field] === schema.visibilityField.value\n}\n\nfunction checkPermissionLevel(\n level: PermissionLevel,\n schema: CollectionSchema,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n if (level === true) return true\n if (level === false) return false\n if (level === 'own') return isOwner(schema, record, userId)\n if (level === 'unclaimed-or-own') {\n if (schema.ownerField) {\n const ownerVal = record.data[schema.ownerField]\n if (!ownerVal || ownerVal === '') return true\n }\n return isOwner(schema, record, userId)\n }\n if (level === 'collaborator') {\n return isOwner(schema, record, userId) || isCollaborator(schema, record, userId)\n }\n if (level === 'team' || level === 'access') {\n if (isOwner(schema, record, userId)) return true\n if (isCollaborator(schema, record, userId)) return true\n if (schema.teamField) {\n const teamId = record.data[schema.teamField]\n if (typeof teamId === 'string' && ctx.isTeamMember(teamId, userId)) return true\n }\n return false\n }\n if (level === 'published') {\n return isOwner(schema, record, userId) || passesVisibilityCheck(schema, record)\n }\n if (level === 'shared') {\n return (\n isOwner(schema, record, userId) ||\n isCollaborator(schema, record, userId) ||\n passesVisibilityCheck(schema, record)\n )\n }\n return false\n}\n\nexport function canRead(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).read, schema, record, userId, ctx)\n}\n\nexport function canCreate(schema: CollectionSchema, role: string): boolean {\n return getRolePermissions(schema, role).create === true\n}\n\nexport function canUpdate(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).update, schema, record, userId, ctx)\n}\n\nexport function canDelete(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).delete, schema, record, userId, ctx)\n}\n\n/** Check if a caller-supplied field write violates writableFields restrictions. */\nexport function checkFieldPermissions(\n schema: CollectionSchema,\n role: string,\n newData: Record<string, unknown>,\n existingData?: Record<string, unknown>,\n): string | null {\n const perms = getRolePermissions(schema, role)\n if (!perms.writableFields) return null // no restrictions\n\n const allowedFields = new Set(perms.writableFields)\n for (const key of Object.keys(newData)) {\n if (existingData && newData[key] === existingData[key]) continue\n if (!allowedFields.has(key)) {\n return `Role '${role}' cannot modify field '${key}'`\n }\n }\n return null\n}\n\n/**\n * `unclaimed-or-own` lets a client claim an empty owner field, but never lets\n * them choose another user's id. Check the post-write record so the invariant\n * covers both creation and updates while still allowing an owner to unclaim.\n */\nexport function checkUnclaimedOwnerTransition(\n schema: CollectionSchema,\n role: string,\n nextData: Record<string, unknown>,\n userId: string,\n): string | null {\n const permissions = getRolePermissions(schema, role)\n if (permissions.update !== 'unclaimed-or-own' || !schema.ownerField) return null\n\n const nextOwner = nextData[schema.ownerField]\n if (nextOwner === undefined || nextOwner === null || nextOwner === '' || nextOwner === userId) {\n return null\n }\n\n return `Role '${role}' can set '${schema.ownerField}' only to their own user id or leave it unclaimed`\n}\n\n// ============================================================================\n// System-Managed Columns (users collection)\n// ============================================================================\n\n/**\n * Columns the system *assigns* — from the identity provider at registration,\n * or from an admin action. A caller who sends one of these has chosen a value\n * and means it, so a record write that would change one is refused loudly\n * rather than silently dropped.\n */\nexport const SYSTEM_ASSIGNED_COLUMNS = new Set(['email', 'name', 'imageUrl', 'role'])\n\n/**\n * Columns the system *maintains* on its own schedule, without telling anyone.\n * `registerUser` bumps `lastSeenAt` on every WS connect and `handleUserUpdate`\n * rewrites it on every 60s presence heartbeat; neither broadcasts, so every\n * client's copy goes stale silently and there is no heal path short of a\n * refetch. Read-modify-write echo (`put(id, { ...rec.data, field })`) is the\n * SDK's own house style, so refusing these would refuse writes over a value the\n * caller never chose and cannot keep current. They are preserved silently.\n */\nexport const SYSTEM_MAINTAINED_COLUMNS = new Set(['createdAt', 'lastSeenAt'])\n\n/** Every `users` column owned by the system rather than by record writes. */\nexport const SYSTEM_MANAGED_COLUMNS = new Set([\n ...SYSTEM_ASSIGNED_COLUMNS,\n ...SYSTEM_MAINTAINED_COLUMNS,\n])\n\n/** Standard user columns. Apps spread these into their users schema. */\nexport const USERS_COLUMNS: ColumnDefinition[] = [\n { name: 'email', storage: 'text', interpretation: 'plain' },\n { name: 'name', storage: 'text', interpretation: 'plain' },\n { name: 'imageUrl', storage: 'text', interpretation: 'plain' },\n { name: 'role', storage: 'text', interpretation: 'plain' },\n { name: 'createdAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'lastSeenAt', storage: 'text', interpretation: { kind: 'datetime' } },\n]\n\nexport const BASE_USERS_SCHEMA: CollectionSchema = {\n name: 'users',\n columns: [...USERS_COLUMNS],\n permissions: {\n '*': { read: 'own', create: false, update: false, delete: false },\n admin: { read: true, create: false, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Schema Registry\n// ============================================================================\n\n/**\n * Lint a CollectionSchema for declarations that look like they should\n * enforce something but don't, due to interactions between top-level\n * fields (visibilityField, ownerField) and per-role permission levels.\n *\n * The SDK has historically silently accepted schemas that imply more\n * enforcement than they actually deliver — e.g., `visibilityField` set\n * but every role's `read: true` means \"anyone can read everything\"\n * regardless of `visibility`. Warn loudly at registration so app\n * authors notice before shipping a privacy bug.\n *\n * Returns an array of warning messages. Empty = clean.\n */\nexport function lintSchema(schema: CollectionSchema): string[] {\n const warnings: string[] = []\n const VISIBILITY_AWARE: ReadonlyArray<PermissionLevel> = ['published', 'shared']\n\n // Roles that grant unconditional read despite visibilityField being declared.\n if (schema.visibilityField) {\n const offendingRoles = Object.entries(schema.permissions)\n .filter(([, perms]) => perms.read === true)\n .map(([role]) => role)\n if (offendingRoles.length > 0) {\n const usesAware = Object.values(schema.permissions).some(\n (p) => typeof p.read === 'string' && VISIBILITY_AWARE.includes(p.read as PermissionLevel),\n )\n if (!usesAware) {\n warnings.push(\n `[${schema.name}] visibilityField is declared but no role uses read: 'published' or 'shared'. ` +\n `Roles with read: true (${offendingRoles.join(', ')}) will see every row regardless of visibility. ` +\n `Change those to read: 'published' (owner OR public) or 'shared' (owner OR collaborator OR public) ` +\n `to actually enforce the filter, or remove visibilityField if you don't intend to gate reads by it.`,\n )\n }\n }\n }\n\n // An ordinary client role that can create rows must not be able to claim\n // someone else's ownership. Server-written collections legitimately carry\n // a recipient/subject id instead: their non-privileged roles all have\n // create:false, while owner/admin writes are already fully trusted.\n if (schema.ownerField) {\n const col = schema.columns.find((c) => c.name === schema.ownerField)\n const spoofableRoles = Object.entries(schema.permissions)\n .filter(\n ([role, permissions]) =>\n role !== 'owner' &&\n role !== 'admin' &&\n permissions.create === true &&\n permissions.update !== 'unclaimed-or-own',\n )\n .map(([role]) => role)\n if (col && !col.userBound && spoofableRoles.length > 0) {\n warnings.push(\n `[${schema.name}] ownerField is '${schema.ownerField}' but that column is not marked userBound: true. ` +\n `Client role(s) ${spoofableRoles.join(', ')} can create a row with someone else's id in this field, bypassing 'own' permission checks. ` +\n `Add userBound: true (and ideally immutable: true) to the column.`,\n )\n }\n }\n\n // userBound columns that aren't text storage — userBound sets the\n // field to the caller's userId (a string), so non-text storage is a\n // schema mistake that will surface as a coercion error at write time.\n for (const col of schema.columns) {\n if (col.userBound && col.storage !== 'text') {\n warnings.push(\n `[${schema.name}] column '${col.name}' is userBound but storage is '${col.storage}'. ` +\n `userBound stamps the user id (a string); use storage: 'text'.`,\n )\n }\n }\n\n return warnings\n}\n\n/**\n * Lint a whole schema set: every per-schema warning, plus the rules that\n * need to see the set. The `'team'` permission level resolves membership from\n * a `team_members` collection (`teamId`, `userId`, `status`); without one it\n * denies everything, silently — a mistake that presents as a sync bug, not\n * as a schema error, so name it here.\n */\nexport function lintSchemas(schemas: CollectionSchema[]): string[] {\n return [...schemas.flatMap((schema) => lintSchema(schema)), ...lintSchemaSet(schemas)]\n}\n\n/** The rules that need to see the whole schema set (per-schema rules live in\n * `lintSchema`; `lintSchemas` composes both). */\nexport function lintSchemaSet(schemas: CollectionSchema[]): string[] {\n const warnings: string[] = []\n if (!schemas.some((schema) => schema.name === 'team_members')) {\n for (const schema of schemas) {\n const teamRoles = Object.entries(schema.permissions)\n .filter(([, p]) => [p.read, p.update, p.delete].includes('team'))\n .map(([role]) => role)\n if (teamRoles.length > 0) {\n warnings.push(\n `[${schema.name}] role(s) ${teamRoles.join(', ')} use the 'team' permission level, but no ` +\n `'team_members' collection is registered. 'team' resolves membership from team_members ` +\n `(teamId, userId, status), so without it those roles are denied everything — silently. ` +\n `Register a team_members schema or use another level.`,\n )\n }\n }\n }\n return warnings\n}\n\nexport class SchemaRegistry {\n private trusted: Map<string, CollectionSchema> = new Map()\n\n constructor(schemas: CollectionSchema[] = []) {\n // registerTrusted runs the per-schema lint; only the set-level rules are\n // this loop's to add.\n for (const w of lintSchemaSet(schemas)) console.warn(`[schema-lint] ${w}`)\n for (const schema of schemas) this.registerTrusted(schema)\n }\n\n registerTrusted(schema: CollectionSchema): void {\n for (const w of lintSchema(schema)) {\n console.warn(`[schema-lint] ${w}`)\n }\n this.trusted.set(schema.name, schema)\n }\n\n get(name: string): CollectionSchema | undefined {\n return this.trusted.get(name)\n }\n\n has(name: string): boolean {\n return this.trusted.has(name)\n }\n\n all(): CollectionSchema[] {\n return Array.from(this.trusted.values())\n }\n\n names(): string[] {\n return Array.from(this.trusted.keys())\n }\n}\n\n// ============================================================================\n// Permission Analysis\n// ============================================================================\n\nexport type PermissionSource = 'explicit' | 'wildcard' | 'default-deny'\n\nexport interface ResolvedPermission {\n level: PermissionLevel | boolean\n source: PermissionSource\n}\n\nexport interface CollectionPermissionSummary {\n collection: string\n ownerField?: string\n collaboratorsField?: string\n teamField?: string\n columns: ColumnDefinition[]\n permissions: Record<\n string,\n {\n read: ResolvedPermission\n create: ResolvedPermission\n update: ResolvedPermission\n delete: ResolvedPermission\n writableFields?: string[]\n }\n >\n}\n\nexport interface PermissionAnalysis {\n roles: string[]\n collections: CollectionPermissionSummary[]\n}\n\nexport function analyzePermissions(schemas: CollectionSchema[]): PermissionAnalysis {\n const roleSet = new Set<string>()\n for (const schema of schemas) {\n for (const role of Object.keys(schema.permissions)) roleSet.add(role)\n }\n roleSet.delete('*')\n const roles = Array.from(roleSet)\n\n const collections: CollectionPermissionSummary[] = schemas.map((schema) => {\n const permissions: CollectionPermissionSummary['permissions'] = {}\n for (const role of roles) {\n let source: PermissionSource\n let perms: RolePermissions\n if (schema.permissions[role]) {\n source = 'explicit'\n perms = schema.permissions[role]\n } else if (schema.permissions['*']) {\n source = 'wildcard'\n perms = schema.permissions['*']\n } else {\n source = 'default-deny'\n perms = { read: false, create: false, update: false, delete: false }\n }\n permissions[role] = {\n read: { level: perms.read, source },\n create: { level: perms.create, source },\n update: { level: perms.update, source },\n delete: { level: perms.delete, source },\n writableFields: perms.writableFields,\n }\n }\n return {\n collection: schema.name,\n ownerField: schema.ownerField,\n collaboratorsField: schema.collaboratorsField,\n teamField: schema.teamField,\n columns: schema.columns,\n permissions,\n }\n })\n\n return { roles, collections }\n}\n","/**\n * Per-collection SQLite table management.\n *\n * Pure functions over the DO's SqlStorage. Extracted from RecordRoom so the\n * CREATE / ALTER / unique-index paths are testable without spinning up a\n * full DurableObject in miniflare.\n *\n * Behavior:\n * - If the table doesn't exist: CREATE with the system columns + every\n * resolved schema column.\n * - If the table exists: PRAGMA table_info, ALTER TABLE ADD COLUMN for any\n * resolved schema columns missing from the table. Idempotent — re-running\n * with the same schema is a no-op.\n * - Computed/expression columns are skipped (no storage).\n * - uniqueOn produces a UNIQUE INDEX (best-effort; logged-and-skipped on\n * constraint conflicts so a deploy can recover from pre-existing dupes).\n *\n * This is the single source of truth for \"does an alive table match the\n * current schema?\" — call it on every DO cold-start (via\n * `ensureAllCollectionTables`) and column-additive migrations propagate\n * automatically as DOs cycle.\n */\n\nimport {\n collectionTableName,\n resolveColumn,\n columnId,\n type CollectionSchema,\n} from '../schemas/registry'\n\nexport interface MigrationLogger {\n warn(msg: string): void\n}\n\nconst NOOP_LOGGER: MigrationLogger = { warn: () => {} }\n\nexport function ensureCollectionTable(\n sql: SqlStorage,\n schema: CollectionSchema,\n logger: MigrationLogger = NOOP_LOGGER,\n): void {\n if (!schema.columns) return\n\n const tbl = collectionTableName(schema.name)\n const resolved = schema.columns.map(resolveColumn)\n\n const tableExists = sql.exec(\n `SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl\n ).toArray().length > 0\n\n if (!tableExists) {\n const storageCols = resolved\n .filter(c => !c.expression)\n .map(c => `\"${c.id}\" ${c.storage === 'number' ? 'REAL' : 'TEXT'}`)\n .join(', ')\n\n const colClause = storageCols ? `, ${storageCols}` : ''\n sql.exec(`\n CREATE TABLE IF NOT EXISTS \"${tbl}\" (\n _row_id TEXT PRIMARY KEY,\n _created_by TEXT NOT NULL,\n _created_at TEXT NOT NULL,\n _updated_at TEXT NOT NULL${colClause}\n )\n `)\n } else {\n const existingCols = new Set<string>()\n const pragmaRows = sql.exec(`PRAGMA table_info(\"${tbl}\")`).toArray()\n for (const row of pragmaRows) {\n existingCols.add((row as { name: string }).name)\n }\n\n for (const col of resolved) {\n if (col.expression) continue\n if (!existingCols.has(col.id)) {\n const sqlType = col.storage === 'number' ? 'REAL' : 'TEXT'\n sql.exec(`ALTER TABLE \"${tbl}\" ADD COLUMN \"${col.id}\" ${sqlType}`)\n }\n }\n }\n\n if (schema.uniqueOn && schema.uniqueOn.length > 0) {\n const uniqueCols = schema.uniqueOn.map(fieldName => {\n const col = resolved.find(c => c.name === fieldName)\n return col ? `\"${col.id}\"` : `\"${columnId(fieldName)}\"`\n })\n const indexName = `uniq_${tbl}_${schema.uniqueOn.join('_').replace(/[^a-zA-Z0-9_]/g, '_')}`\n try {\n sql.exec(\n `CREATE UNIQUE INDEX IF NOT EXISTS \"${indexName}\" ON \"${tbl}\" (${uniqueCols.join(', ')})`\n )\n } catch (e) {\n logger.warn(\n `[RecordRoom] Cannot create UNIQUE index on ${tbl} (${schema.uniqueOn.join(', ')}): ${\n e instanceof Error ? e.message : e\n }`,\n )\n }\n }\n}\n","/**\n * Yjs collaborative editing handlers for RecordRoom\n */\n\nimport * as Y from 'yjs'\nimport { resolveCollection } from './subscriptions'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type {\n YjsDocKey,\n YjsJoinPayload,\n YjsLeavePayload,\n YjsSubscription,\n} from '../../shared/types'\nimport {\n MSG_YJS_SYNC,\n MSG_YJS_AWARENESS,\n MSG,\n RECORD_NOT_FOUND,\n} from '../../shared/protocol/constants'\nimport {\n MSG_SYNC_STEP1,\n MSG_SYNC_STEP2,\n MSG_SYNC_UPDATE,\n createEncoder,\n createDecoder,\n toUint8Array,\n writeVarUint,\n writeVarUint8Array,\n readVarUint,\n readVarUint8Array,\n} from '../../shared/protocol/yjs'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n canRead,\n canUpdate,\n collectionTableName,\n} from '../schemas/registry'\nimport { getRecord } from './records'\n\n/** Parse a Yjs doc key (collection:recordId:fieldName). recordId may contain colons. */\nfunction parseYjsDocKey(\n docKey: YjsDocKey,\n): { collection: string; recordId: string; fieldName: string } | null {\n const firstColon = docKey.indexOf(':')\n const lastColon = docKey.lastIndexOf(':')\n if (firstColon === -1 || lastColon === -1 || firstColon === lastColon) return null\n return {\n collection: docKey.slice(0, firstColon),\n recordId: docKey.slice(firstColon + 1, lastColon),\n fieldName: docKey.slice(lastColon + 1),\n }\n}\n\nfunction hasYjsSubscription(attachment: ConnectionAttachment, target: YjsSubscription): boolean {\n return attachment.yjsSubscriptions.some(\n (subscription) =>\n subscription.collection === target.collection &&\n subscription.recordId === target.recordId &&\n subscription.fieldName === target.fieldName,\n )\n}\n\n// ============================================================================\n// System Collections (permissive, no schema required)\n// ============================================================================\n\n/** Collections that bypass schema checks (permissive access) */\nexport const SYSTEM_COLLECTIONS = new Set(['canvas-settings'])\n\n/**\n * Schemas for system collections.\n * These have empty columns arrays — they only use system columns\n * (_row_id, _created_by, _created_at, _updated_at).\n * Yjs data is stored in the yjs_docs table, not in the record itself.\n */\nexport const SYSTEM_COLLECTION_SCHEMAS: CollectionSchema[] = [\n {\n name: 'canvas-settings',\n columns: [],\n permissions: {\n '*': { read: true, create: true, update: true, delete: true },\n },\n },\n]\n\nexport interface YjsContext {\n sql: SqlStorage\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n schemaRegistry: SchemaRegistry\n getPermissionContext(): PermissionContext\n send(ws: WebSocket, message: { type: string; payload: unknown }): void\n sendBinary(ws: WebSocket, data: Uint8Array): void\n}\n\n/**\n * Create a Yjs doc key from collection, recordId, and fieldName\n */\nexport function getYjsDocKey(collection: string, recordId: string, fieldName: string): YjsDocKey {\n return `${collection}:${recordId}:${fieldName}`\n}\n\n/**\n * Get or create a Y.Doc for a record field.\n * Loads from database if exists, creates new if not.\n */\nexport function getOrCreateYjsDoc(ctx: YjsContext, docKey: YjsDocKey): Y.Doc {\n // Return cached doc if available\n let doc = ctx.yjsDocs.get(docKey)\n if (doc) return doc\n\n // Create new doc\n doc = new Y.Doc()\n\n // Try to load from database\n const row = ctx.sql.exec(`SELECT state FROM yjs_docs WHERE doc_key = ?`, docKey).toArray()[0] as\n | { state: ArrayBuffer }\n | undefined\n\n if (row?.state) {\n try {\n Y.applyUpdate(doc, new Uint8Array(row.state))\n } catch (e) {\n console.error(`Failed to load Yjs doc ${docKey}:`, e)\n }\n }\n\n // Set up auto-save on updates\n const saveDoc = (): void => {\n saveYjsDoc(ctx.sql, docKey, doc!)\n }\n doc.on('update', saveDoc)\n\n ctx.yjsDocs.set(docKey, doc)\n return doc\n}\n\n/** Destroy and evict a document once no live connection subscribes to it. */\nexport function unloadYjsDocIfUnused(\n ctx: YjsContext,\n docKey: YjsDocKey,\n excludeWs: WebSocket | null = null,\n): boolean {\n const target = parseYjsDocKey(docKey)\n if (!target) return false\n\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment && hasYjsSubscription(attachment, target)) return false\n }\n\n const doc = ctx.yjsDocs.get(docKey)\n if (!doc) return false\n\n ctx.yjsDocs.delete(docKey)\n doc.destroy()\n return true\n}\n\n/**\n * Save Yjs doc state to database.\n */\nexport function saveYjsDoc(sql: SqlStorage, docKey: YjsDocKey, doc: Y.Doc): void {\n const state = Y.encodeStateAsUpdate(doc)\n const now = new Date().toISOString()\n sql.exec(\n `INSERT INTO yjs_docs (doc_key, state, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(doc_key) DO UPDATE SET state = ?, updated_at = ?`,\n docKey,\n state,\n now,\n state,\n now,\n )\n}\n\n/**\n * Handle request to join Yjs sync for a record field.\n * System collections (see SYSTEM_COLLECTIONS) are permissive; others require schema.\n */\nexport function handleYjsJoin(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: YjsJoinPayload,\n): void {\n const { collection, recordId, fieldName } = payload\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n\n const schema = ctx.schemaRegistry.get(collection)\n let record = getRecord(ctx.sql, collection, recordId, schema)\n let hasWriteAccess = isSystem // System collections always have write access\n\n if (isSystem) {\n // Auto-create record for system collections (table-mode)\n if (!record) {\n const now = new Date().toISOString()\n if (schema) {\n const tbl = collectionTableName(collection)\n ctx.sql.exec(\n `INSERT OR IGNORE INTO \"${tbl}\" (_row_id, _created_by, _created_at, _updated_at) VALUES (?, ?, ?, ?)`,\n recordId,\n attachment.userId,\n now,\n now,\n )\n }\n record = getRecord(ctx.sql, collection, recordId, schema)\n }\n } else {\n // Regular collections: require schema and record\n if (!schema) {\n const resolution = resolveCollection(ctx, collection)\n ctx.send(ws, {\n type: MSG.ERROR,\n payload: {\n error: resolution.ok\n ? `Schema not registered for collection: ${collection}`\n : resolution.error,\n },\n })\n return\n }\n if (!record) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: RECORD_NOT_FOUND } })\n return\n }\n const permCtx = ctx.getPermissionContext()\n const recordWithId = { ...record, recordId }\n if (!canRead(schema, attachment.role, recordWithId, attachment.userId, permCtx)) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'Permission denied' } })\n return\n }\n hasWriteAccess = canUpdate(schema, attachment.role, recordWithId, attachment.userId, permCtx)\n }\n\n // Get or create Yjs doc and subscribe\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const doc = getOrCreateYjsDoc(ctx, docKey)\n\n if (!hasYjsSubscription(attachment, payload)) {\n attachment.yjsSubscriptions.push({ collection, recordId, fieldName })\n ws.serializeAttachment(attachment)\n }\n\n // Send sync messages\n const docKeyBytes = new TextEncoder().encode(docKey)\n const encoder1 = createEncoder()\n writeVarUint(encoder1, MSG_YJS_SYNC)\n writeVarUint8Array(encoder1, docKeyBytes)\n writeVarUint(encoder1, MSG_SYNC_STEP1)\n writeVarUint8Array(encoder1, Y.encodeStateVector(doc))\n ctx.sendBinary(ws, toUint8Array(encoder1))\n\n const fullState = Y.encodeStateAsUpdate(doc)\n if (fullState.length > 1) {\n const encoder2 = createEncoder()\n writeVarUint(encoder2, MSG_YJS_SYNC)\n writeVarUint8Array(encoder2, docKeyBytes)\n writeVarUint(encoder2, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder2, fullState)\n ctx.sendBinary(ws, toUint8Array(encoder2))\n }\n\n ctx.send(ws, {\n type: MSG.YJS_JOIN,\n payload: { collection, recordId, fieldName, canWrite: hasWriteAccess },\n })\n}\n\n/**\n * Handle request to leave Yjs sync for a record field.\n */\nexport function handleYjsLeave(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: YjsLeavePayload,\n): void {\n const { collection, recordId, fieldName } = payload\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n\n const clientId = attachment.awarenessClientIds?.[docKey]\n if (clientId !== undefined) {\n broadcastAwarenessClientRemoval(ctx, docKey, clientId)\n delete attachment.awarenessClientIds![docKey]\n }\n\n // Remove from subscriptions\n attachment.yjsSubscriptions = attachment.yjsSubscriptions.filter(\n (s) => !(s.collection === collection && s.recordId === recordId && s.fieldName === fieldName),\n )\n ws.serializeAttachment(attachment)\n unloadYjsDocIfUnused(ctx, docKey, ws)\n}\n\n/**\n * Handle binary Yjs sync messages from clients.\n *\n * Protocol:\n * - MSG_SYNC_STEP1: Client sends state vector → Server responds with SYNC_STEP2 (diff)\n * - MSG_SYNC_STEP2/UPDATE: Client sends update → Server applies and broadcasts\n */\nexport function handleYjsBinaryMessage(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n data: Uint8Array,\n): void {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n\n // Handle awareness messages (ephemeral — relay only, no persistence)\n if (messageType === MSG_YJS_AWARENESS) {\n const docKeyBytes = readVarUint8Array(decoder)\n const docKey = new TextDecoder().decode(docKeyBytes) as YjsDocKey\n const payload = readVarUint8Array(decoder)\n\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n if (!hasYjsSubscription(attachment, parsed)) return\n\n // Capture this document's awareness clientId for leave/disconnect cleanup.\n if (attachment.awarenessClientIds?.[docKey] === undefined) {\n try {\n const pd = createDecoder(payload)\n const count = readVarUint(pd)\n if (count > 0) {\n attachment.awarenessClientIds ??= {}\n attachment.awarenessClientIds[docKey] = readVarUint(pd)\n ws.serializeAttachment(attachment)\n }\n } catch {\n /* best-effort */\n }\n }\n\n broadcastAwareness(ctx, docKey, payload, ws)\n return\n }\n\n if (messageType !== MSG_YJS_SYNC) return\n\n // Parse doc key (format: collection:recordId:fieldName)\n // Note: recordId may contain colons (e.g., \"shape:notepad-xxx\")\n // So we split on first colon for collection, last colon for fieldName\n const docKeyBytes = readVarUint8Array(decoder)\n const docKey = new TextDecoder().decode(docKeyBytes) as YjsDocKey\n\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n const { collection, recordId } = parsed\n\n // Verify subscription\n if (!hasYjsSubscription(attachment, parsed)) return\n\n // Load doc from SQLite if not in memory (handles DO hibernation wake-up)\n const doc = getOrCreateYjsDoc(ctx, docKey)\n\n const syncType = readVarUint(decoder)\n\n switch (syncType) {\n case MSG_SYNC_STEP1: {\n // Client requests sync - respond with diff based on their state vector\n const clientStateVector = readVarUint8Array(decoder)\n const diff = Y.encodeStateAsUpdate(doc, clientStateVector)\n\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_SYNC)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint(encoder, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder, diff)\n ctx.sendBinary(ws, toUint8Array(encoder))\n break\n }\n\n case MSG_SYNC_STEP2:\n case MSG_SYNC_UPDATE: {\n // Client sends update - system collections permissive, others check permission\n if (!SYSTEM_COLLECTIONS.has(collection)) {\n const syncSchema = ctx.schemaRegistry.get(collection)\n if (!syncSchema) return\n const record = getRecord(ctx.sql, collection, recordId, syncSchema)\n if (!record) return\n if (\n !canUpdate(\n syncSchema,\n attachment.role,\n { ...record, recordId },\n attachment.userId,\n ctx.getPermissionContext(),\n )\n )\n return\n }\n\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update, 'client')\n broadcastYjsUpdate(ctx, docKey, update, ws)\n break\n }\n }\n}\n\n/**\n * Broadcast a Yjs update to all subscribers of a doc, except the sender.\n */\nexport function broadcastYjsUpdate(\n ctx: YjsContext,\n docKey: YjsDocKey,\n update: Uint8Array,\n excludeWs: WebSocket | null,\n): void {\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n // Build message once\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_SYNC)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint(encoder, MSG_SYNC_UPDATE)\n writeVarUint8Array(encoder, update)\n const message = toUint8Array(encoder)\n\n // Send to all other subscribers\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n if (hasYjsSubscription(attachment, parsed)) {\n ctx.sendBinary(ws, message)\n }\n }\n}\n\n/**\n * Broadcast an awareness update to all subscribers of a doc, except the sender.\n * Awareness is ephemeral (cursors, presence) — NOT persisted to SQLite.\n */\nexport function broadcastAwareness(\n ctx: YjsContext,\n docKey: YjsDocKey,\n payload: Uint8Array,\n excludeWs: WebSocket | null,\n): void {\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n // Build message once\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_AWARENESS)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint8Array(encoder, payload)\n const message = toUint8Array(encoder)\n\n // Send to all other subscribers\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n if (hasYjsSubscription(attachment, parsed)) {\n ctx.sendBinary(ws, message)\n }\n }\n}\n\n/**\n * Broadcast awareness removal for a disconnected client.\n *\n * When a WebSocket closes without a clean leave, other clients still\n * show the disconnected user's cursor. This constructs a null-state\n * awareness update and broadcasts it to remaining subscribers.\n */\nexport function broadcastAwarenessRemoval(ctx: YjsContext, attachment: ConnectionAttachment): void {\n if (!attachment.awarenessClientIds) return\n\n for (const sub of attachment.yjsSubscriptions) {\n const docKey = getYjsDocKey(sub.collection, sub.recordId, sub.fieldName)\n const clientId = attachment.awarenessClientIds[docKey]\n if (clientId !== undefined) broadcastAwarenessClientRemoval(ctx, docKey, clientId)\n }\n}\n\nfunction broadcastAwarenessClientRemoval(\n ctx: YjsContext,\n docKey: YjsDocKey,\n clientId: number,\n): void {\n const payloadEncoder = createEncoder()\n writeVarUint(payloadEncoder, 1)\n writeVarUint(payloadEncoder, clientId)\n writeVarUint(payloadEncoder, 0xffffff) // high clock to override any prior state\n writeVarUint8Array(payloadEncoder, new TextEncoder().encode('null'))\n const removalPayload = toUint8Array(payloadEncoder)\n broadcastAwareness(ctx, docKey, removalPayload, null)\n}\n\n/** Clean up ephemeral awareness and cached documents for a closed connection. */\nexport function handleYjsDisconnect(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n): void {\n broadcastAwarenessRemoval(ctx, attachment)\n\n const docKeys = new Set(\n attachment.yjsSubscriptions.map((subscription) =>\n getYjsDocKey(subscription.collection, subscription.recordId, subscription.fieldName),\n ),\n )\n for (const docKey of docKeys) {\n unloadYjsDocIfUnused(ctx, docKey, ws)\n }\n}\n","/**\n * Yjs Sync Protocol Implementation\n * \n * This implements the y-protocols sync and awareness protocols without\n * depending on y-websocket internal paths. Makes it easier to bundle\n * and template.\n * \n * Protocol spec: https://github.com/yjs/y-protocols\n */\n\nimport * as Y from 'yjs'\n\n// Message type constants\nexport const MSG_SYNC = 0\nexport const MSG_AWARENESS = 1\n\n// Sync message subtypes\nexport const MSG_SYNC_STEP1 = 0 // Send state vector\nexport const MSG_SYNC_STEP2 = 1 // Send diff based on received state vector\nexport const MSG_SYNC_UPDATE = 2 // Send incremental update\n\n// ============================================================================\n// Binary Encoding Utilities (lib0 compatible)\n// ============================================================================\n\nexport function createEncoder(): { data: number[] } {\n return { data: [] }\n}\n\nexport function toUint8Array(encoder: { data: number[] }): Uint8Array {\n return new Uint8Array(encoder.data)\n}\n\nexport function writeVarUint(encoder: { data: number[] }, num: number): void {\n while (num > 0x7f) {\n encoder.data.push((num & 0x7f) | 0x80)\n num = Math.floor(num / 128)\n }\n encoder.data.push(num & 0x7f)\n}\n\nexport function writeVarUint8Array(encoder: { data: number[] }, arr: Uint8Array): void {\n writeVarUint(encoder, arr.length)\n for (let i = 0; i < arr.length; i++) {\n encoder.data.push(arr[i])\n }\n}\n\nexport function createDecoder(data: Uint8Array): { data: Uint8Array; pos: number } {\n return { data, pos: 0 }\n}\n\nexport function readVarUint(decoder: { data: Uint8Array; pos: number }): number {\n let num = 0\n let mult = 1\n const len = decoder.data.length\n while (decoder.pos < len) {\n const byte = decoder.data[decoder.pos++]\n num += (byte & 0x7f) * mult\n mult *= 128\n if (byte < 0x80) {\n return num\n }\n }\n throw new Error('Unexpected end of buffer')\n}\n\nexport function readVarUint8Array(decoder: { data: Uint8Array; pos: number }): Uint8Array {\n const len = readVarUint(decoder)\n const arr = decoder.data.subarray(decoder.pos, decoder.pos + len)\n decoder.pos += len\n return arr\n}\n\nexport function hasContent(decoder: { data: Uint8Array; pos: number }): boolean {\n return decoder.pos < decoder.data.length\n}\n\n// ============================================================================\n// Sync Protocol\n// ============================================================================\n\n/**\n * Encode sync step 1: our state vector\n */\nexport function encodeSyncStep1(doc: Y.Doc): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_STEP1)\n writeVarUint8Array(encoder, Y.encodeStateVector(doc))\n return toUint8Array(encoder)\n}\n\n/**\n * Encode sync step 2: diff based on received state vector\n */\nexport function encodeSyncStep2(doc: Y.Doc, stateVector: Uint8Array): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder, Y.encodeStateAsUpdate(doc, stateVector))\n return toUint8Array(encoder)\n}\n\n/**\n * Encode an incremental update\n */\nexport function encodeUpdate(update: Uint8Array): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_UPDATE)\n writeVarUint8Array(encoder, update)\n return toUint8Array(encoder)\n}\n\nexport interface SyncResult {\n type: 'step1' | 'step2' | 'update'\n response?: Uint8Array\n stateVector?: Uint8Array\n update?: Uint8Array\n}\n\n/**\n * Handle an incoming sync message\n * Returns response to send (if any) and whether this was an update\n */\nexport function handleSyncMessage(doc: Y.Doc, data: Uint8Array): SyncResult {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n \n if (messageType !== MSG_SYNC) {\n throw new Error(`Expected sync message (${MSG_SYNC}), got ${messageType}`)\n }\n \n const syncType = readVarUint(decoder)\n \n switch (syncType) {\n case MSG_SYNC_STEP1: {\n // Received state vector, send our diff\n const stateVector = readVarUint8Array(decoder)\n const response = encodeSyncStep2(doc, stateVector)\n return { type: 'step1', response, stateVector }\n }\n \n case MSG_SYNC_STEP2: {\n // Received diff, apply it\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update)\n return { type: 'step2', update }\n }\n \n case MSG_SYNC_UPDATE: {\n // Received incremental update, apply it\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update)\n return { type: 'update', update }\n }\n \n default:\n throw new Error(`Unknown sync type: ${syncType}`)\n }\n}\n\n// ============================================================================\n// Awareness Protocol\n// ============================================================================\n\nexport interface AwarenessState {\n [key: string]: unknown\n}\n\nexport interface AwarenessStates {\n [clientId: number]: AwarenessState\n}\n\n/**\n * Simple awareness implementation for presence/cursors\n */\nexport class Awareness {\n doc: Y.Doc\n clientID: number\n states: Map<number, AwarenessState> = new Map()\n private meta: Map<number, { clock: number; lastUpdated: number }> = new Map()\n private updateListeners: Set<(changes: { added: number[]; updated: number[]; removed: number[] }) => void> = new Set()\n private changeListeners: Set<(changes: { added: number[]; updated: number[]; removed: number[] }) => void> = new Set()\n\n constructor(doc: Y.Doc) {\n this.doc = doc\n this.clientID = doc.clientID\n }\n \n getStates(): Map<number, AwarenessState> {\n return this.states\n }\n \n getLocalState(): AwarenessState | null {\n return this.states.get(this.clientID) || null\n }\n \n setLocalState(state: AwarenessState | null): void {\n const clientID = this.clientID\n const prevState = this.states.get(clientID)\n \n if (state === null) {\n this.states.delete(clientID)\n this.meta.delete(clientID)\n if (prevState) {\n this.emit({ added: [], updated: [], removed: [clientID] })\n }\n } else {\n const meta = this.meta.get(clientID) || { clock: 0, lastUpdated: Date.now() }\n meta.clock++\n meta.lastUpdated = Date.now()\n this.meta.set(clientID, meta)\n this.states.set(clientID, state)\n this.emit({\n added: prevState ? [] : [clientID],\n updated: prevState ? [clientID] : [],\n removed: [],\n })\n }\n }\n \n setLocalStateField(field: string, value: unknown): void {\n const state = this.getLocalState() || {}\n state[field] = value\n this.setLocalState(state)\n }\n \n on(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[] }) => void): void {\n if (event === 'update') {\n this.updateListeners.add(handler)\n } else if (event === 'change') {\n this.changeListeners.add(handler)\n }\n }\n\n off(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[] }) => void): void {\n if (event === 'update') {\n this.updateListeners.delete(handler)\n } else if (event === 'change') {\n this.changeListeners.delete(handler)\n }\n }\n\n private emit(changes: { added: number[]; updated: number[]; removed: number[] }): void {\n for (const handler of this.updateListeners) {\n handler(changes)\n }\n if (changes.added.length > 0 || changes.updated.length > 0 || changes.removed.length > 0) {\n for (const handler of this.changeListeners) {\n handler(changes)\n }\n }\n }\n \n /**\n * Encode awareness update for specific clients\n */\n encodeUpdate(clientIds?: number[]): Uint8Array {\n const ids = clientIds || Array.from(this.states.keys())\n const encoder = createEncoder()\n writeVarUint(encoder, ids.length)\n \n for (const clientId of ids) {\n const state = this.states.get(clientId)\n const meta = this.meta.get(clientId) || { clock: 0, lastUpdated: Date.now() }\n \n writeVarUint(encoder, clientId)\n writeVarUint(encoder, meta.clock)\n \n const stateStr = state ? JSON.stringify(state) : 'null'\n const stateBytes = new TextEncoder().encode(stateStr)\n writeVarUint8Array(encoder, stateBytes)\n }\n \n return toUint8Array(encoder)\n }\n \n /**\n * Apply awareness update from another client\n */\n applyUpdate(data: Uint8Array): { added: number[]; updated: number[]; removed: number[] } {\n const decoder = createDecoder(data)\n const len = readVarUint(decoder)\n \n const added: number[] = []\n const updated: number[] = []\n const removed: number[] = []\n \n for (let i = 0; i < len; i++) {\n const clientId = readVarUint(decoder)\n const clock = readVarUint(decoder)\n const stateBytes = readVarUint8Array(decoder)\n const stateStr = new TextDecoder().decode(stateBytes)\n const state = stateStr === 'null' ? null : JSON.parse(stateStr) as AwarenessState\n \n const prevMeta = this.meta.get(clientId)\n const prevState = this.states.get(clientId)\n \n // Only apply if this is a newer update\n if (!prevMeta || prevMeta.clock < clock) {\n this.meta.set(clientId, { clock, lastUpdated: Date.now() })\n \n if (state === null) {\n this.states.delete(clientId)\n if (prevState) {\n removed.push(clientId)\n }\n } else {\n this.states.set(clientId, state)\n if (prevState) {\n updated.push(clientId)\n } else {\n added.push(clientId)\n }\n }\n }\n }\n \n if (added.length > 0 || updated.length > 0 || removed.length > 0) {\n this.emit({ added, updated, removed })\n }\n \n return { added, updated, removed }\n }\n \n /**\n * Remove awareness states for disconnected clients\n */\n removeStates(clientIds: number[]): void {\n const removed: number[] = []\n for (const clientId of clientIds) {\n if (this.states.has(clientId)) {\n this.states.delete(clientId)\n this.meta.delete(clientId)\n removed.push(clientId)\n }\n }\n if (removed.length > 0) {\n this.emit({ added: [], updated: [], removed })\n }\n }\n\n /** Release all state and listeners owned by this awareness instance. */\n destroy(): void {\n this.setLocalState(null)\n this.states.clear()\n this.meta.clear()\n this.updateListeners.clear()\n this.changeListeners.clear()\n }\n}\n\n/**\n * Encode a full awareness message\n */\nexport function encodeAwarenessMessage(awareness: Awareness, clientIds?: number[]): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_AWARENESS)\n writeVarUint8Array(encoder, awareness.encodeUpdate(clientIds))\n return toUint8Array(encoder)\n}\n\n/**\n * Decode and apply awareness message\n */\nexport function handleAwarenessMessage(\n awareness: Awareness, \n data: Uint8Array\n): { added: number[]; updated: number[]; removed: number[] } {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n \n if (messageType !== MSG_AWARENESS) {\n throw new Error(`Expected awareness message (${MSG_AWARENESS}), got ${messageType}`)\n }\n \n const update = readVarUint8Array(decoder)\n return awareness.applyUpdate(update)\n}\n\n/**\n * Get message type from raw data\n */\nexport function getMessageType(data: Uint8Array): number {\n const decoder = createDecoder(data)\n return readVarUint(decoder)\n}\n","/**\n * Typed wire-protocol layer — discriminated unions, typed builders, and a\n * type-safe dispatcher for every `MSG.*` the SDK understands.\n *\n * Why this exists\n * ---------------\n *\n * The string `MSG.*` constants in `./constants.ts` are the authoritative\n * wire protocol, but using them directly is error-prone: a typo picks the\n * wrong message with the wrong payload shape and fails silently at\n * runtime. This module pairs every constant with its payload type, so\n * that:\n *\n * 1. Building a message with `clientBuild.gameInput(...)` is payload-\n * checked at the call site — the compiler refuses to ship a wrong\n * shape.\n *\n * 2. Parsing an inbound message via `dispatch(raw, handlers)` narrows the\n * payload type inside each handler automatically, replacing the\n * unsafe `switch (msg.type) { case MSG.X: (payload as any).foo }`\n * pattern.\n *\n * 3. Tightening `BaseRoom.sendTo` / `BaseRoom.broadcast` /\n * `HandlerContext.send` / `SubscriptionContext.send` to accept\n * `ServerMessage` turns the type layer into enforcement: any room\n * that ships a payload inconsistent with its declared arm fails to\n * compile. Without that, the discriminated union is documentation,\n * not contract.\n *\n * 4. Adding a new `MSG.*` is localized: one entry in the discriminated\n * union, one builder function, one handler key in every dispatcher\n * that cares. No grep-and-fix across the codebase.\n *\n * 5. Apps can extend the SDK protocol without forking: `dispatch<M>` is\n * generic over any `M extends ProtocolMessage`, and builders are\n * plain objects so apps compose via spread (`{ ...clientBuild,\n * myMessage: ... }`).\n *\n * Direction split\n * ---------------\n *\n * Some message types carry different payloads depending on who's sending.\n * `MSG.RECORDS_RESUBSCRIBE`, for example, is `{}` when the client asks to\n * but `{ state, tick }` when the server broadcasts the start event.\n * `MSG.CANVAS_ADD` is a flat shape dict on the way in and a `{ shape }`\n * wrapper on the way out. Modelling these with one union would force\n * handlers to juggle a union payload — clunky and error-prone. Instead we\n * split by direction:\n *\n * - `ClientMessage` — what the client sends to the server\n * - `ServerMessage` — what the server sends to the client\n * - `ProtocolMessage = ClientMessage | ServerMessage` (for code that\n * really doesn't care — avoid when possible)\n *\n * Each side gets its own builder (`clientBuild` / `serverBuild`) and each\n * side's dispatcher is parameterised with the union it expects.\n *\n * Payload strictness\n * ------------------\n *\n * Where payload shapes are stable + narrow (ids, flags), we type them\n * precisely. Where they're opaque or escape the protocol layer (record\n * data blobs, Yjs binary frames, game-engine state), we use `unknown` and\n * defer narrowing to the caller. This is intentional: over-typing opaque\n * payloads would require the protocol layer to import application types\n * and defeat the \"thin wire contract\" goal.\n */\n\nimport { MSG } from './constants'\n\n// =============================================================================\n// Envelope + helpers\n// =============================================================================\n\n/**\n * The outer shape of every wire message. `T` is the string discriminator\n * (e.g. `\"game.input\"`) — keeping it as a generic literal type lets the\n * discriminated-union narrowing in `dispatch()` pick the right payload.\n *\n * Callers extending the protocol should pass a string-literal type for\n * `T`, not the widened `string`. `BaseMessage<string, P>` collapses the\n * discriminated union and handler-map key inference falls back to a\n * single untyped `string` key, losing all narrowing.\n */\nexport interface BaseMessage<T extends string, P> {\n type: T\n payload: P\n}\n\n/** Matches when a payload is intentionally empty — `{}` on the wire. */\ntype EmptyPayload = Record<string, never>\n\n/**\n * Frozen empty payload reused by every \"no args\" builder. Frozen so a\n * runaway consumer mutating `.payload` can't corrupt the shared instance\n * that every other call site sees.\n */\nconst EMPTY: EmptyPayload = Object.freeze({}) as EmptyPayload\n\n// =============================================================================\n// Client → Server messages\n// =============================================================================\n\n/**\n * Every message the client can legitimately send. Extend in app code with\n * a string-literal union arm:\n *\n * type MyClientMessage = ClientMessage | BaseMessage<'myapp.foo', { x: number }>\n */\nexport type ClientMessage =\n // ---- Records / CRUD ---------------------------------------------------\n | BaseMessage<typeof MSG.SUBSCRIBE, { subscriptionId: string; query: unknown }>\n | BaseMessage<typeof MSG.UNSUBSCRIBE, { subscriptionId: string }>\n | BaseMessage<\n typeof MSG.PUT,\n { collection: string; recordId: string; data: Record<string, unknown>; requestId?: string }\n >\n | BaseMessage<\n typeof MSG.DELETE,\n { collection: string; recordId: string; requestId?: string }\n >\n // ---- Users ------------------------------------------------------------\n | BaseMessage<typeof MSG.USER_LIST, EmptyPayload>\n | BaseMessage<typeof MSG.SET_ROLE, { userId: string; role: string }>\n | BaseMessage<typeof MSG.USER_UPDATE, EmptyPayload>\n | BaseMessage<typeof MSG.LIST_SCHEMAS, EmptyPayload>\n // ---- Yjs --------------------------------------------------------------\n | BaseMessage<typeof MSG.YJS_JOIN, { collection: string; recordId: string; fieldName: string }>\n | BaseMessage<typeof MSG.YJS_LEAVE, { collection: string; recordId: string; fieldName: string }>\n // Binary yjs sync/awareness frames use numeric `MSG_YJS_SYNC` /\n // `MSG_YJS_AWARENESS` envelope ids from `./constants.ts` and ride the\n // binary WebSocket channel. They bypass this typed layer entirely.\n // ---- Game -------------------------------------------------------------\n // ---- Canvas -----------------------------------------------------------\n // `CANVAS_ADD` sends a flat shape dict as the payload (not `{ shape }`);\n // the server wraps it in `{ shape }` on its rebroadcast. Keep the types\n // aligned with the real wire or `clientBuild` lies. See\n // server/rooms/canvas-room.ts for the server side of this asymmetry.\n // `unknown` here (rather than `Record<string, unknown>`) so closed\n // concrete shape types pass assignability without index signatures.\n | BaseMessage<typeof MSG.CANVAS_ADD, unknown>\n | BaseMessage<typeof MSG.CANVAS_MOVE, { shapeId: string; x: number; y: number }>\n | BaseMessage<\n typeof MSG.CANVAS_RESIZE,\n { shapeId: string; width: number; height: number; x?: number; y?: number }\n >\n | BaseMessage<typeof MSG.CANVAS_DELETE, { shapeId: string }>\n | BaseMessage<\n typeof MSG.CANVAS_UPDATE,\n { shapeId: string; props: Record<string, unknown> }\n >\n // Flat payload in the client→server direction, matching `CANVAS_ADD`.\n | BaseMessage<typeof MSG.CANVAS_VIEWPORT, unknown>\n | BaseMessage<typeof MSG.CANVAS_UNDO, EmptyPayload>\n | BaseMessage<typeof MSG.CANVAS_REDO, EmptyPayload>\n // ---- Cron -------------------------------------------------------------\n // Optional `requestId` opts the mutation into a `CRON_ACK` receipt;\n // frames without one keep the fire-and-forget contract.\n | BaseMessage<typeof MSG.CRON_TRIGGER, { taskName: string; requestId?: string }>\n | BaseMessage<typeof MSG.CRON_PAUSE, { taskName: string; requestId?: string }>\n | BaseMessage<typeof MSG.CRON_RESUME, { taskName: string; requestId?: string }>\n // ---- Jobs -------------------------------------------------------------\n // `JOB_ENQUEUE` uses requestId/JOB_UPDATE-ack so the caller learns the\n // newly-assigned jobId.\n | BaseMessage<\n typeof MSG.JOB_ENQUEUE,\n {\n requestId: string\n type: string\n payload?: unknown\n maxAttempts?: number\n }\n >\n | BaseMessage<typeof MSG.JOB_CANCEL, { jobId: string }>\n | BaseMessage<typeof MSG.JOB_RETRY, { jobId: string }>\n // ---- Presence ---------------------------------------------------------\n // Flat state object — the server merges incoming fields into the peer's\n // stored state; there's no `{ state }` wrapper in the client direction.\n | BaseMessage<typeof MSG.PRESENCE_UPDATE, Record<string, unknown>>\n // ---- Gateway ----------------------------------------------------------\n | BaseMessage<typeof MSG.GW_SCOPE_CONNECT, { scopeType: string; scopeId: string }>\n | BaseMessage<typeof MSG.GW_SCOPE_DISCONNECT, { scopeType: string; scopeId: string }>\n | BaseMessage<typeof MSG.GW_TOKEN_REFRESH, { token: string }>\n\n// =============================================================================\n// Server → Client messages\n// =============================================================================\n\n/**\n * Every message the server can send. Room and handler `send` / `broadcast`\n * signatures are tightened to this union so outbound payloads are\n * compile-checked against the wire contract. As with `ClientMessage`,\n * extend via a string-literal union arm in app code when adding new\n * server-side broadcasts.\n */\nexport type ServerMessage =\n // ---- Records / CRUD ---------------------------------------------------\n | BaseMessage<\n typeof MSG.QUERY_RESULT,\n { subscriptionId: string; records: unknown[] }\n >\n | BaseMessage<\n typeof MSG.RECORD_CHANGE,\n {\n collection: string\n record: unknown\n // Real wire values are `create`/`update`/`delete`, not the\n // underlying storage op name. See `server/handlers/subscriptions.ts`.\n changeType: 'create' | 'update' | 'delete'\n }\n >\n // `subscriptionId` is optional because generic transport errors (bad\n // envelope, missing connection) have no subscription context. Specific\n // query failures include the id so the client can correlate.\n | BaseMessage<typeof MSG.ERROR, { error: string; subscriptionId?: string }>\n // ACK is a mutation ack — it carries the correlation `requestId` from\n // the inbound PUT/DELETE plus a success flag. Server/handlers/records.ts\n // is the authoritative send site.\n | BaseMessage<\n typeof MSG.ACK,\n | { requestId: string; success: true; recordId?: string }\n | { requestId: string; success: false; error: string }\n >\n | BaseMessage<typeof MSG.RESUBSCRIBE, EmptyPayload>\n | BaseMessage<typeof MSG.LIST_SCHEMAS, { schemas: unknown }>\n // ---- Auth -------------------------------------------------------------\n // Emitted once per connection by rooms that enforce role-based writes\n // (Canvas, Game, Cron). Client hooks store `canWrite` in state and\n // disable write APIs when false. See useCanvas /\n // useCronMonitor for the consumer side.\n | BaseMessage<typeof MSG.AUTH, { canWrite: boolean }>\n // ---- Users ------------------------------------------------------------\n // `USER_INFO` carries an app-specific user object (column-shape varies\n // per deployment), so the wire type is opaque. Narrow at the consumer.\n | BaseMessage<typeof MSG.USER_INFO, unknown>\n | BaseMessage<typeof MSG.USER_LIST, { users: unknown[] }>\n // ---- Yjs (JSON frames; binary sync/awareness bypass this layer) -------\n | BaseMessage<\n typeof MSG.YJS_JOIN,\n { collection: string; recordId: string; fieldName: string; canWrite: boolean }\n >\n // ---- Game -------------------------------------------------------------\n // ---- Canvas -----------------------------------------------------------\n // `CANVAS_SHAPES` carries both the shape list and the viewports snapshot\n // so new connections can restore a multi-user view in one message. See\n // server/rooms/canvas-room.ts:onConnect.\n | BaseMessage<\n typeof MSG.CANVAS_SHAPES,\n { shapes: unknown[]; viewports: unknown[] }\n >\n | BaseMessage<typeof MSG.CANVAS_ADD, { shape: unknown }>\n | BaseMessage<typeof MSG.CANVAS_MOVE, { shapeId: string; x: number; y: number }>\n | BaseMessage<\n typeof MSG.CANVAS_RESIZE,\n { shapeId: string; width: number; height: number; x?: number; y?: number }\n >\n | BaseMessage<typeof MSG.CANVAS_DELETE, { shapeId: string }>\n | BaseMessage<\n typeof MSG.CANVAS_UPDATE,\n { shapeId: string; props: Record<string, unknown> }\n >\n | BaseMessage<\n typeof MSG.CANVAS_VIEWPORT,\n { viewport: unknown } | { userId: string; removed: true }\n >\n // ---- Cron -------------------------------------------------------------\n // `CRON_STATUS` server broadcasts carry both tasks and recent history;\n // see server/rooms/cron-room.ts.\n | BaseMessage<typeof MSG.CRON_TASKS, { tasks: unknown }>\n | BaseMessage<typeof MSG.CRON_HISTORY, { history: unknown }>\n | BaseMessage<typeof MSG.CRON_STATUS, { tasks: unknown; recentHistory: unknown }>\n // `CRON_ACK` is the per-mutation receipt for a trigger/pause/resume that\n // carried a `requestId`. Triggers execute synchronously in the DO before\n // the ack is sent, so an ok trigger receipt means the run completed\n // (execution detail rides the history broadcasts). Failures carry a\n // machine-readable `reason` so clients never parse error strings —\n // `read_only` covers the role-downgrade race the client-side gate\n // cannot see.\n | BaseMessage<\n typeof MSG.CRON_ACK,\n | { requestId: string; taskName: string; ok: true }\n | {\n requestId: string\n taskName?: string\n ok: false\n reason: 'read_only' | 'unknown_task' | 'failed'\n error?: string\n }\n >\n // ---- Jobs -------------------------------------------------------------\n // `JOB_UPDATE` is the single push channel — same payload shape carries\n // initial-snapshot, enqueue, progress, success, failure, cancel, and\n // retry events. `kind` discriminates client-side. See useJobs.\n | BaseMessage<\n typeof MSG.JOB_UPDATE,\n {\n kind: 'snapshot' | 'enqueued' | 'progress' | 'succeeded' | 'failed' | 'canceled' | 'retried'\n job?: unknown\n jobs?: unknown[]\n /** Echoes the enqueue requestId when the update is the enqueue ack. */\n requestId?: string\n }\n >\n // ---- Presence ---------------------------------------------------------\n | BaseMessage<typeof MSG.PRESENCE_SYNC, { peers: unknown[] }>\n | BaseMessage<typeof MSG.PRESENCE_JOIN, { peer: unknown }>\n | BaseMessage<typeof MSG.PRESENCE_LEAVE, { userId: string }>\n | BaseMessage<\n typeof MSG.PRESENCE_UPDATE,\n { userId: string; state: Record<string, unknown> }\n >\n // ---- Gateway ----------------------------------------------------------\n | BaseMessage<\n typeof MSG.GW_SCOPE_ERROR,\n { scopeType: string; scopeId: string; error: string }\n >\n | BaseMessage<typeof MSG.GW_USER_UPDATE, unknown>\n\n/**\n * Any wire message, regardless of direction. Use this only when the code\n * path genuinely doesn't care — otherwise pick `ClientMessage` or\n * `ServerMessage` so the handler's payload type is narrower.\n */\nexport type ProtocolMessage = ClientMessage | ServerMessage\n\n// =============================================================================\n// Typed builders\n// =============================================================================\n//\n// Each builder returns a `{ type, payload }` object typed as a specific arm\n// of `ClientMessage` / `ServerMessage`. Call sites that hand the result to\n// `JSON.stringify` ship exactly what the wire protocol expects.\n//\n// `as const` on every return preserves the literal string `type` value\n// (e.g. `\"game.input\"`), which is what the discriminated-union narrowing\n// in `dispatch` keys on. Without it TS widens to `string` and the handler\n// map collapses to a single untyped key.\n// =============================================================================\n\n/** Factories for every client → server message. */\nexport const clientBuild = {\n // Records / CRUD\n subscribe: (subscriptionId: string, query: unknown) =>\n ({ type: MSG.SUBSCRIBE, payload: { subscriptionId, query } }) as const,\n unsubscribe: (subscriptionId: string) =>\n ({ type: MSG.UNSUBSCRIBE, payload: { subscriptionId } }) as const,\n put: (\n collection: string,\n recordId: string,\n data: Record<string, unknown>,\n requestId?: string,\n ) =>\n ({\n type: MSG.PUT,\n payload: { collection, recordId, data, requestId },\n }) as const,\n remove: (collection: string, recordId: string, requestId?: string) =>\n ({ type: MSG.DELETE, payload: { collection, recordId, requestId } }) as const,\n // Users\n userList: () => ({ type: MSG.USER_LIST, payload: EMPTY }) as const,\n setRole: (userId: string, role: string) =>\n ({ type: MSG.SET_ROLE, payload: { userId, role } }) as const,\n userUpdate: () => ({ type: MSG.USER_UPDATE, payload: EMPTY }) as const,\n listSchemas: () => ({ type: MSG.LIST_SCHEMAS, payload: EMPTY }) as const,\n // Yjs\n yjsJoin: (collection: string, recordId: string, fieldName: string) =>\n ({ type: MSG.YJS_JOIN, payload: { collection, recordId, fieldName } }) as const,\n yjsLeave: (collection: string, recordId: string, fieldName: string) =>\n ({ type: MSG.YJS_LEAVE, payload: { collection, recordId, fieldName } }) as const,\n // Canvas — `CANVAS_ADD` / `CANVAS_VIEWPORT` payloads are flat (the shape\n // or viewport object is the whole payload).\n canvasAdd: (shape: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_ADD, payload: shape }) as const,\n canvasMove: (shapeId: string, x: number, y: number) =>\n ({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }) as const,\n canvasResize: (\n shapeId: string,\n width: number,\n height: number,\n x?: number,\n y?: number,\n ) =>\n ({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }) as const,\n canvasDelete: (shapeId: string) =>\n ({ type: MSG.CANVAS_DELETE, payload: { shapeId } }) as const,\n canvasUpdate: (shapeId: string, props: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }) as const,\n canvasViewport: (viewport: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_VIEWPORT, payload: viewport }) as const,\n canvasUndo: () => ({ type: MSG.CANVAS_UNDO, payload: EMPTY }) as const,\n canvasRedo: () => ({ type: MSG.CANVAS_REDO, payload: EMPTY }) as const,\n // Cron\n cronTrigger: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_TRIGGER, payload: { taskName, requestId } }) as const,\n cronPause: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_PAUSE, payload: { taskName, requestId } }) as const,\n cronResume: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_RESUME, payload: { taskName, requestId } }) as const,\n // Jobs\n jobEnqueue: (\n requestId: string,\n type: string,\n payload?: unknown,\n maxAttempts?: number,\n ) =>\n ({\n type: MSG.JOB_ENQUEUE,\n payload: { requestId, type, payload, maxAttempts },\n }) as const,\n jobCancel: (jobId: string) =>\n ({ type: MSG.JOB_CANCEL, payload: { jobId } }) as const,\n jobRetry: (jobId: string) =>\n ({ type: MSG.JOB_RETRY, payload: { jobId } }) as const,\n // Presence\n presenceUpdate: (state: Record<string, unknown>) =>\n ({ type: MSG.PRESENCE_UPDATE, payload: state }) as const,\n // Gateway\n gwScopeConnect: (scopeType: string, scopeId: string) =>\n ({ type: MSG.GW_SCOPE_CONNECT, payload: { scopeType, scopeId } }) as const,\n gwScopeDisconnect: (scopeType: string, scopeId: string) =>\n ({ type: MSG.GW_SCOPE_DISCONNECT, payload: { scopeType, scopeId } }) as const,\n gwTokenRefresh: (token: string) =>\n ({ type: MSG.GW_TOKEN_REFRESH, payload: { token } }) as const,\n}\n\n/** Factories for every server → client message. Used by room DOs and\n * handler modules so server broadcasts are payload-checked too. Opaque\n * slots (`record`, `shape`, `peer`, etc.) take `unknown` so concrete\n * domain types — `RecordResult`, `CanvasShape`, `MediaPeer` — can be\n * passed without casts. */\nexport const serverBuild = {\n // Records / CRUD\n queryResult: (subscriptionId: string, records: unknown[]) =>\n ({ type: MSG.QUERY_RESULT, payload: { subscriptionId, records } }) as const,\n recordChange: (\n collection: string,\n record: unknown,\n changeType: 'create' | 'update' | 'delete',\n ) =>\n ({ type: MSG.RECORD_CHANGE, payload: { collection, record, changeType } }) as const,\n error: (error: string, subscriptionId?: string) =>\n ({ type: MSG.ERROR, payload: { error, subscriptionId } }) as const,\n ackSuccess: (requestId: string, recordId?: string) =>\n ({\n type: MSG.ACK,\n payload: { requestId, success: true as const, recordId },\n }) as const,\n ackFailure: (requestId: string, error: string) =>\n ({\n type: MSG.ACK,\n payload: { requestId, success: false as const, error },\n }) as const,\n resubscribe: () => ({ type: MSG.RESUBSCRIBE, payload: EMPTY }) as const,\n schemas: (schemas: unknown) =>\n ({ type: MSG.LIST_SCHEMAS, payload: { schemas } }) as const,\n // Users\n userInfo: (user: unknown) =>\n ({ type: MSG.USER_INFO, payload: user }) as const,\n userList: (users: unknown[]) =>\n ({ type: MSG.USER_LIST, payload: { users } }) as const,\n // Yjs\n yjsJoin: (collection: string, recordId: string, fieldName: string, canWrite: boolean) =>\n ({\n type: MSG.YJS_JOIN,\n payload: { collection, recordId, fieldName, canWrite },\n }) as const,\n // Canvas\n canvasShapes: (shapes: unknown[], viewports: unknown[]) =>\n ({ type: MSG.CANVAS_SHAPES, payload: { shapes, viewports } }) as const,\n canvasAdd: (shape: unknown) =>\n ({ type: MSG.CANVAS_ADD, payload: { shape } }) as const,\n canvasMove: (shapeId: string, x: number, y: number) =>\n ({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }) as const,\n canvasResize: (\n shapeId: string,\n width: number,\n height: number,\n x?: number,\n y?: number,\n ) =>\n ({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }) as const,\n canvasDelete: (shapeId: string) =>\n ({ type: MSG.CANVAS_DELETE, payload: { shapeId } }) as const,\n canvasUpdate: (shapeId: string, props: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }) as const,\n canvasViewport: (viewport: unknown) =>\n ({ type: MSG.CANVAS_VIEWPORT, payload: { viewport } }) as const,\n canvasViewportRemoved: (userId: string) =>\n ({\n type: MSG.CANVAS_VIEWPORT,\n payload: { userId, removed: true as const },\n }) as const,\n // Cron\n cronTasks: (tasks: unknown) =>\n ({ type: MSG.CRON_TASKS, payload: { tasks } }) as const,\n cronHistory: (history: unknown) =>\n ({ type: MSG.CRON_HISTORY, payload: { history } }) as const,\n cronStatus: (tasks: unknown, recentHistory: unknown) =>\n ({ type: MSG.CRON_STATUS, payload: { tasks, recentHistory } }) as const,\n cronAckSuccess: (requestId: string, taskName: string) =>\n ({\n type: MSG.CRON_ACK,\n payload: { requestId, taskName, ok: true as const },\n }) as const,\n cronAckFailure: (\n requestId: string,\n reason: 'read_only' | 'unknown_task' | 'failed',\n error?: string,\n taskName?: string,\n ) =>\n ({\n type: MSG.CRON_ACK,\n payload: { requestId, taskName, ok: false as const, reason, error },\n }) as const,\n // Jobs\n jobSnapshot: (jobs: unknown[]) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'snapshot' as const, jobs } }) as const,\n jobEnqueued: (job: unknown, requestId?: string) =>\n ({\n type: MSG.JOB_UPDATE,\n payload: { kind: 'enqueued' as const, job, requestId },\n }) as const,\n jobProgress: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'progress' as const, job } }) as const,\n jobSucceeded: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'succeeded' as const, job } }) as const,\n jobFailed: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'failed' as const, job } }) as const,\n jobCanceled: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'canceled' as const, job } }) as const,\n jobRetried: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'retried' as const, job } }) as const,\n // Presence\n presenceSync: (peers: unknown[]) =>\n ({ type: MSG.PRESENCE_SYNC, payload: { peers } }) as const,\n presenceJoin: (peer: unknown) =>\n ({ type: MSG.PRESENCE_JOIN, payload: { peer } }) as const,\n presenceLeave: (userId: string) =>\n ({ type: MSG.PRESENCE_LEAVE, payload: { userId } }) as const,\n presenceUpdate: (userId: string, state: Record<string, unknown>) =>\n ({ type: MSG.PRESENCE_UPDATE, payload: { userId, state } }) as const,\n // Gateway\n gwScopeError: (scopeType: string, scopeId: string, error: string) =>\n ({\n type: MSG.GW_SCOPE_ERROR,\n payload: { scopeType, scopeId, error },\n }) as const,\n gwUserUpdate: (user: unknown) =>\n ({ type: MSG.GW_USER_UPDATE, payload: user }) as const,\n}\n\n// =============================================================================\n// Typed dispatch\n// =============================================================================\n//\n// `dispatch` parses a raw WebSocket frame (or takes an already-parsed\n// object) and routes it to the right handler. Handlers are indexed by the\n// message's string `type`, and TS narrows the `payload` argument to the\n// correct arm of the message union automatically thanks to the\n// discriminated-union lookup.\n//\n// The generic `M` defaults to `ProtocolMessage` so ad-hoc uses get the\n// full SDK vocabulary for free, but apps can pass a narrower (or extended)\n// union:\n//\n// dispatch<ServerMessage>(raw, {\n// })\n//\n// // With an app-defined message (use a STRING LITERAL for the type\n// // parameter — `string` widens the union and kills narrowing):\n// type MyMsg =\n// | ServerMessage\n// | BaseMessage<'myapp.boss_spawn', { hp: number }>\n// dispatch<MyMsg>(raw, {\n// 'myapp.boss_spawn': (p) => { console.log(p.hp) },\n// })\n//\n// =============================================================================\n\n/**\n * Handler map keyed by a message's string `type`. Every key maps to the\n * payload type of the matching arm in `M`, so handlers get automatic\n * payload narrowing with no runtime checks.\n *\n * Narrowing requires each `M` arm to use a string-literal `T`; widening\n * `M` to `BaseMessage<string, unknown>` collapses all keys to a single\n * untyped `string` and the map becomes a plain `Record<string, (p: unknown)\n * => void>`.\n */\nexport type MessageHandlers<M extends BaseMessage<string, unknown> = ProtocolMessage> = {\n [K in M as K['type']]?: (payload: K['payload']) => void\n}\n\n/**\n * Parse and route an incoming wire message. Accepts either the raw JSON\n * string (directly from `ws.onmessage`'s `event.data`) or an already-\n * parsed object. Returns `true` if a handler ran, `false` otherwise\n * (unrecognised type, parse failure, or malformed envelope).\n *\n * Defaults `M` to `ProtocolMessage` so callers who don't care about\n * direction can just do `dispatch(raw, handlers)`; narrow with\n * `dispatch<ClientMessage>` / `dispatch<ServerMessage>` to restrict which\n * arms the handler map is allowed to cover.\n */\nexport function dispatch<M extends BaseMessage<string, unknown> = ProtocolMessage>(\n raw: unknown,\n handlers: MessageHandlers<M>,\n): boolean {\n let msg: { type?: unknown; payload?: unknown }\n if (typeof raw === 'string') {\n try {\n msg = JSON.parse(raw) as { type?: unknown; payload?: unknown }\n } catch {\n return false\n }\n } else if (raw && typeof raw === 'object') {\n msg = raw as { type?: unknown; payload?: unknown }\n } else {\n return false\n }\n if (typeof msg.type !== 'string') return false\n const handler = (handlers as Record<string, ((payload: unknown) => void) | undefined>)[\n msg.type\n ]\n if (!handler) return false\n handler(msg.payload)\n return true\n}\n\n/**\n * Serialise a typed-built message for `ws.send`. A tiny wrapper around\n * `JSON.stringify` kept around so call sites read as\n * `ws.send(encode(clientBuild.gameInput(...)))` rather than paying\n * attention to stringify arguments.\n */\nexport function encode<M extends BaseMessage<string, unknown>>(message: M): string {\n return JSON.stringify(message)\n}\n","/**\n * Record operation handlers for RecordRoom (PUT/DELETE)\n *\n * All collections use table-mode storage (c_* tables with typed columns).\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { RecordResult, PutPayload, DeletePayload } from '../../shared/types'\nimport type { ToolResult } from '../utils/tools'\nimport { DEFAULT_DELETE_WHERE_LIMIT, MAX_DELETE_WHERE_LIMIT } from '../utils/tools'\nimport { serverBuild } from '../../shared/protocol/messages'\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\nimport {\n type CollectionSchema,\n type ResolvedColumn,\n canCreate,\n canUpdate,\n canDelete,\n canRead,\n checkFieldPermissions,\n checkUnclaimedOwnerTransition,\n SYSTEM_ASSIGNED_COLUMNS,\n SYSTEM_MANAGED_COLUMNS,\n coerceValue,\n resolveColumn,\n columnId,\n collectionTableName,\n rowToData,\n dataToColumnValues,\n buildTableSelect,\n} from '../schemas/registry'\nimport {\n broadcastChange,\n executeQuery,\n resolveCollection,\n MAX_FILTERED_SCAN_ROWS,\n type QueryScanState,\n type SubscriptionContext,\n} from './subscriptions'\n\nexport interface RecordContext extends SubscriptionContext {\n state: DurableObjectState\n}\n\n/**\n * Get resolved columns for a schema.\n */\nfunction getResolvedColumns(schema: CollectionSchema): ResolvedColumn[] {\n return (schema.columns ?? []).map(resolveColumn)\n}\n\n/** Refusal text for a record write that tries to set a system-assigned column. */\nfunction systemManagedColumnError(column: string, isUpdate: boolean): string {\n const roleHint =\n column === 'role' ? ', or the admin-gated useUsers().setRole() to change a role' : ''\n // On an update there is a current value to echo, and echoing it succeeds. On\n // a create there is none, and no value at all is accepted — pointing at a\n // \"current value\" would send the caller looking for something that does not\n // exist. The row itself is registration's to make.\n const remedy = isUpdate\n ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.`\n : `A users row is created by registration, so no value for '${column}' is accepted here — omit it.`\n return (\n `FIELD ERROR: '${column}' is system-managed and cannot be set by a record write — ` +\n `nothing was written. Use tools.registerUser({ userId, isAdmin })${roleHint}. ${remedy}`\n )\n}\n\n// ============================================================================\n// Team Membership Change Detection\n// ============================================================================\n\n/**\n * When a team_members record is created/updated/deleted, the affected user's\n * team-scoped subscriptions (teams, tasks, projects, etc.) are stale — the\n * initial query was filtered by their old team list. Send MSG.RESUBSCRIBE to\n * tell the client to re-subscribe all active queries with fresh team data.\n */\nfunction notifyTeamMembershipChange(\n ctx: RecordContext,\n collection: string,\n record: RecordResult,\n): void {\n if (collection !== 'team_members') return\n\n const affectedUserId = record.data.UserId as string\n if (!affectedUserId) return\n\n const webSockets = ctx.state.getWebSockets()\n for (const ws of webSockets) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n if (attachment.userId === affectedUserId) {\n ctx.send(ws, serverBuild.resubscribe())\n }\n }\n}\n\n// ============================================================================\n// Row ↔ Data Mapping\n// ============================================================================\n\ninterface TableRow {\n _row_id: string\n _created_by: string\n _created_at: string\n _updated_at: string\n [key: string]: unknown\n}\n\nfunction tableRowToRecord(\n row: TableRow,\n columns: ResolvedColumn[],\n): {\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n} {\n return {\n data: rowToData(row, columns),\n createdBy: row._created_by,\n createdAt: row._created_at,\n updatedAt: row._updated_at,\n }\n}\n\n// ============================================================================\n// Get Record\n// ============================================================================\n\n/**\n * Get a single record from its c_* table.\n */\nexport function getRecord(\n sql: SqlStorage,\n collection: string,\n recordId: string,\n schema?: CollectionSchema,\n): {\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n} | null {\n if (!schema || !schema.columns) {\n // System collections with empty columns — query with just system columns\n const tbl = collectionTableName(collection)\n try {\n const cursor = sql.exec(`SELECT * FROM \"${tbl}\" WHERE _row_id = ?`, recordId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return {\n data: {},\n createdBy: row._created_by,\n createdAt: row._created_at,\n updatedAt: row._updated_at,\n }\n } catch {\n return null\n }\n }\n\n const columns = getResolvedColumns(schema)\n const selectSql = buildTableSelect(collection, columns) + ` WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, recordId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return tableRowToRecord(row, columns)\n}\n\n/**\n * Handle PUT (create/update) record request via WebSocket.\n * Thin wrapper around putRecord() — translates ToolResult errors to WS messages.\n */\nexport function handlePut(\n ctx: RecordContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: PutPayload,\n): void {\n const { collection, recordId, data, requestId } = payload\n const result = putRecord(ctx, collection, recordId, data, attachment.userId, attachment.role)\n\n if (requestId) {\n ctx.send(\n ws,\n result.success\n ? serverBuild.ackSuccess(requestId, recordId)\n : serverBuild.ackFailure(requestId, result.error),\n )\n } else if (!result.success) {\n ctx.send(ws, serverBuild.error(result.error))\n }\n}\n\n/**\n * Handle DELETE record request via WebSocket.\n * Thin wrapper around deleteRecord() — translates ToolResult errors to WS messages.\n */\nexport function handleDelete(\n ctx: RecordContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: DeletePayload,\n): void {\n const { collection, recordId, requestId } = payload\n const result = deleteRecord(ctx, collection, recordId, attachment.userId, attachment.role)\n\n if (requestId) {\n ctx.send(\n ws,\n result.success\n ? serverBuild.ackSuccess(requestId)\n : serverBuild.ackFailure(requestId, result.error),\n )\n } else if (!result.success) {\n ctx.send(ws, serverBuild.error(result.error))\n }\n}\n\n// ============================================================================\n// Shared Record Operations (used by both WebSocket handlers and Tools API)\n// ============================================================================\n\n/**\n * Put (create/update) a record. Returns ToolResult instead of sending WS messages.\n * Performs schema validation, RBAC checks, and broadcasts changes.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions\n * that have already been authorized at the app level.\n * @param systemUpdate - When true, also skip system-managed field stripping.\n * Used for server-initiated updates to system fields (e.g. user profile sync).\n */\nexport function putRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n data: Record<string, unknown>,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n systemUpdate = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n\n // A write needs declared columns, so a schema-less system collection is not\n // writable through this path even though it resolves.\n const schema = resolved.schema\n if (!schema) {\n return { success: false, error: `Schema not registered for collection: ${collection}` }\n }\n\n const columns = getResolvedColumns(schema)\n for (const col of columns) {\n if (col.id !== col.name && Object.hasOwn(data, col.id)) {\n return {\n success: false,\n error: `FIELD ERROR: physical column id '${col.id}' is not a writable field; use '${col.name}'`,\n }\n }\n }\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n const isUpdate = existing !== null\n\n const mergedData = isUpdate ? { ...existing!.data, ...data } : { ...data }\n\n // Enforce column-level field behaviors\n for (const col of columns) {\n if (col.readonly) continue\n\n if (!isUpdate) {\n // CREATE: apply defaults, userBound, required checks\n if (col.default !== undefined && mergedData[col.name] === undefined) {\n mergedData[col.name] = col.default\n }\n if (col.userBound) {\n mergedData[col.name] = userId\n }\n if (\n col.required &&\n (mergedData[col.name] === undefined ||\n mergedData[col.name] === null ||\n mergedData[col.name] === '')\n ) {\n return { success: false, error: `Required field '${col.name}' is missing` }\n }\n } else {\n // UPDATE: enforce immutable, preserve userBound\n if (\n col.immutable &&\n data[col.name] !== undefined &&\n data[col.name] !== existing!.data[col.name]\n ) {\n return { success: false, error: `Cannot modify immutable field '${col.name}'` }\n }\n if (col.userBound && data[col.name] !== undefined) {\n mergedData[col.name] = existing!.data[col.name] // preserve original\n }\n }\n\n // timestampTrigger: auto-set timestamp when trigger field changes\n if (col.timestampTrigger) {\n const { field: triggerField, value: triggerValue } = col.timestampTrigger\n if (!isUpdate) {\n // CREATE: set timestamp if trigger field is present and matches value (if specified)\n if (mergedData[triggerField] !== undefined && mergedData[triggerField] !== null) {\n if (triggerValue === undefined || mergedData[triggerField] === triggerValue) {\n mergedData[col.name] = new Date().toISOString()\n }\n }\n } else {\n // UPDATE: set timestamp if trigger field changed (and optionally to specified value)\n const oldVal = existing!.data[triggerField]\n const newVal = mergedData[triggerField]\n if (newVal !== oldVal) {\n if (triggerValue === undefined || newVal === triggerValue) {\n mergedData[col.name] = new Date().toISOString()\n }\n }\n }\n }\n }\n\n const finalData = { ...mergedData }\n\n // RBAC checks\n if (!skipUserRbac) {\n const permCtx = ctx.getPermissionContext()\n if (isUpdate) {\n if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {\n return {\n success: false,\n error: `UPDATE DENIED: role=${userRole}, collection=${collection}`,\n }\n }\n } else {\n if (!canCreate(schema, userRole)) {\n return {\n success: false,\n error: `CREATE DENIED: role=${userRole}, collection=${collection}`,\n }\n }\n }\n\n // Check only caller-supplied fields. Defaults, user-bound values, and\n // timestamp triggers are server-owned and must not trip writableFields.\n const fieldError = checkFieldPermissions(\n schema,\n userRole,\n data,\n isUpdate ? existing.data : undefined,\n )\n if (fieldError) {\n return { success: false, error: `FIELD ERROR: ${fieldError}` }\n }\n\n const ownerError = checkUnclaimedOwnerTransition(schema, userRole, finalData, userId)\n if (ownerError) {\n return { success: false, error: `FIELD ERROR: ${ownerError}` }\n }\n }\n\n // System-managed columns on `users` belong to registerUser, not to ordinary\n // record writes, so none of them is ever written from here. How a supplied\n // value is answered depends on whether the caller could have chosen it:\n //\n // - SYSTEM_ASSIGNED (email, name, imageUrl, role): a different value is a\n // change the caller means and will not get. Refuse loudly instead of\n // returning `{ success: true }` over a silently discarded write.\n // - SYSTEM_MAINTAINED (createdAt, lastSeenAt): the server moves these on\n // its own without broadcasting, so a stale echo is an artifact of\n // read-modify-write, not an attempted change. Preserve silently.\n //\n // \"Different\" means different *once written*: `coerceValue` is what decides\n // the stored column, and it maps undefined, null and '' alike to NULL. Those\n // three spellings are one state, so comparing anything but the coerced values\n // refuses writes that would not change a byte.\n if (!systemUpdate && schema.name === 'users') {\n for (const col of columns) {\n if (!SYSTEM_MANAGED_COLUMNS.has(col.name)) continue\n const current = existing?.data[col.name]\n if (\n SYSTEM_ASSIGNED_COLUMNS.has(col.name) &&\n Object.hasOwn(data, col.name) &&\n coerceValue(data[col.name], col.storage, col.interpretation) !==\n coerceValue(current, col.storage, col.interpretation)\n ) {\n return { success: false, error: systemManagedColumnError(col.name, isUpdate) }\n }\n if (current !== undefined) finalData[col.name] = current // preserve existing\n else delete finalData[col.name]\n }\n }\n\n const colValues = dataToColumnValues(finalData, columns)\n const now = new Date().toISOString()\n const tbl = collectionTableName(collection)\n\n // Enforce uniqueOn constraint before INSERT\n if (!isUpdate && schema.uniqueOn && schema.uniqueOn.length > 0) {\n const uniqueWhere = schema.uniqueOn.map((fieldName) => {\n const colSqlId = columnId(fieldName)\n return `\"${colSqlId}\" = ?`\n })\n const uniqueParams = schema.uniqueOn.map((fieldName) => {\n const val = finalData[fieldName]\n return val !== undefined ? val : null\n })\n const existing = ctx.sql.exec(\n `SELECT _row_id FROM \"${tbl}\" WHERE ${uniqueWhere.join(' AND ')} LIMIT 1`,\n ...uniqueParams,\n )\n if (existing.toArray().length > 0) {\n const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? 'null'}`).join(', ')\n return {\n success: false,\n error: `Duplicate: a record with ${fields} already exists in ${collection}`,\n }\n }\n }\n\n if (isUpdate) {\n const setClauses: string[] = [`_updated_at = ?`]\n const params: unknown[] = [now]\n for (const [colId, val] of Object.entries(colValues)) {\n setClauses.push(`\"${colId}\" = ?`)\n params.push(val)\n }\n params.push(recordId)\n ctx.sql.exec(`UPDATE \"${tbl}\" SET ${setClauses.join(', ')} WHERE _row_id = ?`, ...params)\n } else {\n const colIds = Object.keys(colValues)\n const allCols = [\n '_row_id',\n '_created_by',\n '_created_at',\n '_updated_at',\n ...colIds.map((c) => `\"${c}\"`),\n ]\n const placeholders = allCols.map(() => '?').join(', ')\n const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])]\n ctx.sql.exec(`INSERT INTO \"${tbl}\" (${allCols.join(', ')}) VALUES (${placeholders})`, ...params)\n }\n\n // Read back to get computed columns and canonical values\n const saved = getRecord(ctx.sql, collection, recordId, schema)!\n const record: RecordResult = {\n recordId,\n data: saved.data,\n createdBy: saved.createdBy,\n createdAt: saved.createdAt,\n updatedAt: saved.updatedAt,\n }\n broadcastChange(ctx, ctx.state, collection, record, isUpdate ? 'update' : 'create')\n notifyTeamMembershipChange(ctx, collection, record)\n\n return { success: true, data: { recordId, record } }\n}\n\n/**\n * Delete a record. Returns ToolResult instead of sending WS messages.\n * Performs RBAC check and broadcasts the deletion.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function deleteRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n if (!existing) {\n return { success: false, error: RECORD_NOT_FOUND }\n }\n\n if (\n !skipUserRbac &&\n schema &&\n !canDelete(schema, userRole, { ...existing, recordId }, userId, ctx.getPermissionContext())\n ) {\n return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` }\n }\n\n const tbl = collectionTableName(collection)\n ctx.sql.exec(`DELETE FROM \"${tbl}\" WHERE _row_id = ?`, recordId)\n\n const record: RecordResult = {\n recordId,\n data: existing.data,\n createdBy: existing.createdBy,\n createdAt: existing.createdAt,\n updatedAt: existing.updatedAt,\n }\n broadcastChange(ctx, ctx.state, collection, record, 'delete')\n notifyTeamMembershipChange(ctx, collection, record)\n\n return { success: true, data: { deleted: true } }\n}\n\n/**\n * The one `where` validator for the tools API's filtered reads and deletes.\n * `executeQuery` is lenient — a key that names no column is silently dropped\n * — which for a delete truncates and for a read over-returns (a typo'd\n * filter hands the whole readable collection back with `success: true`, and\n * an assistant or action then reasons over it as the filtered set). Every key\n * must be `recordId`, `createdBy`, or a schema column, and every value a\n * primitive (equality only). `null` = nothing to refuse.\n */\nexport function refuseUnknownWhere(\n collection: string,\n schema: CollectionSchema,\n where: Record<string, unknown>,\n): ToolResult | null {\n const columns = (schema.columns ?? []).map(resolveColumn)\n const filterable = ['recordId', 'createdBy', ...columns.map((c) => c.name)]\n const unknown = Object.keys(where).filter(\n (key) =>\n key !== 'recordId' &&\n key !== 'createdBy' &&\n !columns.some((c) => c.id === columnId(key) || c.name === key),\n )\n if (unknown.length > 0) {\n return {\n success: false,\n error: `Unknown field(s) in where for \"${collection}\": ${unknown.join(', ')} — filterable fields: ${filterable.join(', ')}`,\n }\n }\n const nonPrimitive = Object.entries(where).filter(([, v]) => v !== null && typeof v === 'object')\n if (nonPrimitive.length > 0) {\n return {\n success: false,\n error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(', ')}`,\n }\n }\n return null\n}\n\n/**\n * Delete every record matching `where`, capped at one bounded page.\n *\n * The batch primitive behind cascading deletes: a caller that deleted row by\n * row spent one DO subrequest per row and ran out of subrequest budget on a\n * large set, orphaning the rest. One call handles a page; the caller repeats\n * it while `deleted` equals the page size (each page really removes rows, so\n * the loop terminates).\n *\n * Authorization is exactly `records.delete`'s: every matched record is checked\n * with `canDelete` *before* anything is removed, so a denied row refuses the\n * whole page rather than leaving a half-applied batch.\n *\n * `where` is validated strictly here because `executeQuery` is lenient — it\n * silently drops a key that names no column, and ignores `where` altogether on\n * a schemaless system collection. A read that ignores a filter over-returns; a\n * delete that ignores one truncates. So every key must name a real field\n * (`recordId`, `createdBy`, or a schema column), the collection must have a\n * schema, and `limit` must be a finite number — anything else is refused, and\n * collection-wide truncation is not reachable through this tool.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function deleteWhere(\n ctx: RecordContext,\n collection: string,\n where: unknown,\n limit: unknown,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n if (!schema) {\n return {\n success: false,\n error: `records.deleteWhere: collection \"${collection}\" has no schema, so it has no filterable fields — delete its rows by recordId with records.delete`,\n }\n }\n\n if (!where || typeof where !== 'object' || Array.isArray(where) || Object.keys(where).length === 0) {\n return { success: false, error: 'Missing required param: where (must match at least one field)' }\n }\n const whereRefusal = refuseUnknownWhere(collection, schema, where as Record<string, unknown>)\n if (whereRefusal) return whereRefusal\n\n if (limit !== undefined && !(typeof limit === 'number' && Number.isFinite(limit))) {\n return { success: false, error: 'limit must be a finite number' }\n }\n const page = Math.min(\n Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),\n MAX_DELETE_WHERE_LIMIT,\n )\n // `executeQuery`'s limit counts readable records (it scans past rows a\n // per-row filter hides, up to its scan cap), so a short page means \"no more\n // matches\" — the contract the caller's drain loop depends on — unless the\n // scan was capped, which must refuse rather than masquerade as exhaustion.\n const scan: QueryScanState = { capped: false }\n const matched = executeQuery(\n ctx,\n { collection, where: where as Record<string, unknown>, limit: page },\n userId,\n userRole,\n skipUserRbac,\n scan,\n )\n if (scan.capped && matched.length < page) {\n return {\n success: false,\n error:\n `records.deleteWhere scanned ${MAX_FILTERED_SCAN_ROWS} rows without filling a page of ` +\n `readable matches in \"${collection}\" — narrow \\`where\\` (or delete by recordId) and retry`,\n }\n }\n\n if (!skipUserRbac && schema) {\n const permCtx = ctx.getPermissionContext()\n for (const record of matched) {\n if (!canDelete(schema, userRole, record, userId, permCtx)) {\n return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` }\n }\n }\n }\n\n let deleted = 0\n for (const record of matched) {\n if (deleteRecord(ctx, collection, record.recordId, userId, userRole, skipUserRbac).success) {\n deleted++\n }\n }\n if (deleted < matched.length) {\n // A short page must mean \"no more matches\", never \"some deletes failed\" —\n // a drain loop reading it as exhaustion would silently orphan the rest.\n return {\n success: false,\n error: `Deleted ${deleted} of ${matched.length} matched record(s) in \"${collection}\"; the rest failed — retry`,\n }\n }\n return { success: true, data: { deleted } }\n}\n\n/**\n * Read a single record with RBAC check. Returns ToolResult.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function readRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n if (!existing) {\n return { success: false, error: RECORD_NOT_FOUND }\n }\n\n if (\n !skipUserRbac &&\n schema &&\n !canRead(schema, userRole, { ...existing, recordId }, userId, ctx.getPermissionContext())\n ) {\n return { success: false, error: `READ DENIED: role=${userRole}, collection=${collection}` }\n }\n\n const record: RecordResult = {\n recordId,\n data: existing.data,\n createdBy: existing.createdBy,\n createdAt: existing.createdAt,\n updatedAt: existing.updatedAt,\n }\n\n return { success: true, data: { record } }\n}\n","/**\n * Subscription handlers for RecordRoom\n *\n * All collections use table-mode storage (c_* tables with typed columns).\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { Query, RecordResult, SubscribePayload, UnsubscribePayload } from '../../shared/types'\nimport { MSG } from '../../shared/protocol/constants'\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n canRead,\n getRolePermissions,\n resolveColumn,\n buildTableSelect,\n rowToData,\n columnId,\n collectionTableName,\n} from '../schemas/registry'\nimport { SYSTEM_COLLECTIONS } from './yjs'\n\nexport interface SubscriptionContext {\n sql: SqlStorage\n schemaRegistry: SchemaRegistry\n state: DurableObjectState // Needed to get all connected WebSockets\n /**\n * The scope this room was addressed by — `app:{appId}`, `chat:{channelId}`,\n * `conv:{convId}`, `workspace:default`, … Records are sharded across rooms\n * by scope, so the scope is what names the room in an unknown-collection\n * failure. Undefined only for rooms addressed by a unique id rather than a\n * name (`idFromName` populates `DurableObjectId.name`).\n */\n scopeId?: string\n getPermissionContext(): PermissionContext\n /** Typed against `ServerMessage` so outbound broadcasts are\n * compile-checked against the wire contract. */\n send(ws: WebSocket, message: ServerMessage): void\n}\n\n/**\n * Outcome of resolving a collection name against the room's schema set.\n * `schema` is undefined only for a system collection registered without\n * columns, which the query path serves from system columns alone.\n */\nexport type CollectionResolution =\n | { ok: true; schema: CollectionSchema | undefined; isSystem: boolean }\n | { ok: false; error: string }\n\n/**\n * Resolve a collection name against the collections this room actually knows.\n *\n * The single place that decides whether a room can serve a collection at all.\n * Records are sharded across Durable Object rooms by scope, so asking a room\n * for a collection it was never given is a routing mistake, not an empty\n * result — the failure names the collection, the room, and what that room\n * does serve so the caller can fix the scope instead of debugging silence.\n *\n * The `Schema not registered for collection: <name>` prefix is load-bearing:\n * the scaffolded Yjs route probes for an optional `documents` collection with\n * `startsWith` on it. Extend the message, never reword the prefix.\n */\nexport function resolveCollection(\n ctx: SubscriptionContext,\n collection: string,\n): CollectionResolution {\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n const schema = ctx.schemaRegistry.get(collection)\n\n if (!schema && !isSystem) {\n const known = ctx.schemaRegistry.names().sort().join(', ') || '(none)'\n const room = ctx.scopeId ? `Room '${ctx.scopeId}'` : 'This room'\n return {\n ok: false,\n error:\n `Schema not registered for collection: ${collection} — ` +\n `${room} serves only: ${known}. Records are sharded by scope, so ` +\n `address the room that owns '${collection}', or add it to the ` +\n `schemas this room is constructed with.`,\n }\n }\n\n return { ok: true, schema, isSystem }\n}\n\n/**\n * Handle a new subscription request.\n *\n * We no longer store subscriptions server-side - broadcasts go to all clients\n * and they filter locally. This avoids hibernation issues.\n */\nexport function handleSubscribe(\n ctx: SubscriptionContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: SubscribePayload\n): void {\n const { subscriptionId, query } = payload\n\n // Execute query and send initial results\n const records = executeQuery(ctx, query, attachment.userId, attachment.role)\n ctx.send(ws, { type: MSG.QUERY_RESULT, payload: { subscriptionId, records } })\n}\n\n/**\n * Handle unsubscribe request.\n *\n * Since we no longer store subscriptions server-side, this is a no-op.\n * The client handles unsubscription locally.\n */\nexport function handleUnsubscribe(\n _ctx: SubscriptionContext,\n _ws: WebSocket,\n _attachment: ConnectionAttachment,\n _payload: UnsubscribePayload\n): void {\n // No-op - we don't store subscriptions server-side anymore\n}\n\n/**\n * Execute a query and return matching records.\n * All collections use table-mode (c_* tables).\n *\n * `skipUserRbac` lets a server-action caller (i.e. the app itself, via\n * `X-App-Action`) bypass the per-user read filter for parity with the other\n * `tools.*` operations (`get`, `create`, `update`, `remove`).\n */\n/** The scan budget for one limited, row-filtered query call — the bound that\n * keeps `limit` a bound on work when unreadable rows dominate a table. The\n * scan stops on the first batch boundary at or past this, so the exact worst\n * case is `MAX + batch − 1` rows where `batch = max(limit, 200)` — i.e.\n * O(cap + limit), and a `limit` above the cap is honored as asked. */\nexport const MAX_FILTERED_SCAN_ROWS = 5_000\n\n/** Out-param for {@link executeQuery}: `capped` is set when a limited,\n * row-filtered scan stopped on {@link MAX_FILTERED_SCAN_ROWS} rather than on\n * exhausting the rows — the one case a short result does NOT mean \"no more\n * matches\". */\nexport interface QueryScanState {\n capped: boolean\n}\n\nexport function executeQuery(\n ctx: SubscriptionContext,\n query: Query,\n userId: string,\n userRole: string,\n skipUserRbac: boolean = false,\n scanState?: QueryScanState,\n): RecordResult[] {\n const resolved = resolveCollection(ctx, query.collection)\n\n // Row-returning callers (WS subscribe, the debug query view) contract on an\n // array, so an unknown collection degrades to \"no rows\" here. The surfaces\n // that report success/failure to a caller — the `records.*` tools and the\n // record write/read operations — reject it at `resolveCollection` first, so\n // no tool call reaches this line with a collection the room does not know.\n if (!resolved.ok) return []\n\n if (!resolved.schema) {\n // System collection without schema — query the c_* table directly\n return executeSystemQuery(ctx, query)\n }\n\n return executeTableQuery(\n ctx,\n query,\n resolved.schema,\n userId,\n userRole,\n resolved.isSystem,\n skipUserRbac,\n scanState,\n )\n}\n\n/**\n * Query a system collection (no schema, just system columns).\n */\nfunction executeSystemQuery(\n ctx: SubscriptionContext,\n query: Query,\n): RecordResult[] {\n const tbl = collectionTableName(query.collection)\n let sql = `SELECT _row_id, _created_by, _created_at, _updated_at FROM \"${tbl}\"`\n const params: unknown[] = []\n\n sql += ` ORDER BY _created_at DESC`\n\n if (query.limit) {\n sql += ` LIMIT ?`\n params.push(query.limit)\n }\n\n try {\n const cursor = ctx.sql.exec(sql, ...params)\n return cursor.toArray().map((row: unknown) => {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string }\n return {\n recordId: r._row_id,\n data: {},\n createdBy: r._created_by,\n createdAt: r._created_at,\n updatedAt: r._updated_at,\n }\n })\n } catch {\n return []\n }\n}\n\n/**\n * Preload the current user's team IDs from the c_team_members collection table.\n * Returns null if no team_members collection schema is registered (non-workspace DOs).\n */\nfunction preloadUserTeamIds(ctx: SubscriptionContext, userId: string): string[] | null {\n const tmSchema = ctx.schemaRegistry.get('team_members')\n if (!tmSchema?.columns?.length) return null\n\n const cursor = ctx.sql.exec(\n `SELECT col_teamid FROM c_team_members WHERE col_userid = ? AND (col_status = 'active' OR col_status IS NULL)`,\n userId\n )\n return cursor.toArray().map(r => (r as Record<string, unknown>).col_teamid as string)\n}\n\nfunction executeTableQuery(\n ctx: SubscriptionContext,\n query: Query,\n schema: CollectionSchema,\n userId: string,\n userRole: string,\n isSystem: boolean,\n skipUserRbac: boolean = false,\n scanState?: QueryScanState,\n): RecordResult[] {\n const columns = (schema.columns ?? []).map(resolveColumn)\n const perms = getRolePermissions(schema, userRole)\n\n let sql = buildTableSelect(query.collection, columns)\n const params: unknown[] = []\n const whereClauses: string[] = []\n\n // SQL pushdown for team-scoped reads: filter at the SQL level instead\n // of fetching all rows and checking team membership per-record.\n // Skipped entirely when the caller is an app action bypassing user RBAC.\n let skipPerRecordCheck = skipUserRbac\n if (!skipUserRbac && schema.teamField && perms.read === 'team') {\n // '_rowId' is a sentinel: the record's own ID is the team ID (e.g. teams table)\n // Push down only when the field resolves to a real column — a synthesized\n // id for a misdeclared field would throw `no such column` out of SQL,\n // where the per-row check merely (and correctly) matches nothing.\n const filterCol = schema.teamField === '_rowId'\n ? '_row_id'\n : columns.find(c => c.name === schema.teamField)?.id\n const teamIds = filterCol ? preloadUserTeamIds(ctx, userId) : null\n\n if (filterCol) {\n if (teamIds && teamIds.length > 0) {\n const placeholders = teamIds.map(() => '?').join(', ')\n whereClauses.push(`(\"${filterCol}\" IN (${placeholders}) OR _created_by = ?)`)\n params.push(...teamIds, userId)\n } else {\n whereClauses.push(`_created_by = ?`)\n params.push(userId)\n }\n skipPerRecordCheck = true\n }\n } else if (!skipUserRbac && perms.read === 'own') {\n // Same shape as the team pushdown. 'own' is exactly `isOwner`: the\n // declared ownerField when there is one, else the row's creator — so the\n // SQL predicate is the whole check and the per-row pass can be skipped.\n const ownerCol = schema.ownerField\n ? columns.find(c => c.name === schema.ownerField)?.id\n : '_created_by'\n if (ownerCol) {\n whereClauses.push(`\"${ownerCol}\" = ?`)\n params.push(userId)\n skipPerRecordCheck = true\n }\n } else if (!skipUserRbac && perms.read === true) {\n // Unconditional read: the per-row check cannot refuse anything, and\n // skipping it makes `limit` safe to push into SQL.\n skipPerRecordCheck = true\n }\n\n if (query.where) {\n for (const [fieldName, value] of Object.entries(query.where)) {\n const colId = columnId(fieldName)\n const col = columns.find(c => c.id === colId || c.name === fieldName)\n if (col) {\n whereClauses.push(`\"${col.id}\" = ?`)\n params.push(value)\n } else if (fieldName === 'recordId') {\n whereClauses.push(`_row_id = ?`)\n params.push(value)\n } else if (fieldName === 'createdBy') {\n whereClauses.push(`_created_by = ?`)\n params.push(value)\n }\n }\n }\n\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`\n }\n\n if (query.orderBy) {\n const dir = query.orderDir === 'asc' ? 'ASC' : 'DESC'\n if (query.orderBy === 'createdAt') {\n sql += ` ORDER BY _created_at ${dir}`\n } else if (query.orderBy === 'updatedAt') {\n sql += ` ORDER BY _updated_at ${dir}`\n } else {\n const colId = columnId(query.orderBy)\n const col = columns.find(c => c.id === colId || c.name === query.orderBy)\n if (col) {\n sql += ` ORDER BY \"${col.id}\" ${dir}`\n }\n }\n } else {\n sql += ` ORDER BY _created_at DESC`\n }\n\n const readPage = (limitClause: string, limitParams: number[]): { records: RecordResult[]; scanned: number } => {\n const cursor = ctx.sql.exec(sql + limitClause, ...params, ...limitParams)\n const rows = cursor.toArray()\n const records: RecordResult[] = []\n for (const row of rows) {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string; [key: string]: unknown }\n const record: RecordResult = {\n recordId: r._row_id,\n data: rowToData(r, columns),\n createdBy: r._created_by,\n createdAt: r._created_at,\n updatedAt: r._updated_at,\n }\n if (isSystem || skipPerRecordCheck || canRead(schema, userRole, { data: record.data, createdBy: record.createdBy, recordId: record.recordId }, userId, ctx.getPermissionContext())) {\n records.push(record)\n }\n }\n return { records, scanned: rows.length }\n }\n\n // `limit` counts the records the caller receives. When the SQL predicates\n // are the whole check it pushes straight into SQL; when a per-row read\n // filter still applies, a SQL LIMIT would end the result early (a page of\n // unreadable rows reads as \"no more matches\"), so scan in bounded batches\n // until the limit is met, the rows run out, or the scan cap is hit — the\n // cap keeps `limit` a bound on work, and `scanState.capped` makes hitting\n // it observable so a caller's drain loop cannot read a capped short page\n // as exhaustion.\n if (!query.limit) return readPage('', []).records\n const rowFiltered = !isSystem && !skipPerRecordCheck\n if (!rowFiltered) return readPage(' LIMIT ?', [query.limit]).records\n\n const results: RecordResult[] = []\n const batch = Math.max(query.limit, 200)\n for (let offset = 0; ; offset += batch) {\n const { records, scanned } = readPage(' LIMIT ? OFFSET ?', [batch, offset])\n for (const record of records) {\n results.push(record)\n if (results.length >= query.limit) return results\n }\n if (scanned < batch) return results\n if (offset + batch >= MAX_FILTERED_SCAN_ROWS) {\n // A full final page leaves \"capped\" and \"exhausted exactly on the\n // boundary\" indistinguishable; one single-row probe settles it, so\n // `capped` never fires on a table that simply ended here.\n if (scanState && readPage(' LIMIT ? OFFSET ?', [1, offset + batch]).scanned > 0) {\n scanState.capped = true\n }\n return results\n }\n }\n}\n\n/**\n * Check if a record matches a subscription's query and permissions\n */\nexport function recordMatchesSubscription(\n record: { recordId: string; data: Record<string, unknown>; createdBy: string },\n collection: string,\n query: Query,\n userId: string,\n userRole: string,\n schema: CollectionSchema,\n ctx: PermissionContext\n): boolean {\n // Collection must match\n if (query.collection !== collection) return false\n\n // Permission check (includes collaborator/team)\n if (!canRead(schema, userRole, { data: record.data, createdBy: record.createdBy, recordId: record.recordId }, userId, ctx)) {\n return false\n }\n\n // Filter check (equality only)\n if (query.where) {\n for (const [field, value] of Object.entries(query.where)) {\n if (record.data[field] !== value) return false\n }\n }\n\n return true\n}\n\n/**\n * Broadcast a record change to all connected clients who can read it.\n *\n * Instead of matching subscriptions (which are lost on hibernation),\n * we broadcast to ALL clients and include the collection name.\n * The client filters based on its local subscriptions.\n */\nexport function broadcastChange(\n ctx: SubscriptionContext,\n state: DurableObjectState,\n collection: string,\n record: RecordResult,\n changeType: 'create' | 'update' | 'delete'\n): void {\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n const schema = ctx.schemaRegistry.get(collection)\n\n if (!schema && !isSystem) return // Schema not loaded yet\n\n const webSockets = state.getWebSockets()\n\n for (const ws of webSockets) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n // System collections broadcast to all; others check permissions\n const canBroadcast = isSystem || recordMatchesSubscription(\n record, collection, { collection }, attachment.userId, attachment.role, schema!, ctx.getPermissionContext()\n )\n\n if (canBroadcast) {\n ctx.send(ws, { type: MSG.RECORD_CHANGE, payload: { collection, record, changeType } })\n }\n }\n}\n","/**\n * User management handlers for RecordRoom\n *\n * Users are stored in the c_users table (table-mode).\n * System-managed fields (email, name, role, etc.) can only be set by registerUser().\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { CollectionSchema, SetRolePayload } from '../../shared/types'\nimport { MSG, isAnonymousUserId } from '../../shared/protocol/constants'\nimport {\n canRead,\n getRolePermissions,\n type PermissionContext,\n type User,\n type ResolvedColumn,\n SchemaRegistry,\n resolveColumn,\n buildTableSelect,\n rowToData,\n} from '../schemas/registry'\nimport { putRecord, type RecordContext } from './records'\n\nexport interface UserContext {\n sql: SqlStorage\n state: DurableObjectState\n schemaRegistry: SchemaRegistry\n getPermissionContext(): PermissionContext\n send(ws: WebSocket, message: { type: string; payload: unknown }): void\n}\n\n/**\n * User data stored in the users collection.\n * Core fields are system-managed, apps can add their own fields.\n */\ninterface UserRecord {\n // System-managed fields\n email: string\n name: string\n imageUrl?: string\n role: string\n createdAt: string\n lastSeenAt: string\n // App-specific fields are dynamic\n [key: string]: unknown\n}\n\ninterface TableRow {\n _row_id: string\n _created_by: string\n _created_at: string\n _updated_at: string\n [key: string]: unknown\n}\n\n/**\n * Get resolved columns for the users schema.\n */\nfunction getUsersColumns(schemaRegistry: SchemaRegistry): ResolvedColumn[] {\n const schema = schemaRegistry.get('users')\n return (schema?.columns ?? []).map(resolveColumn)\n}\n\n/**\n * Convert a c_users row to User type (core fields only).\n */\nfunction rowToUser(row: TableRow, columns: ResolvedColumn[]): User {\n const data = rowToData(row, columns)\n return {\n id: row._row_id,\n email: (data.email as string) || '',\n name: (data.name as string) || '',\n imageUrl: data.imageUrl as string | undefined,\n role: (data.role as string) || 'viewer',\n createdAt: (data.createdAt as string) || row._created_at,\n lastSeenAt: (data.lastSeenAt as string) || row._updated_at,\n }\n}\n\n/**\n * Convert a c_users row to full UserRecord (including app-specific fields).\n */\nfunction rowToUserRecord(row: TableRow, columns: ResolvedColumn[]): UserRecord {\n const data = rowToData(row, columns)\n return {\n email: (data.email as string) || '',\n name: (data.name as string) || '',\n imageUrl: data.imageUrl as string | undefined,\n role: (data.role as string) || 'viewer',\n createdAt: (data.createdAt as string) || row._created_at,\n lastSeenAt: (data.lastSeenAt as string) || row._updated_at,\n ...data,\n }\n}\n\n/**\n * Get a single user by ID from the c_users table.\n */\nexport function getUser(sql: SqlStorage, userId: string, schemaRegistry?: SchemaRegistry): User | null {\n // If we have the schema registry, use table-mode columns for proper field mapping\n if (schemaRegistry) {\n const columns = getUsersColumns(schemaRegistry)\n const selectSql = buildTableSelect('users', columns) + ` WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, userId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return rowToUser(row, columns)\n }\n // Fallback: direct column query with known core column names\n const cursor = sql.exec(\n `SELECT * FROM c_users WHERE _row_id = ?`,\n userId\n )\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return {\n id: row._row_id,\n email: (row.col_email as string) || '',\n name: (row.col_name as string) || '',\n imageUrl: row.col_imageurl as string | undefined,\n role: (row.col_role as string) || 'viewer',\n createdAt: (row.col_createdat as string) || row._created_at,\n lastSeenAt: (row.col_lastseenat as string) || row._updated_at,\n }\n}\n\n/**\n * Get full user record including app-specific fields.\n */\nexport function getUserRecord(sql: SqlStorage, userId: string, schemaRegistry?: SchemaRegistry): { data: UserRecord; createdBy: string } | null {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` WHERE _row_id = ?`\n : `SELECT * FROM c_users WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, userId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n\n return {\n data: columns.length > 0 ? rowToUserRecord(row, columns) : {\n email: (row.col_email as string) || '',\n name: (row.col_name as string) || '',\n imageUrl: row.col_imageurl as string | undefined,\n role: (row.col_role as string) || 'viewer',\n createdAt: (row.col_createdat as string) || row._created_at,\n lastSeenAt: (row.col_lastseenat as string) || row._updated_at,\n },\n createdBy: row._created_by,\n }\n}\n\n/**\n * Get all users from the c_users table.\n */\nexport function getAllUsers(sql: SqlStorage, schemaRegistry?: SchemaRegistry): User[] {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM c_users ORDER BY _created_at DESC`\n const cursor = sql.exec(selectSql)\n return cursor.toArray().map((row: unknown) => {\n const r = row as TableRow\n if (columns.length > 0) return rowToUser(r, columns)\n return {\n id: r._row_id,\n email: (r.col_email as string) || '',\n name: (r.col_name as string) || '',\n imageUrl: r.col_imageurl as string | undefined,\n role: (r.col_role as string) || 'viewer',\n createdAt: (r.col_createdat as string) || r._created_at,\n lastSeenAt: (r.col_lastseenat as string) || r._updated_at,\n }\n })\n}\n\n/**\n * Get all user records including app-specific fields.\n */\nexport function getAllUserRecords(sql: SqlStorage, schemaRegistry?: SchemaRegistry): Array<{ recordId: string; data: UserRecord; createdBy: string }> {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM c_users ORDER BY _created_at DESC`\n const cursor = sql.exec(selectSql)\n return cursor.toArray().map((row: unknown) => {\n const r = row as TableRow\n return {\n recordId: r._row_id,\n createdBy: r._created_by,\n data: columns.length > 0 ? rowToUserRecord(r, columns) : {\n email: (r.col_email as string) || '',\n name: (r.col_name as string) || '',\n imageUrl: r.col_imageurl as string | undefined,\n role: (r.col_role as string) || 'viewer',\n createdAt: (r.col_createdat as string) || r._created_at,\n lastSeenAt: (r.col_lastseenat as string) || r._updated_at,\n },\n }\n })\n}\n\n/**\n * Register or update a user in the c_users table.\n *\n * This is the ONLY way to set system-managed fields (email, name, role, etc.).\n * Normal mutations via handlePut will reject changes to system-managed fields.\n *\n * Role derivation (in order of priority):\n * 1. isAdmin=true (global admin, canvas owner, or app owner) → always 'admin'\n * 2. Existing role in users collection (preserved)\n * 3. Default role (configurable per-app, defaults to 'member')\n *\n * This allows each miniapp to define its own role hierarchy while\n * ensuring admins and owners always have full access.\n */\nexport async function registerUser(\n sql: SqlStorage,\n userId: string,\n name: string,\n email: string,\n imageUrl: string | undefined,\n isAdmin: boolean,\n defaultRole: string = 'viewer',\n schemaRegistry?: SchemaRegistry\n): Promise<User> {\n const now = new Date().toISOString()\n\n // Get existing user record\n const existing = getUserRecord(sql, userId, schemaRegistry)\n\n // Derive role:\n // 1. DeepSpace global admins are ALWAYS admin\n // 2. Otherwise, preserve existing role\n // 3. Fall back to default role\n let role: string\n if (isAdmin) {\n role = 'admin'\n } else if (existing?.data.role) {\n role = existing.data.role\n } else {\n role = defaultRole\n }\n\n if (existing) {\n // Update existing user - only update system-managed columns.\n // When connecting with token-only (no profile), name='Anonymous' and email=''.\n // Don't overwrite real values with defaults.\n const updatedEmail = email || existing.data.email\n const updatedName = (name && name !== 'Anonymous') ? name : existing.data.name\n const updatedImageUrl = imageUrl ?? existing.data.imageUrl\n\n sql.exec(\n `UPDATE c_users SET col_email = ?, col_name = ?, col_imageurl = ?, col_role = ?, col_lastseenat = ?, _updated_at = ? WHERE _row_id = ?`,\n updatedEmail, updatedName, updatedImageUrl ?? null, role, now, now, userId\n )\n\n return {\n id: userId,\n email: updatedEmail,\n name: updatedName,\n imageUrl: updatedImageUrl,\n role,\n createdAt: existing.data.createdAt,\n lastSeenAt: now\n }\n }\n\n // Create new user record\n sql.exec(\n `INSERT INTO c_users (_row_id, _created_by, _created_at, _updated_at, col_email, col_name, col_imageurl, col_role, col_createdat, col_lastseenat) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n userId, userId, now, now, email, name, imageUrl ?? null, role, now, now\n )\n\n return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now }\n}\n\ntype UserRow = { recordId: string; data: UserRecord; createdBy: string }\ntype PublicIdentity = { id: string; name: string; imageUrl?: string; role: string }\n\n/**\n * What one socket may see of the roster. Anonymous sockets and roles whose\n * users policy is switched off (`read: false`, or no `read` at all — the same\n * \"deny\" `canRead` uses) get nothing: that is a policy statement, not a row\n * filter. Admins get whole rows — email and any app-defined column — so their\n * read still goes through the schema's row policy. Everyone else gets the\n * public-identity projection of the whole roster: a row-level policy (`'own'`,\n * `'team'`, a predicate) guards full-row reads on the records/query path, and\n * nothing it protects survives this projection — applying it here left every\n * member of a `read: 'own'` room (every `chat:*` room, since messaging ships\n * no users schema) with a roster of just themselves and peers rendered as\n * \"Unknown\".\n */\nfunction rosterFor(\n ctx: UserContext,\n schema: CollectionSchema,\n records: UserRow[],\n publicRoster: PublicIdentity[],\n attachment: ConnectionAttachment,\n): Array<Record<string, unknown>> {\n if (isAnonymousUserId(attachment.userId)) return []\n if (attachment.role === 'admin') {\n const permissionContext = ctx.getPermissionContext()\n return records\n .filter((record) => canRead(schema, 'admin', record, attachment.userId, permissionContext))\n .map((record) => ({ id: record.recordId, ...record.data }))\n }\n if (!getRolePermissions(schema, attachment.role).read) return []\n // `roster: 'read-policy'` opts a tenant/team-scoped app out of the shared\n // roster: only the rows the caller's read policy grants, still projected.\n if (schema.roster === 'read-policy') {\n const permissionContext = ctx.getPermissionContext()\n const readable = new Set(\n records\n .filter((record) => canRead(schema, attachment.role, record, attachment.userId, permissionContext))\n .map((record) => record.recordId),\n )\n return publicRoster.filter((user) => readable.has(user.id))\n }\n return publicRoster\n}\n\nfunction publicIdentities(records: UserRow[]): PublicIdentity[] {\n return records.map(({ recordId, data }) => ({\n id: recordId,\n name: data.name,\n ...(data.imageUrl ? { imageUrl: data.imageUrl } : {}),\n role: data.role,\n }))\n}\n\n/** Handle user list requests without bypassing the users collection's privacy boundary. */\nexport function handleUserList(\n ctx: UserContext,\n ws: WebSocket,\n attachment: ConnectionAttachment\n): void {\n // Public rooms support anonymous sockets, but that must not turn the users\n // table into a public directory; a room with no users schema has none.\n const schema = ctx.schemaRegistry.get('users')\n if (!schema) {\n ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } })\n return\n }\n const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry)\n const users = rosterFor(ctx, schema, records, publicIdentities(records), attachment)\n ctx.send(ws, { type: MSG.USER_LIST, payload: { users } })\n}\n\n/**\n * Push the current roster to every connected socket (except `except`). The\n * client asks for `user.list` once per connection and never again, so anything\n * that changes what the roster shows — a user registering, a rename, a new\n * avatar, a role change — must push, or every open tab keeps rendering the\n * peer it has never seen as \"Unknown\" until it reconnects. The roster is read\n * and projected once; each socket then gets its own view of it.\n */\nexport function broadcastUserList(\n ctx: UserContext,\n except?: WebSocket | ReadonlySet<WebSocket>,\n): void {\n const schema = ctx.schemaRegistry.get('users')\n if (!schema) return\n const skip = except instanceof Set ? except : new Set(except ? [except] : [])\n const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry)\n const publicRoster = publicIdentities(records)\n for (const other of ctx.state.getWebSockets()) {\n if (skip.has(other)) continue\n const attachment = other.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n ctx.send(other, {\n type: MSG.USER_LIST,\n payload: { users: rosterFor(ctx, schema, records, publicRoster, attachment) },\n })\n }\n}\n\n/**\n * Handle user profile update.\n *\n * Called when the client's profile loads after the initial WS connection.\n * Updates the user's name/email/imageUrl in c_users and pushes the updated\n * roster to every connected client so names refresh in real time.\n */\nexport interface UserUpdatePayload {\n name?: string\n email?: string\n imageUrl?: string\n}\n\nexport function handleUserUpdate(\n ctx: RecordContext,\n _ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: UserUpdatePayload\n): void {\n const existing = getUserRecord(ctx.sql, attachment.userId, ctx.schemaRegistry)\n if (!existing) return\n\n const data: Record<string, unknown> = { lastSeenAt: new Date().toISOString() }\n if (payload.name && payload.name !== 'Anonymous') data.name = payload.name\n if (payload.email) data.email = payload.email\n if (payload.imageUrl !== undefined) data.imageUrl = payload.imageUrl || undefined\n\n // systemUpdate=true bypasses system-managed field stripping so we can\n // write to name/email/imageUrl. broadcastChange fires automatically,\n // updating any useQuery('users') subscriptions on connected clients; the\n // `user.list` roster (`useUsers()`) is a separate request/response and\n // needs the explicit push.\n putRecord(ctx, 'users', attachment.userId, data, attachment.userId, 'admin', true, true)\n const rosterChanged =\n (data.name !== undefined && data.name !== existing.data.name) ||\n (data.imageUrl !== undefined && data.imageUrl !== existing.data.imageUrl)\n if (rosterChanged) broadcastUserList(ctx)\n}\n\n/**\n * Handle set role request (admin only).\n * Updates the role field in the c_users table.\n */\nexport async function handleSetRole(\n ctx: UserContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: SetRolePayload\n): Promise<void> {\n if (attachment.role !== 'admin') {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'Admin access required' } })\n return\n }\n\n const existing = getUserRecord(ctx.sql, payload.userId, ctx.schemaRegistry)\n if (!existing) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'User not found' } })\n return\n }\n\n // Update role in the c_users table\n const now = new Date().toISOString()\n ctx.sql.exec(\n `UPDATE c_users SET col_role = ?, _updated_at = ? WHERE _row_id = ?`,\n payload.role, now, payload.userId\n )\n\n // Close the changed user's live sockets. Reconnect runs the ordinary role\n // lookup again, so no connection keeps permissions from before the change.\n const closing = new Set<WebSocket>()\n for (const otherWs of ctx.state.getWebSockets()) {\n const otherAttachment = otherWs.deserializeAttachment() as ConnectionAttachment | null\n if (otherAttachment?.userId !== payload.userId) continue\n closing.add(otherWs)\n try {\n otherWs.close(1008, 'role-changed')\n } catch {\n // Already closing.\n }\n }\n\n // Then push the roster to everyone else — one read, each socket its own\n // projection — so their tabs see the new role instead of holding the stale\n // one until reconnect. The closed sockets are skipped by identity rather\n // than trusted to drop the frame.\n broadcastUserList(ctx, closing)\n}\n","/**\n * Tools API HTTP handlers for RecordRoom\n *\n * Provides an HTTP interface for agent tool calls (records, schemas, users).\n *\n * Caller identity is supplied via HTTP headers:\n *\n * X-User-Id: <userId> — identifies the caller (required for any\n * tool that touches user-bound data).\n * X-App-Action: 'true' — bypass user RBAC because the app's\n * server-side code is already the trust\n * boundary. Used by server actions and\n * cron jobs; unsafe to pass from clients.\n *\n * The userId is looked up in the users collection to derive the caller's\n * role. All operations go through the same RBAC checks as the WebSocket\n * path unless flagged as an app action.\n */\n\nimport * as Y from 'yjs'\nimport type { ToolResult } from '../utils/tools'\nimport { BUILT_IN_TOOLS } from '../utils/tools'\nimport type { YjsDocKey } from '../../shared/types'\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\nimport { executeQuery, resolveCollection, type SubscriptionContext } from './subscriptions'\nimport {\n getRecord,\n putRecord,\n deleteRecord,\n deleteWhere,\n refuseUnknownWhere,\n readRecord,\n type RecordContext,\n} from './records'\nimport { getUser, getAllUsers, registerUser } from './users'\nimport {\n getOrCreateYjsDoc,\n unloadYjsDocIfUnused,\n broadcastYjsUpdate,\n getYjsDocKey,\n SYSTEM_COLLECTIONS,\n type YjsContext,\n} from './yjs'\nimport { canRead, canUpdate } from '../schemas/registry'\n\nexport interface ToolsApiContext extends SubscriptionContext {\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n sendBinary: (ws: WebSocket, data: Uint8Array) => void\n ownerUserId?: string\n}\n\nconst CORS_HEADERS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, X-User-Id, X-App-Action',\n 'Content-Type': 'application/json',\n}\n\n/**\n * Handle /tools/ API requests.\n * Called from handleApiRequest when path starts with 'tools/'.\n */\nexport async function handleToolsRequest(\n ctx: ToolsApiContext,\n request: Request,\n path: string,\n): Promise<Response> {\n if (request.method === 'OPTIONS') {\n return new Response(null, { headers: CORS_HEADERS })\n }\n\n // GET /tools/list — return all tool schemas\n if (path === 'tools/list') {\n return Response.json({ tools: BUILT_IN_TOOLS }, { headers: CORS_HEADERS })\n }\n\n // GET /tools/describe/:toolName — return single tool schema\n if (path.startsWith('tools/describe/')) {\n const toolName = path.replace('tools/describe/', '')\n const tool = BUILT_IN_TOOLS.find((t) => t.name === toolName)\n if (!tool) {\n return Response.json(\n { error: `Tool not found: ${toolName}` },\n { status: 404, headers: CORS_HEADERS },\n )\n }\n return Response.json({ tool }, { headers: CORS_HEADERS })\n }\n\n // POST /tools/execute — execute a tool\n // Path may include a trailing roomId (e.g., tools/execute/{roomId}) from the call script,\n // but the DO doesn't need it since the outer worker already routed to the correct DO.\n if (path.startsWith('tools/execute') && request.method === 'POST') {\n return handleToolExecute(ctx, request)\n }\n\n return Response.json({ error: 'Not found' }, { status: 404, headers: CORS_HEADERS })\n}\n\n/**\n * POST /tools/execute\n *\n * Body: { tool: string, params: Record<string, unknown> }\n * Headers: X-User-Id (required for identity-bound tools)\n * X-App-Action: 'true' (optional, bypasses user RBAC)\n */\nasync function handleToolExecute(ctx: ToolsApiContext, request: Request): Promise<Response> {\n let body: { tool: string; params: Record<string, unknown> }\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json(\n { success: false, error: 'Invalid JSON body' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n const { tool, params = {} } = body\n\n if (!tool) {\n return Response.json(\n { success: false, error: 'Missing \"tool\" in request body' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Caller identity is an HTTP header. Missing header ⇒ anonymous viewer.\n const userId = request.headers.get('x-user-id') ?? ''\n const isAppAction = request.headers.get('x-app-action')?.toLowerCase() === 'true'\n\n // Look up user role for RBAC. If the caller is the deploy-time owner, grant\n // admin — mirrors the WebSocket path which registers the owner explicitly.\n // The HTTP tools API doesn't go through WebSocket, so the owner may not\n // exist in the users table yet.\n let userRole = 'viewer'\n if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {\n userRole = 'admin'\n } else if (userId) {\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (user) {\n userRole = user.role\n }\n }\n\n // Yjs tools have a dedicated dispatcher because they need Yjs-specific context.\n if (tool.startsWith('yjs.')) {\n const result = executeYjsTool(ctx, tool, params, userId, userRole)\n const status = result.success ? 200 : result.error?.includes('not found') ? 404 : 400\n return Response.json(result, { status, headers: CORS_HEADERS })\n }\n\n const recordCtx: RecordContext = {\n sql: ctx.sql,\n schemaRegistry: ctx.schemaRegistry,\n scopeId: ctx.scopeId,\n state: ctx.state,\n getPermissionContext: ctx.getPermissionContext,\n send: ctx.send,\n }\n\n const result = await executeTool(recordCtx, tool, params, userId, userRole, isAppAction)\n const status = result.success ? 200 : result.error?.includes('not found') ? 404 : 400\n return Response.json(result, { status, headers: CORS_HEADERS })\n}\n\n/**\n * Dispatch a tool call to the appropriate handler.\n */\nasync function executeTool(\n ctx: RecordContext,\n toolName: string,\n params: Record<string, unknown>,\n userId: string,\n userRole: string,\n skipUserRbac: boolean,\n): Promise<ToolResult> {\n switch (toolName) {\n // ---- Records ----\n case 'records.query': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n // An unknown collection is a wrong-room mistake, not an empty table. The\n // other `records.*` tools inherit this from putRecord/readRecord/\n // deleteRecord; the query path is row-returning, so it asks directly.\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n // A `where` key that names no field is refused, not dropped: dropped, it\n // returned the whole readable collection as if filtered (the same class\n // `records.deleteWhere` refuses). Schemaless system collections have no\n // filterable fields, so a `where` on them is refused the same way.\n const where = params.where as Record<string, unknown> | undefined\n if (where && typeof where === 'object' && Object.keys(where).length > 0) {\n if (!resolved.schema) {\n return {\n success: false,\n error: `records.query: collection \"${collection}\" has no schema, so it has no filterable fields — query it without \\`where\\``,\n }\n }\n const refusal = refuseUnknownWhere(collection, resolved.schema, where)\n if (refusal) return refusal\n }\n // No default `limit` here: this dispatch is the SDK's general record-read\n // path (chat history, cron, app `actions.query`), which must return every\n // row. The assistant's page-size default is applied upstream in the AI\n // tool layer (`applyAiToolDefaults` in `buildTools`).\n const query = {\n collection,\n where,\n orderBy: params.orderBy as string | undefined,\n orderDir: params.orderDir as 'asc' | 'desc' | undefined,\n limit: params.limit as number | undefined,\n }\n const records = executeQuery(ctx, query, userId, userRole, skipUserRbac)\n return { success: true, data: { records, count: records.length } }\n }\n\n case 'records.get': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n return readRecord(ctx, collection, recordId, userId, userRole, skipUserRbac)\n }\n\n case 'records.create': {\n const collection = params.collection as string\n const data = params.data as Record<string, unknown>\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!data) return { success: false, error: 'Missing required param: data' }\n const recordId = (params.recordId as string) || generateId()\n return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac)\n }\n\n case 'records.update': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n const data = params.data as Record<string, unknown>\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n if (!data) return { success: false, error: 'Missing required param: data' }\n return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac)\n }\n\n case 'records.delete': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n return deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac)\n }\n\n // Bounded batch delete — one subrequest per page instead of one per row.\n // Same authorization posture as `records.delete`; `deleteWhere` itself\n // validates `where` (every key must name a real field) and `limit`, so no\n // dispatch path can reach a collection-wide truncation.\n case 'records.deleteWhere': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n return deleteWhere(ctx, collection, params.where, params.limit, userId, userRole, skipUserRbac)\n }\n\n // ---- Users ----\n // Bootstrap or refresh the `users` row for a given userId. Same path\n // the SDK uses on WS connect via registerUser, exposed for server\n // contexts (CLI-only callers, action handlers) where there's no WS\n // connection to trigger the natural registration. System-managed\n // columns (email, name, imageUrl, role) are written through\n // registerUser's privileged path, bypassing the SYSTEM_MANAGED\n // stripping that blocks naive tools.create / tools.update.\n case 'users.register': {\n const targetUserId = (params.userId as string) || userId\n if (!targetUserId) return { success: false, error: 'Missing userId' }\n const name = typeof params.name === 'string' ? params.name : ''\n const email = typeof params.email === 'string' ? params.email : ''\n const imageUrl = typeof params.imageUrl === 'string' ? params.imageUrl : undefined\n const isAdmin = params.isAdmin === true\n const user = await registerUser(\n ctx.sql,\n targetUserId,\n name,\n email,\n imageUrl,\n isAdmin,\n 'member',\n ctx.schemaRegistry,\n )\n return { success: true, data: { user } }\n }\n\n // ---- Schema ----\n case 'schema.list': {\n const schemas = ctx.schemaRegistry.all()\n return {\n success: true,\n data: {\n schemas: schemas.map((s) => ({\n name: s.name,\n columns: s.columns,\n permissions: s.permissions,\n ...(s.ownerField ? { ownerField: s.ownerField } : {}),\n ...(s.collaboratorsField ? { collaboratorsField: s.collaboratorsField } : {}),\n ...(s.teamField ? { teamField: s.teamField } : {}),\n })),\n },\n }\n }\n\n case 'schema.describe': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n const schema = ctx.schemaRegistry.get(collection)\n if (!schema) return { success: false, error: `Schema not found: ${collection}` }\n return { success: true, data: { schema } }\n }\n\n // ---- Users ----\n case 'user.current': {\n if (!userId) return { success: false, error: 'No userId provided' }\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (!user) return { success: false, error: 'User not found' }\n return { success: true, data: { user } }\n }\n\n case 'user.list': {\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n return { success: true, data: { users } }\n }\n\n default:\n return { success: false, error: `Unknown tool: ${toolName}` }\n }\n}\n\n// ============================================================================\n// Yjs Tool Execution\n// ============================================================================\n\n/**\n * Check RBAC permission for Yjs doc access.\n * System collections are permissive (same as handleYjsJoin).\n * Returns null if permitted, or an error ToolResult if denied.\n */\nfunction checkYjsPermission(\n ctx: ToolsApiContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n action: 'read' | 'write',\n): ToolResult | null {\n if (SYSTEM_COLLECTIONS.has(collection)) return null\n\n const resolution = resolveCollection(ctx, collection)\n if (!resolution.ok) return { success: false, error: resolution.error }\n const schema = resolution.schema\n if (!schema)\n return { success: false, error: `Schema not registered for collection: ${collection}` }\n\n const record = getRecord(ctx.sql, collection, recordId, schema)\n if (!record) return { success: false, error: `${RECORD_NOT_FOUND}: ${collection}/${recordId}` }\n\n const recordWithId = { ...record, recordId }\n const permCtx = ctx.getPermissionContext()\n if (action === 'read' && !canRead(schema, userRole, recordWithId, userId, permCtx)) {\n return { success: false, error: 'Permission denied: cannot read this record' }\n }\n if (action === 'write' && !canUpdate(schema, userRole, recordWithId, userId, permCtx)) {\n return { success: false, error: 'Permission denied: cannot update this record' }\n }\n\n return null\n}\n\n/**\n * Build a YjsContext from ToolsApiContext for calling Yjs helpers.\n */\nfunction buildYjsCtx(ctx: ToolsApiContext): YjsContext {\n return {\n sql: ctx.sql,\n state: ctx.state,\n yjsDocs: ctx.yjsDocs,\n schemaRegistry: ctx.schemaRegistry,\n getPermissionContext: ctx.getPermissionContext,\n send: ctx.send,\n sendBinary: ctx.sendBinary,\n }\n}\n\n/** Use a cached document for one synchronous operation, then release it if idle. */\nfunction withYjsDoc<T>(ctx: YjsContext, docKey: YjsDocKey, operation: (doc: Y.Doc) => T): T {\n const doc = getOrCreateYjsDoc(ctx, docKey)\n try {\n return operation(doc)\n } finally {\n unloadYjsDocIfUnused(ctx, docKey)\n }\n}\n\n/** Execute a Yjs tool (yjs.list, yjs.getText, yjs.setText). */\nfunction executeYjsTool(\n ctx: ToolsApiContext,\n toolName: string,\n params: Record<string, unknown>,\n userId: string,\n userRole: string,\n): ToolResult {\n switch (toolName) {\n case 'yjs.list': {\n const rows = ctx.sql\n .exec(`SELECT doc_key, updated_at FROM yjs_docs ORDER BY updated_at DESC`)\n .toArray() as Array<{ doc_key: string; updated_at: string }>\n\n const docs = rows.map((row) => {\n const firstColon = row.doc_key.indexOf(':')\n const lastColon = row.doc_key.lastIndexOf(':')\n return {\n docKey: row.doc_key,\n collection: row.doc_key.slice(0, firstColon),\n recordId: row.doc_key.slice(firstColon + 1, lastColon),\n fieldName: row.doc_key.slice(lastColon + 1),\n updatedAt: row.updated_at,\n }\n })\n return { success: true, data: { docs, count: docs.length } }\n }\n\n case 'yjs.getText': {\n const { collection, recordId, fieldName } = params as {\n collection: string\n recordId: string\n fieldName: string\n }\n if (!collection || !recordId || !fieldName) {\n return { success: false, error: 'Missing required params: collection, recordId, fieldName' }\n }\n\n const permError = checkYjsPermission(ctx, collection, recordId, userId, userRole, 'read')\n if (permError) return permError\n\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const yjsCtx = buildYjsCtx(ctx)\n return withYjsDoc(yjsCtx, docKey, (doc) => {\n const ytext = doc.getText(fieldName)\n const text = ytext.toString()\n\n const sharedTypes: Record<string, string> = {}\n for (const [key, type] of doc.share) {\n if (type instanceof Y.Text) {\n sharedTypes[key || '(default)'] = `Y.Text (${type.length} chars)`\n } else if (type instanceof Y.Map) {\n sharedTypes[key] = `Y.Map (${(type as Y.Map<unknown>).size} entries)`\n } else if (type instanceof Y.Array) {\n sharedTypes[key] = `Y.Array (${(type as Y.Array<unknown>).length} items)`\n }\n }\n\n return { success: true, data: { docKey, text, sharedTypes } }\n })\n }\n\n case 'yjs.setText': {\n const { collection, recordId, fieldName, text } = params as {\n collection: string\n recordId: string\n fieldName: string\n text: string\n }\n if (!collection || !recordId || !fieldName) {\n return { success: false, error: 'Missing required params: collection, recordId, fieldName' }\n }\n if (text === undefined || text === null) {\n return { success: false, error: 'Missing required param: text' }\n }\n\n const permError = checkYjsPermission(ctx, collection, recordId, userId, userRole, 'write')\n if (permError) return permError\n\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const yjsCtx = buildYjsCtx(ctx)\n return withYjsDoc(yjsCtx, docKey, (doc) => {\n const ytext = doc.getText(fieldName)\n let capturedUpdate: Uint8Array | null = null\n const updateHandler = (update: Uint8Array) => {\n capturedUpdate = update\n }\n doc.on('update', updateHandler)\n\n try {\n doc.transact(() => {\n ytext.delete(0, ytext.length)\n ytext.insert(0, text)\n })\n } finally {\n doc.off('update', updateHandler)\n }\n\n if (capturedUpdate) {\n broadcastYjsUpdate(yjsCtx, docKey, capturedUpdate, null)\n }\n\n return { success: true, data: { docKey, length: text.length } }\n })\n }\n\n default:\n return { success: false, error: `Unknown Yjs tool: ${toolName}` }\n }\n}\n\nfunction generateId(): string {\n return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\n}\n","/**\n * HTTP Debug API handlers for RecordRoom\n */\n\nimport * as Y from 'yjs'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { Query, YjsDocKey } from '../../shared/types'\nimport {\n resolveColumn,\n buildTableSelect,\n rowToData,\n collectionTableName,\n} from '../schemas/registry'\nimport { executeQuery, type SubscriptionContext } from './subscriptions'\nimport { getAllUsers, getUser } from './users'\nimport { handleToolsRequest } from './tools-api'\n\nexport interface DebugApiContext extends SubscriptionContext {\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n sendBinary: (ws: WebSocket, data: Uint8Array) => void\n}\n\nconst CORS_HEADERS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n 'Content-Type': 'application/json',\n}\n\n/**\n * Handle HTTP API requests (for debugging)\n */\nexport async function handleApiRequest(\n ctx: DebugApiContext,\n request: Request,\n url: URL\n): Promise<Response> {\n if (request.method === 'OPTIONS') {\n return new Response(null, { headers: CORS_HEADERS })\n }\n\n const path = url.pathname.replace('/api/', '')\n\n // Tools API endpoints\n if (path.startsWith('tools/')) {\n return handleToolsRequest(ctx, request, path)\n }\n\n // Debug status endpoint\n if (path === 'debug/status') {\n return handleDebugStatus(ctx)\n }\n\n // Debug SQL - run arbitrary SQL queries\n if (path === 'debug/sql') {\n return handleDebugSql(ctx, request, url)\n }\n\n // Debug user-role by email endpoint\n if (path === 'debug/user-role') {\n return handleDebugUserRole(ctx, url)\n }\n\n // Debug set-role endpoint — POST { email, role } or { userId, role }\n if (path === 'debug/set-role' && request.method === 'POST') {\n return handleDebugSetRole(ctx, request)\n }\n\n // Debug records endpoint\n if (path.startsWith('debug/records/')) {\n return handleDebugRecords(ctx, path)\n }\n\n // Debug query endpoint\n if (path === 'debug/query') {\n return handleDebugQuery(ctx, url)\n }\n\n return Response.json({ error: 'Not found' }, { status: 404, headers: CORS_HEADERS })\n}\n\n/**\n * GET /api/debug/user-role - Get user role info by email or userId\n *\n * Usage:\n * /api/debug/user-role?email=ctc@gmail.com\n * /api/debug/user-role?userId=user_39JIhFoZkRsPtj5u3dprEOYKgVR\n */\nfunction handleDebugUserRole(ctx: DebugApiContext, url: URL): Response {\n const email = url.searchParams.get('email')\n const userId = url.searchParams.get('userId')\n\n if (!email && !userId) {\n return Response.json({ error: 'email or userId parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n\n const user = userId\n ? users.find(u => u.id === userId)\n : users.find(u => u.email === email)\n\n if (!user) {\n return Response.json({\n error: `User not found`,\n registeredUsers: users.map(u => ({ id: u.id, email: u.email, role: u.role })),\n }, { status: 404, headers: CORS_HEADERS })\n }\n\n return Response.json({\n userId: user.id,\n email: user.email,\n name: user.name,\n role: user.role,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * POST /api/debug/set-role - Change a user's role\n *\n * Body: { email: string, role: string } or { userId: string, role: string }\n *\n * Usage:\n * POST /api/debug/set-role { \"email\": \"ctc@gmail.com\", \"role\": \"viewer\" }\n * POST /api/debug/set-role { \"userId\": \"user_39JIh...\", \"role\": \"admin\" }\n */\nasync function handleDebugSetRole(ctx: DebugApiContext, request: Request): Promise<Response> {\n const body = await request.json() as { email?: string; userId?: string; role?: string }\n\n if (!body.role) {\n return Response.json({ error: 'role is required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!body.email && !body.userId) {\n return Response.json({ error: 'email or userId is required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n const user = body.userId\n ? users.find(u => u.id === body.userId)\n : users.find(u => u.email === body.email)\n\n if (!user) {\n return Response.json({\n error: 'User not found',\n registeredUsers: users.map(u => ({ id: u.id, email: u.email, role: u.role })),\n }, { status: 404, headers: CORS_HEADERS })\n }\n\n // Update role in c_users table\n const now = new Date().toISOString()\n ctx.sql.exec(\n `UPDATE c_users SET col_role = ?, _updated_at = ? WHERE _row_id = ?`,\n body.role, now, user.id\n )\n\n return Response.json({\n userId: user.id,\n email: user.email,\n previousRole: user.role,\n newRole: body.role,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * GET /api/debug/status - Get server status\n */\nfunction handleDebugStatus(ctx: DebugApiContext): Response {\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n const schemas = ctx.schemaRegistry.all()\n const connections: Array<{ userId: string; role: string; subscriptions: number }> = []\n \n for (const ws of ctx.state.getWebSockets()) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment) {\n connections.push({\n userId: attachment.userId,\n role: attachment.role,\n subscriptions: attachment.subscriptions.length,\n })\n }\n }\n\n return Response.json(\n { users, schemas: schemas.map(s => s.name), connections },\n { headers: CORS_HEADERS }\n )\n}\n\n/**\n * GET /api/debug/records/:collection - Get records in a collection\n */\nfunction handleDebugRecords(ctx: DebugApiContext, path: string): Response {\n const collection = path.replace('debug/records/', '')\n const schema = ctx.schemaRegistry.get(collection)\n const columns = (schema?.columns ?? []).map(resolveColumn)\n\n try {\n const selectSql = columns.length > 0\n ? buildTableSelect(collection, columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM \"${collectionTableName(collection)}\" ORDER BY _created_at DESC`\n\n const cursor = ctx.sql.exec(selectSql)\n const records = cursor.toArray().map((row: unknown) => {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string; [key: string]: unknown }\n return {\n recordId: r._row_id,\n data: columns.length > 0 ? rowToData(r, columns) : {},\n createdBy: r._created_by,\n createdAt: r._created_at,\n }\n })\n return Response.json({ collection, records }, { headers: CORS_HEADERS })\n } catch (error) {\n return Response.json({\n error: error instanceof Error ? error.message : 'Query failed',\n collection,\n }, { status: 400, headers: CORS_HEADERS })\n }\n}\n\n/**\n * GET /api/debug/query - Test query as a specific user\n */\nfunction handleDebugQuery(ctx: DebugApiContext, url: URL): Response {\n const collection = url.searchParams.get('collection')\n const userId = url.searchParams.get('userId')\n const role = url.searchParams.get('role')\n\n if (!collection) {\n return Response.json({ error: 'collection parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!userId) {\n return Response.json({ error: 'userId parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!role) {\n return Response.json({ error: 'role parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n // Get user info\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (!user) {\n return Response.json({ error: `User not found: ${userId}` }, { status: 404, headers: CORS_HEADERS })\n }\n\n // Build query from URL params\n const query: Query = { collection }\n \n // Parse where filters from URL (e.g., where.userId=xxx)\n const where: Record<string, unknown> = {}\n for (const [key, value] of url.searchParams) {\n if (key.startsWith('where.')) {\n const field = key.replace('where.', '')\n where[field] = value\n }\n }\n if (Object.keys(where).length > 0) {\n query.where = where\n }\n\n // Execute with specified role (override user's actual role for testing)\n const records = executeQuery(ctx, query, userId, role)\n\n return Response.json({\n query,\n asUser: { id: userId, name: user.name, actualRole: user.role, testingAsRole: role },\n recordCount: records.length,\n records,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * GET/POST /api/debug/sql - Run arbitrary SQL queries\n * \n * GET: /api/debug/sql?q=SELECT * FROM users\n * POST: { \"sql\": \"SELECT * FROM users\", \"params\": [] }\n * \n * Special queries:\n * - \"SHOW TABLES\" or \".tables\" - list all tables\n * - \"DESCRIBE <table>\" or \".schema <table>\" - show table schema\n */\nasync function handleDebugSql(\n ctx: DebugApiContext,\n request: Request,\n url: URL\n): Promise<Response> {\n let sql: string\n let params: unknown[] = []\n\n if (request.method === 'POST') {\n const body = await request.json() as { sql: string; params?: unknown[] }\n sql = body.sql\n params = body.params ?? []\n } else {\n sql = url.searchParams.get('q') ?? ''\n }\n\n if (!sql.trim()) {\n return Response.json({ \n error: 'SQL query required',\n usage: {\n get: '/api/debug/sql?q=SELECT * FROM users',\n post: '{ \"sql\": \"SELECT * FROM users\", \"params\": [] }',\n special: [\n 'SHOW TABLES - list all tables',\n 'DESCRIBE <table> - show table schema',\n ],\n },\n }, { status: 400, headers: CORS_HEADERS })\n }\n\n const sqlUpper = sql.trim().toUpperCase()\n\n try {\n // Handle special commands\n if (sqlUpper === 'SHOW TABLES' || sqlUpper === '.TABLES') {\n const cursor = ctx.sql.exec(\n `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`\n )\n const tables = cursor.toArray().map((r: unknown) => (r as { name: string }).name)\n return Response.json({ tables }, { headers: CORS_HEADERS })\n }\n\n if (sqlUpper.startsWith('DESCRIBE ') || sqlUpper.startsWith('.SCHEMA ')) {\n const tableName = sql.trim().split(/\\s+/)[1]\n const cursor = ctx.sql.exec(`PRAGMA table_info(${tableName})`)\n const columns = cursor.toArray().map((r: unknown) => {\n const col = r as { cid: number; name: string; type: string; notnull: number; dflt_value: string | null; pk: number }\n return {\n name: col.name,\n type: col.type,\n notNull: col.notnull === 1,\n defaultValue: col.dflt_value,\n primaryKey: col.pk === 1,\n }\n })\n return Response.json({ table: tableName, columns }, { headers: CORS_HEADERS })\n }\n\n // Execute the query\n const cursor = ctx.sql.exec(sql, ...params)\n const rows = cursor.toArray()\n\n return Response.json({ \n sql,\n rowCount: rows.length,\n rows,\n }, { headers: CORS_HEADERS })\n } catch (error) {\n return Response.json({ \n error: error instanceof Error ? error.message : 'Query failed',\n sql,\n }, { status: 400, headers: CORS_HEADERS })\n }\n}\n","/**\n * RecordRoom Durable Object\n *\n * SQLite-based storage with query-based real-time subscriptions.\n * Extends BaseRoom for WebSocket/connection infrastructure.\n *\n * Architecture:\n * - Data stored in SQLite (single `records` table)\n * - Clients subscribe to QUERIES, not collections\n * - On record change, server evaluates which subscriptions match\n * - Only matching subscribers receive updates\n *\n * Protocol:\n * - SUBSCRIBE { subscriptionId, query } → QUERY_RESULT { subscriptionId, records }\n * - UNSUBSCRIBE { subscriptionId }\n * - PUT { collection, recordId, data } → broadcasts RECORD_CHANGE to matching\n * - DELETE { collection, recordId } → broadcasts RECORD_CHANGE to matching\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type {\n YjsDocKey,\n SubscribePayload,\n UnsubscribePayload,\n PutPayload,\n DeletePayload,\n SetRolePayload,\n YjsJoinPayload,\n YjsLeavePayload,\n} from '../../shared/types'\nimport { ROLE_ANONYMOUS, MSG, isAnonymousUserId } from '../../shared/protocol/constants'\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n BASE_USERS_SCHEMA,\n} from '../schemas/registry'\nimport { ensureCollectionTable as ensureCollectionTableImpl } from './collection-table-migration'\nimport {\n handleSubscribe,\n handleUnsubscribe,\n handlePut,\n handleDelete,\n handleUserList,\n handleUserUpdate,\n handleSetRole,\n registerUser,\n getUser,\n broadcastUserList,\n handleYjsJoin,\n handleYjsLeave,\n handleYjsBinaryMessage,\n handleApiRequest,\n} from '../handlers'\nimport { SYSTEM_COLLECTION_SCHEMAS, handleYjsDisconnect } from '../handlers/yjs'\n\n/**\n * RecordRoom configuration options\n */\nexport interface RecordRoomConfig {\n /**\n * User ID of the app owner.\n * This user automatically gets 'admin' role on connect.\n */\n ownerUserId?: string\n}\n\n/**\n * RecordRoom Durable Object\n */\nexport class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private schemaRegistry: SchemaRegistry\n private initPromise: Promise<void> | null = null\n /** Yjs docs loaded in memory (key: collection:recordId:fieldName) */\n private yjsDocs: Map<YjsDocKey, Y.Doc> = new Map()\n /** Owner user ID — gets admin role automatically */\n private ownerUserId: string | null\n /** True until the first fetch() completes — detects hibernation wake-up */\n private freshConstruct = true\n\n /**\n * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so\n * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any\n * truthy value on the worker to opt in). Off by default.\n */\n private get perfLogEnabled(): boolean {\n return !!(this.env as { DEEPSPACE_DO_PERF?: unknown })?.DEEPSPACE_DO_PERF\n }\n\n /**\n * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes\n * with no auth of its own, so it is gated here at the DO's single ingress.\n * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`\n * (`deepspace dev start`/`test run` set it automatically). Deployments holding shared\n * data override this to always return false.\n */\n protected get debugRoutesEnabled(): boolean {\n return (this.env as { ALLOW_DEBUG_ROUTES?: unknown })?.ALLOW_DEBUG_ROUTES === 'true'\n }\n\n constructor(\n state: DurableObjectState,\n env: unknown,\n schemas: CollectionSchema[] = [],\n config: RecordRoomConfig = {},\n ) {\n super(state, env)\n this.schemaRegistry = new SchemaRegistry([\n ...SYSTEM_COLLECTION_SCHEMAS,\n BASE_USERS_SCHEMA,\n ...schemas,\n ])\n this.ownerUserId = config.ownerUserId ?? null\n\n // Log hibernation wake-up (WebSockets preserved but memory cleared)\n const wsCount = this.state.getWebSockets().length\n if (wsCount > 0) {\n console.log('[DO] Woke from hibernation:', wsCount, 'connections')\n }\n }\n\n // ============================================================================\n // Permission Context\n // ============================================================================\n\n private getPermissionContext(): PermissionContext {\n const teamMembersSchema = this.schemaRegistry.get('team_members')\n const hasTeamMembers = !!teamMembersSchema?.columns?.length\n\n return {\n isTeamMember: (teamId: string, userId: string): boolean => {\n if (!hasTeamMembers) return false\n const cursor = this.sql.exec(\n `SELECT 1 FROM c_team_members WHERE col_teamid = ? AND col_userid = ? AND (col_status = 'active' OR col_status IS NULL) LIMIT 1`,\n teamId,\n userId,\n )\n return cursor.toArray().length > 0\n },\n }\n }\n\n // ============================================================================\n // HTTP Entry Point (overrides BaseRoom.fetch)\n // ============================================================================\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n // Internal control-plane endpoints (e.g. POST /internal/disconnect-sockets)\n // are shared by every room type via BaseRoom. Handle them first — before\n // the DB-init + WS path — since closing sockets needs no schema init.\n // Only reachable via DO stub fetch from the app worker (see BaseRoom.fetch).\n if (url.pathname.startsWith('/internal/')) {\n const internal = await this.handleInternalRequest(request, url)\n if (internal) return internal\n }\n\n const fetchStart = Date.now()\n const isColdStart = this.freshConstruct\n const needsInit = !this.initPromise\n this.freshConstruct = false\n\n await this.ensureInitialized()\n const initMs = Date.now() - fetchStart\n\n // HTTP API endpoints (tools, debug, etc.)\n if (url.pathname.startsWith('/api/')) {\n if (url.pathname.startsWith('/api/debug/') && !this.debugRoutesEnabled) {\n return new Response('Not Found', { status: 404 })\n }\n return handleApiRequest(this.createHandlerContext(), request, url)\n }\n\n // WebSocket upgrade — delegate to BaseRoom\n if (request.headers.get('Upgrade') === 'websocket') {\n // Store timing info for logging in onConnect\n this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs }\n return super.fetch(request)\n }\n\n return new Response('Not Found', { status: 404 })\n }\n\n /** Timing info from the current fetch(), used by onConnect for logging */\n private _fetchTiming: {\n fetchStart: number\n isColdStart: boolean\n needsInit: boolean\n initMs: number\n } | null = null\n\n // ============================================================================\n // Database\n // ============================================================================\n\n private ensureInitialized(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this.initializeDatabase()\n }\n return this.initPromise\n }\n\n private async initializeDatabase(): Promise<void> {\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS yjs_docs (\n doc_key TEXT PRIMARY KEY,\n state BLOB NOT NULL,\n updated_at TEXT NOT NULL\n );\n `)\n\n this.ensureAllCollectionTables()\n }\n\n // ============================================================================\n // Table-Mode Schema Management\n // ============================================================================\n\n private ensureCollectionTable(schema: CollectionSchema): void {\n ensureCollectionTableImpl(this.sql, schema, console)\n }\n\n private ensureAllCollectionTables(): void {\n for (const schema of this.schemaRegistry.all()) {\n this.ensureCollectionTable(schema)\n }\n }\n\n // ============================================================================\n // BaseRoom Lifecycle Hooks\n // ============================================================================\n\n protected async onConnect(ws: WebSocket, user: UserAttachment): Promise<ConnectionAttachment> {\n await this.ensureInitialized()\n\n const timing = this._fetchTiming ?? {\n fetchStart: Date.now(),\n isColdStart: false,\n needsInit: false,\n initMs: 0,\n }\n\n const userId = user.userId\n const userName = user.userName\n const userEmail = user.userEmail\n const userImageUrl = user.userImageUrl\n const isAuthenticated = !isAnonymousUserId(userId)\n\n let attachment: ConnectionAttachment\n\n if (isAuthenticated) {\n const isOwner = this.ownerUserId != null && userId === this.ownerUserId\n const regStart = Date.now()\n const defaultRole = this.schemaRegistry.get('users')?.defaultRole ?? 'member'\n const before = getUser(this.sql, userId, this.schemaRegistry)\n const registeredUser = await registerUser(\n this.sql,\n userId,\n userName,\n userEmail,\n userImageUrl,\n isOwner,\n defaultRole,\n this.schemaRegistry,\n )\n const regMs = Date.now() - regStart\n // A first registration (or a changed name/avatar/role) changes what\n // every other tab's roster should show — push it, or peers who joined\n // after a tab connected render as \"Unknown\" there until it reconnects.\n if (\n !before ||\n before.name !== registeredUser.name ||\n before.imageUrl !== registeredUser.imageUrl ||\n before.role !== registeredUser.role\n ) {\n broadcastUserList(this.createUserContext(), ws)\n }\n\n attachment = {\n userId: registeredUser.id,\n userName,\n userEmail,\n userImageUrl,\n role: registeredUser.role,\n subscriptions: [],\n yjsSubscriptions: [],\n }\n\n this.send(ws, { type: MSG.USER_INFO, payload: registeredUser })\n\n const totalMs = Date.now() - timing.fetchStart\n if (this.perfLogEnabled)\n console.log(\n `[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`,\n )\n } else {\n attachment = {\n userId,\n userName: 'Anonymous',\n userEmail: '',\n role: ROLE_ANONYMOUS,\n subscriptions: [],\n yjsSubscriptions: [],\n }\n\n this.send(ws, {\n type: MSG.USER_INFO,\n payload: {\n id: userId,\n name: 'Anonymous',\n email: '',\n role: ROLE_ANONYMOUS,\n createdAt: new Date().toISOString(),\n lastSeenAt: new Date().toISOString(),\n },\n })\n\n const totalMs = Date.now() - timing.fetchStart\n if (this.perfLogEnabled)\n console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`)\n }\n\n this._fetchTiming = null\n return attachment\n }\n\n /**\n * Required to satisfy BaseRoom's abstract contract, but never invoked:\n * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),\n * so the BaseRoom dispatch path that would call this never runs. All real\n * message handling lives in `handleRecordMessage`.\n */\n protected onMessage(): void {\n throw new Error('RecordRoom.onMessage is unreachable; use webSocketMessage')\n }\n\n // ============================================================================\n // Hibernation API — RecordRoom overrides these directly (rather than using\n // BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run\n // ensureInitialized() before every message after a hibernation wake-up.\n // ============================================================================\n\n async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void> {\n await this.ensureInitialized()\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) {\n this.send(ws, { type: MSG.ERROR, payload: { error: 'Connection not found' } })\n return\n }\n\n // Handle binary messages (Yjs sync)\n if (message instanceof ArrayBuffer) {\n try {\n await handleYjsBinaryMessage(\n this.createYjsContext(),\n ws,\n attachment,\n new Uint8Array(message),\n )\n } catch (e) {\n console.error('[RecordRoom] Yjs binary message error:', e)\n }\n return\n }\n\n // Handle JSON messages\n try {\n const msg = JSON.parse(message)\n await this.handleRecordMessage(ws, attachment, msg)\n } catch (e) {\n const errMsg = e instanceof Error ? e.message : String(e)\n const msgPreview = typeof message === 'string' ? message.slice(0, 200) : '(non-string)'\n console.error(`[RecordRoom] Message handler error: ${errMsg}`, { message: msgPreview })\n this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } })\n }\n }\n\n async webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void> {\n console.log(`[RecordRoom] webSocketClose code=${code} reason=${reason}`)\n try {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment) {\n handleYjsDisconnect(this.createYjsContext(), ws, attachment)\n }\n } catch {\n /* best-effort */\n }\n }\n\n async webSocketError(_ws: WebSocket, error: unknown): Promise<void> {\n console.error(`[RecordRoom] webSocketError:`, error)\n }\n\n // ============================================================================\n // Message Routing\n // ============================================================================\n\n private async handleRecordMessage(\n ws: WebSocket,\n attachment: ConnectionAttachment,\n msg: { type: string; payload: unknown },\n ): Promise<void> {\n const { type, payload } = msg\n const ctx = this.createHandlerContext()\n const recordCtx = this.createRecordContext()\n const userCtx = this.createUserContext()\n const yjsCtx = this.createYjsContext()\n\n switch (type) {\n case MSG.SUBSCRIBE:\n handleSubscribe(ctx, ws, attachment, payload as SubscribePayload)\n break\n\n case MSG.UNSUBSCRIBE:\n handleUnsubscribe(ctx, ws, attachment, payload as UnsubscribePayload)\n break\n\n case MSG.PUT:\n await handlePut(recordCtx, ws, attachment, payload as PutPayload)\n break\n\n case MSG.DELETE:\n await handleDelete(recordCtx, ws, attachment, payload as DeletePayload)\n break\n\n case MSG.USER_LIST:\n handleUserList(userCtx, ws, attachment)\n break\n\n case MSG.USER_UPDATE:\n handleUserUpdate(\n recordCtx,\n ws,\n attachment,\n payload as { name?: string; email?: string; imageUrl?: string },\n )\n break\n\n case MSG.SET_ROLE:\n await handleSetRole(userCtx, ws, attachment, payload as SetRolePayload)\n break\n\n case MSG.YJS_JOIN:\n await handleYjsJoin(yjsCtx, ws, attachment, payload as YjsJoinPayload)\n break\n\n case MSG.YJS_LEAVE:\n handleYjsLeave(yjsCtx, ws, attachment, payload as YjsLeavePayload)\n break\n\n case MSG.LIST_SCHEMAS:\n this.handleListSchemas(ws)\n break\n\n default:\n this.send(ws, { type: MSG.ERROR, payload: { error: `Unknown message type: ${type}` } })\n }\n }\n\n // ============================================================================\n // Schema Discovery\n // ============================================================================\n\n private handleListSchemas(ws: WebSocket): void {\n const schemas = this.schemaRegistry.all()\n this.send(ws, {\n type: MSG.LIST_SCHEMAS,\n payload: { schemas },\n })\n }\n\n // ============================================================================\n // Context Factory Methods\n // ============================================================================\n\n private createHandlerContext() {\n return {\n sql: this.sql,\n state: this.state,\n schemaRegistry: this.schemaRegistry,\n // Every record room stub is addressed with `idFromName(scopeId)`, so the\n // DO's own name is the scope. Handlers use it to name the room when a\n // caller asks it for a collection that lives in a different scope.\n scopeId: this.state.id.name,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n sendBinary: (ws: WebSocket, data: Uint8Array) => this.sendBinaryHelper(ws, data),\n yjsDocs: this.yjsDocs,\n ownerUserId: this.ownerUserId ?? undefined,\n }\n }\n\n private createRecordContext() {\n return {\n ...this.createHandlerContext(),\n state: this.state,\n }\n }\n\n private createUserContext() {\n return {\n sql: this.sql,\n state: this.state,\n schemaRegistry: this.schemaRegistry,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n }\n }\n\n private createYjsContext() {\n return {\n sql: this.sql,\n state: this.state,\n yjsDocs: this.yjsDocs,\n schemaRegistry: this.schemaRegistry,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n sendBinary: (ws: WebSocket, data: Uint8Array) => this.sendBinaryHelper(ws, data),\n }\n }\n\n // ============================================================================\n // Utilities (preserves original API that handlers expect)\n // ============================================================================\n\n private send(ws: WebSocket, message: ServerMessage): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n } catch {\n // Ignore send errors\n }\n }\n\n private sendBinaryHelper(ws: WebSocket, data: Uint8Array): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n // Ignore send errors\n }\n }\n}\n","/**\n * YjsRoom — Lightweight Durable Object for collaborative Yjs documents.\n * Extends BaseRoom for WebSocket/connection infrastructure.\n *\n * Unlike RecordRoom (schemas, RBAC, queries, user state), YjsRoom is\n * purpose-built for Yjs: sync, relay, persist. One DO per document.\n *\n * Architecture (SOTA for Yjs + Cloudflare DOs):\n * - Auth verified at the worker edge, role passed to DO via URL params\n * - DO is a thin Yjs sync relay: receive → apply → persist → broadcast\n * - Viewers can observe but not write; members/admins can write\n * - State persisted as a single binary blob in SQLite\n *\n * Uses the shared yjs-protocol.ts encoding utilities — no duplication.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport {\n MSG_SYNC,\n MSG_AWARENESS,\n MSG_SYNC_STEP1,\n MSG_SYNC_STEP2,\n MSG_SYNC_UPDATE,\n createEncoder,\n createDecoder,\n toUint8Array,\n writeVarUint,\n writeVarUint8Array,\n readVarUint,\n readVarUint8Array,\n} from '../../shared/protocol/yjs'\n\n// ============================================================================\n// Connection attachment (extends UserAttachment, survives hibernation)\n// ============================================================================\n\ninterface YjsAttachment extends UserAttachment {\n role: string\n canWrite: boolean\n awarenessClientId: number | null\n}\n\ninterface AwarenessEntry {\n clock: number\n stateBytes: Uint8Array\n}\n\n// ============================================================================\n// YjsRoom Durable Object\n// ============================================================================\n\nexport class YjsRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private doc: Y.Doc | null = null\n private initialized = false\n private awarenessStates = new Map<number, AwarenessEntry>()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n // --------------------------------------------------------------------------\n // Initialization & persistence\n // --------------------------------------------------------------------------\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS yjs_state (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n doc BLOB NOT NULL,\n updated_at TEXT NOT NULL\n )\n `)\n }\n\n private getDoc(): Y.Doc {\n if (this.doc) return this.doc\n\n this.ensureInitialized()\n this.doc = new Y.Doc()\n\n // Load persisted state\n const rows = this.sql.exec('SELECT doc FROM yjs_state WHERE id = 1').toArray()\n if (rows.length > 0 && rows[0].doc) {\n Y.applyUpdate(this.doc, new Uint8Array(rows[0].doc as ArrayBuffer))\n }\n\n // Auto-save on every update\n this.doc.on('update', () => this.persistDoc())\n\n return this.doc\n }\n\n private persistDoc(): void {\n if (!this.doc) return\n const state = Y.encodeStateAsUpdate(this.doc)\n const now = new Date().toISOString()\n this.sql.exec(\n `INSERT INTO yjs_state (id, doc, updated_at) VALUES (1, ?, ?)\n ON CONFLICT(id) DO UPDATE SET doc = ?, updated_at = ?`,\n state,\n now,\n state,\n now,\n )\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle Hooks\n // --------------------------------------------------------------------------\n\n protected onConnect(ws: WebSocket, user: UserAttachment): YjsAttachment {\n const role = ((user as Record<string, unknown>).role as string) ?? 'viewer'\n const canWrite = role === 'member' || role === 'admin'\n\n const attachment: YjsAttachment = {\n ...user,\n role,\n canWrite,\n awarenessClientId: null,\n }\n\n // Initial sync: send our state vector (STEP1) + full state (STEP2)\n const doc = this.getDoc()\n\n const step1 = createEncoder()\n writeVarUint(step1, MSG_SYNC)\n writeVarUint(step1, MSG_SYNC_STEP1)\n writeVarUint8Array(step1, Y.encodeStateVector(doc))\n ws.send(toUint8Array(step1).buffer)\n\n const fullState = Y.encodeStateAsUpdate(doc)\n if (fullState.length > 1) {\n const step2 = createEncoder()\n writeVarUint(step2, MSG_SYNC)\n writeVarUint(step2, MSG_SYNC_STEP2)\n writeVarUint8Array(step2, fullState)\n ws.send(toUint8Array(step2).buffer)\n }\n\n // Tell client their write access\n ws.send(JSON.stringify({ type: 'auth', canWrite }))\n this.sendAwarenessSnapshot(ws)\n\n return attachment\n }\n\n protected onMessage(\n _ws: WebSocket,\n _user: UserAttachment,\n _message: { type: string; [key: string]: unknown },\n ): void {\n // YjsRoom only handles binary messages; JSON messages are ignored\n // (ping/pong handled by auto-response)\n }\n\n protected onBinaryMessage(ws: WebSocket, _user: UserAttachment, data: ArrayBuffer): void {\n const bytes = new Uint8Array(data)\n const decoder = createDecoder(bytes)\n const messageType = readVarUint(decoder)\n\n if (messageType === MSG_SYNC) {\n this.handleSync(ws, decoder, bytes)\n } else if (messageType === MSG_AWARENESS) {\n this.handleAwareness(ws, decoder, bytes)\n }\n }\n\n protected onDisconnect(ws: WebSocket, _user: UserAttachment): void {\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (attachment?.awarenessClientId == null) return\n\n const clientId = attachment.awarenessClientId\n const prevClock = this.awarenessStates.get(clientId)?.clock ?? 0\n this.awarenessStates.delete(clientId)\n\n const msg = this.encodeAwarenessMessage([\n {\n clientId,\n clock: prevClock + 1,\n stateBytes: new TextEncoder().encode('null'),\n },\n ])\n for (const peer of this.state.getWebSockets()) {\n if (peer === ws) continue\n try {\n peer.send(msg.buffer)\n } catch {\n /* dead socket */\n }\n }\n }\n\n // --------------------------------------------------------------------------\n // Sync protocol\n // --------------------------------------------------------------------------\n\n private handleSync(\n ws: WebSocket,\n decoder: { data: Uint8Array; pos: number },\n rawMessage: Uint8Array,\n ): void {\n const syncType = readVarUint(decoder)\n const doc = this.getDoc()\n\n switch (syncType) {\n case MSG_SYNC_STEP1: {\n const clientStateVector = readVarUint8Array(decoder)\n const diff = Y.encodeStateAsUpdate(doc, clientStateVector)\n const enc = createEncoder()\n writeVarUint(enc, MSG_SYNC)\n writeVarUint(enc, MSG_SYNC_STEP2)\n writeVarUint8Array(enc, diff)\n ws.send(toUint8Array(enc).buffer)\n break\n }\n case MSG_SYNC_STEP2:\n case MSG_SYNC_UPDATE: {\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (!attachment?.canWrite) return\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update, 'remote')\n this.broadcastRaw(ws, rawMessage)\n break\n }\n }\n }\n\n private handleAwareness(\n ws: WebSocket,\n decoder: { data: Uint8Array; pos: number },\n rawMessage: Uint8Array,\n ): void {\n const updates = this.readAwarenessUpdates(decoder)\n if (updates.length === 0) return\n\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (attachment && attachment.awarenessClientId === null) {\n attachment.awarenessClientId = updates[0].clientId\n ws.serializeAttachment(attachment)\n }\n\n for (const update of updates) {\n const existing = this.awarenessStates.get(update.clientId)\n if (existing && existing.clock >= update.clock) continue\n\n if (new TextDecoder().decode(update.stateBytes) === 'null') {\n this.awarenessStates.delete(update.clientId)\n } else {\n this.awarenessStates.set(update.clientId, {\n clock: update.clock,\n stateBytes: update.stateBytes,\n })\n }\n }\n\n this.broadcastRaw(ws, rawMessage)\n }\n\n private readAwarenessUpdates(decoder: {\n data: Uint8Array\n pos: number\n }): Array<AwarenessEntry & { clientId: number }> {\n const update = readVarUint8Array(decoder)\n const updateDecoder = createDecoder(update)\n const count = readVarUint(updateDecoder)\n const updates: Array<AwarenessEntry & { clientId: number }> = []\n\n for (let i = 0; i < count; i++) {\n const clientId = readVarUint(updateDecoder)\n const clock = readVarUint(updateDecoder)\n const stateBytes = readVarUint8Array(updateDecoder)\n updates.push({ clientId, clock, stateBytes })\n }\n\n return updates\n }\n\n private encodeAwarenessMessage(\n updates: Array<AwarenessEntry & { clientId: number }>,\n ): Uint8Array {\n const inner = createEncoder()\n writeVarUint(inner, updates.length)\n for (const update of updates) {\n writeVarUint(inner, update.clientId)\n writeVarUint(inner, update.clock)\n writeVarUint8Array(inner, update.stateBytes)\n }\n\n const outer = createEncoder()\n writeVarUint(outer, MSG_AWARENESS)\n writeVarUint8Array(outer, toUint8Array(inner))\n return toUint8Array(outer)\n }\n\n private sendAwarenessSnapshot(ws: WebSocket): void {\n if (this.awarenessStates.size === 0) return\n\n const updates = Array.from(this.awarenessStates, ([clientId, entry]) => ({\n clientId,\n clock: entry.clock,\n stateBytes: entry.stateBytes,\n }))\n const msg = this.encodeAwarenessMessage(updates)\n try {\n ws.send(msg.buffer)\n } catch {\n /* dead socket */\n }\n }\n\n // --------------------------------------------------------------------------\n // Broadcasting\n // --------------------------------------------------------------------------\n\n private broadcastRaw(sender: WebSocket, rawMessage: Uint8Array): void {\n for (const ws of this.state.getWebSockets()) {\n if (ws === sender) continue\n try {\n ws.send(rawMessage.buffer)\n } catch {\n /* dead socket */\n }\n }\n }\n}\n","/**\n * CanvasRoom — Spatial canvas Durable Object (tldraw-style).\n *\n * Extends BaseRoom with Yjs-backed spatial operations.\n * Each shape is a Y.Map entry, enabling multi-user concurrent editing.\n *\n * Features:\n * - Shape CRUD (add, move, resize, delete, update properties)\n * - Viewport awareness (each user's visible region)\n * - Per-user undo/redo stacks\n *\n * Message types: canvas.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\nimport { ROLES } from '../../shared/roles'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CanvasShape {\n id: string\n type: string\n x: number\n y: number\n width: number\n height: number\n rotation?: number\n props: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\nexport interface Viewport {\n userId: string\n x: number\n y: number\n width: number\n height: number\n zoom: number\n}\n\ninterface CanvasAttachment extends UserAttachment {\n viewport: Viewport | null\n /** True for member/admin roles; false for viewers and unauthenticated anon. */\n canWrite: boolean\n}\n\n// Message types that mutate shared canvas state. Anything not in this set\n// (viewport broadcasts, the implicit shape snapshot on connect) is\n// presence-like and intentionally open to viewers.\nconst CANVAS_WRITE_TYPES: ReadonlySet<string> = new Set([\n MSG.CANVAS_ADD,\n MSG.CANVAS_MOVE,\n MSG.CANVAS_RESIZE,\n MSG.CANVAS_DELETE,\n MSG.CANVAS_UPDATE,\n MSG.CANVAS_UNDO,\n MSG.CANVAS_REDO,\n])\n\ninterface UndoEntry {\n type: 'add' | 'delete' | 'update'\n shapeId: string\n before?: Record<string, unknown>\n after?: Record<string, unknown>\n}\n\n// ============================================================================\n// CanvasRoom\n// ============================================================================\n\nexport class CanvasRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private doc: Y.Doc | null = null\n private initialized = false\n private viewports: Map<string, Viewport> = new Map()\n private undoStacks: Map<string, UndoEntry[]> = new Map()\n private redoStacks: Map<string, UndoEntry[]> = new Map()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n // --------------------------------------------------------------------------\n // Initialization & persistence\n // --------------------------------------------------------------------------\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS canvas_state (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n doc BLOB NOT NULL,\n updated_at TEXT NOT NULL\n )\n `)\n }\n\n private getDoc(): Y.Doc {\n if (this.doc) return this.doc\n\n this.ensureInitialized()\n this.doc = new Y.Doc()\n\n const rows = this.sql.exec('SELECT doc FROM canvas_state WHERE id = 1').toArray()\n if (rows.length > 0 && rows[0].doc) {\n Y.applyUpdate(this.doc, new Uint8Array(rows[0].doc as ArrayBuffer))\n }\n\n this.doc.on('update', () => this.persistDoc())\n return this.doc\n }\n\n private persistDoc(): void {\n if (!this.doc) return\n const state = Y.encodeStateAsUpdate(this.doc)\n const now = new Date().toISOString()\n this.sql.exec(\n `INSERT INTO canvas_state (id, doc, updated_at) VALUES (1, ?, ?)\n ON CONFLICT(id) DO UPDATE SET doc = ?, updated_at = ?`,\n state, now, state, now,\n )\n }\n\n private getShapesMap(): Y.Map<Record<string, unknown>> {\n return this.getDoc().getMap('shapes') as Y.Map<Record<string, unknown>>\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle\n // --------------------------------------------------------------------------\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected onConnect(ws: WebSocket, user: UserAttachment): CanvasAttachment {\n this.ensureInitialized()\n\n const role = (user.role as string | undefined) ?? ROLES.VIEWER\n const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN\n\n const attachment: CanvasAttachment = {\n ...user,\n viewport: null,\n canWrite,\n }\n\n // Tell the client whether their role grants write access, so the hook\n // can short-circuit local writes and the UI can render disabled\n // controls instead of bouncing every click off the server.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n\n // Send current shapes\n const shapes = this.getAllShapes()\n this.sendTo(ws, {\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes,\n viewports: Array.from(this.viewports.values()),\n },\n })\n\n return attachment\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown }\n ): Promise<void> {\n this.ensureInitialized()\n const { type, payload } = message as { type: string; payload: Record<string, unknown> }\n\n // Viewers and unauthenticated anon connections can observe but not\n // mutate. Reject write attempts at the gate so a bug in any single\n // handler can't accidentally re-open the hole.\n if (CANVAS_WRITE_TYPES.has(type) && !(user as CanvasAttachment).canWrite) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Write access denied: viewer role cannot modify the canvas' },\n })\n return\n }\n\n switch (type) {\n case MSG.CANVAS_ADD: {\n const shape = payload as unknown as Partial<CanvasShape>\n const id = shape.id ?? crypto.randomUUID()\n const now = new Date().toISOString()\n\n const newShape: CanvasShape = {\n id,\n type: shape.type ?? 'rect',\n x: shape.x ?? 0,\n y: shape.y ?? 0,\n width: shape.width ?? 100,\n height: shape.height ?? 100,\n rotation: shape.rotation,\n props: shape.props ?? {},\n createdBy: user.userId,\n createdAt: now,\n updatedAt: now,\n }\n\n this.getShapesMap().set(id, newShape as unknown as Record<string, unknown>)\n\n this.pushUndo(user.userId, { type: 'add', shapeId: id, after: newShape as unknown as Record<string, unknown> })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: newShape } })\n break\n }\n\n case MSG.CANVAS_MOVE: {\n const { shapeId, x, y } = payload as { shapeId: string; x: number; y: number }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = { ...existing, x, y, updatedAt: new Date().toISOString() }\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }, ws)\n break\n }\n\n case MSG.CANVAS_RESIZE: {\n const { shapeId, width, height, x, y } = payload as { shapeId: string; width: number; height: number; x?: number; y?: number }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = { ...existing, width, height, updatedAt: new Date().toISOString() }\n if (x !== undefined) (updated as Record<string, unknown>).x = x\n if (y !== undefined) (updated as Record<string, unknown>).y = y\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }, ws)\n break\n }\n\n case MSG.CANVAS_DELETE: {\n const { shapeId } = payload as { shapeId: string }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n shapesMap.delete(shapeId)\n\n this.pushUndo(user.userId, { type: 'delete', shapeId, before: existing })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId } })\n break\n }\n\n case MSG.CANVAS_UPDATE: {\n const { shapeId, props } = payload as { shapeId: string; props: Record<string, unknown> }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = {\n ...existing,\n props: { ...(existing.props as Record<string, unknown> ?? {}), ...props },\n updatedAt: new Date().toISOString(),\n }\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }, ws)\n break\n }\n\n case MSG.CANVAS_VIEWPORT: {\n const viewport = payload as unknown as Viewport\n viewport.userId = user.userId\n this.viewports.set(user.userId, viewport)\n this.broadcast({ type: MSG.CANVAS_VIEWPORT, payload: { viewport } }, ws)\n break\n }\n\n case MSG.CANVAS_UNDO: {\n this.handleUndo(user.userId)\n break\n }\n\n case MSG.CANVAS_REDO: {\n this.handleRedo(user.userId)\n break\n }\n\n default:\n this.sendTo(ws, { type: MSG.ERROR, payload: { error: `Unknown canvas message type: ${type}` } })\n }\n }\n\n protected onDisconnect(_ws: WebSocket, user: UserAttachment): void {\n this.viewports.delete(user.userId)\n this.undoStacks.delete(user.userId)\n this.redoStacks.delete(user.userId)\n this.broadcast({ type: MSG.CANVAS_VIEWPORT, payload: { userId: user.userId, removed: true } })\n }\n\n // --------------------------------------------------------------------------\n // Undo / Redo\n // --------------------------------------------------------------------------\n\n private pushUndo(userId: string, entry: UndoEntry): void {\n if (!this.undoStacks.has(userId)) this.undoStacks.set(userId, [])\n const stack = this.undoStacks.get(userId)!\n stack.push(entry)\n if (stack.length > 100) stack.shift()\n }\n\n private clearRedo(userId: string): void {\n this.redoStacks.set(userId, [])\n }\n\n private handleUndo(userId: string): void {\n const stack = this.undoStacks.get(userId)\n if (!stack || stack.length === 0) return\n\n const entry = stack.pop()!\n const shapesMap = this.getShapesMap()\n\n if (!this.redoStacks.has(userId)) this.redoStacks.set(userId, [])\n this.redoStacks.get(userId)!.push(entry)\n\n switch (entry.type) {\n case 'add':\n shapesMap.delete(entry.shapeId)\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId: entry.shapeId } })\n break\n case 'delete':\n if (entry.before) {\n shapesMap.set(entry.shapeId, entry.before)\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: entry.before } })\n }\n break\n case 'update':\n if (entry.before) {\n shapesMap.set(entry.shapeId, entry.before)\n this.broadcast({\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes: this.getAllShapes(),\n viewports: Array.from(this.viewports.values()),\n },\n })\n }\n break\n }\n }\n\n private handleRedo(userId: string): void {\n const stack = this.redoStacks.get(userId)\n if (!stack || stack.length === 0) return\n\n const entry = stack.pop()!\n const shapesMap = this.getShapesMap()\n\n if (!this.undoStacks.has(userId)) this.undoStacks.set(userId, [])\n this.undoStacks.get(userId)!.push(entry)\n\n switch (entry.type) {\n case 'add':\n if (entry.after) {\n shapesMap.set(entry.shapeId, entry.after)\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: entry.after } })\n }\n break\n case 'delete':\n shapesMap.delete(entry.shapeId)\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId: entry.shapeId } })\n break\n case 'update':\n if (entry.after) {\n shapesMap.set(entry.shapeId, entry.after)\n this.broadcast({\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes: this.getAllShapes(),\n viewports: Array.from(this.viewports.values()),\n },\n })\n }\n break\n }\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n private getAllShapes(): CanvasShape[] {\n const shapesMap = this.getShapesMap()\n const shapes: CanvasShape[] = []\n shapesMap.forEach((value, key) => {\n shapes.push({ id: key, ...value } as unknown as CanvasShape)\n })\n return shapes\n }\n}\n","/**\n * Standard DeepSpace role constants.\n *\n * Every DeepSpace app uses the same three roles.\n * Apps can import these instead of defining them locally.\n */\n\nexport const ROLES = {\n VIEWER: 'viewer',\n MEMBER: 'member',\n ADMIN: 'admin',\n} as const\n\nexport type Role = (typeof ROLES)[keyof typeof ROLES]\n\nexport const ROLE_CONFIG: Record<Role, { title: string; badgeVariant: string; description: string }> = {\n viewer: { title: 'Viewer', badgeVariant: 'secondary', description: 'Read-only access' },\n member: { title: 'Member', badgeVariant: 'default', description: 'Can create and edit own content' },\n admin: { title: 'Admin', badgeVariant: 'warning', description: 'Full access to all features' },\n}\n","/**\n * PresenceRoom — Ephemeral presence-tracking Durable Object.\n *\n * Extends BaseRoom. No SQLite — purely in-memory presence state.\n * Tracks who is present in a given scope (canvas, doc, thread, etc.)\n * and broadcasts join/leave/state-update events to all connected peers.\n *\n * Each scope ID maps to its own DO instance. Clients connect via\n * /ws/presence/:scopeId and receive real-time presence for that scope.\n *\n * Peers can attach small ephemeral state (cursor position, typing indicator,\n * viewport, selection, etc.) via MSG.PRESENCE_UPDATE.\n *\n * Message types: presence.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PresencePeer {\n userId: string\n userName: string\n joinedAt: string\n /** Small per-user state (cursor, typing, viewport, etc.) */\n state: Record<string, unknown>\n}\n\ninterface PresenceAttachment extends UserAttachment {\n joinedAt: string\n}\n\nconst MAX_PRESENCE_STATE_BYTES = 16 * 1024\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction stateSize(state: Record<string, unknown>): number {\n return new TextEncoder().encode(JSON.stringify(state)).byteLength\n}\n\n// ============================================================================\n// PresenceRoom\n// ============================================================================\n\nexport class PresenceRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private peers: Map<string, PresencePeer> = new Map()\n private peerSockets: Map<string, WebSocket> = new Map()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n /**\n * Durable Objects can hibernate and clear heap while Cloudflare keeps\n * WebSocket connections. Deserialize attachments from already-connected\n * sockets so `peers` matches reality before we send PRESENCE_SYNC.\n */\n private hydratePeersFromLiveSockets(connectingWs: WebSocket): void {\n for (const existing of this.state.getWebSockets()) {\n if (existing === connectingWs) continue\n const att = existing.deserializeAttachment() as PresenceAttachment | null\n if (!att?.userId) continue\n if (this.peers.has(att.userId)) continue\n\n const joinedAt = typeof att.joinedAt === 'string' ? att.joinedAt : new Date().toISOString()\n\n const recovered: PresencePeer = {\n userId: att.userId,\n userName: att.userName,\n joinedAt,\n state: {},\n }\n this.peers.set(att.userId, recovered)\n this.peerSockets.set(att.userId, existing)\n }\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle\n // --------------------------------------------------------------------------\n\n protected onConnect(ws: WebSocket, user: UserAttachment): PresenceAttachment {\n this.hydratePeersFromLiveSockets(ws)\n\n const now = new Date().toISOString()\n\n const peer: PresencePeer = {\n userId: user.userId,\n userName: user.userName,\n joinedAt: now,\n state: {},\n }\n\n // Send existing peers to the new connection (before adding self)\n this.sendTo(ws, {\n type: MSG.PRESENCE_SYNC,\n payload: { peers: Array.from(this.peers.values()) },\n })\n\n // Now add self and notify others\n this.peers.set(user.userId, peer)\n this.peerSockets.set(user.userId, ws)\n this.broadcast({ type: MSG.PRESENCE_JOIN, payload: { peer } }, ws)\n\n const attachment: PresenceAttachment = {\n ...user,\n joinedAt: now,\n }\n\n return attachment\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n const { type, payload } = message as { type: string; payload: Record<string, unknown> }\n\n switch (type) {\n case MSG.PRESENCE_UPDATE: {\n let peer = this.peers.get(user.userId)\n if (!peer) {\n const att = user as PresenceAttachment\n const joinedAt =\n typeof att.joinedAt === 'string' ? att.joinedAt : new Date().toISOString()\n peer = {\n userId: user.userId,\n userName: user.userName,\n joinedAt,\n state: {},\n }\n this.peers.set(user.userId, peer)\n this.peerSockets.set(user.userId, ws)\n }\n\n if (!isPlainObject(payload)) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Presence state must be an object' },\n })\n return\n }\n const nextState = { ...peer.state, ...payload }\n if (stateSize(nextState) > MAX_PRESENCE_STATE_BYTES) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Presence state exceeds 16 KiB' },\n })\n return\n }\n\n peer.state = nextState\n this.peers.set(user.userId, peer)\n\n // Broadcast the update to all other peers\n this.broadcast(\n {\n type: MSG.PRESENCE_UPDATE,\n payload: { userId: user.userId, state: peer.state },\n },\n ws,\n )\n break\n }\n\n default:\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: `Unknown presence message type: ${type}` },\n })\n }\n }\n\n protected onDisconnect(ws: WebSocket, user: UserAttachment): void {\n this.peers.delete(user.userId)\n this.peerSockets.delete(user.userId)\n this.broadcast({ type: MSG.PRESENCE_LEAVE, payload: { userId: user.userId } }, ws)\n }\n}\n","/** Pure validation and evaluation for CronRoom schedules. */\n\nexport interface CronTask {\n name: string\n /** Interval in minutes (interval mode) — mutually exclusive with `schedule`. */\n intervalMinutes?: number\n /** 5-field cron expression (cron mode) — requires `timezone`. */\n schedule?: string\n /** IANA timezone string (e.g. \"America/New_York\"). Required with `schedule`. */\n timezone?: string\n /** Whether the task starts paused. */\n paused?: boolean\n}\n\nconst TASK_NAME_RE = /^[a-z0-9-]+$/\n\n/** Reject invalid task configuration before CronRoom mutates storage. */\nexport function validateTask(task: CronTask): CronTask {\n if (!task.name || typeof task.name !== 'string') {\n throw new Error('CronTask.name is required')\n }\n if (task.name.length > 64 || !TASK_NAME_RE.test(task.name)) {\n throw new Error(\n `CronTask.name \"${task.name}\" must be lowercase alphanumeric with hyphens, 1-64 chars`,\n )\n }\n\n const hasInterval = task.intervalMinutes != null\n const hasSchedule = task.schedule != null\n const hasTimezone = task.timezone != null\n if (hasInterval && (hasSchedule || hasTimezone)) {\n throw new Error(`CronTask \"${task.name}\" cannot mix intervalMinutes with schedule/timezone`)\n }\n if (!hasInterval && !(hasSchedule && hasTimezone)) {\n throw new Error(\n `CronTask \"${task.name}\" must declare either intervalMinutes or schedule+timezone`,\n )\n }\n if (\n hasInterval &&\n (!Number.isInteger(task.intervalMinutes) ||\n task.intervalMinutes! < 1 ||\n task.intervalMinutes! > 10080)\n ) {\n throw new Error(`CronTask \"${task.name}\" intervalMinutes must be an integer 1..10080`)\n }\n if (hasSchedule) {\n parseCronExpression(task.schedule!)\n if (typeof task.timezone !== 'string' || task.timezone.length === 0) {\n throw new Error(`CronTask \"${task.name}\" timezone must be a non-empty IANA string`)\n }\n try {\n new Intl.DateTimeFormat('en-US', { timeZone: task.timezone })\n } catch {\n throw new Error(\n `CronTask \"${task.name}\" timezone \"${task.timezone}\" is not a valid IANA timezone`,\n )\n }\n }\n return task\n}\n\ninterface ParsedCron {\n minute: Set<number>\n hour: Set<number>\n dayOfMonth: Set<number>\n month: Set<number>\n dayOfWeek: Set<number>\n}\n\n/** Parse a 5-field expression with literals, lists, ranges, and steps. */\nexport function parseCronExpression(expression: string): ParsedCron {\n const fields = expression.trim().split(/\\s+/)\n if (fields.length !== 5) {\n throw new Error(\n `Cron expression must have exactly 5 fields, got ${fields.length}: \"${expression}\"`,\n )\n }\n const [minute, hour, dayOfMonth, month, dayOfWeek] = fields\n return {\n minute: parseField(minute, 0, 59, 'minute'),\n hour: parseField(hour, 0, 23, 'hour'),\n dayOfMonth: parseField(dayOfMonth, 1, 31, 'day-of-month'),\n month: parseField(month, 1, 12, 'month'),\n dayOfWeek: parseField(dayOfWeek, 0, 6, 'day-of-week'),\n }\n}\n\nfunction parseField(field: string, min: number, max: number, label: string): Set<number> {\n const result = new Set<number>()\n\n for (const part of field.split(',')) {\n const value = part.trim()\n if (value === '*') {\n for (let i = min; i <= max; i++) result.add(i)\n continue\n }\n\n const allStep = value.match(/^\\*\\/(\\d+)$/)\n if (allStep) {\n const step = Number.parseInt(allStep[1], 10)\n if (step <= 0) throw new Error(`Cron ${label} step must be positive: \"${value}\"`)\n for (let i = min; i <= max; i += step) result.add(i)\n continue\n }\n\n const range = value.match(/^(\\d+)-(\\d+)(?:\\/(\\d+))?$/)\n if (range) {\n const start = Number.parseInt(range[1], 10)\n const end = Number.parseInt(range[2], 10)\n const step = range[3] ? Number.parseInt(range[3], 10) : 1\n if (start < min || end > max || start > end || step <= 0) {\n throw new Error(`Cron ${label} range out of bounds: \"${value}\" (${min}..${max})`)\n }\n for (let i = start; i <= end; i += step) result.add(i)\n continue\n }\n\n const literal = Number.parseInt(value, 10)\n if (Number.isNaN(literal) || literal < min || literal > max || String(literal) !== value) {\n throw new Error(`Cron ${label} value invalid: \"${value}\" (${min}..${max})`)\n }\n result.add(literal)\n }\n\n if (result.size === 0) {\n throw new Error(`Cron ${label} field \"${field}\" produced no valid values`)\n }\n return result\n}\n\n/**\n * Find the next UTC minute whose wall-clock fields match in `timezone`.\n * The one-year bound rejects schedules that are not useful to this room.\n */\nexport function nextCronFire(expression: string, timezone: string, from: Date): Date | null {\n const parsed = parseCronExpression(expression)\n const start = Math.floor(from.getTime() / 60_000) * 60_000 + 60_000\n const limit = start + 366 * 24 * 60 * 60_000\n for (let timestamp = start; timestamp < limit; timestamp += 60_000) {\n const candidate = new Date(timestamp)\n if (cronMatches(parsed, timezone, candidate)) return candidate\n }\n return null\n}\n\nfunction cronMatches(parsed: ParsedCron, timezone: string, date: Date): boolean {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n weekday: 'short',\n hour12: false,\n })\n const parts = Object.fromEntries(\n formatter.formatToParts(date).map((part) => [part.type, part.value]),\n )\n const minute = Number.parseInt(parts.minute, 10)\n const hour = Number.parseInt(parts.hour, 10) % 24\n const dayOfMonth = Number.parseInt(parts.day, 10)\n const month = Number.parseInt(parts.month, 10)\n const weekday: Record<string, number> = {\n Sun: 0,\n Mon: 1,\n Tue: 2,\n Wed: 3,\n Thu: 4,\n Fri: 5,\n Sat: 6,\n }\n const dayOfWeek = weekday[parts.weekday] ?? 0\n return (\n parsed.minute.has(minute) &&\n parsed.hour.has(hour) &&\n parsed.dayOfMonth.has(dayOfMonth) &&\n parsed.month.has(month) &&\n parsed.dayOfWeek.has(dayOfWeek)\n )\n}\n","/**\n * CronRoom — Per-app scheduled task execution Durable Object.\n *\n * Extends BaseRoom. One DO per app shards cron work and avoids the\n * dispatch-worker's global KV-poll bottleneck. The DO alarm triggers\n * `onTask(name)` on the configured cadence; each execution is recorded\n * to a per-app `cron_history` table. Subscribers (admin clients via the\n * `useCronMonitor` hook) get pushes over the WebSocket.\n *\n * Tasks declare *either* `intervalMinutes` (run every N minutes) *or*\n * `schedule` + `timezone` (5-field cron expression evaluated against an\n * IANA timezone via `Intl.DateTimeFormat`). Cron mode is DST-aware\n * because the wall-clock comparison happens after the timezone shift,\n * not before.\n *\n * Message types: cron.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { nextCronFire, validateTask, type CronTask } from './cron-schedule'\nimport { MSG } from '../../shared/protocol/constants'\nimport { serverBuild } from '../../shared/protocol/messages'\nimport { ROLES } from '../../shared/roles'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CronRoomConfig {\n tasks: CronTask[]\n}\n\nexport interface CronExecution {\n taskName: string\n startedAt: string\n completedAt: string | null\n success: boolean\n durationMs: number\n error?: string\n}\n\ninterface CronAttachment extends UserAttachment {\n /** True for member/admin roles; false for viewers and unauthenticated anon. */\n canWrite: boolean\n}\n\n// Cron commands that mutate scheduler state. Read-only messages\n// (CRON_TASKS, CRON_HISTORY) are allowed for any connected client per the\n// SDK contract that viewers can observe but not write.\nconst CRON_WRITE_TYPES: ReadonlySet<string> = new Set([\n MSG.CRON_TRIGGER,\n MSG.CRON_PAUSE,\n MSG.CRON_RESUME,\n])\n\n// ============================================================================\n// CronRoom\n// ============================================================================\n\nexport abstract class CronRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private tasks: CronTask[]\n private initialized = false\n\n constructor(state: DurableObjectState, env: unknown, config: CronRoomConfig) {\n super(state, env)\n // Validate at construction time — bad configs should never reach DB.\n this.tasks = config.tasks.map(validateTask)\n }\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS cron_tasks (\n name TEXT PRIMARY KEY,\n interval_minutes INTEGER,\n schedule TEXT,\n timezone TEXT,\n paused INTEGER NOT NULL DEFAULT 0,\n last_run_at TEXT,\n next_run_at TEXT\n )\n `)\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS cron_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_name TEXT NOT NULL,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n success INTEGER NOT NULL DEFAULT 0,\n duration_ms INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL\n )\n `)\n\n // Sync configured tasks. Insert new ones; refresh schedule/interval/timezone\n // on existing ones (so editing src/cron.ts and re-deploying picks up the\n // change without a manual reset). Preserve last_run_at / paused.\n for (const task of this.tasks) {\n const existing = this.sql\n .exec(`SELECT name FROM cron_tasks WHERE name = ?`, task.name)\n .toArray()\n if (existing.length === 0) {\n this.sql.exec(\n `INSERT INTO cron_tasks (name, interval_minutes, schedule, timezone, paused)\n VALUES (?, ?, ?, ?, ?)`,\n task.name,\n task.intervalMinutes ?? null,\n task.schedule ?? null,\n task.timezone ?? null,\n task.paused ? 1 : 0,\n )\n } else {\n this.sql.exec(\n `UPDATE cron_tasks\n SET interval_minutes = ?, schedule = ?, timezone = ?\n WHERE name = ?`,\n task.intervalMinutes ?? null,\n task.schedule ?? null,\n task.timezone ?? null,\n task.name,\n )\n }\n }\n\n // Remove tasks no longer in config\n const configNames = new Set(this.tasks.map((t) => t.name))\n const dbTasks = this.sql.exec(`SELECT name FROM cron_tasks`).toArray()\n for (const row of dbTasks) {\n if (!configNames.has((row as { name: string }).name)) {\n this.sql.exec(`DELETE FROM cron_tasks WHERE name = ?`, (row as { name: string }).name)\n }\n }\n\n // Schedule the next alarm\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // BaseRoom Lifecycle\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected onConnect(ws: WebSocket, user: UserAttachment): CronAttachment {\n this.ensureInitialized()\n\n const role = (user.role as string | undefined) ?? ROLES.VIEWER\n const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN\n\n // Tell the client whether trigger / pause / resume will be accepted,\n // so the monitor UI can render those controls as disabled for viewers\n // instead of trying and surfacing an ERROR after the fact.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n\n // Send current task list and recent history\n this.sendTo(ws, {\n type: MSG.CRON_TASKS,\n payload: { tasks: this.getTaskStates() },\n })\n\n this.sendTo(ws, {\n type: MSG.CRON_HISTORY,\n payload: { history: this.getRecentHistory(50) },\n })\n\n return { ...user, canWrite }\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n this.ensureInitialized()\n // Default `payload` so a payload-less frame reaches the typed reject\n // paths below instead of throwing into BaseRoom's catch-all logger.\n const { type, payload = {} } = message as { type: string; payload?: Record<string, unknown> }\n\n if (CRON_WRITE_TYPES.has(type) && !(user as CronAttachment).canWrite) {\n const error = 'Write access denied: viewer role cannot modify cron tasks'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName: payload.taskName as string | undefined,\n reason: 'read_only',\n error,\n })\n return\n }\n\n try {\n await this.dispatchMessage(ws, type, payload)\n } catch (error) {\n // A mid-operation throw (storage, alarm scheduling, broadcast) must\n // still settle the caller's receipt — an unacked requestId would leave\n // the client's mutation promise pending until unmount.\n try {\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName: payload.taskName as string | undefined,\n reason: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n } catch {\n // Socket already dead — the client's onclose drains its pendings.\n }\n throw error\n }\n }\n\n private async dispatchMessage(\n ws: WebSocket,\n type: string,\n payload: Record<string, unknown>,\n ): Promise<void> {\n switch (type) {\n case MSG.CRON_TRIGGER: {\n const taskName = payload.taskName as string\n if (!taskName) {\n const error = 'Missing taskName'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, { ok: false, reason: 'failed', error })\n return\n }\n const execution = await this.executeTask(taskName)\n if (!execution) {\n const error = `Unknown cron task: ${taskName}`\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName,\n reason: 'unknown_task',\n error,\n })\n break\n }\n this.ackMutation(\n ws,\n payload.requestId,\n execution.success\n ? { ok: true, taskName }\n : { ok: false, taskName, reason: 'failed', error: execution.error },\n )\n break\n }\n\n case MSG.CRON_PAUSE:\n case MSG.CRON_RESUME: {\n const taskName = payload.taskName as string\n if (!taskName) {\n const error = 'Missing taskName'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, { ok: false, reason: 'failed', error })\n return\n }\n // Same rejection contract as CRON_TRIGGER: a name the scheduler does\n // not know must never ack ok — the UPDATE below would match zero rows\n // and the receipt would report a state flip that never happened.\n const known =\n this.sql.exec(`SELECT name FROM cron_tasks WHERE name = ?`, taskName).toArray().length > 0\n if (!known) {\n const error = `Unknown cron task: ${taskName}`\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName,\n reason: 'unknown_task',\n error,\n })\n break\n }\n const paused = type === MSG.CRON_PAUSE\n this.sql.exec(`UPDATE cron_tasks SET paused = ? WHERE name = ?`, paused ? 1 : 0, taskName)\n if (!paused) this.scheduleNextAlarm()\n this.broadcastStatus()\n this.ackMutation(ws, payload.requestId, { ok: true, taskName })\n break\n }\n\n case MSG.CRON_TASKS: {\n this.sendTo(ws, {\n type: MSG.CRON_TASKS,\n payload: { tasks: this.getTaskStates() },\n })\n break\n }\n\n case MSG.CRON_HISTORY: {\n const limit = (payload.limit as number) ?? 50\n this.sendTo(ws, {\n type: MSG.CRON_HISTORY,\n payload: { history: this.getRecentHistory(limit) },\n })\n break\n }\n\n default:\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: `Unknown cron message type: ${type}` },\n })\n }\n }\n\n protected async onAlarm(): Promise<void> {\n this.ensureInitialized()\n const now = new Date()\n\n // Find all tasks due to run\n const tasks = this.sql\n .exec(\n `SELECT * FROM cron_tasks WHERE paused = 0 AND (next_run_at IS NULL OR next_run_at <= ?)`,\n now.toISOString(),\n )\n .toArray()\n\n for (const row of tasks) {\n const task = row as {\n name: string\n interval_minutes: number | null\n schedule: string | null\n timezone: string | null\n }\n await this.executeTask(task.name)\n }\n\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // Task Execution\n // ==========================================================================\n\n private async executeTask(taskName: string): Promise<CronExecution | null> {\n // Resolve the configured task before invoking application code. A manual\n // trigger must not turn an arbitrary caller-provided name into a reported\n // successful execution.\n const taskRow = this.sql\n .exec(`SELECT interval_minutes, schedule, timezone FROM cron_tasks WHERE name = ?`, taskName)\n .toArray()[0] as\n | { interval_minutes: number | null; schedule: string | null; timezone: string | null }\n | undefined\n if (!taskRow) return null\n\n const startedAt = new Date().toISOString()\n const start = Date.now()\n let success = true\n let error: string | undefined\n\n try {\n await this.onTask(taskName)\n } catch (e) {\n success = false\n error = e instanceof Error ? e.message : String(e)\n console.error(`[CronRoom] Task \"${taskName}\" failed:`, e)\n }\n\n const durationMs = Date.now() - start\n const completedAt = new Date().toISOString()\n\n // Record execution\n this.sql.exec(\n `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n taskName,\n startedAt,\n completedAt,\n success ? 1 : 0,\n durationMs,\n error ?? null,\n completedAt,\n )\n\n // Update last_run_at and compute next_run_at\n const nextRunAt = computeNextRunAt(taskRow, new Date(completedAt))\n this.sql.exec(\n `UPDATE cron_tasks SET last_run_at = ?, next_run_at = ? WHERE name = ?`,\n completedAt,\n nextRunAt,\n taskName,\n )\n\n // Trim history to last 500 entries\n this.sql.exec(\n `DELETE FROM cron_history WHERE id NOT IN (SELECT id FROM cron_history ORDER BY id DESC LIMIT 500)`,\n )\n\n // Broadcast update to monitors\n this.broadcastStatus()\n return { taskName, startedAt, completedAt, success, durationMs, error }\n }\n\n // ==========================================================================\n // Scheduling\n // ==========================================================================\n\n private scheduleNextAlarm(): void {\n const now = Date.now()\n let earliestMs = Infinity\n\n const tasks = this.sql\n .exec(\n `SELECT interval_minutes, schedule, timezone, next_run_at FROM cron_tasks WHERE paused = 0`,\n )\n .toArray()\n\n for (const row of tasks) {\n const t = row as {\n interval_minutes: number | null\n schedule: string | null\n timezone: string | null\n next_run_at: string | null\n }\n let nextMs: number | null = null\n if (t.next_run_at) {\n nextMs = new Date(t.next_run_at).getTime()\n } else if (t.interval_minutes) {\n // Cold-start interval task — run on the next alarm.\n nextMs = now\n } else if (t.schedule && t.timezone) {\n // Cold-start cron task — find the next minute boundary that matches.\n const next = nextCronFire(t.schedule, t.timezone, new Date(now))\n if (next) nextMs = next.getTime()\n }\n if (nextMs != null && nextMs < earliestMs) {\n earliestMs = nextMs\n }\n }\n\n if (earliestMs < Infinity) {\n const alarmTime = Math.max(earliestMs, now + 1000) // At least 1s from now\n this.state.storage.setAlarm(alarmTime)\n }\n }\n\n // ==========================================================================\n // Helpers\n // ==========================================================================\n\n private getTaskStates(): Record<string, unknown>[] {\n return this.sql\n .exec(`SELECT * FROM cron_tasks`)\n .toArray()\n .map((row) => {\n const r = row as Record<string, unknown>\n return {\n name: r.name,\n intervalMinutes: r.interval_minutes,\n schedule: r.schedule,\n timezone: r.timezone,\n paused: r.paused === 1,\n lastRunAt: r.last_run_at,\n nextRunAt: r.next_run_at,\n }\n })\n }\n\n private getRecentHistory(limit: number): CronExecution[] {\n return this.sql\n .exec(`SELECT * FROM cron_history ORDER BY id DESC LIMIT ?`, limit)\n .toArray()\n .map((row) => {\n const r = row as Record<string, unknown>\n return {\n taskName: r.task_name as string,\n startedAt: r.started_at as string,\n completedAt: r.completed_at as string | null,\n success: r.success === 1,\n durationMs: r.duration_ms as number,\n error: r.error as string | undefined,\n }\n })\n }\n\n private broadcastStatus(): void {\n this.broadcast({\n type: MSG.CRON_STATUS,\n payload: {\n tasks: this.getTaskStates(),\n recentHistory: this.getRecentHistory(10),\n },\n })\n }\n\n /**\n * Receipt for a mutation frame, addressed only to its sender. Receipts\n * are opt-in by correlation id: frames without a `requestId` keep the\n * fire-and-forget contract, so the (untyped) `requestId` from the wire\n * is the single gate here rather than a check at every call site.\n * Frames go through the shared `serverBuild` constructors so the wire\n * shape is enforced by the protocol types, not re-spelled here.\n */\n private ackMutation(\n ws: WebSocket,\n requestId: unknown,\n result:\n | { ok: true; taskName: string }\n | {\n ok: false\n taskName?: string\n reason: 'read_only' | 'unknown_task' | 'failed'\n error?: string\n },\n ): void {\n if (typeof requestId !== 'string') return\n this.sendTo(\n ws,\n result.ok\n ? serverBuild.cronAckSuccess(requestId, result.taskName)\n : serverBuild.cronAckFailure(requestId, result.reason, result.error, result.taskName),\n )\n }\n\n // ==========================================================================\n // Lifecycle Hook (subclass implements)\n // ==========================================================================\n\n /**\n * Execute a scheduled task by name.\n * Called both by the alarm scheduler and manual trigger.\n */\n protected abstract onTask(taskName: string): void | Promise<void>\n}\n\n/**\n * Compute the next ISO timestamp the given task should run at, using\n * `from` as the reference moment.\n */\nfunction computeNextRunAt(\n task: { interval_minutes: number | null; schedule: string | null; timezone: string | null },\n from: Date,\n): string | null {\n if (task.interval_minutes) {\n return new Date(from.getTime() + task.interval_minutes * 60 * 1000).toISOString()\n }\n if (task.schedule && task.timezone) {\n const next = nextCronFire(task.schedule, task.timezone, from)\n return next ? next.toISOString() : null\n }\n return null\n}\n","/**\n * JobRoom — Per-app durable background-job execution Durable Object.\n *\n * Solves the \"long job dies when the response goes out\" problem on\n * Cloudflare Workers. `ctx.waitUntil` only gets 30s after a response;\n * jobs that need minutes-to-hours live here instead: rows in SQLite,\n * picked up by DO alarms (15-min wall budget per tick), resumable\n * across ticks via `ctx.continue` for the rare longer cases.\n *\n * Lifecycle: queued → running → succeeded | failed | canceled\n *\n * Crash recovery: if an isolate is recycled mid-run, the row stays at\n * `running`. On next init, rows older than ~16 min are either retried\n * (if attempts left) or marked failed.\n *\n * See the abstract `onJob` method below for the subclass contract.\n * Message types: job.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\nimport { ROLES } from '../../shared/roles'\nimport { serverBuild, type ServerMessage } from '../../shared/protocol/messages'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type JobStatus =\n | 'queued'\n | 'running'\n | 'succeeded'\n | 'failed'\n | 'canceled'\n\n/**\n * Job record exposed to `onJob` handlers and clients. Payloads are typed\n * as `unknown` at the SDK layer; subclasses narrow via a generic param.\n */\nexport interface Job<P = unknown> {\n id: string\n type: string\n status: JobStatus\n payload: P\n result?: unknown\n error?: string\n progress?: number\n progressMessage?: string\n attempts: number\n maxAttempts: number\n enqueuedAt: string\n startedAt?: string | null\n completedAt?: string | null\n enqueuedBy?: string | null\n /** Set when a previous run called `ctx.continue(state)`. */\n resumeFrom?: unknown\n}\n\nexport interface JobContext {\n /** Set progress (0..1) with an optional message. Broadcasts. */\n progress(value: number, message?: string): void\n /**\n * Save a resumable checkpoint and re-run `onJob` on the next alarm\n * with `job.resumeFrom = state`. The return value of `onJob` is\n * ignored once `continue` has been called.\n */\n continue(state: unknown, options?: { afterMs?: number }): void\n /**\n * Fires when a JOB_CANCEL arrives while this job is running in this\n * isolate. Cross-isolate cancels still mark the row `canceled` but\n * can't fire this signal.\n */\n signal: AbortSignal\n}\n\nexport interface JobRoomConfig {\n /** Default for jobs enqueued without explicit `maxAttempts`. Default 1 (no auto-retry). */\n defaultMaxAttempts?: number\n /** TTL for terminal rows (succeeded/failed/canceled) in ms. Default 24h. */\n retentionMs?: number\n /** Job count in the initial snapshot to a new subscriber. Default 100. */\n snapshotLimit?: number\n /** Delay before a failed attempt is retried, in ms. Default 1000. */\n retryBackoffMs?: number\n /** Re-check whether a connected user may mutate the queue. Defaults to member/admin roles. */\n authorizeWrite?: (user: UserAttachment) => boolean | Promise<boolean>\n /** Re-check whether a connected user may observe the queue. Defaults to authorizeWrite. */\n authorizeRead?: (user: UserAttachment) => boolean | Promise<boolean>\n}\n\ninterface JobAttachment extends UserAttachment {\n canWrite: boolean\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** 1 min past CF's 15-min alarm wall — anything older was killed, not running. */\nconst RUNNING_STALE_MS = 16 * 60 * 1000\nconst DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1000\nconst DEFAULT_SNAPSHOT_LIMIT = 100\n\n// ============================================================================\n// JobRoom\n// ============================================================================\n\nexport abstract class JobRoom<\n E = Record<string, unknown>,\n P = unknown,\n R = unknown,\n> extends BaseRoom<E> {\n private initialized = false\n private readonly defaultMaxAttempts: number\n private readonly retentionMs: number\n private readonly snapshotLimit: number\n private readonly retryBackoffMs: number\n private readonly authorizeWrite: (user: UserAttachment) => boolean | Promise<boolean>\n private readonly authorizeRead: (user: UserAttachment) => boolean | Promise<boolean>\n private broadcastQueue: Promise<void> = Promise.resolve()\n\n /** In-flight jobs in this isolate; lets same-isolate JOB_CANCEL fire the signal. */\n private readonly inFlight = new Map<string, AbortController>()\n\n /** Set by `ctx.continue` inside `executeJob`; reset before every run. */\n private continueState: { checkpoint: unknown; afterMs: number } | null = null\n\n constructor(state: DurableObjectState, env: unknown, config: JobRoomConfig = {}) {\n super(state, env)\n this.defaultMaxAttempts = Math.max(1, config.defaultMaxAttempts ?? 1)\n this.retentionMs = Math.max(0, config.retentionMs ?? DEFAULT_RETENTION_MS)\n this.snapshotLimit = Math.max(1, config.snapshotLimit ?? DEFAULT_SNAPSHOT_LIMIT)\n this.retryBackoffMs = Math.max(0, config.retryBackoffMs ?? 1000)\n this.authorizeWrite =\n config.authorizeWrite ?? ((user) => user.role === ROLES.MEMBER || user.role === ROLES.ADMIN)\n this.authorizeRead = config.authorizeRead ?? this.authorizeWrite\n }\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n status TEXT NOT NULL,\n payload TEXT,\n result TEXT,\n error TEXT,\n progress REAL,\n progress_message TEXT,\n checkpoint TEXT,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 1,\n enqueued_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n enqueued_by TEXT,\n next_run_at TEXT\n )\n `)\n\n this.sql.exec(\n `CREATE INDEX IF NOT EXISTS jobs_status_next_run ON jobs (status, next_run_at)`,\n )\n this.sql.exec(\n `CREATE INDEX IF NOT EXISTS jobs_completed_at ON jobs (completed_at)`,\n )\n\n this.recoverStuckRunning()\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // BaseRoom Lifecycle\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected async onConnect(ws: WebSocket, user: UserAttachment): Promise<JobAttachment> {\n this.ensureInitialized()\n const { canRead, canWrite } = await this.resolveAccess(user)\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n this.sendTo(ws, serverBuild.jobSnapshot(canRead ? this.getRecentJobs(this.snapshotLimit) : []))\n return { ...user, canWrite }\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n this.ensureInitialized()\n const { type, payload } = message as {\n type: string\n payload: Record<string, unknown>\n }\n\n if (type === MSG.JOB_ENQUEUE || type === MSG.JOB_CANCEL || type === MSG.JOB_RETRY) {\n const attachment = user as JobAttachment\n const { canWrite } = await this.resolveAccess(user)\n if (attachment.canWrite !== canWrite) {\n attachment.canWrite = canWrite\n ws.serializeAttachment(attachment)\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n } else if (!canWrite) {\n // Repeat the denial so a newly attempted enqueue rejects immediately.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite: false } })\n }\n if (!canWrite) {\n this.sendTo(ws, serverBuild.error('Write access denied: job queue requires member access'))\n return\n }\n }\n\n switch (type) {\n case MSG.JOB_ENQUEUE: {\n const requestId = String(payload.requestId ?? '')\n const jobType = String(payload.type ?? '')\n if (!jobType) {\n this.sendTo(ws, serverBuild.error('JOB_ENQUEUE missing type'))\n return\n }\n const job = this.insertJob({\n type: jobType,\n payload: payload.payload,\n maxAttempts:\n typeof payload.maxAttempts === 'number'\n ? Math.max(1, Math.floor(payload.maxAttempts))\n : undefined,\n enqueuedBy: user.userId,\n })\n // Send the requestId-echoing ack only to the enqueuer (so their\n // pending promise resolves) and broadcast the same job *without*\n // the requestId to all other subscribers (so they upsert into\n // their `jobs` list without seeing somebody else's correlation id).\n this.sendTo(ws, serverBuild.jobEnqueued(job, requestId))\n this.broadcast(serverBuild.jobEnqueued(job), ws)\n this.scheduleNextAlarm()\n break\n }\n\n case MSG.JOB_CANCEL: {\n const jobId = String(payload.jobId ?? '')\n if (!jobId) {\n this.sendTo(ws, serverBuild.error('JOB_CANCEL missing jobId'))\n return\n }\n this.cancelJob(jobId)\n break\n }\n\n case MSG.JOB_RETRY: {\n const jobId = String(payload.jobId ?? '')\n if (!jobId) {\n this.sendTo(ws, serverBuild.error('JOB_RETRY missing jobId'))\n return\n }\n this.retryJob(jobId)\n break\n }\n\n default:\n this.sendTo(ws, serverBuild.error(`Unknown job message type: ${type}`))\n }\n }\n\n protected async onAlarm(): Promise<void> {\n this.ensureInitialized()\n // A recycled isolate can leave a recently-started row in `running`.\n // Initialization arms a recovery alarm for the exact stale boundary;\n // re-check here so that row becomes runnable even if no request wakes the\n // room in the meantime.\n this.recoverStuckRunning()\n await this.drainDueJobs()\n this.pruneExpired()\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // HTTP — internal enqueue path for cross-isolate callers\n // ==========================================================================\n\n /**\n * One internal endpoint, `POST /enqueue`, used by the `enqueueJob`\n * helper at the bottom of this file. The room is not meant to be\n * reachable from the public internet; callers route through the app\n * worker (or call `this.enqueue(...)` directly from a subclass).\n *\n * Subclasses overriding `onRequest` should call `super.onRequest(req)`\n * for paths they don't handle.\n */\n protected async onRequest(request: Request): Promise<Response> {\n this.ensureInitialized()\n const url = new URL(request.url)\n if (request.method !== 'POST' || !url.pathname.endsWith('/enqueue')) {\n return new Response('Not Found', { status: 404 })\n }\n const body = (await request.json().catch(() => null)) as\n | { type?: unknown; payload?: unknown; maxAttempts?: unknown; enqueuedBy?: unknown }\n | null\n if (!body || typeof body.type !== 'string' || !body.type) {\n return new Response(JSON.stringify({ error: 'type required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const job = this.insertJob({\n type: body.type,\n payload: body.payload,\n maxAttempts:\n typeof body.maxAttempts === 'number'\n ? Math.max(1, Math.floor(body.maxAttempts))\n : undefined,\n enqueuedBy: typeof body.enqueuedBy === 'string' ? body.enqueuedBy : undefined,\n })\n this.broadcast(serverBuild.jobEnqueued(job))\n this.scheduleNextAlarm()\n return new Response(JSON.stringify({ job }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // ==========================================================================\n // Public enqueue surface (server-side callers in the same isolate)\n // ==========================================================================\n\n /**\n * Synchronously persist a new job and arm the alarm. Returns the new\n * job. This is the in-isolate path — useful for a subclass `onJob`\n * that wants to chain follow-up work without paying a fetch hop:\n *\n * protected async onJob(job: Job, ctx: JobContext) {\n * // ... do work ...\n * if (needsFollowup) this.enqueue('followup', { parent: job.id })\n * }\n *\n * Workers code that lives outside the DO isolate (HTTP routes, cron\n * tasks, server actions) cannot call this directly — they must go\n * through the `enqueueJob` helper at the bottom of this file, which\n * routes via the DO's `/enqueue` HTTP endpoint.\n */\n public enqueue(\n type: string,\n payload?: unknown,\n options?: { maxAttempts?: number; enqueuedBy?: string },\n ): Job {\n this.ensureInitialized()\n const job = this.insertJob({\n type,\n payload,\n maxAttempts: options?.maxAttempts,\n enqueuedBy: options?.enqueuedBy,\n })\n this.broadcast(serverBuild.jobEnqueued(job))\n this.scheduleNextAlarm()\n return job\n }\n\n // ==========================================================================\n // Mutation helpers\n // ==========================================================================\n\n private insertJob(input: {\n type: string\n payload?: unknown\n maxAttempts?: number\n enqueuedBy?: string\n }): Job {\n const id = crypto.randomUUID()\n const enqueuedAt = new Date().toISOString()\n const maxAttempts = Math.max(1, input.maxAttempts ?? this.defaultMaxAttempts)\n const payloadJson = input.payload === undefined ? null : safeStringify(input.payload)\n\n this.sql.exec(\n `INSERT INTO jobs (id, type, status, payload, attempts, max_attempts, enqueued_at, enqueued_by, next_run_at)\n VALUES (?, ?, 'queued', ?, 0, ?, ?, ?, ?)`,\n id,\n input.type,\n payloadJson,\n maxAttempts,\n enqueuedAt,\n input.enqueuedBy ?? null,\n enqueuedAt,\n )\n\n return this.getJobById(id)!\n }\n\n private cancelJob(jobId: string): void {\n const row = this.sql\n .exec(`SELECT status FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as { status: JobStatus } | undefined\n if (!row) return\n // Terminal statuses are already resolved.\n if (row.status === 'succeeded' || row.status === 'failed' || row.status === 'canceled') {\n return\n }\n const completedAt = new Date().toISOString()\n // We intentionally do NOT clear `progress` / `progress_message` here:\n // \"canceled at 60%\" is useful diagnostic info, unlike succeeded/failed\n // where the terminal state already implies completion. `checkpoint`\n // is internal scaffolding that has no purpose after cancellation.\n this.sql.exec(\n `UPDATE jobs\n SET status = 'canceled',\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL\n WHERE id = ?`,\n completedAt,\n jobId,\n )\n // Best-effort: fire the AbortSignal if the job is running in this isolate.\n const controller = this.inFlight.get(jobId)\n if (controller) controller.abort()\n const job = this.getJobById(jobId)\n if (job) this.broadcast(serverBuild.jobCanceled(job))\n }\n\n private retryJob(jobId: string): void {\n const row = this.sql\n .exec(`SELECT status, attempts, max_attempts FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as\n | { status: JobStatus; attempts: number; max_attempts: number }\n | undefined\n if (!row) return\n // Retry is only meaningful for terminal failures / cancels. Live jobs\n // are already on track; succeeded jobs should be re-enqueued fresh.\n if (row.status !== 'failed' && row.status !== 'canceled') return\n\n // Lift max_attempts so the manual retry actually has a budget to run\n // even if the previous attempts exhausted the original limit.\n const newMax = Math.max(row.max_attempts, row.attempts + 1)\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n max_attempts = ?,\n error = NULL,\n result = NULL,\n completed_at = NULL,\n next_run_at = ?\n WHERE id = ?`,\n newMax,\n new Date().toISOString(),\n jobId,\n )\n const job = this.getJobById(jobId)\n if (job) this.broadcast(serverBuild.jobRetried(job))\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // Alarm / execution\n // ==========================================================================\n\n /**\n * Drain due jobs in FIFO order. All due jobs share the alarm's\n * 15-min wall budget (a CF property, not a per-job allocation).\n */\n private async drainDueJobs(): Promise<void> {\n // Freeze the cutoff at the start: rows whose `next_run_at` is\n // written during this drain (retries, `ctx.continue`) fall out\n // and pick up on the next alarm with a fresh budget. Without\n // this, `ctx.continue({ afterMs: 0 })` re-enters the same tick.\n const cutoff = new Date().toISOString()\n while (true) {\n const row = this.sql\n .exec(\n `SELECT * FROM jobs\n WHERE status = 'queued'\n AND (next_run_at IS NULL OR next_run_at <= ?)\n ORDER BY enqueued_at ASC\n LIMIT 1`,\n cutoff,\n )\n .toArray()[0] as unknown as JobRow | undefined\n if (!row) return\n await this.executeJob(row)\n }\n }\n\n private async executeJob(row: JobRow): Promise<void> {\n const startedAt = row.started_at ?? new Date().toISOString()\n const attemptsBefore = row.attempts\n const attempt = attemptsBefore + 1\n\n this.sql.exec(\n `UPDATE jobs SET status = 'running', started_at = ?, attempts = ? WHERE id = ?`,\n startedAt,\n attempt,\n row.id,\n )\n\n const controller = new AbortController()\n this.inFlight.set(row.id, controller)\n this.continueState = null\n\n const job: Job = {\n ...rowToJobView(row),\n status: 'running',\n attempts: attempt,\n startedAt,\n }\n\n let outcome: 'succeeded' | 'failed' | 'continued' = 'succeeded'\n let result: unknown\n let errorMessage: string | undefined\n\n try {\n const ctx = this.makeContext(row.id, controller.signal)\n result = await this.onJob(job as Job<P>, ctx)\n if (this.continueState) outcome = 'continued'\n } catch (e) {\n outcome = 'failed'\n errorMessage = e instanceof Error ? e.message : String(e)\n console.error(`[JobRoom] Job \"${row.type}\" (${row.id}) failed:`, e)\n } finally {\n this.inFlight.delete(row.id)\n }\n\n // Cancellation racing the finish: if a cancel landed while we were\n // running, the row is now 'canceled' and we should not overwrite it.\n const currentStatus = this.readStatus(row.id)\n if (currentStatus === 'canceled') {\n this.continueState = null\n return\n }\n\n if (outcome === 'continued') {\n const state = this.continueState!\n this.continueState = null\n const nextRunAt = new Date(Date.now() + Math.max(0, state.afterMs)).toISOString()\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n checkpoint = ?,\n next_run_at = ?\n WHERE id = ?`,\n safeStringify(state.checkpoint),\n nextRunAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobProgress(updated))\n return\n }\n\n const completedAt = new Date().toISOString()\n\n if (outcome === 'succeeded') {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'succeeded',\n result = ?,\n completed_at = ?,\n error = NULL,\n checkpoint = NULL,\n next_run_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n result === undefined ? null : safeStringify(result),\n completedAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobSucceeded(updated))\n return\n }\n\n // Failure path. Retry if we have attempts left.\n if (attempt < row.max_attempts) {\n // Back off the retry by `retryBackoffMs` so a tight crash-loop\n // doesn't burn the alarm wall budget. Clear progress so the next\n // attempt starts fresh — a job that died at 80% should not show\n // \"queued at 80%\" until the retry handler reports its own progress.\n const nextRunAt = new Date(Date.now() + this.retryBackoffMs).toISOString()\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n error = ?,\n next_run_at = ?,\n started_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n errorMessage ?? 'unknown error',\n nextRunAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobRetried(updated))\n return\n }\n\n this.sql.exec(\n `UPDATE jobs\n SET status = 'failed',\n error = ?,\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n errorMessage ?? 'unknown error',\n completedAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobFailed(updated))\n }\n\n private makeContext(jobId: string, signal: AbortSignal): JobContext {\n return {\n progress: (value: number, message?: string) => {\n const clamped = Math.max(0, Math.min(1, value))\n this.sql.exec(\n `UPDATE jobs SET progress = ?, progress_message = ? WHERE id = ?`,\n clamped,\n message ?? null,\n jobId,\n )\n const updated = this.getJobById(jobId)\n if (updated) this.broadcast(serverBuild.jobProgress(updated))\n },\n continue: (state: unknown, options?: { afterMs?: number }) => {\n // Last call wins if invoked multiple times, matching the intuition\n // that handlers can revise their checkpoint as work progresses.\n this.continueState = {\n checkpoint: state,\n afterMs: Math.max(0, options?.afterMs ?? 0),\n }\n },\n signal,\n }\n }\n\n private readStatus(jobId: string): JobStatus | null {\n const row = this.sql\n .exec(`SELECT status FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as { status: JobStatus } | undefined\n return row?.status ?? null\n }\n\n /** Reauthorize every live event so role revocation does not leave a stale reader. */\n protected broadcast(message: ServerMessage, exclude?: WebSocket): void {\n this.broadcastQueue = this.broadcastQueue.then(async () => {\n await Promise.all(\n this.getWebSockets().map(async (ws) => {\n if (ws === exclude) return\n try {\n const user = this.getAttachment(ws)\n if (!user) return\n if (await this.authorizeRead(user)) this.sendTo(ws, message)\n } catch {\n // Authorization failures deny this event without poisoning the queue.\n }\n }),\n )\n })\n this.state.waitUntil(this.broadcastQueue)\n }\n\n private async resolveAccess(\n user: UserAttachment,\n ): Promise<{ canRead: boolean; canWrite: boolean }> {\n const canRead = await this.authorizeRead(user)\n if (!canRead) return { canRead: false, canWrite: false }\n const canWrite = this.authorizeRead === this.authorizeWrite || (await this.authorizeWrite(user))\n return { canRead: true, canWrite }\n }\n\n // ==========================================================================\n // Scheduling / recovery / pruning\n // ==========================================================================\n\n private scheduleNextAlarm(): void {\n const queued = this.sql\n .exec(\n `SELECT next_run_at FROM jobs\n WHERE status = 'queued'\n ORDER BY (next_run_at IS NULL) ASC, next_run_at ASC\n LIMIT 1`,\n )\n .toArray()[0] as { next_run_at: string | null } | undefined\n\n const now = Date.now()\n const targets: number[] = []\n if (queued) {\n const queuedAt = queued.next_run_at ? new Date(queued.next_run_at).getTime() : now\n targets.push(Number.isFinite(queuedAt) ? Math.max(now, queuedAt) : now)\n }\n\n const running = this.sql\n .exec(\n `SELECT started_at FROM jobs\n WHERE status = 'running'\n ORDER BY started_at ASC\n LIMIT 1`,\n )\n .toArray()[0] as { started_at: string | null } | undefined\n if (running) {\n const startedAt = running.started_at ? new Date(running.started_at).getTime() : Number.NaN\n targets.push(Number.isFinite(startedAt) ? Math.max(now, startedAt + RUNNING_STALE_MS) : now)\n }\n\n if (targets.length === 0) return\n // 50ms floor so we don't thrash the alarm with sub-tick re-arms.\n this.state.storage.setAlarm(Math.max(Math.min(...targets), now + 50))\n }\n\n private recoverStuckRunning(): void {\n const cutoff = new Date(Date.now() - RUNNING_STALE_MS).toISOString()\n const stuck = this.sql\n .exec(\n `SELECT id, attempts, max_attempts FROM jobs\n WHERE status = 'running' AND (started_at IS NULL OR started_at <= ?)`,\n cutoff,\n )\n .toArray() as { id: string; attempts: number; max_attempts: number }[]\n\n for (const r of stuck) {\n if (r.attempts >= r.max_attempts) {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'failed',\n error = 'Isolate recycled or wall time exceeded',\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL\n WHERE id = ?`,\n new Date().toISOString(),\n r.id,\n )\n } else {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n started_at = NULL,\n next_run_at = ?,\n error = 'Previous attempt killed mid-run; retrying',\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n new Date().toISOString(),\n r.id,\n )\n }\n }\n }\n\n private pruneExpired(): void {\n if (this.retentionMs === 0) return\n const cutoff = new Date(Date.now() - this.retentionMs).toISOString()\n this.sql.exec(\n `DELETE FROM jobs\n WHERE status IN ('succeeded', 'failed', 'canceled')\n AND completed_at IS NOT NULL\n AND completed_at < ?`,\n cutoff,\n )\n }\n\n // ==========================================================================\n // Read helpers\n // ==========================================================================\n\n private getRecentJobs(limit: number): Job[] {\n const rows = this.sql\n .exec(\n `SELECT * FROM jobs\n ORDER BY (status IN ('queued','running')) DESC, enqueued_at DESC\n LIMIT ?`,\n limit,\n )\n .toArray() as unknown as JobRow[]\n return rows.map(rowToJobView)\n }\n\n private getJobById(id: string): Job | null {\n const row = this.sql.exec(`SELECT * FROM jobs WHERE id = ?`, id).toArray()[0] as unknown as\n | JobRow\n | undefined\n return row ? rowToJobView(row) : null\n }\n\n // ==========================================================================\n // Handler — subclass implements\n // ==========================================================================\n\n /**\n * Run a single job. Return a value for success; throw for failure\n * (retried until attempts >= maxAttempts). Use `ctx.progress` for\n * progress, `ctx.continue(state)` to span multiple alarms, and\n * `ctx.signal` to honor client cancellations.\n */\n protected abstract onJob(job: Job<P>, ctx: JobContext): Promise<R | void> | R | void\n}\n\n// ============================================================================\n// Cross-isolate enqueue helper\n// ============================================================================\n\n/**\n * Enqueue a job from worker code that has the DO namespace. Routed by\n * `roomId` (typically `app:<APP_NAME>`; pass different ids for sharded\n * queues). Returns the new jobId; throws on failure.\n *\n * const jobId = await enqueueJob(\n * env.JOB_ROOMS,\n * `app:${env.APP_NAME}`,\n * 'ai-summarize',\n * { text },\n * { maxAttempts: 3 },\n * )\n */\nexport async function enqueueJob(\n namespace: DurableObjectNamespace,\n roomId: string,\n type: string,\n payload?: unknown,\n options?: { maxAttempts?: number; enqueuedBy?: string },\n): Promise<string> {\n const stub = namespace.get(namespace.idFromName(roomId))\n const res = await stub.fetch(\n new Request('https://job-room.internal/enqueue', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n type,\n payload,\n maxAttempts: options?.maxAttempts,\n enqueuedBy: options?.enqueuedBy,\n }),\n }),\n )\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`enqueueJob failed: ${res.status} ${text}`)\n }\n const data = (await res.json()) as { job?: { id?: string } }\n if (!data.job?.id) throw new Error('enqueueJob: malformed response')\n return data.job.id\n}\n\n// ============================================================================\n// Internals\n// ============================================================================\n\ninterface JobRow {\n id: string\n type: string\n status: JobStatus\n payload: string | null\n result: string | null\n error: string | null\n progress: number | null\n progress_message: string | null\n checkpoint: string | null\n attempts: number\n max_attempts: number\n enqueued_at: string\n started_at: string | null\n completed_at: string | null\n enqueued_by: string | null\n next_run_at: string | null\n}\n\nfunction rowToJobView(row: JobRow): Job {\n return {\n id: row.id,\n type: row.type,\n status: row.status,\n payload: row.payload == null ? undefined : JSON.parse(row.payload),\n result: row.result == null ? undefined : JSON.parse(row.result),\n error: row.error ?? undefined,\n progress: row.progress ?? undefined,\n progressMessage: row.progress_message ?? undefined,\n attempts: row.attempts,\n maxAttempts: row.max_attempts,\n enqueuedAt: row.enqueued_at,\n startedAt: row.started_at,\n completedAt: row.completed_at,\n enqueuedBy: row.enqueued_by,\n resumeFrom: row.checkpoint == null ? undefined : JSON.parse(row.checkpoint),\n }\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n // Fallback so cyclic payloads don't break the insert.\n return JSON.stringify({ __unserializable__: String(value) })\n }\n}\n","/**\n * DO Manifest — Dynamic Durable Object binding declarations.\n *\n * Apps export a `__DO_MANIFEST__` array in their worker.ts.\n * The CLI extracts it and sends it to the deploy worker,\n * which uses it to generate dynamic CF API bindings and migrations.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nexport interface DOManifestEntry {\n /** CF binding name, e.g. 'RECORD_ROOMS' */\n binding: string\n /** Exported class name, e.g. 'AppRecordRoom' */\n className: string\n /** Whether this DO uses SQLite storage */\n sqlite: boolean\n}\n\nexport type DOManifest = DOManifestEntry[]\n\n/**\n * Utility type: auto-generates Env bindings from a manifest.\n *\n * @example\n * const manifest = [\n * { binding: 'RECORD_ROOMS', className: 'AppRecordRoom', sqlite: true },\n * ] as const satisfies DOManifest\n *\n * type Env = BaseEnv & DOBindings<typeof manifest>\n * // => { RECORD_ROOMS: DurableObjectNamespace }\n */\nexport type DOBindings<T extends readonly DOManifestEntry[]> = {\n [K in T[number]['binding']]: DurableObjectNamespace\n}\n\n/** Default manifest for apps that don't declare one */\nexport const DEFAULT_DO_MANIFEST: DOManifest = [\n { binding: 'RECORD_ROOMS', className: 'AppRecordRoom', sqlite: true },\n { binding: 'YJS_ROOMS', className: 'AppYjsRoom', sqlite: true },\n]\n\n/**\n * Shape-validate a DO manifest received over the wire (e.g. from the CLI's\n * deploy form-field). Without this, malformed input gets passed straight to\n * `deployToWfP`'s `.filter(...).map(...)` chain and crashes the route mid-deploy.\n *\n * Mirrors the contract of `validateBindingManifest` for non-DO bindings.\n */\nexport function validateDoManifest(\n manifest: unknown,\n): { valid: true; manifest: DOManifest } | { valid: false; reason: string } {\n if (!Array.isArray(manifest)) {\n return { valid: false, reason: 'doManifest must be an array' }\n }\n const seen = new Set<string>()\n for (const entry of manifest) {\n if (!entry || typeof entry !== 'object') {\n return { valid: false, reason: `doManifest entry must be an object (got ${entry === null ? 'null' : typeof entry})` }\n }\n const e = entry as Record<string, unknown>\n if (typeof e.binding !== 'string' || !e.binding) {\n return { valid: false, reason: `doManifest entry missing 'binding' (string)` }\n }\n if (typeof e.className !== 'string' || !e.className) {\n return { valid: false, reason: `doManifest entry \"${e.binding}\" missing 'className' (string)` }\n }\n if (typeof e.sqlite !== 'boolean') {\n return { valid: false, reason: `doManifest entry \"${e.binding}\" 'sqlite' must be a boolean` }\n }\n if (seen.has(e.binding)) {\n return { valid: false, reason: `doManifest has duplicate binding \"${e.binding}\"` }\n }\n seen.add(e.binding)\n }\n return { valid: true, manifest: manifest as DOManifest }\n}\n","/**\n * Binding Manifest — non-DO bindings declared by an app's wrangler.toml that\n * the deploy-worker should pass through to Cloudflare's WfP upload API.\n *\n * Apps don't export a `__BINDING_MANIFEST__`; the CLI extracts these from the\n * normalized vite/wrangler output config at deploy time. This file just owns\n * the types + validation so both sides (CLI client and deploy-worker server)\n * agree on the shape.\n */\n/// <reference types=\"@cloudflare/workers-types\" />\n\n/**\n * A single non-DO binding the app declares. Mirrors CF's WfP binding API.\n *\n * Provisionable resources (d1, kv_namespace, vectorize, r2_bucket, queue) accept\n * the literal string `\"auto\"` in their ID field to request platform-side\n * provisioning at deploy time. When `\"auto\"` is used, the deploy-worker creates\n * the resource on the platform CF account, persists the resulting CF ID in the\n * app registry, and substitutes the real ID before forwarding to WfP. The\n * sentinel sticks around in this type because:\n * 1. `wrangler` parsing requires a non-empty string in the id field\n * 2. The CLI passes the unresolved manifest through to the deploy-worker\n * 3. The deploy-worker is the only side with CF API credentials\n *\n * Companion fields (`database_name`, `title`, `dimensions`, `metric`) are only\n * used when `\"auto\"` is set — they tell the provisioner how to create the\n * resource. After provisioning these fields are still present on the wire but\n * ignored by WfP.\n */\nexport type CustomBinding =\n | {\n type: 'vectorize'\n name: string\n /** Either a pre-existing index name or the literal `\"auto\"`. */\n index_name: string\n /** Required when `index_name === \"auto\"`. */\n dimensions?: number\n /** Required when `index_name === \"auto\"`. */\n metric?: 'cosine' | 'euclidean' | 'dot-product'\n }\n | { type: 'ai'; name: string }\n | {\n type: 'r2_bucket'\n name: string\n /** Either a pre-existing bucket name or the literal `\"auto\"`. */\n bucket_name: string\n }\n | {\n type: 'kv_namespace'\n name: string\n /** Either a pre-existing KV namespace ID or the literal `\"auto\"`. */\n namespace_id: string\n /** Required when `namespace_id === \"auto\"`. Human-readable namespace title. */\n title?: string\n }\n | {\n type: 'd1'\n name: string\n /** Either a pre-existing D1 database UUID or the literal `\"auto\"`. */\n id: string\n /** Required when `id === \"auto\"`. Human-readable database name. */\n database_name?: string\n }\n | {\n type: 'queue'\n name: string\n /** Either a pre-existing queue name or the literal `\"auto\"`. */\n queue_name: string\n }\n | { type: 'browser_rendering'; name: string }\n | { type: 'analytics_engine'; name: string; dataset?: string }\n | { type: 'hyperdrive'; name: string; id: string }\n | {\n type: 'ratelimit'\n name: string\n namespace_id: string\n simple: { limit: number; period: 10 | 60 }\n }\n\nexport type CustomBindingManifest = CustomBinding[]\n\n/** Sentinel string in an ID field that requests platform-side provisioning. */\nexport const AUTO_PROVISION_SENTINEL = 'auto'\n\n/** Binding types whose ID field accepts the `\"auto\"` sentinel for provisioning. */\nexport const AUTO_PROVISIONABLE_TYPES = new Set([\n 'd1',\n 'kv_namespace',\n 'vectorize',\n 'r2_bucket',\n 'queue',\n])\n\n/** Vectorize metric values accepted by CF when creating an index. */\nconst VECTORIZE_METRICS = new Set(['cosine', 'euclidean', 'dot-product'])\n\n/**\n * True if a binding has the `\"auto\"` sentinel in its primary ID field. Used by\n * the deploy-worker to decide which entries need provisioning and by the\n * validator to enforce companion-field requirements.\n */\nexport function isAutoProvision(b: CustomBinding): boolean {\n switch (b.type) {\n case 'd1':\n return b.id === AUTO_PROVISION_SENTINEL\n case 'kv_namespace':\n return b.namespace_id === AUTO_PROVISION_SENTINEL\n case 'vectorize':\n return b.index_name === AUTO_PROVISION_SENTINEL\n case 'r2_bucket':\n return b.bucket_name === AUTO_PROVISION_SENTINEL\n case 'queue':\n return b.queue_name === AUTO_PROVISION_SENTINEL\n default:\n return false\n }\n}\n\n/** Binding `type` values an app is allowed to declare. */\nexport const ALLOWED_BINDING_TYPES = new Set([\n 'vectorize',\n 'ai',\n 'r2_bucket',\n 'kv_namespace',\n 'd1',\n 'queue',\n 'browser_rendering',\n 'analytics_engine',\n 'hyperdrive',\n 'ratelimit',\n])\n\n/**\n * Binding NAMES the SDK reserves on every app — apps may not redeclare them.\n *\n * Includes:\n * - Static-asset + service bindings the platform sets up automatically.\n * - SDK-managed env (auth, identity, owner JWT).\n * - The auto-attached cost-tracking AE dataset (`USAGE_EVENTS`).\n *\n * DO binding names (RECORD_ROOMS, YJS_ROOMS, etc.) are NOT in this set\n * because they live in a separate manifest (`__DO_MANIFEST__`).\n */\nexport const RESERVED_BINDING_NAMES = new Set([\n 'ASSETS',\n 'PLATFORM_WORKER',\n 'API_WORKER',\n 'DEEPSPACE_APP_ID',\n 'DEEPSPACE_RESOURCE_ID',\n 'DEEPSPACE_RELEASE_OPERATION_ID',\n 'APP_NAME',\n 'OWNER_USER_ID',\n 'AUTH_JWT_PUBLIC_KEY',\n 'AUTH_JWT_ISSUER',\n 'AUTH_WORKER_URL',\n 'APP_IDENTITY_TOKEN',\n 'APP_OWNER_JWT',\n 'USAGE_EVENTS',\n])\n\n/**\n * Per-binding validation error. `binding` is undefined for top-level\n * shape failures (e.g. manifest is not an array).\n */\nexport interface ValidationError {\n binding?: CustomBinding\n reason: string\n}\n\n/**\n * Validate a binding manifest. Returns errors; an empty array means valid.\n *\n * Used both client-side (CLI) for friendly fail-fast and server-side\n * (deploy-worker) as a security boundary — apps can't sneak in reserved\n * binding names by editing the wire format.\n */\nexport function validateBindingManifest(\n manifest: unknown,\n): { valid: true; bindings: CustomBindingManifest } | { valid: false; errors: ValidationError[] } {\n if (!Array.isArray(manifest)) {\n return { valid: false, errors: [{ reason: 'Manifest must be an array' }] }\n }\n const errors: ValidationError[] = []\n const seenNames = new Set<string>()\n for (const entry of manifest) {\n if (!entry || typeof entry !== 'object') {\n errors.push({\n reason: `Entry must be an object (got ${entry === null ? 'null' : typeof entry})`,\n })\n continue\n }\n const e = entry as Record<string, unknown>\n const type = e.type\n const name = e.name\n if (typeof type !== 'string' || !ALLOWED_BINDING_TYPES.has(type)) {\n errors.push({\n reason: `Disallowed binding type: ${typeof type === 'string' ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(', ')}`,\n })\n continue\n }\n if (typeof name !== 'string' || !name) {\n errors.push({ reason: `Binding of type \"${type}\" missing 'name' (string)` })\n continue\n }\n if (RESERVED_BINDING_NAMES.has(name)) {\n errors.push({ reason: `Binding name \"${name}\" is reserved by the SDK` })\n continue\n }\n if (seenNames.has(name)) {\n errors.push({ reason: `Duplicate binding name \"${name}\"` })\n continue\n }\n seenNames.add(name)\n // Now safely typed: type is in ALLOWED_BINDING_TYPES + name is a string.\n const binding = entry as CustomBinding\n const fieldErr = requiredFieldError(binding)\n if (fieldErr) errors.push({ binding, reason: fieldErr })\n }\n if (errors.length) return { valid: false, errors }\n return { valid: true, bindings: manifest as CustomBindingManifest }\n}\n\nfunction requiredFieldError(b: CustomBinding): string | null {\n switch (b.type) {\n case 'vectorize': {\n if (!b.index_name) return `vectorize binding \"${b.name}\" missing index_name (or \"auto\")`\n if (b.index_name === AUTO_PROVISION_SENTINEL) {\n // Auto-provisioning needs the index shape upfront — CF requires both\n // dimensions and metric at index-create time.\n if (typeof b.dimensions !== 'number' || b.dimensions <= 0) {\n return `vectorize binding \"${b.name}\" with index_name=\"auto\" requires \"dimensions\" (positive number)`\n }\n if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {\n return `vectorize binding \"${b.name}\" with index_name=\"auto\" requires \"metric\" (one of: ${[...VECTORIZE_METRICS].join(', ')})`\n }\n }\n return null\n }\n case 'r2_bucket':\n return b.bucket_name ? null : `r2_bucket binding \"${b.name}\" missing bucket_name (or \"auto\")`\n case 'kv_namespace': {\n if (!b.namespace_id)\n return `kv_namespace binding \"${b.name}\" missing namespace_id (or \"auto\")`\n if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {\n return `kv_namespace binding \"${b.name}\" with namespace_id=\"auto\" requires \"title\"`\n }\n return null\n }\n case 'd1': {\n if (!b.id) return `d1 binding \"${b.name}\" missing id (or \"auto\")`\n if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {\n return `d1 binding \"${b.name}\" with id=\"auto\" requires \"database_name\"`\n }\n return null\n }\n case 'queue':\n return b.queue_name ? null : `queue binding \"${b.name}\" missing queue_name (or \"auto\")`\n case 'hyperdrive':\n // Hyperdrive auto-provisioning is out of scope for v1 (needs upstream\n // connection-string management). Only pre-existing IDs accepted.\n if (!b.id) return `hyperdrive binding \"${b.name}\" missing id`\n if (b.id === AUTO_PROVISION_SENTINEL) {\n return `hyperdrive binding \"${b.name}\": auto-provisioning not yet supported (provide a real id)`\n }\n return null\n case 'ratelimit':\n if (!/^\\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {\n return `ratelimit binding \"${b.name}\" requires a positive integer namespace_id string`\n }\n if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {\n return `ratelimit binding \"${b.name}\" requires simple.limit (positive integer)`\n }\n if (b.simple.period !== 10 && b.simple.period !== 60) {\n return `ratelimit binding \"${b.name}\" requires simple.period (10 or 60)`\n }\n return null\n case 'ai':\n case 'browser_rendering':\n case 'analytics_engine':\n return null // no required fields beyond `name`\n default: {\n // Exhaustiveness guard — adding a new variant without a case fails here.\n const _exhaustive: never = b\n void _exhaustive\n return null\n }\n }\n}\n\n/**\n * Convert vite/wrangler's normalized config (from `.wrangler/deploy/config.json`)\n * into a CustomBindingManifest.\n *\n * Vite normalizes wrangler.toml into object/array structures with shapes like\n * `{ ai: { binding: 'AI' } }`, `{ vectorize: [{ binding, index_name }] }`,\n * etc. We extract each known shape with explicit field plucks (no broad\n * `as` casts) and return a flat array.\n */\nexport function bindingManifestFromOutputConfig(\n outputConfig: Record<string, unknown>,\n): CustomBindingManifest {\n const out: CustomBindingManifest = []\n\n // ai: single object\n const aiBinding = pluckString(outputConfig.ai, 'binding')\n if (aiBinding) out.push({ type: 'ai', name: aiBinding })\n\n // vectorize: array of { binding, index_name, dimensions?, metric? }\n // `dimensions` and `metric` are only required when index_name === \"auto\"\n // (auto-provisioning); the validator enforces that, so we just pluck both\n // here and let it decide.\n for (const v of asObjectArray(outputConfig.vectorize)) {\n const name = pluckString(v, 'binding')\n const index_name = pluckString(v, 'index_name')\n if (!name || !index_name) continue\n const dimensions = pluckNumber(v, 'dimensions')\n const metric = pluckString(v, 'metric')\n out.push({\n type: 'vectorize',\n name,\n index_name,\n ...(dimensions != null && { dimensions }),\n ...(metric &&\n (metric === 'cosine' || metric === 'euclidean' || metric === 'dot-product') && { metric }),\n })\n }\n\n // r2_buckets: array of { binding, bucket_name }\n for (const r of asObjectArray(outputConfig.r2_buckets)) {\n const name = pluckString(r, 'binding')\n const bucket_name = pluckString(r, 'bucket_name')\n if (name && bucket_name) out.push({ type: 'r2_bucket', name, bucket_name })\n }\n\n // kv_namespaces: array of { binding, id, title? }\n // `title` is only required when id === \"auto\"; pluck both here and let\n // the validator enforce the dependency.\n for (const k of asObjectArray(outputConfig.kv_namespaces)) {\n const name = pluckString(k, 'binding')\n const namespace_id = pluckString(k, 'id')\n if (!name || !namespace_id) continue\n const title = pluckString(k, 'title')\n out.push({\n type: 'kv_namespace',\n name,\n namespace_id,\n ...(title && { title }),\n })\n }\n\n // d1_databases: array of { binding, database_id, database_name? }\n // `database_name` is only required when database_id === \"auto\"; pluck both\n // and let the validator enforce.\n for (const d of asObjectArray(outputConfig.d1_databases)) {\n const name = pluckString(d, 'binding')\n const id = pluckString(d, 'database_id')\n if (!name || !id) continue\n const database_name = pluckString(d, 'database_name')\n out.push({\n type: 'd1',\n name,\n id,\n ...(database_name && { database_name }),\n })\n }\n\n // queues.producers: array of { binding, queue }\n const queues = isObject(outputConfig.queues) ? outputConfig.queues : null\n for (const q of asObjectArray(queues?.producers)) {\n const name = pluckString(q, 'binding')\n const queue_name = pluckString(q, 'queue')\n if (name && queue_name) out.push({ type: 'queue', name, queue_name })\n }\n\n // browser: { binding } (Cloudflare names this `browser` not `browser_rendering`)\n const browserBinding = pluckString(outputConfig.browser, 'binding')\n if (browserBinding) out.push({ type: 'browser_rendering', name: browserBinding })\n\n // analytics_engine_datasets: array of { binding, dataset? }\n for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {\n const name = pluckString(a, 'binding')\n const dataset = pluckString(a, 'dataset')\n if (name) out.push({ type: 'analytics_engine', name, dataset: dataset ?? undefined })\n }\n\n // hyperdrive: array of { binding, id }\n for (const h of asObjectArray(outputConfig.hyperdrive)) {\n const name = pluckString(h, 'binding')\n const id = pluckString(h, 'id')\n if (name && id) out.push({ type: 'hyperdrive', name, id })\n }\n\n // Wrangler resolves its native `ratelimits` table into the Vite build\n // output. Translate it to the Workers upload binding only at this boundary.\n for (const r of asObjectArray(outputConfig.ratelimits)) {\n const name = pluckString(r, 'name')\n const namespace_id = pluckString(r, 'namespace_id')\n const simple = isObject(r.simple) ? r.simple : null\n const limit = pluckNumber(simple, 'limit')\n const period = pluckNumber(simple, 'period')\n if (!name || !namespace_id || limit === null || (period !== 10 && period !== 60)) continue\n out.push({ type: 'ratelimit', name, namespace_id, simple: { limit, period } })\n }\n\n return out\n}\n\n// ---- internal helpers (no broad `as` casts) ----\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\nfunction pluckString(v: unknown, key: string): string | null {\n if (!isObject(v)) return null\n const val = v[key]\n return typeof val === 'string' && val.length > 0 ? val : null\n}\n\nfunction pluckNumber(v: unknown, key: string): number | null {\n if (!isObject(v)) return null\n const val = v[key]\n return typeof val === 'number' && Number.isFinite(val) ? val : null\n}\n\nfunction asObjectArray(v: unknown): Record<string, unknown>[] {\n if (!Array.isArray(v)) return []\n return v.filter(isObject)\n}\n","/**\n * Pure helpers for computing Cloudflare Durable Object migrations from a\n * declared manifest + the bindings already registered on a deployed script.\n *\n * Lives in the SDK (not deploy-worker) so the logic is testable with vitest\n * and reusable from other CF deploy paths if we ever add them.\n */\n\nimport type { DOManifestEntry } from './do-manifest'\n\n/** Subset of CF's `bindings` API response we read from. */\nexport interface ExistingDOBinding {\n /** Binding name in `env`, e.g. 'RECORD_ROOMS' */\n name: string\n /** Always `'durable_object_namespace'` for DO bindings. */\n type: string\n /** SDK class name, e.g. 'AppRecordRoom' */\n class_name?: string\n}\n\n/** What goes in the CF script-upload `migrations` block. */\nexport interface DoMigrationDirective {\n tag: string\n new_sqlite_classes?: string[]\n deleted_classes?: string[]\n}\n\nexport interface DoMigrationPlan {\n /** New SQLite classes to register (present in manifest, absent in existing). */\n newSqliteClasses: string[]\n /** Classes to delete (present in existing, absent in manifest). */\n deletedClasses: string[]\n /** True when there's actual delta — only then should the migrations block be sent. */\n needsMigration: boolean\n /** The full directive to splat into the CF script-upload metadata. Null if no migration is needed. */\n directive: DoMigrationDirective | null\n}\n\nexport interface ComputeDoMigrationOptions {\n /**\n * Override for the timestamp baked into the migration tag. Tests pass a\n * fixed value to assert determinism; production omits this and gets\n * `Date.now()`, which guarantees lifetime tag uniqueness even across\n * cycles like `[A]→[A,B]→[A]→[A,B]`.\n */\n now?: number\n}\n\n/**\n * Compute the migration plan for a deploy.\n *\n * manifest: what the app declares now\n * existing: what CF currently has registered for this script\n *\n * Behavior:\n * - new_sqlite_classes ← in manifest, not in existing, sqlite=true\n * - deleted_classes ← in existing, not in manifest\n * - needsMigration ← either of the above is non-empty\n * - tag ← content-addressed by (add, remove) so:\n * - identical re-deploy → unchanged tag → no-op (and\n * `needsMigration` is false anyway)\n * - any class change → unique tag → CF processes\n *\n * Bug history: an earlier version computed `tag = v${count}`. Removing a class\n * dropped the count, the migration block was skipped (no NEW classes), and CF\n * retained the orphaned class registration with its SQLite storage. The\n * `deleted_classes` path closes that gap; the content-addressed tag prevents\n * tag collisions when class sets are added and removed in different orders.\n */\nexport function computeDoMigration(\n manifest: readonly DOManifestEntry[],\n existing: readonly ExistingDOBinding[],\n options?: ComputeDoMigrationOptions,\n): DoMigrationPlan {\n const existingNames = new Set(\n existing.filter((b) => b.type === 'durable_object_namespace').map((b) => b.name),\n )\n const manifestBindingNames = new Set(manifest.map((e) => e.binding))\n\n const newSqliteClasses = manifest\n .filter((e) => e.sqlite && !existingNames.has(e.binding))\n .map((e) => e.className)\n .sort()\n\n const deletedClasses = [...existingNames]\n .filter((name) => !manifestBindingNames.has(name))\n .map((name) => {\n // We need the className for `deleted_classes`. CF's bindings API returns\n // `class_name` per binding; fall back to the binding name when the API\n // didn't include it (older responses, or non-DO entries that slipped\n // through the type filter).\n const m = existing.find((b) => b.name === name)\n return m?.class_name ?? name\n })\n .sort()\n\n const needsMigration = newSqliteClasses.length > 0 || deletedClasses.length > 0\n const directive: DoMigrationDirective | null = needsMigration\n ? {\n tag: migrationTag(newSqliteClasses, deletedClasses, options),\n ...(newSqliteClasses.length ? { new_sqlite_classes: newSqliteClasses } : {}),\n ...(deletedClasses.length ? { deleted_classes: deletedClasses } : {}),\n }\n : null\n\n return { newSqliteClasses, deletedClasses, needsMigration, directive }\n}\n\n/**\n * Unique-per-deploy migration tag.\n *\n * CF requires tags to be unique *across the script's lifetime* — reusing a\n * tag rejects the deploy. Pure content-addressing (hash of add+remove) is\n * insufficient because cycles produce identical content: `[A] → [A,B] → [A]\n * → [A,B]` step 4's delta `(add=[B], remove=[])` matches step 2's verbatim.\n * Even adding the prior state doesn't help — the cycle returns to the same\n * prior state, same delta, same tag, CF rejects.\n *\n * Format: `m-<base36-millis>-<delta-hash>`\n * - base36 timestamp guarantees lifetime uniqueness (CF deploys can't\n * collide on the millisecond)\n * - delta-hash suffix is for human debuggability — eyeballing \"ah, that\n * migration removed ClassFoo\" is easier than reading raw timestamps\n *\n * Tests inject `now` so they can assert deterministic tag values; production\n * uses `Date.now()`.\n */\nfunction migrationTag(\n addClasses: string[],\n removeClasses: string[],\n options?: ComputeDoMigrationOptions,\n): string {\n const ts = (options?.now ?? Date.now()).toString(36)\n const delta = fnv1a32(\n JSON.stringify({ add: [...addClasses].sort(), remove: [...removeClasses].sort() }),\n )\n return `m-${ts}-${delta}`\n}\n\nfunction fnv1a32(s: string): string {\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193) >>> 0\n }\n return h.toString(16).padStart(8, '0')\n}\n","/**\n * App-name validation + sanitization helpers.\n *\n * Strategy: validate strictly so we have a precise definition of \"valid\",\n * but DON'T reject non-conforming names — sanitize them, warn the user, and\n * proceed. Hard rejection would break apps whose `wrangler.toml name` was\n * something like `My_App` (previously deployed as `my-app` via silent\n * server-side sanitization). The new behavior preserves \"still deploys,\"\n * but the CLI now surfaces a warning so the user can fix the name when\n * convenient instead of being silently surprised by their hostname.\n *\n * Rules track Cloudflare's WfP script-name constraints (RFC 1035 host label,\n * no consecutive dashes) plus our 2-char minimum so subdomains read sensibly.\n */\n\nexport const APP_NAME_RULES = {\n /** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */\n pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,\n minLength: 2,\n maxLength: 63,\n} as const\n\nexport type AppNameValidation =\n | { valid: true; name: string }\n | { valid: false; reason: string }\n\n/**\n * Strict validation: returns valid only if the name already conforms.\n * Useful as a precondition test or for CI lints.\n */\nexport function validateAppName(raw: unknown): AppNameValidation {\n if (typeof raw !== 'string') {\n return { valid: false, reason: 'App name must be a string' }\n }\n const name = raw.trim()\n if (!name) {\n return { valid: false, reason: 'App name is required' }\n }\n if (name.length < APP_NAME_RULES.minLength) {\n return { valid: false, reason: `App name \"${name}\" is too short (min ${APP_NAME_RULES.minLength} chars)` }\n }\n if (name.length > APP_NAME_RULES.maxLength) {\n return { valid: false, reason: `App name \"${name}\" is too long (max ${APP_NAME_RULES.maxLength} chars)` }\n }\n if (!APP_NAME_RULES.pattern.test(name)) {\n return {\n valid: false,\n reason:\n `App name \"${name}\" is invalid. Use lowercase letters, digits, and single hyphens only ` +\n `(must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`,\n }\n }\n return { valid: true, name }\n}\n\nexport type AppNameResolution =\n | { ok: true; name: string; warning?: string }\n | { ok: false; reason: string }\n\n/**\n * Resolve an app name for deploy: prefer the input as-is if valid, otherwise\n * sanitize and warn. Hard-fail only if even sanitization can't produce a\n * valid name (empty, all-non-alphanumeric, too short, too long).\n *\n * The intent is \"what previously worked still works, with a friendly warning\n * about non-conforming names.\"\n */\nexport function resolveAppName(raw: unknown): AppNameResolution {\n const strict = validateAppName(raw)\n if (strict.valid) return { ok: true, name: strict.name }\n\n if (typeof raw !== 'string') return { ok: false, reason: strict.reason }\n const trimmed = raw.trim()\n if (!trimmed) return { ok: false, reason: 'App name is required' }\n\n const sanitized = sanitizeAppName(trimmed)\n const sanitizedStrict = validateAppName(sanitized)\n if (!sanitizedStrict.valid) {\n return {\n ok: false,\n reason: `App name \"${trimmed}\" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`,\n }\n }\n return {\n ok: true,\n name: sanitized,\n warning:\n `App name \"${trimmed}\" doesn't conform to deploy rules — using \"${sanitized}\" instead. ` +\n `Update wrangler.toml's \\`name\\` to silence this warning.`,\n }\n}\n\n/**\n * Lossy normalization that mirrors what the deploy-worker historically did\n * server-side. Keep only [a-z0-9-], collapse consecutive dashes, trim leading\n * and trailing dashes.\n */\nfunction sanitizeAppName(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n","/**\n * AI Chat Schemas\n *\n * Pre-built collection schemas for DO-backed AI chat history.\n * The worker is the only writer; the client reads via `useQuery`.\n */\n\nimport type { CollectionSchema } from './registry'\n\n// member.create is false on both collections: writes flow exclusively through\n// the worker's /api/ai/* routes, which use X-App-Action to bypass RBAC.\n// Allowing direct WS creates would let a user PUT fake `role: 'assistant'`\n// rows into their own chats and have the next turn's loadMessages feed them\n// to the LLM as if they were real assistant outputs.\nexport const AI_CHATS_SCHEMA: CollectionSchema = {\n name: 'ai-chats',\n columns: [\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true, required: true },\n { name: 'title', storage: 'text', interpretation: 'plain' },\n { name: 'model', storage: 'text', interpretation: 'plain' },\n { name: 'compactedSummary', storage: 'text', interpretation: 'plain' },\n { name: 'compactedThroughId', storage: 'text', interpretation: 'plain' },\n ],\n ownerField: 'userId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'own', create: false, update: 'own', delete: 'own' },\n viewer: { read: 'own', create: false, update: false, delete: false },\n },\n}\n\nexport const AI_MESSAGES_SCHEMA: CollectionSchema = {\n name: 'ai-messages',\n columns: [\n { name: 'chatId', storage: 'text', interpretation: 'plain', immutable: true, required: true },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true, required: true },\n { name: 'role', storage: 'text', interpretation: 'plain', required: true },\n { name: 'content', storage: 'text', interpretation: 'plain' },\n { name: 'parts', storage: 'text', interpretation: { kind: 'json' } },\n ],\n ownerField: 'userId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'own', create: false, update: 'own', delete: 'own' },\n viewer: { read: 'own', create: false, update: false, delete: false },\n },\n}\n","/**\n * Messaging Schemas\n *\n * Pre-built collection schemas for messaging functionality.\n * Any app can import these to add channels, messages, reactions, etc.\n *\n * @example\n * ```typescript\n * import { CHANNELS_SCHEMA, MESSAGES_SCHEMA, REACTIONS_SCHEMA } from 'deepspace/worker'\n * export const schemas = [usersSchema, CHANNELS_SCHEMA, MESSAGES_SCHEMA, REACTIONS_SCHEMA]\n * ```\n */\n\nimport type { CollectionSchema } from './registry'\n\nexport const CHANNELS_SCHEMA: CollectionSchema = {\n name: 'channels',\n columns: [\n { name: 'name', storage: 'text', interpretation: 'plain' },\n { name: 'description', storage: 'text', interpretation: 'plain' },\n {\n name: 'type',\n storage: 'text',\n interpretation: { kind: 'select', options: ['public', 'private', 'dm'] },\n },\n {\n name: 'createdBy',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n { name: 'archived', storage: 'number', interpretation: { kind: 'boolean' } },\n ],\n ownerField: 'createdBy',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: false },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const MESSAGES_SCHEMA: CollectionSchema = {\n name: 'messages',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'content', storage: 'text', interpretation: 'plain' },\n {\n name: 'authorId',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n { name: 'parentMessageId', storage: 'text', interpretation: 'plain' },\n { name: 'edited', storage: 'number', interpretation: { kind: 'boolean' } },\n { name: 'editedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n // Soft-delete flag. Slack-style messaging keeps the row + parent IDs\n // intact (so threads remain coherent) and renders a tombstone instead.\n // Hard-delete via remove() still works if an app wants it.\n { name: 'deleted', storage: 'number', interpretation: { kind: 'boolean' } },\n ],\n ownerField: 'authorId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const REACTIONS_SCHEMA: CollectionSchema = {\n name: 'reactions',\n columns: [\n { name: 'messageId', storage: 'text', interpretation: 'plain' },\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'emoji', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n ],\n ownerField: 'userId',\n uniqueOn: ['messageId', 'emoji', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: false, delete: true },\n member: { read: true, create: true, update: false, delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const CHANNEL_MEMBERS_SCHEMA: CollectionSchema = {\n name: 'channel-members',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n { name: 'joinedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n ownerField: 'userId',\n uniqueOn: ['channelId', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: false, delete: true },\n member: { read: true, create: true, update: false, delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const CHANNEL_INVITATIONS_SCHEMA: CollectionSchema = {\n name: 'channel-invitations',\n columns: [\n {\n name: 'channelId',\n storage: 'text',\n interpretation: 'plain',\n required: true,\n immutable: true,\n },\n {\n name: 'invitedUserId',\n storage: 'text',\n interpretation: 'plain',\n required: true,\n immutable: true,\n },\n {\n name: 'invitedBy',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n {\n name: 'status',\n storage: 'text',\n interpretation: { kind: 'select', options: ['pending', 'accepted', 'declined'] },\n required: true,\n },\n ],\n uniqueOn: ['channelId', 'invitedUserId'],\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: true, delete: false },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const READ_RECEIPTS_SCHEMA: CollectionSchema = {\n name: 'read-receipts',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n { name: 'lastReadAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n ownerField: 'userId',\n uniqueOn: ['channelId', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n viewer: { read: false, create: false, update: false, delete: false },\n },\n}\n","/**\n * Shared conversation schemas for RecordRoom-based conversations.\n *\n * These define the collections inside a per-conversation RecordRoom DO.\n * Used by apps that have messaging/conversation features (slack-clone,\n * helpdesk, mail, reddit-clone, etc.).\n *\n * Each conversation gets its own RecordRoom DO keyed by `conv:{id}`.\n */\n\nimport type { CollectionSchema } from './registry'\n\n// Re-export data interfaces from types (single source of truth)\nexport type {\n ConvMessageData,\n ConvReactionData,\n ConvMemberData,\n ConvReadCursorData,\n ConvVoteData,\n} from '../../shared/types'\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nexport const CONVERSATION_SCHEMAS: CollectionSchema[] = [\n {\n name: 'conv_messages',\n columns: [\n { name: 'Content', storage: 'text', interpretation: 'plain' },\n { name: 'AuthorId', storage: 'text', interpretation: 'plain' },\n { name: 'ParentId', storage: 'text', interpretation: 'plain' },\n { name: 'Edited', storage: 'number', interpretation: 'boolean' },\n { name: 'MessageType', storage: 'text', interpretation: 'plain' },\n { name: 'Metadata', storage: 'text', interpretation: 'json' },\n ],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'AuthorId',\n },\n {\n name: 'conv_reactions',\n columns: [\n { name: 'MessageId', storage: 'text', interpretation: 'plain' },\n { name: 'Emoji', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['MessageId', 'Emoji', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: false, delete: 'own' },\n admin: { read: true, create: true, update: false, delete: true },\n },\n ownerField: 'UserId',\n },\n {\n name: 'conv_members',\n columns: [\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'UserName', storage: 'text', interpretation: 'plain' },\n { name: 'Role', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n {\n name: 'conv_read_cursors',\n columns: [\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'LastReadAt', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n]\n\n/**\n * Voting schemas for Reddit-style apps.\n * Add these alongside CONVERSATION_SCHEMAS for apps that need voting.\n */\nexport const VOTING_SCHEMAS: CollectionSchema[] = [\n {\n name: 'conv_votes',\n columns: [\n { name: 'TargetId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'Direction', storage: 'number', interpretation: 'plain' },\n ],\n uniqueOn: ['TargetId', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n]\n","/**\n * Directory DO Schemas\n *\n * Purpose-built collections for the `dir:{appName}` global DO type.\n * This is the cross-app-visible directory layer — any app can subscribe\n * to another app's directory in real-time via SHARED_CONNECTIONS.\n *\n * Five collections covering all standard communication/social patterns:\n * - conversations: channels, DMs, email threads, support chats\n * - conversation_state: per-user metadata (read cursor, stars, labels, folders)\n * - communities: groups, forums, boards, projects\n * - memberships: user membership in communities\n * - posts: feed items, tweets, Q&A questions, announcements\n *\n * Domain-specific data (tickets, CRM, procurement) stays in app DOs.\n * Message content stays in conv:{id} DOs (CONVERSATION_SCHEMAS).\n */\n\nimport type { CollectionSchema } from './registry'\n\n// Re-export data interfaces from types (single source of truth)\nexport type {\n DirectoryConversationData,\n ConversationStateData,\n DirectoryCommunityData,\n DirectoryMembershipData,\n DirectoryPostData,\n} from '../../shared/types'\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nconst conversationsSchema: CollectionSchema = {\n name: 'conversations',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Description', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'dm', 'group', 'email', 'outbound-email', 'support', 'announcement'] } },\n { name: 'Visibility', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'restricted'] } },\n { name: 'CreatedBy', storage: 'text', interpretation: 'plain' },\n { name: 'ParticipantHash', storage: 'text', interpretation: 'plain' },\n { name: 'ParticipantIds', storage: 'text', interpretation: 'json' },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['active', 'archived', 'resolved', 'closed'] } },\n { name: 'AssigneeId', storage: 'text', interpretation: 'plain' },\n { name: 'LinkedRef', storage: 'text', interpretation: 'json' },\n { name: 'LastMessageAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'LastMessagePreview', storage: 'text', interpretation: 'plain' },\n { name: 'LastMessageAuthor', storage: 'text', interpretation: 'plain' },\n { name: 'MessageCount', storage: 'number', interpretation: 'plain' },\n ],\n collaboratorsField: 'ParticipantIds',\n visibilityField: 'Visibility',\n permissions: {\n viewer: { read: 'shared', create: false, update: false, delete: false },\n member: { read: 'shared', create: true, update: true, delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'CreatedBy',\n}\n\nconst conversationStateSchema: CollectionSchema = {\n name: 'conversation_state',\n columns: [\n { name: 'ConversationId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'LastReadAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'LastReadMessageCount', storage: 'number', interpretation: 'plain' },\n { name: 'Starred', storage: 'number', interpretation: 'boolean' },\n { name: 'Archived', storage: 'number', interpretation: 'boolean' },\n { name: 'Trashed', storage: 'number', interpretation: 'boolean' },\n { name: 'Labels', storage: 'text', interpretation: 'json' },\n { name: 'Folder', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['ConversationId', 'UserId'],\n ownerField: 'UserId',\n permissions: {\n viewer: { read: false, create: false, update: false, delete: false },\n member: { read: 'own', create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\nconst communitiesSchema: CollectionSchema = {\n name: 'communities',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Description', storage: 'text', interpretation: 'plain' },\n { name: 'CreatedBy', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['community', 'forum', 'board', 'project', 'server'] } },\n { name: 'Visibility', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'restricted'] } },\n { name: 'MemberCount', storage: 'number', interpretation: 'plain' },\n { name: 'Rules', storage: 'text', interpretation: 'json' },\n { name: 'IconUrl', storage: 'text', interpretation: 'plain' },\n { name: 'CoverUrl', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['Name'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'CreatedBy',\n}\n\nconst membershipsSchema: CollectionSchema = {\n name: 'memberships',\n columns: [\n { name: 'CommunityId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'UserName', storage: 'text', interpretation: 'plain' },\n { name: 'Role', storage: 'text', interpretation: { kind: 'select', options: ['member', 'moderator', 'admin'] } },\n { name: 'JoinedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n uniqueOn: ['CommunityId', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n}\n\nconst postsSchema: CollectionSchema = {\n name: 'posts',\n columns: [\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'Content', storage: 'text', interpretation: 'plain' },\n { name: 'AuthorId', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['post', 'question', 'answer', 'tweet', 'announcement', 'link'] } },\n { name: 'CommunityId', storage: 'text', interpretation: 'plain' },\n { name: 'ParentId', storage: 'text', interpretation: 'plain' },\n { name: 'ConversationId', storage: 'text', interpretation: 'plain' },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['draft', 'published', 'pinned', 'archived', 'accepted'] } },\n { name: 'Tags', storage: 'text', interpretation: 'json' },\n { name: 'LinkUrl', storage: 'text', interpretation: 'plain' },\n ],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'AuthorId',\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\n/** All directory schemas for the `dir:{appName}` global DO type. */\nexport const DIRECTORY_SCHEMAS: CollectionSchema[] = [\n conversationsSchema,\n conversationStateSchema,\n communitiesSchema,\n membershipsSchema,\n postsSchema,\n]\n","/**\n * Workspace DO Schemas\n *\n * All collections for the workspace:default Durable Object.\n * This DO consolidates shared, cross-app business data:\n *\n * - teams / team_members — workspace-wide team management (replaces built-in teams)\n * - tasks / projects / tags — team-scoped task management\n * - people — shared contacts directory\n * - transactions / accounts — shared financial ledger\n *\n * Team-scoped collections use teamField RBAC: members see only\n * their team's records; admins see everything.\n */\n\nimport type { CollectionSchema } from './registry'\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nexport const workspaceTeamsSchema: CollectionSchema = {\n name: 'teams',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'CreatedBy', storage: 'text', interpretation: 'plain' },\n { name: 'IsOpen', storage: 'number', interpretation: { kind: 'boolean' } },\n ],\n teamField: '_rowId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'team', create: true, update: 'team', delete: 'own' },\n },\n}\n\nexport const workspaceTeamMembersSchema: CollectionSchema = {\n name: 'team_members',\n columns: [\n { name: 'TeamId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'RoleInTeam', storage: 'text', interpretation: { kind: 'select', options: ['admin', 'member'] } },\n { name: 'JoinedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'Email', storage: 'text', interpretation: { kind: 'email' } },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['active', 'invited'] } },\n ],\n teamField: 'TeamId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'team', create: true, update: 'team', delete: 'team' },\n },\n}\n\n// ============================================================================\n// Tasks / Projects / Tags (team-scoped)\n// ============================================================================\n\nexport const workspaceTasksSchema: CollectionSchema = {\n name: 'tasks',\n columns: [\n { name: 'TeamId', storage: 'text', interpretation: 'plain' },\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'Notes', storage: 'text', interpretation: 'plain' },\n { name: 'Completed', storage: 'number', interpretation: { kind: 'boolean' } },\n { name: 'CompletedAt', storage: 'number', interpretation: 'plain' },\n { name: 'Deleted', storage: 'number', interpretation: { kind: 'boolean' } },\n { name: 'DeletedAt', storage: 'number', interpretation: 'plain' },\n { name: 'Priority', storage: 'text', interpretation: { kind: 'select', options: ['none', 'low', 'medium', 'high'] } },\n { name: 'DueDate', storage: 'text', interpretation: 'plain' },\n { name: 'ProjectId', storage: 'text', interpretation: 'plain' },\n { name: 'KanbanStatus', storage: 'text', interpretation: { kind: 'select', options: ['backlog', 'ready', 'in_progress', 'review', 'done'] } },\n { name: 'Order', storage: 'number', interpretation: 'plain' },\n { name: 'AssignedUser', storage: 'text', interpretation: { kind: 'json' } },\n { name: 'AssignedBy', storage: 'text', interpretation: { kind: 'json' } },\n { name: 'TagIds', storage: 'text', interpretation: { kind: 'json' } },\n { name: 'CreatedAt', storage: 'number', interpretation: 'plain' },\n ],\n teamField: 'TeamId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'team', create: true, update: 'team', delete: 'team' },\n viewer: { read: false, create: false, update: false, delete: false },\n },\n}\n\nexport const workspaceProjectsSchema: CollectionSchema = {\n name: 'projects',\n columns: [\n { name: 'TeamId', storage: 'text', interpretation: 'plain' },\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'Notes', storage: 'text', interpretation: 'plain' },\n { name: 'Color', storage: 'text', interpretation: 'plain' },\n { name: 'ParentId', storage: 'text', interpretation: 'plain' },\n { name: 'Order', storage: 'number', interpretation: 'plain' },\n { name: 'CreatedAt', storage: 'number', interpretation: 'plain' },\n ],\n teamField: 'TeamId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'team', create: true, update: 'team', delete: 'team' },\n viewer: { read: false, create: false, update: false, delete: false },\n },\n}\n\nexport const workspaceTagsSchema: CollectionSchema = {\n name: 'tags',\n columns: [\n { name: 'TeamId', storage: 'text', interpretation: 'plain' },\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Color', storage: 'text', interpretation: 'plain' },\n { name: 'CreatedAt', storage: 'number', interpretation: 'plain' },\n ],\n teamField: 'TeamId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'team', create: true, update: 'team', delete: 'team' },\n viewer: { read: false, create: false, update: false, delete: false },\n },\n}\n\n// ============================================================================\n// People (shared contacts directory)\n// ============================================================================\n\nexport const workspacePeopleSchema: CollectionSchema = {\n name: 'people',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Email', storage: 'text', interpretation: { kind: 'email' } },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['employee', 'customer', 'vendor', 'contact'] } },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['active', 'inactive', 'archived'] } },\n { name: 'CompanyId', storage: 'text', interpretation: 'plain' },\n { name: 'Department', storage: 'text', interpretation: 'plain' },\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'LastContactedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'Metadata', storage: 'text', interpretation: { kind: 'json' } },\n ],\n permissions: {\n '*': { read: true, create: true, update: true, delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Ledger (shared financial data)\n// ============================================================================\n\nexport const workspaceTransactionsSchema: CollectionSchema = {\n name: 'transactions',\n columns: [\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['revenue', 'expense', 'payment', 'transfer', 'invoice', 'refund'] } },\n { name: 'Amount', storage: 'number', interpretation: { kind: 'currency', symbol: '$', decimals: 2 } },\n { name: 'Currency', storage: 'text', interpretation: 'plain' },\n { name: 'Description', storage: 'text', interpretation: 'plain' },\n { name: 'Category', storage: 'text', interpretation: 'plain' },\n { name: 'CounterpartyRef', storage: 'text', interpretation: 'plain' },\n { name: 'OrderRef', storage: 'text', interpretation: 'plain' },\n { name: 'Account', storage: 'text', interpretation: 'plain' },\n ],\n permissions: {\n '*': { read: true, create: true, update: true, delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\nexport const workspaceAccountsSchema: CollectionSchema = {\n name: 'accounts',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Balance', storage: 'number', interpretation: { kind: 'currency', symbol: '$', decimals: 2 } },\n { name: 'Currency', storage: 'text', interpretation: 'plain' },\n ],\n permissions: {\n '*': { read: true, create: true, update: true, delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Content Sharing (universal cross-app content discovery)\n// ============================================================================\n\n/**\n * Universal sharing index. Any app can create share records for any content type.\n *\n * ContentType: 'document' | 'slide' | 'spreadsheet' | ... (extensible)\n * ShareType: 'channel' | 'team' | 'direct' | 'link' | 'org' (extensible)\n * ShareTarget: the ID of the target (channelId, teamId, userId, linkId, orgId)\n * Permission: 'view' | 'edit' — what the share grants\n *\n * Content itself lives in the owner's app DO.\n * This table is the discovery layer: \"what has been shared, with whom, and how\".\n */\nexport const workspaceContentSharesSchema: CollectionSchema = {\n name: 'content_shares',\n ownerField: 'OwnerId',\n columns: [\n { name: 'ContentType', storage: 'text', interpretation: { kind: 'select', options: ['document', 'slide', 'spreadsheet'] } },\n { name: 'ContentId', storage: 'text', interpretation: 'plain' },\n { name: 'OwnerId', storage: 'text', interpretation: 'plain' },\n { name: 'OwnerName', storage: 'text', interpretation: 'plain' },\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'ShareType', storage: 'text', interpretation: { kind: 'select', options: ['self', 'channel', 'team', 'direct', 'link', 'org'] } },\n { name: 'ShareTarget', storage: 'text', interpretation: 'plain' },\n { name: 'Permission', storage: 'text', interpretation: { kind: 'select', options: ['view', 'edit'] } },\n { name: 'SharedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'SharedBy', storage: 'text', interpretation: 'plain' },\n { name: 'SourceApp', storage: 'text', interpretation: 'plain' },\n { name: 'WordCount', storage: 'number', interpretation: 'plain' },\n { name: 'LastEditedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n permissions: {\n '*': { read: true, create: true, update: true, delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Form Responses (cross-app: deepspace-forms → sheets-editor)\n// ============================================================================\n\nexport const workspaceFormResponsesSchema: CollectionSchema = {\n name: 'form_responses',\n columns: [\n { name: 'FormId', storage: 'text', interpretation: 'plain' },\n { name: 'FormTitle', storage: 'text', interpretation: 'plain' },\n { name: 'RespondentId', storage: 'text', interpretation: 'plain' },\n { name: 'RespondentEmail', storage: 'text', interpretation: { kind: 'email' } },\n { name: 'RespondentName', storage: 'text', interpretation: 'plain' },\n { name: 'Data', storage: 'text', interpretation: { kind: 'json' } },\n { name: 'SubmittedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n permissions: {\n '*': { read: true, create: true, update: false, delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Email Handles (cross-app user identity)\n// ============================================================================\n\n/**\n * Maps DeepSpace users to their claimed @app.space email handles.\n * Shared across all apps so any app can look up a user's email address.\n * All handles are under the @app.space domain.\n */\nexport const workspaceEmailHandlesSchema: CollectionSchema = {\n name: 'email_handles',\n columns: [\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'Handle', storage: 'text', interpretation: 'plain' },\n { name: 'EmailAddress', storage: 'text', interpretation: { kind: 'email' } },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['active', 'disabled'] } },\n ],\n uniqueOn: ['Handle'],\n ownerField: 'UserId',\n permissions: {\n '*': { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Export\n// ============================================================================\n\nexport const WORKSPACE_SCHEMAS: CollectionSchema[] = [\n workspaceTeamsSchema,\n workspaceTeamMembersSchema,\n workspaceTasksSchema,\n workspaceProjectsSchema,\n workspaceTagsSchema,\n workspacePeopleSchema,\n workspaceTransactionsSchema,\n workspaceAccountsSchema,\n workspaceContentSharesSchema,\n workspaceFormResponsesSchema,\n workspaceEmailHandlesSchema,\n]\n","/**\n * Shared Durable Object Schemas\n *\n * Central registry of global DO types with fixed schemas.\n * Apps connect to shared DOs via SHARED_CONNECTIONS in constants.ts.\n * The ScopeRegistry resolves collection names to the correct scope automatically.\n *\n * Scope tiers:\n * - App DO (app:{appHandle}) — private to each app, app defines tables\n * - Dir DO (dir:{appHandle}) — cross-app directory (conversations, communities, posts)\n * - Workspace DO (workspace:default) — shared business data (teams, tasks, people, ledger)\n * - Conv DO (conv:{id}) — single conversation (messages, reactions, members)\n */\n\nimport type { CollectionSchema } from './registry'\nimport { CONVERSATION_SCHEMAS, VOTING_SCHEMAS } from './conversation'\nimport { DIRECTORY_SCHEMAS } from './directory'\nimport { WORKSPACE_SCHEMAS } from './workspace'\n\n// ============================================================================\n// Registry types\n// ============================================================================\n\nexport interface SharedConnection {\n type: string\n instanceId?: string\n}\n\nexport interface GlobalDOType {\n name: string\n schemas: CollectionSchema[]\n description: string\n}\n\n// ============================================================================\n// Global DO type registry\n// ============================================================================\n\nexport const GLOBAL_DO_TYPES: GlobalDOType[] = [\n {\n name: 'workspace',\n schemas: WORKSPACE_SCHEMAS,\n description: 'Shared workspace data (teams, tasks, projects, people, ledger)',\n },\n {\n name: 'conv',\n schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],\n description: 'Conversations (messages, reactions, members, voting)',\n },\n {\n name: 'dir',\n schemas: DIRECTORY_SCHEMAS,\n description: 'App directory (conversations, communities, posts) — cross-app accessible',\n },\n]\n\n/** All valid global DO type names. */\nexport const GLOBAL_DO_TYPE_NAMES = GLOBAL_DO_TYPES.map(t => t.name)\n\n/** Look up a global DO type by name, returns null if not found. */\nexport function getGlobalDOType(name: string): GlobalDOType | null {\n return GLOBAL_DO_TYPES.find(t => t.name === name) ?? null\n}\n\n/** Get the fixed schemas for a global DO type. Returns empty array if unknown type. */\nexport function getGlobalDOSchemas(typeName: string): CollectionSchema[] {\n return getGlobalDOType(typeName)?.schemas ?? []\n}\n\n/** All collection names reserved by global DO types. Apps must not reuse these. */\nexport const RESERVED_COLLECTION_NAMES = new Set(\n GLOBAL_DO_TYPES.flatMap(t => t.schemas.map(s => s.name)),\n)\n","/**\n * JWT verification for DeepSpace workers.\n *\n * jose-based ES256 verification (jose runs on the Cloudflare Workers\n * edge runtime). Imported public keys are cached per-PEM to avoid\n * re-importing on every request, and `azp` is matched against an\n * optional list of authorized-party patterns supporting `*` wildcards.\n */\n\nimport { importSPKI, jwtVerify } from 'jose'\nimport type { JwtVerifierConfig, VerifyOutcome, JwtClaims } from './types'\nimport { decodeJwtPayload, normalizeArray } from './utils'\n\nconst DEFAULT_CLOCK_SKEW_MS = 5_000\n\n/**\n * Check if azp matches any pattern in authorizedParties.\n * Supports wildcards like \"https://*.app.space\"\n */\nfunction matchesAuthorizedParty(\n azp: string | null | undefined,\n patterns: string[],\n): boolean {\n // Tokens without azp are valid (e.g., server-issued tokens)\n if (!azp) return true\n\n for (const pattern of patterns) {\n if (pattern === azp) return true\n\n if (pattern.includes('*')) {\n const escaped: string = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const regexPattern: string = escaped.replace(/\\*/g, '[^/]*')\n const regex: RegExp = new RegExp(`^${regexPattern}$`)\n if (regex.test(azp)) return true\n\n // \"*.example.com\" also allows \"example.com\"\n if (pattern.includes('*.')) {\n const rootPattern: string = pattern.replace('*.', '')\n if (rootPattern === azp) return true\n }\n }\n }\n\n return false\n}\n\n// Cache imported keys to avoid re-importing on every request\nconst keyCache = new Map<string, CryptoKey>()\n\nasync function getPublicKey(pem: string): Promise<CryptoKey> {\n const cached = keyCache.get(pem)\n if (cached) return cached\n // .dev.vars stores PEM with literal \\n — replace with actual newlines\n const normalized = pem.replace(/\\\\n/g, '\\n')\n const key = await importSPKI(normalized, 'ES256')\n keyCache.set(pem, key)\n return key\n}\n\n/**\n * Verify a DeepSpace JWT token.\n *\n * @param config - Verification configuration (public key, issuer, audience)\n * @param token - The JWT string to verify\n * @returns VerifyOutcome with either the verified result or error details\n */\nexport async function verifyJwt(\n config: JwtVerifierConfig,\n token: string | null | undefined,\n): Promise<VerifyOutcome> {\n if (!token) {\n return { result: null }\n }\n\n try {\n const publicKey = await getPublicKey(config.publicKey)\n\n const audience = normalizeArray(config.audience)\n const clockTolerance = Math.floor(\n (config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1000,\n )\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: config.issuer,\n audience: audience && audience.length === 1 ? audience[0] : audience,\n clockTolerance,\n })\n\n const claims = payload as JwtClaims\n\n if (!claims.sub) {\n throw new Error('JWT verification succeeded but subject claim is missing')\n }\n\n // Check authorized parties if configured\n const authorizedParties = normalizeArray(config.authorizedParties)\n if (authorizedParties) {\n const azp = claims.azp\n if (!matchesAuthorizedParty(azp, authorizedParties)) {\n throw new Error(\n `Authorized party '${azp}' does not match any allowed pattern`,\n )\n }\n }\n\n return {\n result: {\n userId: claims.sub,\n claims,\n },\n }\n } catch (error) {\n return {\n result: null,\n error,\n debug: decodeJwtPayload(token),\n }\n }\n}\n","import type { TokenDebugInfo } from './types'\n\nconst BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/\n\nexport function bufferToHex(bytes: ArrayBuffer | Uint8Array): string {\n const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)\n let hex = ''\n for (const byte of view) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function normalizeArray<T>(value?: T | T[] | null): T[] | undefined {\n if (value == null) return undefined\n if (Array.isArray(value)) {\n return value.length ? value : undefined\n }\n return [value]\n}\n\nexport function decodeJwtPayload(token: string | null | undefined): TokenDebugInfo | undefined {\n if (!token) return undefined\n const parts = token.split('.')\n if (parts.length < 2) return undefined\n const payloadSegment = parts[1]\n if (!payloadSegment) return undefined\n if (!BASE64_URL_REGEX.test(payloadSegment)) return undefined\n try {\n const normalized = payloadSegment\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payloadSegment.length / 4) * 4, '=')\n const bytes = base64ToUint8Array(normalized)\n const json = new TextDecoder().decode(bytes)\n const payload = JSON.parse(json) as Partial<TokenDebugInfo>\n return {\n iss: payload.iss ?? null,\n aud: payload.aud ?? null,\n azp: (payload as Record<string, unknown>)?.azp as string | null ?? null,\n exp: payload.exp ?? null,\n iat: payload.iat ?? null,\n }\n } catch {\n return undefined\n }\n}\n\nfunction base64ToUint8Array(value: string): Uint8Array {\n if (typeof atob === 'function') {\n const binary = atob(value)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) {\n out[i] = binary.charCodeAt(i)\n }\n return out\n }\n if (typeof Buffer !== 'undefined') {\n return Uint8Array.from(Buffer.from(value, 'base64'))\n }\n throw new Error('No base64 decoder available in current environment')\n}\n","/**\n * HMAC primitives used by the per-app identity tokens (APP_IDENTITY_TOKEN)\n * minted at deploy time and verified on every /internal/* call into the\n * platform worker. Auth-provider-agnostic.\n */\n\nimport { bufferToHex } from './utils'\n\nlet nodeCryptoPromise: Promise<typeof import('node:crypto') | null> | null = null\n\nasync function loadNodeCrypto(): Promise<typeof import('node:crypto') | null> {\n if (!nodeCryptoPromise) {\n nodeCryptoPromise = import('node:crypto')\n .then((module) => module)\n .catch(() => null)\n }\n return nodeCryptoPromise\n}\n\nfunction hasSubtleCrypto(): boolean {\n return typeof globalThis.crypto?.subtle?.importKey === 'function'\n}\n\nexport async function computeHmacHex(secret: string, payload: string): Promise<string> {\n if (hasSubtleCrypto()) {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return bufferToHex(signature)\n }\n\n const nodeCrypto = await loadNodeCrypto()\n if (!nodeCrypto) {\n throw new Error('Crypto implementation unavailable for HMAC computation')\n }\n return nodeCrypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nexport async function timingSafeEqualHex(a: string, b: string): Promise<boolean> {\n if (a.length !== b.length) {\n return false\n }\n\n const nodeCrypto = await loadNodeCrypto()\n if (nodeCrypto?.timingSafeEqual) {\n const aBuf = Buffer.from(a, 'hex')\n const bBuf = Buffer.from(b, 'hex')\n try {\n return nodeCrypto.timingSafeEqual(aBuf, bBuf)\n } catch {\n return false\n }\n }\n\n let diff = 0\n for (let i = 0; i < a.length; i += 1) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\n/**\n * Constant-time equality for arbitrary strings (hash-then-compare: SHA-256\n * normalizes lengths so neither length nor content short-circuits). The one\n * implementation every worker's bearer/secret checks share.\n */\nexport async function timingSafeEqualStrings(a: string, b: string): Promise<boolean> {\n const encoder = new TextEncoder()\n const [aHash, bHash] = await Promise.all([\n crypto.subtle.digest('SHA-256', encoder.encode(a)),\n crypto.subtle.digest('SHA-256', encoder.encode(b)),\n ])\n const aBytes = new Uint8Array(aHash)\n const bBytes = new Uint8Array(bHash)\n let diff = 0\n for (let i = 0; i < aBytes.length; i++) diff |= aBytes[i] ^ bBytes[i]\n return diff === 0\n}\n","/**\n * Test-account identification, shared by every platform worker.\n *\n * Test accounts (created via `deepspace test accounts`) are real Better Auth\n * users demarcated three ways, and a caller may present as any of them\n * depending on where the check runs:\n * - their email always ends with @deepspace.test (enforced at creation)\n * - their JWTs carry `isTestAccount: true` (minted by the auth-worker,\n * the source of truth)\n * - their billing profile's subscription tier is 'test' (mirrored by the\n * api-worker on every authenticated request)\n *\n * Gate anything a test account must not do with these helpers rather than\n * hand-rolled string comparisons, so the policy stays in one place. Prefer\n * checking the claim when a verified JWT is at hand, and the tier as the\n * fallback for persisted state that may outlive a claim (e.g. rows written\n * before a guard existed).\n */\n\nimport type { JwtClaims } from './types'\n\nexport const TEST_ACCOUNT_EMAIL_SUFFIX = '@deepspace.test'\n\n/** True when the email belongs to a test account (case-insensitive). */\nexport function isTestAccountEmail(email: string | null | undefined): boolean {\n return !!email && email.trim().toLowerCase().endsWith(TEST_ACCOUNT_EMAIL_SUFFIX)\n}\n\n/** True when verified JWT claims identify the caller as a test account. */\nexport function isTestAccountClaims(\n claims: JwtClaims | Record<string, unknown> | null | undefined,\n): boolean {\n return claims?.isTestAccount === true\n}\n\n/** True when a billing subscription tier marks a test account. */\nexport function isTestAccountTier(tier: string | null | undefined): boolean {\n return tier === 'test'\n}\n","/**\n * Better Auth configuration factory for DeepSpace\n *\n * Provides pre-configured Better Auth instances for Cloudflare Workers + D1.\n */\n\nimport { betterAuth, type BetterAuthOptions } from 'better-auth'\nimport { organization, twoFactor } from 'better-auth/plugins'\nimport { SignJWT, importPKCS8 } from 'jose'\n\nexport interface DeepSpaceAuthConfig {\n /** D1 database binding */\n database: D1Database\n /** Base URL for the auth worker (e.g. \"https://auth.deep.space\") */\n baseURL: string\n /** Secret for session signing */\n secret: string\n /** Google OAuth credentials (optional) */\n google?: { clientId: string; clientSecret: string }\n /** GitHub OAuth credentials (optional) */\n github?: { clientId: string; clientSecret: string }\n /** Microsoft (Entra ID) OAuth credentials (optional) */\n microsoft?: { clientId: string; clientSecret: string }\n /**\n * Apple (Sign in with Apple) credentials (optional). Pass the raw\n * Sign-in-with-Apple key material; the ES256 client-secret JWT is minted\n * on demand, so there is no six-month token to rotate by hand.\n */\n apple?: { clientId: string; teamId: string; keyId: string; privateKey: string }\n /** Enable email/password authentication */\n emailAndPassword?: boolean\n /** Trusted origins for CORS */\n trustedOrigins?: string[]\n /**\n * Called after a new user row is created (any flow: social OAuth callback,\n * email/password, server-side `auth.api.signUpEmail`). When the signup came\n * in over HTTP, `ctx.request` exposes the inbound request; some better-auth\n * entry points carry standalone `ctx.headers` instead of (or as well as) a\n * request, so both are forwarded. For server-side creations (`auth.api.*`\n * calls) an endpoint context usually still exists but may carry NEITHER —\n * guard every field.\n *\n * Better Auth awaits `user.create.after` hooks inline before the response\n * is sent, so keep this cheap (e.g. a single D1 write) and hand slow work\n * to `ctx.waitUntil` at the call site. Exceptions are caught and logged so\n * a failing observer can never break signup itself.\n */\n onUserCreated?: (\n user: { id: string; email: string; name: string },\n ctx: { request?: Request; headers?: Headers } | null,\n ) => Promise<void>\n}\n\n/**\n * Create a Better Auth instance configured for DeepSpace.\n *\n * This is called per-request in the auth worker since D1 bindings\n * are request-scoped in Cloudflare Workers.\n */\n/**\n * A static `{ clientId, clientSecret }` provider config, or an async resolver\n * that returns one (Apple mints its client secret on demand). The union keeps\n * the static google/github/microsoft assignments type-checked while allowing\n * Apple's function form, which better-auth's own types don't model.\n */\ntype SocialProviderConfig =\n | { clientId: string; clientSecret: string }\n | (() => Promise<{ clientId: string; clientSecret: string }>)\n\nexport function createDeepSpaceAuth(config: DeepSpaceAuthConfig) {\n const socialProviders: Record<string, SocialProviderConfig> = {}\n\n if (config.google) {\n socialProviders.google = config.google\n }\n if (config.github) {\n socialProviders.github = config.github\n }\n if (config.microsoft) {\n socialProviders.microsoft = config.microsoft\n }\n if (config.apple) {\n const apple = config.apple\n // better-auth resolves every social provider's config in one Promise.all\n // when it builds its request context, so an exception here rejects the\n // whole context and 500s *all* auth flows — not just Apple. Mint the fresh\n // client-secret JWT (we sign on demand rather than store a rotating token)\n // inside try/catch so a bad Apple key degrades to \"Apple unavailable\"\n // instead of taking Google/GitHub/Microsoft/password down with it.\n socialProviders.apple = async () => {\n try {\n return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) }\n } catch (err) {\n console.error('[deepspace] failed to mint Apple client secret; Apple sign-in disabled', err)\n return { clientId: apple.clientId, clientSecret: '' }\n }\n }\n }\n\n return betterAuth({\n database: config.database,\n baseURL: config.baseURL,\n secret: config.secret,\n // Login is a one-time human act; everything after it is agents minting\n // short-lived JWTs from this session. 30 days idle (vs the 7-day\n // default) with daily sliding renewal means any agent active within a\n // month keeps the session alive indefinitely — the human re-logs-in\n // only after a full month of zero activity. Short-lived access stays\n // short: CLI JWTs expire in minutes (auth-worker /api/auth/token).\n session: {\n expiresIn: 60 * 60 * 24 * 30,\n updateAge: 60 * 60 * 24,\n },\n emailAndPassword: {\n enabled: config.emailAndPassword ?? true,\n },\n socialProviders: socialProviders as Parameters<typeof betterAuth>[0]['socialProviders'],\n trustedOrigins: config.trustedOrigins ?? [\n 'https://deep.space',\n 'https://*.deep.space',\n 'https://*.app.space',\n 'http://localhost:*',\n // Apple posts its OAuth callback from this origin (form_post).\n 'https://appleid.apple.com',\n ],\n // Apple returns its callback as a cross-site form_post, which a\n // SameSite=Lax cookie won't ride along with — so the OAuth handshake\n // cookie must be SameSite=None. Scoped to just that cookie; the session\n // cookie stays Lax. Gated on Apple being configured: deployments without\n // Apple keep Lax entirely. Note that when Apple IS configured, this also\n // relaxes the handshake cookie for Google/GitHub/Microsoft on the same\n // instance — which is safe, since the state is encrypted and verified\n // independently of SameSite (plus a separate per-flow CSRF cookie).\n ...(config.apple\n ? {\n advanced: {\n cookies: {\n state: { attributes: { sameSite: 'none' as const, secure: true } },\n oauth_state: { attributes: { sameSite: 'none' as const, secure: true } },\n },\n },\n }\n : {}),\n // Observer hook for new-user creation (see DeepSpaceAuthConfig.onUserCreated).\n // Two-layer wiring guard — be honest about what each layer catches:\n // 1. buildDatabaseHooks's return type is pinned to\n // BetterAuthOptions['databaseHooks'], so a better-auth upgrade that\n // renames/reshapes the option fails tsc.\n // 2. A LOCAL typo of this key would still compile (betterAuth's generic\n // options parameter defeats excess-property checking even for plain\n // keys) — that case is caught by the runtime assertions in\n // __tests__/betterAuth-hooks.test.ts, which read the wired hook back\n // off auth.options. Don't rename the key without running them.\n databaseHooks: buildDatabaseHooks(config.onUserCreated),\n plugins: [organization(), twoFactor()],\n })\n}\n\n/**\n * Wire onUserCreated into better-auth's user.create.after hook. The wrapper\n * try/catches so a throwing observer can't fail the signup — better-auth\n * awaits `after` hooks inline before responding. Returns undefined when no\n * observer is configured (better-auth treats that as \"no hooks\").\n */\nfunction buildDatabaseHooks(\n onUserCreated: DeepSpaceAuthConfig['onUserCreated'],\n): BetterAuthOptions['databaseHooks'] {\n if (!onUserCreated) return undefined\n return {\n user: {\n create: {\n after: async (user, ctx) => {\n try {\n await onUserCreated(\n { id: user.id, email: user.email, name: user.name },\n // Forward BOTH request and standalone headers — some better-auth\n // entry points populate ctx.headers without a request.\n ctx ? { request: ctx.request, headers: ctx.headers ?? undefined } : null,\n )\n } catch (err) {\n console.error('[deepspace] onUserCreated hook failed (signup unaffected)', err)\n }\n },\n },\n },\n }\n}\n\n/**\n * Mint the \"Sign in with Apple\" client-secret JWT (ES256) from the\n * downloaded .p8 key. Apple caps the lifetime at six months; we use ~180\n * days and re-mint on demand, so nothing has to be rotated manually.\n */\n// Isolate-level cache of the minted Apple client secret. Apple permits\n// reusing a client secret for up to six months, but better-auth resolves the\n// provider config on every /api/auth/* context build — so without this we'd\n// importPKCS8 + ES256-sign on every auth request, including non-Apple ones.\nlet cachedAppleSecret: { keyId: string; token: string; expiresAt: number } | null = null\n\nasync function generateAppleClientSecret(apple: {\n clientId: string\n teamId: string\n keyId: string\n privateKey: string\n}): Promise<string> {\n const now = Math.floor(Date.now() / 1000)\n // Reuse until an hour before expiry; re-mint if the signing key rotates.\n if (\n cachedAppleSecret &&\n cachedAppleSecret.keyId === apple.keyId &&\n cachedAppleSecret.expiresAt - now > 3600\n ) {\n return cachedAppleSecret.token\n }\n // PEM secrets in this stack are stored with escaped newlines (mirrors the\n // JWT_PRIVATE_KEY handling in the auth worker); normalize before importPKCS8\n // so an escaped-newline key doesn't throw. No-op for real-newline keys.\n const pem = apple.privateKey.replace(/\\\\n/g, '\\n')\n const key = await importPKCS8(pem, 'ES256')\n const expiresAt = now + 180 * 24 * 60 * 60\n const token = await new SignJWT({})\n .setProtectedHeader({ alg: 'ES256', kid: apple.keyId })\n .setIssuer(apple.teamId)\n .setSubject(apple.clientId)\n .setAudience('https://appleid.apple.com')\n .setIssuedAt(now)\n .setExpirationTime(expiresAt)\n .sign(key)\n cachedAppleSecret = { keyId: apple.keyId, token, expiresAt }\n return token\n}\n\nexport type DeepSpaceAuth = ReturnType<typeof createDeepSpaceAuth>\n","/**\n * Subscription primitives shared between client (useSubscription hook) and\n * server (requireSubscription helper). Pulled into a shared module so the\n * entitlement gate has a single source of truth — if it ever diverges between\n * client and server, gated features silently disagree about who's allowed in,\n * which is a security bug, not a UX bug.\n */\n\nexport type SubscriptionStatus =\n | 'none'\n | 'trialing'\n | 'active'\n | 'past_due'\n | 'canceled'\n | 'incomplete'\n | 'incomplete_expired'\n | 'unpaid'\n | 'paused'\n\n/** Per-interval price advertised by a plan. */\nexport interface PlanPrice {\n interval: 'month' | 'year'\n priceCents: number\n currency?: string\n}\n\n/**\n * Plan shape returned by `/api/subscriptions/me` and consumed by\n * `<PricingTable plans={sub.plans} onSelect={...}>`. Fields beyond slug/rank\n * are optional so a free tier (no prices, no trial) doesn't need to fabricate\n * them.\n */\nexport interface PlanInfo {\n slug: string\n rank: number\n name: string\n trialDays?: number | null\n prices: PlanPrice[]\n}\n\n// Statuses that grant access to paid features. Other statuses (past_due,\n// unpaid, incomplete, paused, canceled) keep the user's recorded planSlug so\n// the UI can surface \"your Pro plan is past-due\" — but they MUST NOT unlock.\n//\n// Typed as `ReadonlySet<string>` (not `Set<SubscriptionStatus>`) so callers\n// passing a server-supplied string don't need a runtime cast; unknown statuses\n// fall through as \"not entitled\", which is the safe default.\nexport const ENTITLED_STATUSES: ReadonlySet<string> = new Set([\n 'active',\n 'trialing',\n])\n\n/**\n * True iff the subscription currently grants access to paid features.\n *\n * - Free tier always counts as entitled — no payment required.\n * - Otherwise the status must be in `ENTITLED_STATUSES`.\n *\n * `status` is typed as `string` deliberately: server responses can carry\n * statuses we haven't enumerated yet (e.g. if Stripe adds a new one), and\n * those should fall through as not-entitled rather than crashing the check.\n */\nexport function isEntitled(tier: string, status: string): boolean {\n if (tier === 'free') return true\n return ENTITLED_STATUSES.has(status)\n}\n","/**\n * Server-side subscription helpers. Called from the developer's worker\n * (Hono Context) — they proxy to the api-worker's `/api/subscriptions/me`\n * using the worker's signed app-identity headers, so the same trust\n * model the SDK hook uses applies here.\n */\n\nimport type { Context } from 'hono'\nimport { apiWorkerFetch } from './utils/proxies'\nimport type { ApiWorkerEnv } from './utils/proxies'\nimport { appendAppIdentity } from './utils/app-identity'\nimport { isEntitled, type PlanInfo, type SubscriptionStatus } from '../shared/subscription'\n\n// Re-export shared types so consumers can import them from this barrel.\nexport type { PlanInfo, SubscriptionStatus }\n\nexport interface SubscriptionRead {\n tier: string\n status: SubscriptionStatus\n currentPeriodEnd: number | null\n cancelAtPeriodEnd: boolean\n trialEndsAt: number | null\n plans: PlanInfo[]\n}\n\ninterface StarterAppEnv extends ApiWorkerEnv {\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n}\n\nfunction appIdentityHeaders(\n c: Context<{ Bindings: StarterAppEnv }>,\n extra?: Record<string, string>,\n): Headers {\n const h = new Headers(extra ?? {})\n appendAppIdentity(h, c.env)\n // The api-worker's auth middleware only honors `Authorization: Bearer <jwt>`.\n // If the inbound request doesn't carry a Bearer, `/api/subscriptions/me`\n // will 401 — calling code should ensure the browser used `getAuthToken()`\n // or otherwise put a JWT on the request before reaching here.\n const authz = c.req.header('authorization')\n if (authz) h.set('authorization', authz)\n return h\n}\n\nexport async function getSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n): Promise<SubscriptionRead> {\n const res = await apiWorkerFetch(c.env, '/api/subscriptions/me', {\n method: 'GET',\n headers: appIdentityHeaders(c),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n // 401/403 means the caller isn't authenticated or isn't allowed —\n // distinct from 5xx/network blips so route handlers can map it to a\n // 401 response instead of a 500. See `requireSubscription` docs example.\n if (res.status === 401 || res.status === 403) {\n throw new SubscriptionAuthError(\n body.error ?? `subscription auth failed (${res.status})`,\n res.status,\n )\n }\n throw new Error(body.error ?? `subscription read failed (${res.status})`)\n }\n return (await res.json()) as SubscriptionRead\n}\n\n/**\n * Enforce a tier gate inside a route. Throws `SubscriptionRequiredError` if\n * the caller's subscription doesn't meet the requested tier OR isn't currently\n * entitled (status ∈ {active, trialing}). Callers do\n * `await requireSubscription(c, { atLeast: 'pro' })` and let the thrown\n * response propagate.\n *\n * Why the status gate: a row with `planSlug='pro'` and `status='past_due'`\n * means \"this user used to be on Pro but their payment is overdue\" — paid\n * features should not unlock. Same for canceled/unpaid/incomplete.\n */\nexport async function requireSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: { tier?: string; atLeast?: string },\n): Promise<SubscriptionRead> {\n const sub = await getSubscription(c)\n // Free-tier gates pass unconditionally; everything else needs entitlement.\n if (!isEntitled(sub.tier, sub.status)) {\n throw new SubscriptionRequiredError(opts.tier ?? opts.atLeast ?? 'paid', sub.tier)\n }\n if (opts.tier && sub.tier !== opts.tier) {\n throw new SubscriptionRequiredError(opts.tier, sub.tier)\n }\n if (opts.atLeast) {\n const target = sub.plans.find((p) => p.slug === opts.atLeast)?.rank\n const current = sub.plans.find((p) => p.slug === sub.tier)?.rank\n if (target == null || current == null || current < target) {\n throw new SubscriptionRequiredError(opts.atLeast, sub.tier)\n }\n }\n return sub\n}\n\nexport class SubscriptionRequiredError extends Error {\n readonly required: string\n readonly current: string\n constructor(required: string, current: string) {\n super(`subscription tier required: ${required} (have: ${current})`)\n this.required = required\n this.current = current\n this.name = 'SubscriptionRequiredError'\n }\n}\n\n/**\n * Thrown by `getSubscription` / `requireSubscription` when the upstream\n * `/api/subscriptions/me` rejects the caller with 401 or 403 — typically\n * because the inbound request didn't carry a Bearer token, or carried one\n * the api-worker can't verify. Distinct from `SubscriptionRequiredError`\n * (which is about tier/entitlement, not identity) so route handlers can\n * map identity failures to 401 and tier failures to 402.\n */\nexport class SubscriptionAuthError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'SubscriptionAuthError'\n this.status = status\n }\n}\n\n/**\n * Cancel one customer's subscription, or every subscription on a given plan.\n *\n * Forwards the inbound `Authorization` header — the platform verifies the\n * actor is the app owner before calling Stripe. As with `refundInvoice`,\n * gate this in your own admin route too; the platform check is the second\n * layer, not the first.\n *\n * Defaults to `atPeriodEnd: true` so customers aren't cut off mid-cycle.\n * Pass `atPeriodEnd: false` for an immediate cancel (refund handled\n * separately if you want one).\n */\nexport interface CancelSubscriptionOpts {\n /** Cancel one specific customer's subscription. Mutually exclusive with `planSlug`. */\n userId?: string\n /** Cancel every active subscription on this plan. Mutually exclusive with `userId`. */\n planSlug?: string\n /** Default true. Pass false for an immediate cancel. */\n atPeriodEnd?: boolean\n /** Optional free-form audit reason. */\n reason?: string\n}\n\nexport interface CancelSubscriptionResult {\n success: boolean\n canceled: number\n failures: Array<{ stripeSubscriptionId: string; error: string }>\n atPeriodEnd: boolean\n /**\n * True when the matching subscription set was larger than the server-side\n * batch limit (currently 50). Loop the call until this returns false to\n * cancel every remaining row — the `cancel_at_period_end` flag is\n * idempotent, so re-flagging an already-flagged subscription is a no-op.\n */\n hasMore: boolean\n}\n\nexport async function cancelSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: CancelSubscriptionOpts,\n): Promise<CancelSubscriptionResult> {\n const authz = c.req.header('authorization')\n if (!authz) {\n throw new CancelSubscriptionError('missing authorization header (forward the caller JWT)', 401)\n }\n const res = await apiWorkerFetch(c.env, '/api/subscriptions/admin-cancel', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n authorization: authz,\n },\n body: JSON.stringify({\n appId: c.env.DEEPSPACE_APP_ID,\n userId: opts.userId,\n planSlug: opts.planSlug,\n atPeriodEnd: opts.atPeriodEnd,\n reason: opts.reason,\n }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new CancelSubscriptionError(body.error ?? `cancel failed (${res.status})`, res.status)\n }\n return (await res.json()) as CancelSubscriptionResult\n}\n\nexport class CancelSubscriptionError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'CancelSubscriptionError'\n this.status = status\n }\n}\n","// Server-side refund helper. Forwards the inbound user's JWT to the platform,\n// which verifies the caller is the app owner before any Stripe call. Wrap\n// your route with admin/role gating before calling refundInvoice — only the\n// app owner's JWT will pass the platform's `not_app_owner` check, but you\n// still want to reject non-admin paths in your own app's auth.\n\nimport type { Context } from 'hono'\nimport { apiWorkerFetch } from './utils/proxies'\nimport type { ApiWorkerEnv } from './utils/proxies'\nimport { appendAppIdentity } from './utils/app-identity'\n\ninterface StarterAppEnv extends ApiWorkerEnv {\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n}\n\nexport interface RefundResult {\n success: boolean\n stripeRefundId: string\n amountRefunded: number\n status: 'pending' | 'succeeded' | 'failed' | 'canceled' | 'requires_action' | null\n}\n\nexport interface RefundOpts {\n invoiceId: string\n amount?: number\n reason?: 'requested_by_customer' | 'duplicate' | 'fraudulent'\n requestNonce?: string\n}\n\nexport async function refundInvoice(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: RefundOpts,\n): Promise<RefundResult> {\n // The platform requires a verified actor — forward the inbound user's JWT.\n // No JWT → the helper can't authenticate; let the platform return 401 so\n // the caller gets a clear error rather than a silent no-op.\n const authz = c.req.header('authorization')\n const nonce = opts.requestNonce ?? crypto.randomUUID()\n const headers = new Headers({ 'Content-Type': 'application/json' })\n appendAppIdentity(headers, c.env)\n if (authz) headers.set('authorization', authz)\n const res = await apiWorkerFetch(c.env, '/api/refunds/create', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n invoiceId: opts.invoiceId,\n amount: opts.amount,\n reason: opts.reason,\n requestNonce: nonce,\n }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new RefundError(body.error ?? `refund failed (${res.status})`, res.status)\n }\n return (await res.json()) as RefundResult\n}\n\nexport class RefundError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'RefundError'\n this.status = status\n }\n}\n"],"mappings":"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","names":["CORS_HEADERS","fail","url","executeTool","tool","records","tool","cleaned","n","Y","cursor","row","existing","docKeyBytes","docKey","parsed","cursor","row","Y","CORS_HEADERS","tool","result","status","CORS_HEADERS","cursor","isOwner","Y","Y","canRead","updated"]}
1
+ {"version":3,"sources":["../src/server/utils/cors.ts","../src/server/utils/plan-quotas.ts","../src/server/utils/registry-client.ts","../src/server/utils/tools.ts","../src/shared/app-files.ts","../src/server/utils/r2-storage-quota.ts","../src/server/utils/scoped-r2-files.ts","../src/server/utils/proxies.ts","../src/server/utils/cron.ts","../src/server/utils/app-role.ts","../src/server/utils/ai.ts","../src/server/utils/agent.ts","../src/shared/ai-models.ts","../src/server/utils/composio-tools.ts","../src/server/utils/app-identity.ts","../src/server/utils/screenshot.ts","../src/server/utils/chat-context.ts","../src/shared/protocol/constants.ts","../src/server/utils/chat-history.ts","../src/server/utils/metering.ts","../src/server/utils/knowledge-base.ts","../src/server/utils/d1-migrations.ts","../src/shared/room-identity-headers.ts","../src/server/rooms/base-room.ts","../src/server/schemas/registry.ts","../src/server/rooms/collection-table-migration.ts","../src/server/handlers/yjs.ts","../src/shared/protocol/yjs.ts","../src/shared/protocol/messages.ts","../src/server/handlers/records.ts","../src/server/handlers/subscriptions.ts","../src/server/handlers/users.ts","../src/server/handlers/tools-api.ts","../src/server/handlers/debug-api.ts","../src/server/rooms/record-room.ts","../src/server/rooms/yjs-room.ts","../src/server/rooms/canvas-room.ts","../src/shared/roles.ts","../src/server/rooms/presence-room.ts","../src/server/rooms/cron-schedule.ts","../src/server/rooms/cron-room.ts","../src/server/rooms/job-room.ts","../src/server/rooms/do-manifest.ts","../src/server/rooms/binding-manifest.ts","../src/server/rooms/do-migration.ts","../src/server/rooms/app-name.ts","../src/server/schemas/ai-chat.ts","../src/server/schemas/messaging.ts","../src/server/schemas/conversation.ts","../src/server/schemas/directory.ts","../src/server/schemas/shared-do.ts","../src/server/auth/jwtVerifier.ts","../src/server/auth/utils.ts","../src/server/auth/internalAuth.ts","../src/server/auth/testAccounts.ts","../src/server/auth/betterAuth.ts","../src/shared/subscription.ts","../src/server/subscription.ts","../src/server/refunds.ts"],"sourcesContent":["/**\n * Wildcard-aware CORS origin matching for the platform workers.\n *\n * Hono's array form of `cors({ origin: [...] })` is an exact `includes()` — it\n * never expands `*`. Every worker that listed `https://*.app.space` or\n * `http://localhost:*` in an array had a dead entry: the wildcard rows matched\n * nothing, silently narrowing the effective CORS surface to the literal\n * entries. This helper is the single implementation both the api and deploy\n * workers pass to Hono's function-form `origin`, so the pattern semantics\n * can't drift between them.\n *\n * `*` stands for exactly one host label or a port: it never crosses a dot or\n * a slash, so `https://*.app.space` matches `https://x.app.space` but neither\n * `https://a.b.app.space` nor `https://x.app.space.evil.com`.\n */\nexport function matchOrigin(origin: string, patterns: string[]): string | null {\n if (!origin) return null\n const matches = patterns.some((pattern) => {\n const rx = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\\\\\*/g, '[^./]+')\n return new RegExp(`^${rx}$`).test(origin)\n })\n return matches ? origin : null\n}\n\n/**\n * The platform's standard browser-origin allow-list, derived from the\n * environment's apexes so a second constellation (staging) trusts its own\n * hosts and only its own. Both values are explicit deployment-plane config.\n * Dedupes for environments that serve both roles from one apex.\n */\nexport function platformCorsPatterns(env: { APP_DOMAIN: string; PLATFORM_DOMAIN: string }): string[] {\n const app = env.APP_DOMAIN\n const platform = env.PLATFORM_DOMAIN\n return [\n ...new Set([`https://${platform}`, `https://*.${platform}`, `https://*.${app}`]),\n 'http://localhost:*',\n ]\n}\n","/**\n * Deployed-app quota defaults — the ONE copy.\n *\n * Two workers use these numbers for different jobs: the deploy worker\n * ENFORCES them at registration time, the api worker DISPLAYS them via\n * GET /api/plan-limits. They used to be independent literal sets (plus the\n * same numbers again in six wrangler [vars] blocks as overrides) and had\n * already diverged once — the display copy was missing the admin tier\n * entirely. The env vars remain the per-plane override mechanism; only the\n * fallbacks live here.\n */\nexport const DEPLOYED_APPS_DEFAULTS = {\n free: 1,\n starter: 4,\n premium: 10,\n admin: 100,\n} as const\n\n/** Parse a quota env override; anything non-integer/negative = fallback. */\nexport function parseQuotaLimit(raw: string | undefined, fallback: number): number {\n if (!raw) return fallback\n const n = Number(raw)\n return Number.isInteger(n) && n >= 0 ? n : fallback\n}\n","/**\n * The ONE registry client. Every platform worker reaches the app-registry\n * Durable Object (hosted by the deploy worker, singleton `v1:global`) through\n * a cross-script REGISTRY binding — and before this module, each of the five\n * workers hand-rolled its own copy of the same mechanics: the instance name\n * appeared in 6 places, the `https://registry/` URL in 6, `APP_ID_RE` in 5,\n * and `RegistryClientError` + `registryErrorJson` existed byte-identical in\n * two workers. Copies that must agree and nothing enforcing it.\n *\n * This module owns the MECHANICS: instance name, stub lookup, the wire call,\n * the error type, and the standard route mapping. Each worker still declares\n * its own typed convenience wrapper listing only the actions it uses — that\n * subset is genuinely per-worker; the plumbing is not.\n */\n\n/** The singleton registry instance — one DO holds every app/route/collaborator. */\nexport const REGISTRY_INSTANCE = 'v1:global'\n\n/** Public app ids have one canonical ULID-minted shape. */\nexport const APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/\nexport const STRICT_APP_ID_RE = APP_ID_RE\n\n/** Internal physical locators survive identity migration and are not public ids. */\nexport const RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —\n * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */\nexport function mintUlid(now = Date.now()): string {\n let ts = ''\n let t = now\n for (let i = 0; i < 10; i++) {\n ts = CROCKFORD[t % 32] + ts\n t = Math.floor(t / 32)\n }\n const rand = new Uint8Array(10)\n crypto.getRandomValues(rand)\n let rs = ''\n let acc = 0\n let bits = 0\n for (const byte of rand) {\n acc = (acc << 8) | byte\n bits += 8\n while (bits >= 5) {\n bits -= 5\n rs += CROCKFORD[(acc >> bits) & 31]\n }\n }\n return `${ts}${rs}`.slice(0, 26)\n}\n\n/** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the\n * deploy worker's authenticated `POST /api/apps/mint`, which registers the id\n * to its caller in the same step — an id with no owner never exists. */\nexport function mintAppId(now = Date.now()): string {\n return `app_${mintUlid(now)}`\n}\n\nexport class RegistryClientError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n ) {\n super(message)\n this.name = 'RegistryClientError'\n }\n}\n\nexport function registryStub(ns: DurableObjectNamespace): DurableObjectStub {\n return ns.get(ns.idFromName(REGISTRY_INSTANCE))\n}\n\n/**\n * One registry action over the wire. Non-2xx answers throw\n * {@link RegistryClientError} carrying the DO's structured `code` (or\n * `'error'` for a transport-shaped body) — callers distinguish a registry-\n * REPORTED miss (`code === 'not_found'`) from an outage by that code, never\n * by treating every throw as \"not found\".\n */\nexport async function callRegistry<T>(\n stub: DurableObjectStub,\n body: { action: string } & Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch('https://registry/', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n const json = (await res.json().catch(() => ({}))) as { error?: string; code?: string }\n if (!res.ok) {\n throw new RegistryClientError(\n json.error ?? `Registry error (${res.status})`,\n json.code ?? 'error',\n res.status,\n )\n }\n return json as T\n}\n\n/** Standard mapping of a registry failure onto a route response. */\nexport function registryErrorJson(\n c: { json: (body: unknown, status?: number) => Response },\n err: unknown,\n): Response {\n if (err instanceof RegistryClientError) {\n return c.json({ error: err.message, code: err.code }, err.status as 400)\n }\n throw err\n}\n","/**\n * Tools API Types and Definitions\n * \n * MCP-like interface for agent tool calls.\n * Built-in tools for records, schemas, users, and Yjs documents.\n */\n\nexport interface ToolSchema {\n name: string\n description: string\n params: Record<string, {\n type: 'string' | 'number' | 'boolean' | 'object' | 'array'\n description: string\n required?: boolean\n default?: unknown\n }>\n}\n\n/**\n * Discriminated union so callers don't have to guard on `error` being\n * defined when `success` is false — TS enforces the invariant.\n */\nexport type ToolResult =\n | { success: true; data?: unknown }\n | { success: false; error: string }\n\n/**\n * Page size the assistant defaults to for `records.query` when it omits\n * `limit`. Keeps a model-issued unbounded scan from blowing the tool-result\n * byte cap and gives the model a usable first page. Raise `limit` to page\n * through more (still subject to the cap; see\n * `DEFAULT_CONTEXT_CONFIG.toolResultCap` / `capToolResultSize`, which truncates\n * oversized pages gracefully rather than dropping them).\n *\n * Applied by the AI tool layer via `applyAiToolDefaults`, never by the shared\n * tools-api dispatch, so internal record readers (chat history, cron, app\n * `actions.query`) stay unbounded.\n */\nexport const DEFAULT_QUERY_LIMIT = 50\n\n/**\n * Page size `records.deleteWhere` uses when the caller omits `limit`, and the\n * ceiling it clamps any larger `limit` to.\n *\n * The tool exists so a cascade costs one subrequest per *page* instead of one\n * per row (see `deleteChatCascade`); the ceiling keeps a single call's SQL and\n * delete broadcasts bounded, and callers page by repeating the call until it\n * reports fewer deletions than the page size.\n */\nexport const DEFAULT_DELETE_WHERE_LIMIT = 100\nexport const MAX_DELETE_WHERE_LIMIT = 500\n\n/**\n * Fill in assistant-only parameter defaults for a built-in tool call.\n *\n * Applied by the AI tool layer (`buildTools`) before a model-issued tool call\n * is dispatched, so the default only affects calls the assistant makes.\n * `records.query` doubles as the SDK's general record-read primitive (chat\n * history, cron, app `actions.query`); those callers reach the tools dispatch\n * directly and must return every row, so this default must not live in the\n * dispatch itself. Pure and non-mutating.\n */\nexport function applyAiToolDefaults(\n toolName: string,\n params: Record<string, unknown>,\n): Record<string, unknown> {\n if (toolName === 'records.query' && params.limit === undefined) {\n return { ...params, limit: DEFAULT_QUERY_LIMIT }\n }\n return params\n}\n\n/**\n * Built-in tool definitions for record, schema, user, and Yjs operations.\n */\nexport const BUILT_IN_TOOLS: ToolSchema[] = [\n // Record tools (structured collections with RBAC)\n {\n name: 'records.query',\n description:\n 'Query records from a collection with optional filtering, ordering, and pagination. ' +\n `Returns at most \\`limit\\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: ` +\n 'an oversized page is truncated to the leading records that fit and flagged with ' +\n '`{ truncated, returned, total }`, so narrow with a `where` filter (or page via `orderBy` + `limit`) ' +\n 'to see the rest.',\n params: {\n collection: { type: 'string', description: 'Collection name to query', required: true },\n where: { type: 'object', description: 'Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column (an unknown key is refused, not ignored)', required: false },\n orderBy: { type: 'string', description: 'Field to order by (or \"createdAt\"/\"updatedAt\")', required: false },\n orderDir: { type: 'string', description: 'Order direction: \"asc\" or \"desc\" (default: \"desc\")', required: false },\n limit: { type: 'number', description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false },\n }\n },\n {\n name: 'records.get',\n description: 'Get a single record by ID from a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to retrieve', required: true },\n }\n },\n {\n name: 'records.create',\n description: 'Create a new record in a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n data: { type: 'object', description: 'Record data matching the collection schema', required: true },\n recordId: { type: 'string', description: 'Custom record ID (auto-generated if omitted)', required: false },\n }\n },\n {\n name: 'records.update',\n description: 'Update an existing record in a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to update', required: true },\n data: { type: 'object', description: 'Fields to update (merged with existing data)', required: true },\n }\n },\n {\n name: 'records.delete',\n description: 'Delete a record from a collection',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID to delete', required: true },\n }\n },\n {\n name: 'records.deleteWhere',\n description:\n 'Delete every record matching a filter, in one bounded batch. ' +\n `Deletes at most \\`limit\\` records (default ${DEFAULT_DELETE_WHERE_LIMIT}, max ${MAX_DELETE_WHERE_LIMIT}) and returns ` +\n '`{ deleted }`; repeat the same call until `deleted` is less than the limit to drain a larger set. ' +\n 'A non-empty `where` whose every key names a real field (`recordId`, `createdBy`, or a schema column) is required — ' +\n 'an unknown key is refused rather than ignored, so this tool never deletes a whole collection. ' +\n 'Every matched record goes through the same delete permission check as `records.delete`; ' +\n 'if any of them is denied, nothing in the batch is deleted.',\n params: {\n collection: { type: 'string', description: 'Collection name (must have a schema)', required: true },\n where: { type: 'object', description: 'Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column', required: true },\n limit: { type: 'number', description: `Maximum number of records to delete in this call (default: ${DEFAULT_DELETE_WHERE_LIMIT}, max: ${MAX_DELETE_WHERE_LIMIT})`, required: false },\n }\n },\n // Schema tools\n {\n name: 'schema.list',\n description: 'List all registered collection schemas with their fields and permissions',\n params: {}\n },\n {\n name: 'schema.describe',\n description: 'Get the full schema for a specific collection including field types and role permissions',\n params: {\n collection: { type: 'string', description: 'Collection name to describe', required: true },\n }\n },\n // User tools\n {\n name: 'user.current',\n description: 'Get the current user\\'s profile (ID, name, email, role)',\n params: {}\n },\n {\n name: 'user.list',\n description:\n 'List the users this caller may see, exactly as the room\\'s roster does: an admin gets whole rows ' +\n '(including email and any app-defined column) through the users collection\\'s read policy; everyone ' +\n 'else gets the public identity projection (id, name, imageUrl, role, lastSeenAt) of the roster the ' +\n 'users policy allows them — nothing if that policy denies reads, and only their own scope if the ' +\n 'schema sets `roster: \\'read-policy\\'`. A caller with no identity gets an empty list. ' +\n 'Called from a server action (X-App-Action), it returns whole rows like the other RBAC-off tools.',\n params: {}\n },\n // Yjs tools (collaborative document access)\n {\n name: 'yjs.list',\n description: 'List all Yjs documents stored in this room, showing collection, recordId, fieldName, and last update time',\n params: {}\n },\n {\n name: 'yjs.getText',\n description: 'Read text content from a Yjs document (for fields using useYjsText)',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID', required: true },\n fieldName: { type: 'string', description: 'Field name (e.g., \"content\")', required: true },\n }\n },\n {\n name: 'yjs.setText',\n description: 'Write text content to a Yjs document (replaces existing text, broadcasts to connected clients)',\n params: {\n collection: { type: 'string', description: 'Collection name', required: true },\n recordId: { type: 'string', description: 'Record ID', required: true },\n fieldName: { type: 'string', description: 'Field name (e.g., \"content\")', required: true },\n text: { type: 'string', description: 'The text content to set', required: true },\n }\n },\n]\n","/**\n * App-files limits, chunk planning, and failure reading — shared by every\n * caller of the files API: the browser hook (`client/storage/useR2Files.ts`),\n * the CLI (`cli/lib/app-files-api.ts`), the server handler\n * (`server/utils/scoped-r2-files.ts`), and the docs that quote them.\n *\n * There are three numbers here and they answer three different questions.\n * Conflating them is what produced the old 25 MiB per-file ceiling: that was\n * never a product decision, it was the size of one HTTP request leaking out as\n * a rule about files. A request is bounded by physics — Cloudflare rejects an\n * oversized body before any worker runs, and the worker's isolate is ~128 MiB.\n * A FILE is not, once it travels as a sequence of bounded requests.\n *\n * The other half of this module is failure reading. An upload refused at the\n * edge answers with a Cloudflare HTML page, which every caller used to feed\n * straight into `JSON.parse` and report as \"Unexpected token '<' … is not\n * valid JSON\". `describeFilesFailure` exists to make that impossible.\n */\n\n/**\n * The largest single file the app-files API accepts.\n *\n * Reachable only through the multipart transport: a file above\n * {@link UPLOAD_PART_BYTES} is uploaded as a sequence of parts, so no request\n * and no isolate ever holds more than one part. Enforced by the shared handler\n * (`server/utils/scoped-r2-files.ts`) twice — once at init against the\n * declared total, so an impossible upload fails before a byte moves, and again\n * at complete against the assembled object, because the declared total is\n * client-supplied. Callers check it locally too, but only to fail early;\n * skipping the client check cannot widen what is accepted.\n */\nexport const MAX_APP_FILE_BYTES = 1024 * 1024 * 1024\n\n/**\n * The most any ONE request body may carry — a whole small file on the\n * single-request path, or one part on the multipart path.\n *\n * This is the transport's real physical bound, and the only thing 25 MiB ever\n * meant. Above it the worker risks exhausting its isolate holding the body;\n * well above it (~100 MB) Cloudflare refuses the request before the worker\n * runs at all, which is why callers must read failures as text first.\n */\nexport const MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024\n\n/**\n * The part size clients chunk at, and the threshold above which they must.\n *\n * Below the {@link MAX_UPLOAD_REQUEST_BYTES} bound with headroom for the\n * request envelope, and comfortably inside R2's own part rules: parts must be\n * at least 5 MiB and every non-final part must be exactly the same size\n * (R2 errors 10011 / 10048). The server advertises this at init — a client\n * chunks at the size it is told, not at this constant, so the number can move\n * without stranding older callers mid-upload.\n */\nexport const UPLOAD_PART_BYTES = 20 * 1024 * 1024\n\n/**\n * The highest part number the server accepts.\n *\n * The server derives the only valid part number and byte length from the\n * declared total and {@link UPLOAD_PART_BYTES}. The maximum part count is\n * therefore the exact number needed to reach the file ceiling.\n *\n * R2 allows 10,000 parts, so this is far inside what the API permits.\n */\nexport const MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES)\n\n/**\n * The largest base64 payload the single-request JSON shape can carry.\n *\n * Derived from the ENCODED size, because that is what the request body is:\n * base64 spends 4 characters per 3 bytes, so a payload measured only by its\n * decoded size can sit under {@link MAX_UPLOAD_REQUEST_BYTES} and still\n * produce a body a third larger than the server will accept — a client check\n * that passes everything the server then refuses. Roughly 18.75 MiB.\n */\nexport const MAX_BASE64_UPLOAD_BYTES = Math.floor((MAX_UPLOAD_REQUEST_BYTES * 3) / 4)\n\n/** Byte ranges for a chunked upload: `[start, end)`, `end` exclusive. */\nexport interface UploadPart {\n partNumber: number\n start: number\n end: number\n}\n\n/**\n * Split a file into the parts the multipart transport expects.\n *\n * Every part is exactly `partSize` except the last, which is what R2 requires\n * of non-final parts. Both chunking clients plan with this, so \"which bytes\n * are part N\" is answered in one place.\n */\nexport function planUploadParts(totalBytes: number): UploadPart[] {\n const parts: UploadPart[] = []\n for (let start = 0; start < totalBytes; start += UPLOAD_PART_BYTES) {\n parts.push({\n partNumber: parts.length + 1,\n start,\n end: Math.min(start + UPLOAD_PART_BYTES, totalBytes),\n })\n }\n return parts\n}\n\nconst GIB = 1024 * 1024 * 1024\n\n/**\n * The customer-visible storage schedule, in bytes, PER ACCOUNT — the total an\n * owner's uploaded files may occupy across every app they own, not a budget\n * each app gets separately. This is the number the billing page advertises.\n *\n * Admitted by the shared files handler (`server/utils/scoped-r2-files.ts`),\n * which sums the owner's apps rather than the one being written to.\n *\n * It deliberately does NOT govern the repo store — see\n * `REPO_STORE_LIMIT_BYTES` below, and docs/platform/r2-storage.md.\n */\nexport const ACCOUNT_STORAGE_LIMIT_BYTES = {\n test: 0,\n free: 1 * GIB,\n starter: 10 * GIB,\n premium: 30 * GIB,\n admin: 100 * GIB,\n} as const\n\n/** The account storage limit for a billing tier; unknown or missing tiers get free. */\nexport function storageLimitForTier(tier: string | null | undefined): number {\n if (tier && Object.hasOwn(ACCOUNT_STORAGE_LIMIT_BYTES, tier)) {\n return ACCOUNT_STORAGE_LIMIT_BYTES[tier as keyof typeof ACCOUNT_STORAGE_LIMIT_BYTES]\n }\n return ACCOUNT_STORAGE_LIMIT_BYTES.free\n}\n\n/**\n * The repo store's budget, per app, flat across every tier.\n *\n * Git packs, retained rollback bundles, and stored deploy assets are platform\n * bookkeeping, not something a customer buys — so this is a working ceiling,\n * not an entitlement, and it is deliberately small. An app that cannot fit its\n * SOURCE plus a few rollback bundles in this has something in the wrong place:\n * user uploads belong in the app-files allocation above, which is what the\n * account schedule sizes.\n *\n * Being flat is the point. Tying it to the tier table meant every raise to the\n * customer-facing number silently multiplied platform-side storage per app,\n * and made a push depend on a billing lookup that could refuse it.\n */\nexport const REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024\n\n/** The one storage-quota refusal sentence. `usedBytes` is net of any object\n * the upload replaces, so an upsert is charged only for its growth. */\nexport function storageQuotaMessage(\n incomingBytes: number,\n usedBytes: number,\n limitBytes: number,\n): string {\n const free = Math.max(0, limitBytes - usedBytes)\n return (\n `This upload needs ${formatBytes(incomingBytes)} but the app's file storage has ` +\n `${formatBytes(free)} free (${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used). ` +\n `Delete files with \\`deepspace app files rm <key>\\`, or upgrade the owner's plan.`\n )\n}\n\n/**\n * The largest single file a deploy may ship as a static asset.\n *\n * Cloudflare Static Assets refuses anything larger, so this is a hard edge of\n * the platform rather than a policy dial — which is why the CLI may check it\n * locally. It lives here so the deploy worker and the CLI cannot drift; the\n * per-DEPLOY total is deliberately NOT here, because it is env-configurable\n * per environment and only the server can know it.\n */\nexport const MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024\n\n/** MIME types that must download rather than render in the app's origin. */\nexport const DANGEROUS_MIME_TYPES = new Set<string>([\n 'text/html',\n 'application/xhtml+xml',\n 'image/svg+xml',\n 'application/xml',\n 'text/xml',\n 'application/javascript',\n 'text/javascript',\n 'application/ecmascript',\n 'text/ecmascript',\n])\n\nexport function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`\n if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`\n return `${bytes} B`\n}\n\n/** Encode an R2 key as URL path segments while preserving its hierarchy. */\nexport function encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/')\n}\n\n/** The one oversize sentence, for whichever caller notices first. */\nexport function oversizeMessage(bytes: number, limit = MAX_APP_FILE_BYTES): string {\n return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(limit)} per file.`\n}\n\n/**\n * The one over-the-request-bound sentence. Distinct from\n * {@link oversizeMessage} because it names a fixable mistake: the file is fine,\n * the caller just sent it in one piece instead of in parts.\n */\nexport function oversizeRequestMessage(bytes: number): string {\n return (\n `This request carries ${formatBytes(bytes)}; one request may carry ` +\n `${formatBytes(MAX_UPLOAD_REQUEST_BYTES)}. Files above ` +\n `${formatBytes(UPLOAD_PART_BYTES)} upload in parts.`\n )\n}\n\n/** A part is bounded tighter than a request; say which number was crossed. */\nexport function oversizePartMessage(bytes: number): string {\n return (\n `This part carries ${formatBytes(bytes)}; a part may carry ` +\n `${formatBytes(UPLOAD_PART_BYTES)} — the size the upload advertised.`\n )\n}\n\n/**\n * Turn a failed files-API response into a sentence worth showing.\n *\n * `body` is the raw response text. A JSON `{ error }` is the app's own\n * refusal and is passed through; anything else came from the edge (an HTML\n * error page, an empty body, a proxy) and is described by its status instead\n * of being echoed, because echoing it is what produced the JSON-parse noise.\n */\nexport function describeFilesFailure(status: number, body: string): string {\n const trimmed = body.trim()\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed) as { error?: string }\n if (parsed.error) return parsed.error\n } catch {\n // Not the app's JSON after all — fall through to the status description.\n }\n }\n if (status === 413) {\n return (\n `The upload is too large. Files are limited to ${formatBytes(MAX_APP_FILE_BYTES)}, ` +\n `and one request may carry ${formatBytes(MAX_UPLOAD_REQUEST_BYTES)} — a larger request ` +\n `is rejected by the network before the app receives it.`\n )\n }\n if (status === 411) return 'The upload did not declare its length.'\n if (status === 401 || status === 403) return 'Not authorized to write these files.'\n if (status === 415) return 'That file type is not accepted.'\n if (status >= 500) return `The files service failed (HTTP ${status}). Retry shortly.`\n return `Upload failed (HTTP ${status}).`\n}\n","/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { storageQuotaMessage } from '../../shared/app-files'\n\nconst CORS_HEADERS = { 'Access-Control-Allow-Origin': '*' } as const\nconst QUOTA_SUMMARY_VERSION = 1\nconst MAX_QUOTA_SCAN_OBJECTS = 500_000\nconst MAX_QUOTA_SCAN_LISTS = 900\nconst QUOTA_REPAIR_COOLDOWN_MS = 60_000\nconst PENDING_MARKER_MAX_AGE_MS = 60 * 60_000\nconst EMPTY_MULTIPART_MARKER_MAX_AGE_MS = 60 * 60_000\nconst ACTIVE_MULTIPART_MARKER_MAX_AGE_MS = 8 * 24 * 60 * 60_000\n\n/**\n * Everything the files handler needs to enforce one account allocation.\n *\n * `quotaKey` is required because an exact scan followed by an uncoordinated\n * write cannot enforce a limit under concurrency. Callers that do not want a\n * quota omit `storage`; callers that do want one must name its serialized\n * summary.\n */\nexport interface StorageAdmission {\n /** The app prefix receiving this request's write. */\n prefix: string\n /** Internal R2 key for the account's ETag-serialized usage summary. */\n quotaKey: string\n /** Resolve the account's limit. `null` fails writes closed. */\n limitBytes: () => Promise<number | null>\n /** Resolve the other app prefixes in the account. `null` fails closed. */\n siblingPrefixes?: () => Promise<string[] | null>\n}\n\ninterface StorageReservation {\n markerKey: string | null\n}\n\ninterface QuotaMarkerSpec {\n key: string\n body?: string\n metadata: Record<string, string>\n}\n\nexport interface MultipartQuotaReservation {\n key: string\n etag: string\n reservationId: string\n uploadId: string\n objectKey: string\n uploadKey: string\n admitted: boolean\n declaredBytes: number\n reservedBytes: number\n}\n\ninterface QuotaSummary {\n version: typeof QUOTA_SUMMARY_VERSION\n prefixes: string[]\n usedBytes: number\n measuredAt: number\n dirty: boolean\n}\n\ninterface QuotaSnapshot {\n etag: string | null\n summary: QuotaSummary | null\n}\n\ninterface QuotaScanBudget {\n lists: number\n objects: number\n staleKeys: string[]\n}\n\ninterface MultipartMarkerState {\n version: 1\n reservationId: string\n uploadId: string\n objectKey: string\n uploadKey: string\n admitted: boolean\n declaredBytes: number\n}\n\nfunction fail(\n status: number,\n error: string,\n code?: string,\n extra?: Record<string, unknown>,\n): Response {\n return Response.json(\n { error, ...(code ? { code } : {}), ...extra },\n { status, headers: CORS_HEADERS },\n )\n}\n\nexport function quotaUnavailable(message: string): Response {\n return fail(503, message, 'storage_limit_unavailable')\n}\n\nfunction quotaKeySegment(value: string): string {\n return encodeURIComponent(value)\n}\n\nfunction markerPrefix(storage: StorageAdmission, kind: 'pending' | 'multipart'): string {\n return `__deepspace/quota/${kind}/${quotaKeySegment(storage.quotaKey)}/`\n}\n\nfunction pendingMarkerPrefix(storage: StorageAdmission): string {\n return markerPrefix(storage, 'pending')\n}\n\nfunction multipartMarkerPrefix(storage: StorageAdmission): string {\n return markerPrefix(storage, 'multipart')\n}\n\nexport function multipartMarkerKey(storage: StorageAdmission, reservationId: string): string {\n return `${multipartMarkerPrefix(storage)}${quotaKeySegment(reservationId)}`\n}\n\nfunction accountPrefixesEqual(left: string[], right: string[]): boolean {\n return left.length === right.length && left.every((prefix, index) => prefix === right[index])\n}\n\nasync function allocationPrefixes(storage: StorageAdmission): Promise<string[] | null> {\n const siblings = storage.siblingPrefixes ? await storage.siblingPrefixes() : []\n if (siblings === null) return null\n return [...new Set([storage.prefix, ...siblings])].sort()\n}\n\nfunction countScan(budget: QuotaScanBudget, count: number): void {\n budget.lists++\n budget.objects += count\n if (budget.lists > MAX_QUOTA_SCAN_LISTS || budget.objects > MAX_QUOTA_SCAN_OBJECTS) {\n throw new Error('quota scan work limit exceeded')\n }\n}\n\n/**\n * Measure stored bytes under one app prefix.\n *\n * The account has one pair of marker scans rather than one pair per app, so an\n * empty historical identity costs a single bounded list call. Object count has\n * its own larger bound because tiny objects are independently amplifiable.\n */\nasync function prefixUsage(\n bucket: R2Bucket,\n prefix: string,\n budget: QuotaScanBudget,\n): Promise<number> {\n let total = 0\n let cursor: string | undefined\n do {\n const page = await bucket.list({ prefix, cursor, limit: 1000 })\n countScan(budget, page.objects.length)\n for (const object of page.objects) total += object.size\n cursor = page.truncated ? page.cursor : undefined\n } while (cursor !== undefined)\n return total\n}\n\nfunction markerBytes(object: R2Object): number {\n const raw = object.customMetadata?.reservedBytes\n const bytes = raw === undefined ? NaN : Number(raw)\n if (!Number.isSafeInteger(bytes) || bytes < 0) throw new Error('invalid quota marker')\n return bytes\n}\n\nfunction markerMaxAge(object: R2Object, fallback: number): number {\n if (object.customMetadata?.quotaKind !== 'multipart') return fallback\n return object.customMetadata.active === 'true'\n ? ACTIVE_MULTIPART_MARKER_MAX_AGE_MS\n : EMPTY_MULTIPART_MARKER_MAX_AGE_MS\n}\n\nasync function markerUsage(\n bucket: R2Bucket,\n prefix: string,\n maxAgeMs: number,\n budget: QuotaScanBudget,\n excludedKey?: string,\n): Promise<number> {\n let total = 0\n let cursor: string | undefined\n const now = Date.now()\n do {\n const options = {\n prefix,\n cursor,\n limit: 1000,\n include: ['customMetadata'],\n } as R2ListOptions & { include: ['customMetadata'] }\n const page = await bucket.list(options)\n countScan(budget, page.objects.length)\n for (const object of page.objects) {\n if (now - object.uploaded.getTime() > markerMaxAge(object, maxAgeMs)) {\n if (budget.staleKeys.length < 1000) budget.staleKeys.push(object.key)\n } else if (object.key !== excludedKey) {\n total += markerBytes(object)\n }\n }\n cursor = page.truncated ? page.cursor : undefined\n } while (cursor !== undefined)\n return total\n}\n\nasync function measuredAllocationUsage(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n excludedMarkerKey?: string,\n): Promise<number> {\n const budget: QuotaScanBudget = { lists: 0, objects: 0, staleKeys: [] }\n let total = 0\n // Sequential by design: an account with many historical identities must not\n // turn one request into hundreds of concurrent R2 operations.\n for (const prefix of prefixes) total += await prefixUsage(bucket, prefix, budget)\n total += await markerUsage(\n bucket,\n pendingMarkerPrefix(storage),\n PENDING_MARKER_MAX_AGE_MS,\n budget,\n excludedMarkerKey,\n )\n total += await markerUsage(\n bucket,\n multipartMarkerPrefix(storage),\n ACTIVE_MULTIPART_MARKER_MAX_AGE_MS,\n budget,\n excludedMarkerKey,\n )\n if (budget.staleKeys.length > 0) {\n await bucket.delete(budget.staleKeys).catch((error) => {\n console.error('[files:quota] stale marker cleanup:', error)\n })\n }\n return total\n}\n\nfunction isQuotaSummary(value: unknown): value is QuotaSummary {\n if (!value || typeof value !== 'object') return false\n const summary = value as Partial<QuotaSummary>\n return (\n summary.version === QUOTA_SUMMARY_VERSION &&\n Array.isArray(summary.prefixes) &&\n summary.prefixes.every((prefix) => typeof prefix === 'string') &&\n Number.isSafeInteger(summary.usedBytes) &&\n (summary.usedBytes ?? -1) >= 0 &&\n Number.isSafeInteger(summary.measuredAt) &&\n (summary.measuredAt ?? -1) >= 0 &&\n typeof summary.dirty === 'boolean'\n )\n}\n\nasync function readQuotaSummary(bucket: R2Bucket, key: string): Promise<QuotaSnapshot> {\n const object = await bucket.get(key)\n if (!object) return { etag: null, summary: null }\n try {\n const parsed = await object.json<unknown>()\n return { etag: object.etag, summary: isQuotaSummary(parsed) ? parsed : null }\n } catch {\n return { etag: object.etag, summary: null }\n }\n}\n\nasync function writeQuotaSummary(\n bucket: R2Bucket,\n key: string,\n previousEtag: string | null,\n summary: QuotaSummary,\n): Promise<QuotaSnapshot | null> {\n const onlyIf = previousEtag\n ? { etagMatches: previousEtag }\n : new Headers({ 'If-None-Match': '*' })\n const object = await bucket.put(key, JSON.stringify(summary), {\n onlyIf,\n httpMetadata: { contentType: 'application/json' },\n })\n return object ? { etag: object.etag, summary } : null\n}\n\nasync function ensureQuotaSummary(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n forceMeasure = false,\n excludedMarkerKey?: string,\n): Promise<QuotaSnapshot | null> {\n for (let attempt = 0; attempt < 4; attempt++) {\n const current = await readQuotaSummary(bucket, storage.quotaKey)\n if (\n !forceMeasure &&\n current.summary &&\n accountPrefixesEqual(current.summary.prefixes, prefixes)\n ) {\n return current\n }\n const usedBytes = await measuredAllocationUsage(bucket, storage, prefixes, excludedMarkerKey)\n const repaired = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n version: QUOTA_SUMMARY_VERSION,\n prefixes,\n usedBytes,\n measuredAt: Date.now(),\n dirty: false,\n })\n if (repaired) return repaired\n }\n return null\n}\n\nexport async function markQuotaDirty(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n): Promise<void> {\n if (!storage) return\n try {\n for (let attempt = 0; attempt < 4; attempt++) {\n const current = await readQuotaSummary(bucket, storage.quotaKey)\n if (!current.summary || current.summary.dirty) return\n const written = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n ...current.summary,\n dirty: true,\n })\n if (written) return\n }\n console.error('[files:quota] could not mark summary reclaimable after concurrent writes')\n } catch (error) {\n console.error('[files:quota] could not mark summary reclaimable:', error)\n }\n}\n\nasync function neutralizeMarker(bucket: R2Bucket, markerKey: string): Promise<boolean> {\n for (let attempt = 0; attempt < 4; attempt++) {\n const marker = await bucket.get(markerKey)\n if (!marker) return true\n if (markerBytes(marker) === 0) return true\n // Preserve multipart recovery state if the following delete keeps failing;\n // a completion retry can then neutralize/delete the same marker again.\n const body = await marker.arrayBuffer()\n const object = await bucket.put(markerKey, body, {\n onlyIf: { etagMatches: marker.etag },\n customMetadata: {\n ...marker.customMetadata,\n reservedBytes: '0',\n releasedAt: String(Date.now()),\n },\n })\n if (object) return true\n }\n return false\n}\n\n/**\n * Make a reservation stop counting before attempting its best-effort delete.\n * A failed delete therefore leaves a zero-byte cleanup record, not days of\n * phantom quota. `summaryMayBeStale` is false only when admission refused\n * before its counter reservation landed.\n */\nexport async function releaseQuotaMarker(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n markerKey: string | null,\n summaryMayBeStale: boolean,\n): Promise<boolean> {\n try {\n if (markerKey && !(await neutralizeMarker(bucket, markerKey))) {\n console.error('[files:quota] could not neutralize reservation marker')\n return false\n }\n if (summaryMayBeStale) await markQuotaDirty(bucket, storage)\n if (markerKey) {\n for (let attempt = 0; attempt < 4; attempt++) {\n try {\n await bucket.delete(markerKey)\n break\n } catch (error) {\n if (attempt === 3) throw error\n }\n }\n }\n return true\n } catch (error) {\n console.error('[files:quota] reservation cleanup:', error)\n return false\n }\n}\n\nfunction parseMultipartMarker(value: unknown): MultipartMarkerState | null {\n if (!value || typeof value !== 'object') return null\n const marker = value as Partial<MultipartMarkerState>\n if (\n marker.version !== 1 ||\n typeof marker.reservationId !== 'string' ||\n !marker.reservationId ||\n typeof marker.uploadId !== 'string' ||\n !marker.uploadId ||\n typeof marker.objectKey !== 'string' ||\n !marker.objectKey ||\n typeof marker.uploadKey !== 'string' ||\n !marker.uploadKey ||\n typeof marker.admitted !== 'boolean' ||\n !Number.isSafeInteger(marker.declaredBytes) ||\n (marker.declaredBytes ?? -1) < 0\n ) {\n return null\n }\n return marker as MultipartMarkerState\n}\n\nfunction multipartMarkerBody(reservation: MultipartQuotaReservation): string {\n return JSON.stringify({\n version: 1,\n reservationId: reservation.reservationId,\n uploadId: reservation.uploadId,\n objectKey: reservation.objectKey,\n uploadKey: reservation.uploadKey,\n admitted: reservation.admitted,\n declaredBytes: reservation.declaredBytes,\n } satisfies MultipartMarkerState)\n}\n\nexport async function readMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n): Promise<MultipartQuotaReservation | null | 'invalid'> {\n if (!storage) return null\n const key = multipartMarkerKey(storage, reservationId)\n let object: R2ObjectBody | null\n try {\n object = await bucket.get(key)\n } catch (error) {\n console.error('[files:quota] reservation read:', error)\n return 'invalid'\n }\n if (!object) return null\n let marker: MultipartMarkerState | null = null\n try {\n marker = parseMultipartMarker(await object.json<unknown>())\n } catch {\n return 'invalid'\n }\n if (!marker || marker.reservationId !== reservationId) return 'invalid'\n let reservedBytes: number\n try {\n reservedBytes = markerBytes(object)\n } catch {\n return 'invalid'\n }\n return {\n key,\n etag: object.etag,\n reservationId,\n uploadId: marker.uploadId,\n objectKey: marker.objectKey,\n uploadKey: marker.uploadKey,\n admitted: marker.admitted,\n declaredBytes: marker.declaredBytes,\n reservedBytes,\n }\n}\n\n/** Refresh an active multipart marker without carrying a per-part ledger. */\nexport async function refreshMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n uploadId: string,\n objectKey: string,\n): Promise<MultipartQuotaReservation | null | 'invalid' | 'busy'> {\n if (!storage) return null\n for (let attempt = 0; attempt < 4; attempt++) {\n const reservation = await readMultipartReservation(bucket, storage, reservationId)\n if (reservation === null || reservation === 'invalid') return reservation ?? 'invalid'\n if (\n !reservation.admitted ||\n reservation.uploadId !== uploadId ||\n reservation.objectKey !== objectKey\n ) {\n return 'invalid'\n }\n const object = await bucket.put(reservation.key, multipartMarkerBody(reservation), {\n onlyIf: { etagMatches: reservation.etag },\n customMetadata: {\n quotaKind: 'multipart',\n reservedBytes: String(reservation.reservedBytes),\n active: 'true',\n },\n })\n if (object) return { ...reservation, etag: object.etag }\n }\n return 'busy'\n}\n\nexport async function activateMultipartReservation(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n reservationId: string,\n): Promise<MultipartQuotaReservation | null> {\n if (!storage) return null\n for (let attempt = 0; attempt < 4; attempt++) {\n const reservation = await readMultipartReservation(bucket, storage, reservationId)\n if (!reservation || reservation === 'invalid') return null\n if (reservation.admitted) return reservation\n const next = { ...reservation, admitted: true }\n const object = await bucket.put(reservation.key, multipartMarkerBody(next), {\n onlyIf: { etagMatches: reservation.etag },\n customMetadata: {\n quotaKind: 'multipart',\n reservedBytes: String(reservation.reservedBytes),\n active: 'false',\n },\n })\n if (object) {\n return { ...next, etag: object.etag }\n }\n }\n return null\n}\n\nfunction quotaExceeded(\n incomingBytes: number,\n summaryBytes: number,\n replacedBytes: number,\n limitBytes: number,\n): Response {\n const usedBytes = Math.max(0, summaryBytes - replacedBytes)\n return fail(\n 409,\n storageQuotaMessage(incomingBytes, usedBytes, limitBytes),\n 'storage_quota_exceeded',\n { usedBytes, limitBytes },\n )\n}\n\nasync function reserveQuotaSummary(\n bucket: R2Bucket,\n storage: StorageAdmission,\n prefixes: string[],\n initial: QuotaSnapshot,\n chargeBytes: number,\n incomingBytes: number,\n replacedBytes: number,\n limitBytes: number,\n markerKey: string | null,\n): Promise<Response | null> {\n if (chargeBytes === 0) return null\n let current = initial\n let repaired = false\n for (let attempt = 0; attempt < 8; attempt++) {\n if (!current.summary || !accountPrefixesEqual(current.summary.prefixes, prefixes)) {\n const ensured = await ensureQuotaSummary(\n bucket,\n storage,\n prefixes,\n false,\n markerKey ?? undefined,\n )\n if (!ensured) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n current = ensured\n }\n const summary = current.summary\n if (!summary) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n if (summary.usedBytes + chargeBytes > limitBytes) {\n const recentlyMeasured = Date.now() - summary.measuredAt < QUOTA_REPAIR_COOLDOWN_MS\n if (repaired || (!summary.dirty && recentlyMeasured)) {\n return quotaExceeded(incomingBytes, summary.usedBytes, replacedBytes, limitBytes)\n }\n const refreshed = await ensureQuotaSummary(\n bucket,\n storage,\n prefixes,\n true,\n markerKey ?? undefined,\n )\n if (!refreshed?.summary) {\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n current = refreshed\n repaired = true\n continue\n }\n const written = await writeQuotaSummary(bucket, storage.quotaKey, current.etag, {\n ...summary,\n usedBytes: summary.usedBytes + chargeBytes,\n })\n if (written) return null\n current = await readQuotaSummary(bucket, storage.quotaKey)\n }\n return quotaUnavailable('Storage is busy with other uploads. Retry shortly.')\n}\n\nexport async function admitStorage(\n bucket: R2Bucket,\n storage: StorageAdmission | undefined,\n incomingBytes: number,\n replacedBytes: number,\n marker?: QuotaMarkerSpec,\n): Promise<StorageReservation | Response> {\n if (!storage) return { markerKey: null }\n let markerKey: string | null = null\n try {\n const limit = await storage.limitBytes()\n if (limit === null) {\n return quotaUnavailable(\n 'The storage limit for this app could not be verified; nothing was uploaded. Retry shortly.',\n )\n }\n const prefixes = await allocationPrefixes(storage)\n if (prefixes === null) {\n return quotaUnavailable(\n 'The apps in this storage allocation could not be enumerated; nothing was uploaded. Retry shortly.',\n )\n }\n const initial = await ensureQuotaSummary(bucket, storage, prefixes)\n if (!initial) {\n return quotaUnavailable(\n 'The storage already used by this account could not be measured; nothing was uploaded. Retry shortly.',\n )\n }\n\n const chargeBytes = Math.max(0, incomingBytes - replacedBytes)\n const effectiveMarker =\n marker ??\n (chargeBytes > 0\n ? {\n key: `${pendingMarkerPrefix(storage)}${crypto.randomUUID()}`,\n metadata: { quotaKind: 'pending' },\n }\n : null)\n if (effectiveMarker) {\n const object = await bucket.put(effectiveMarker.key, effectiveMarker.body ?? '', {\n onlyIf: new Headers({ 'If-None-Match': '*' }),\n customMetadata: {\n ...effectiveMarker.metadata,\n reservedBytes: String(chargeBytes),\n },\n })\n if (!object) {\n return fail(\n 409,\n 'An upload with this request id is already in progress.',\n 'reservation_conflict',\n )\n }\n markerKey = effectiveMarker.key\n }\n\n const refusal = await reserveQuotaSummary(\n bucket,\n storage,\n prefixes,\n initial,\n chargeBytes,\n incomingBytes,\n replacedBytes,\n limit,\n effectiveMarker?.key ?? null,\n )\n if (refusal) {\n await releaseQuotaMarker(bucket, storage, effectiveMarker?.key ?? null, false)\n return refusal\n }\n return { markerKey: effectiveMarker?.key ?? null }\n } catch (error) {\n console.error('[files:quota] admission:', error)\n await releaseQuotaMarker(bucket, storage, markerKey, true)\n return quotaUnavailable(\n 'The storage used by this account could not be verified. Retry shortly.',\n )\n }\n}\n\nexport async function storageUsage(\n bucket: R2Bucket,\n storage: StorageAdmission,\n): Promise<number | null> {\n try {\n const prefixes = await allocationPrefixes(storage)\n if (prefixes === null) return null\n const current = await ensureQuotaSummary(bucket, storage, prefixes)\n return current?.summary?.usedBytes ?? null\n } catch (error) {\n console.error('[files:quota] usage:', error)\n return null\n }\n}\n","/**\n * Shared Scoped R2 Files Handler\n *\n * Provides a secure, prefix-scoped R2 files API that enforces:\n * 1. All R2 keys are validated against the resolved prefix (no bypass)\n * 2. Path traversal (`..`, `.`) is rejected\n * 3. Mutations (upload/delete) require authentication by default\n *\n * Each worker provides a `resolvePrefix` callback for its scoping rules.\n * The security invariants are enforced here once — not per-worker.\n *\n * Routes:\n * POST /api/files/upload → upload one file in one request\n * POST /api/files/multipart → begin a chunked upload\n * PUT /api/files/multipart/part → send one part\n * POST /api/files/multipart/complete → assemble the parts\n * DELETE /api/files/multipart → abandon a chunked upload\n * GET /api/files → list (prefix + optional user prefix)\n * GET /api/files/:key → download (validated against prefix)\n * DELETE /api/files/:key → delete (validated against prefix)\n *\n * ## One transport discipline\n *\n * Both upload paths obey the rule the deploy asset transport established\n * (`docs/platform/deploy-asset-transport.md`): nothing here\n * ever holds more than one bounded request in memory. The single-request path\n * exists so a small file still costs one round trip; above\n * {@link UPLOAD_PART_BYTES} a file travels as parts, each streamed into R2\n * through a `FixedLengthStream` with a required `Content-Length`. That is why\n * a 1 GiB file is admissible in a ~128 MiB isolate.\n *\n * Validation is identical on both paths and on both mounts, because it lives\n * here: the prefix scoping, the traversal check, the MIME refusals, and both\n * size bounds. A hand-rolled request that skips every client guard reaches\n * exactly these checks.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport {\n ACCOUNT_STORAGE_LIMIT_BYTES,\n DANGEROUS_MIME_TYPES,\n MAX_APP_FILE_BYTES,\n MAX_DEPLOY_ASSET_FILE_BYTES,\n MAX_UPLOAD_PARTS,\n MAX_UPLOAD_REQUEST_BYTES,\n UPLOAD_PART_BYTES,\n encodeKeyPath,\n formatBytes,\n oversizeMessage,\n oversizePartMessage,\n oversizeRequestMessage,\n storageLimitForTier,\n storageQuotaMessage,\n} from '../../shared/app-files'\nimport {\n activateMultipartReservation,\n admitStorage,\n markQuotaDirty,\n multipartMarkerKey,\n quotaUnavailable,\n readMultipartReservation,\n refreshMultipartReservation,\n releaseQuotaMarker,\n storageUsage,\n} from './r2-storage-quota'\nimport type { MultipartQuotaReservation, StorageAdmission } from './r2-storage-quota'\n\nexport type { StorageAdmission } from './r2-storage-quota'\n\n// The limits this handler enforces, re-exported so workers and their tests\n// cite the constants rather than copies of the numbers — two copies of one\n// limit is how the CLI and the workers drift. `formatBytes` rides along for\n// the same reason.\nexport {\n ACCOUNT_STORAGE_LIMIT_BYTES,\n MAX_APP_FILE_BYTES,\n MAX_DEPLOY_ASSET_FILE_BYTES,\n MAX_UPLOAD_REQUEST_BYTES,\n UPLOAD_PART_BYTES,\n MAX_UPLOAD_PARTS,\n formatBytes,\n storageLimitForTier,\n storageQuotaMessage,\n}\n\nconst CORS_HEADERS = { 'Access-Control-Allow-Origin': '*' } as const\n\n/** Every refusal in this file, in one shape. */\nfunction fail(\n status: number,\n error: string,\n code?: string,\n extra?: Record<string, unknown>,\n): Response {\n return Response.json(\n { error, ...(code ? { code } : {}), ...extra },\n { status, headers: CORS_HEADERS },\n )\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ScopeContext {\n userId: string | null\n url: URL\n}\n\nexport type PrefixResult =\n | { prefix: string; excludedPrefixes?: readonly string[]; error?: undefined }\n | { prefix?: undefined; error: string }\n\nexport interface ScopedR2Config {\n /**\n * Resolve the R2 key prefix for the given scope.\n * Called with the `?scope=` query param value (default: 'self').\n */\n resolvePrefix: (scope: string, ctx: ScopeContext) => PrefixResult\n\n /**\n * Require a non-null userId for upload and delete.\n * @default true\n */\n requireAuthForMutations?: boolean\n}\n\n/**\n * The storage-quota contract for one allocation, supplied per request by the\n * mount. The handler is the enforcer; the mount only knows whose limit\n * applies.\n */\nexport interface ScopedR2Auth {\n userId: string | null\n /** Absent = no quota on this mount (e.g. an app's own bucket). */\n storage?: StorageAdmission\n /** Account-wide quota details are private to an authorized owner surface. */\n includeStorageUsage?: boolean\n}\n\nexport type ScopedR2Handler = (\n request: Request,\n url: URL,\n bucket: R2Bucket,\n auth: ScopedR2Auth,\n) => Promise<Response>\n\n// ============================================================================\n// Path Security\n// ============================================================================\n\n/**\n * Build a safe Content-Disposition header value from an uploader-supplied\n * filename. The legacy `filename=` parameter is double-quoted, so a name\n * containing `\"` or CR/LF can break out of the value and inject arbitrary\n * response headers (response-splitting). We strip those characters for the\n * legacy parameter and use RFC 5987 `filename*` with percent-encoding for\n * the canonical, UTF-8-safe form that modern browsers prefer.\n */\nfunction contentDisposition(originalName: string, disposition: 'inline' | 'attachment'): string {\n // The legacy `filename=` value must be ASCII as well as header-safe; Fetch\n // implementations may reject raw Unicode header bytes. `filename*` carries\n // the exact UTF-8 name for modern clients.\n const safe = originalName.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, '_') || 'download'\n // encodeURIComponent leaves five characters that RFC 5987 does not admit\n // in an attr-char value, so escape those too.\n const encoded = encodeURIComponent(originalName).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n return `${disposition}; filename=\"${safe}\"; filename*=UTF-8''${encoded}`\n}\n\n/**\n * Reject path traversal attempts.\n * Returns null if the path contains `..` or `.` segments.\n */\nfunction sanitizeSubpath(raw: string): string | null {\n if (!raw) return ''\n const segments = raw.split('/')\n if (segments.some((s) => s === '..' || s === '.')) return null\n return raw\n}\n\n/**\n * Refuse a request body past {@link MAX_UPLOAD_REQUEST_BYTES}.\n *\n * This is where the transport's physical bound is real. Clients check it too,\n * but only to fail early — a hand-rolled request that skips them reaches here,\n * and without this the worker would buffer the whole body into a ~128 MiB\n * isolate. Callers check Content-Length BEFORE reading the body, then the\n * decoded size, which catches a body that lied about (or omitted) its length.\n * The 8 KiB slack leaves room for multipart headers around a maximum body.\n */\nfunction overRequestBound(bytes: number, slack = 0): Response | null {\n if (bytes <= MAX_UPLOAD_REQUEST_BYTES + slack) return null\n return fail(413, oversizeRequestMessage(bytes), 'too_large', {\n maxBytes: MAX_UPLOAD_REQUEST_BYTES,\n partSize: UPLOAD_PART_BYTES,\n })\n}\n\n/** Refuse a FILE past {@link MAX_APP_FILE_BYTES} — the product ceiling. */\nfunction overCeiling(bytes: number): Response | null {\n if (bytes <= MAX_APP_FILE_BYTES) return null\n return fail(413, oversizeMessage(bytes), 'too_large', { maxBytes: MAX_APP_FILE_BYTES })\n}\n\n/**\n * Refuse a part past {@link UPLOAD_PART_BYTES}.\n *\n * Tighter than the request bound on purpose. Parts must be uniform, so a\n * client sending a bigger one has ignored what init told it — and this is the\n * number {@link MAX_UPLOAD_PARTS} is computed against, so admitting a larger\n * part would silently raise the real ceiling above the advertised one.\n */\nfunction overPartBound(bytes: number): Response | null {\n if (bytes <= UPLOAD_PART_BYTES) return null\n return fail(413, oversizePartMessage(bytes), 'too_large', { maxBytes: UPLOAD_PART_BYTES })\n}\n\n/**\n * The length a request declares, or the refusal for one that declares none.\n *\n * `Number(null)` is 0, so reading the header without testing for its ABSENCE\n * treats a chunked body as an empty one — and it then passes every size bound\n * before anything has been read. That is not theoretical: a chunked control\n * body sailed past the 64 KiB cap into an unbounded `request.json()`, and a\n * chunked part reached `FixedLengthStream(0)` and failed as \"declared 0 bytes\n * but its body did not carry them\", blaming the client for the server's own\n * missing check. One helper, so both paths ask the question the same way.\n */\nfunction declaredLength(request: Request, what: string): number | Response {\n const raw = request.headers.get('content-length')\n const declared = raw === null ? NaN : Number(raw)\n if (!Number.isSafeInteger(declared) || declared < 0) {\n return fail(\n 411,\n `${what} must declare a Content-Length. A chunked or length-less body is refused: ` +\n `R2 cannot size a write it has no length for.`,\n 'length_required',\n )\n }\n return declared\n}\n\n/**\n * Validate that a key falls within the expected prefix.\n *\n * This is the core security check: the resolved prefix is derived from\n * authenticated context (userId, widgetId, appName), so ensuring the\n * requested key starts with the prefix prevents cross-scope access.\n */\nfunction isKeyWithinScope(\n key: string,\n prefix: string,\n excludedPrefixes: readonly string[],\n): boolean {\n return key.startsWith(prefix) && !excludedPrefixes.some((excluded) => key.startsWith(excluded))\n}\n\n/**\n * The key a caller can address within its own scope: the stored key minus the\n * resolved prefix. Clients never learn the prefix (it is derived from their\n * authenticated context), so every response that names an absolute key also\n * carries this spelling — the one that works with `list(prefix)` filters and\n * `?key=` upserts. The inverse of {@link anchorKey}, and the only place the\n * slice happens.\n */\nfunction scopeRelativeKey(key: string, prefix: string): string {\n return key.slice(prefix.length)\n}\n\n/**\n * Anchor a caller-supplied key under the resolved prefix.\n *\n * The one place a client gets to name a location. Used by the single-request\n * upsert (`?key=`) and by every multipart call (`?uploadKey=`), so a key can\n * never address another scope on either path — and a stolen `uploadId` is\n * inert, because the key it must be resumed with is rebuilt from the caller's\n * own authenticated prefix.\n */\nfunction anchorKey(\n raw: string,\n prefix: string,\n excludedPrefixes: readonly string[],\n): { key: string } | Response {\n const safe = sanitizeSubpath(raw)\n if (safe === null) return fail(400, 'Invalid key: traversal not allowed', 'invalid_key')\n if (!safe) return fail(400, 'Invalid key: empty', 'invalid_key')\n const key = `${prefix}${safe}`\n return isKeyWithinScope(key, prefix, excludedPrefixes)\n ? { key }\n : fail(403, 'Access denied: key belongs to a private scope', 'forbidden')\n}\n\n/**\n * The handler's own verbs: subpaths that name an operation rather than a key.\n *\n * Exported because the owner mount has to know them. That mount anchors a\n * caller's key under the app's prefix before dispatching here, and a verb must\n * arrive un-prefixed — so both sides read this one table and cannot drift.\n */\nconst VERB_ROUTES = new Set([\n 'POST upload',\n 'POST multipart',\n 'DELETE multipart',\n 'PUT multipart/part',\n 'POST multipart/complete',\n])\n\nexport function isFilesVerbPath(method: string, subpath: string): boolean {\n return VERB_ROUTES.has(`${method} ${subpath}`)\n}\n\n// ============================================================================\n// MIME Security\n// ============================================================================\n\n// The refused set lives in shared/app-files.ts so the CLI and the browser\n// hook check the same list before sending; this handler stays the enforcer.\n/**\n * Normalize the stored media type. Active content is valid downloadable data;\n * the download boundary forces it to an attachment so it cannot execute in\n * the app's origin.\n */\nfunction resolveMimeType(raw: string): string {\n const mimeType = raw.split(';')[0].trim().toLowerCase() || 'application/octet-stream'\n return mimeType\n}\n\n/**\n * R2 names its refusals with a numeric code at the end of the message\n * (`\"uploadPart: The specified multipart upload does not exist. (10024)\"`).\n * Reading it is what lets a client be told which mistake it made instead of\n * being handed a 500.\n */\nfunction r2ErrorCode(error: unknown): number | null {\n const message = error instanceof Error ? error.message : String(error)\n const match = /\\((\\d{4,6})\\)\\s*$/.exec(message.trim())\n return match ? Number(match[1]) : null\n}\n\n/**\n * Turn an R2 multipart failure into an honest status and sentence.\n *\n * The codes are R2's documented ones (`r2/api/error-codes`). Note that the\n * local emulator is not faithful for every one of them — it answers 10001\n * (\"internal error\") where production R2 answers 10024 on `complete` for an\n * unknown session — so this maps what R2 documents, and the tests assert\n * against the operations the emulator does reproduce.\n */\nfunction multipartFailure(error: unknown): Response {\n switch (r2ErrorCode(error)) {\n case 10024: // NoSuchUpload\n return fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n )\n case 10011: // EntityTooSmall\n case 10025: // InvalidPart — a named part was never uploaded\n case 10048: // InvalidPart — non-final parts must all be the same size\n return fail(\n 400,\n 'The parts do not assemble: every part except the last must be exactly the size the ' +\n 'upload advertised, and each must be uploaded before it is named.',\n 'bad_parts',\n )\n case 10012: // MetadataTooLarge\n case 10020: // InvalidObjectName\n return fail(400, 'The upload names a key or metadata R2 will not accept.', 'invalid_key')\n default: {\n const message = error instanceof Error ? error.message : String(error)\n console.error('[files:multipart] R2 error:', message)\n return fail(\n 503,\n 'The files service could not complete that upload. Retry shortly.',\n 'r2_error',\n )\n }\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create a scoped R2 files handler.\n *\n * Security guarantees:\n * - Download/delete keys are validated to start with the resolved prefix\n * - Path traversal (`..`) is rejected at the entry point\n * - Mutations require a non-null userId by default\n *\n * @returns A handler function: `(request, url, bucket, auth) => Promise<Response>`\n */\nexport function createScopedR2Handler(config: ScopedR2Config): ScopedR2Handler {\n const requireAuthForMutations = config.requireAuthForMutations ?? true\n\n return async (request, url, bucket, auth) => {\n // url.pathname preserves percent-encoding (e.g. spaces → %20), but R2\n // keys are stored with raw characters. Decode so the lookup matches.\n let subpathRaw: string\n try {\n subpathRaw = decodeURIComponent(url.pathname.replace('/api/files', '').replace(/^\\//, ''))\n } catch {\n return Response.json(\n { error: 'Invalid path encoding' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Sanitize — reject traversal attempts\n const subpath = sanitizeSubpath(subpathRaw)\n if (subpath === null) {\n return Response.json(\n { error: 'Invalid path: traversal not allowed' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Every operation defaults to per-user storage. Public app-scope reads\n // must say `?scope=app`; upload/list responses preserve that selector in\n // their URLs. This is load-bearing because user keys are nested below the\n // app prefix — treating every keyed GET as app scope exposed known private\n // keys anonymously.\n const scope = url.searchParams.get('scope') || 'self'\n if (scope !== 'self' && scope !== 'app') {\n return Response.json({ error: 'Invalid scope' }, { status: 400, headers: CORS_HEADERS })\n }\n const result = config.resolvePrefix(scope, { userId: auth.userId, url })\n\n if (!result.prefix) {\n const errorMsg = result.error ?? 'Invalid scope'\n const status = errorMsg.toLowerCase().includes('auth') ? 401 : 400\n return Response.json({ error: errorMsg }, { status, headers: CORS_HEADERS })\n }\n\n const prefix: string = result.prefix\n const excludedPrefixes = result.excludedPrefixes ?? []\n\n // ── Upload: one request, or a chunked session ───────────────────────\n if (isFilesVerbPath(request.method, subpath)) {\n if (requireAuthForMutations && !auth.userId) {\n // Nothing past this point reads the body, so tell the client rather\n // than leaving the runtime to tear the connection down mid-upload.\n await request.body?.cancel().catch(() => undefined)\n return fail(401, 'Authentication required', 'unauthorized')\n }\n return subpath === 'upload'\n ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope)\n : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath)\n }\n\n // ── List ────────────────────────────────────────────────────────────\n if (!subpath && request.method === 'GET') {\n return handleList(bucket, prefix, excludedPrefixes, url, scope, auth)\n }\n\n // ── Download ────────────────────────────────────────────────────────\n if (subpath && request.method === 'GET') {\n if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {\n return Response.json(\n { error: 'Access denied: key outside scope' },\n { status: 403, headers: CORS_HEADERS },\n )\n }\n return handleDownload(request, bucket, subpath, scope)\n }\n\n // ── Delete ──────────────────────────────────────────────────────────\n if (subpath && request.method === 'DELETE') {\n if (requireAuthForMutations && !auth.userId) {\n return Response.json(\n { error: 'Authentication required' },\n { status: 401, headers: CORS_HEADERS },\n )\n }\n if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {\n return Response.json(\n { error: 'Access denied: key outside scope' },\n { status: 403, headers: CORS_HEADERS },\n )\n }\n // R2 deletes are idempotent, so a typo'd key used to report exactly what\n // a real one did. Report which happened — but keep the 200: this is a\n // shipped API and deployed apps branch on `res.ok`, so turning an\n // idempotent DELETE into a 404 would break them. `deleted` carries the\n // fact; the CLI is where it becomes a visible refusal.\n const existed = (await bucket.head(subpath)) !== null\n if (existed) {\n await bucket.delete(subpath)\n // The high-water summary never decrements speculatively. Marking it\n // reclaimable lets the next would-be refusal repair immediately.\n await markQuotaDirty(bucket, auth.storage)\n }\n return Response.json(\n { success: true, deleted: existed ? subpath : null, existed },\n { headers: CORS_HEADERS },\n )\n }\n\n return Response.json({ error: 'Method not allowed' }, { status: 405, headers: CORS_HEADERS })\n }\n}\n\n// ============================================================================\n// Operation Handlers\n// ============================================================================\n\nasync function handleUpload(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n): Promise<Response> {\n try {\n // Before anything reads the body.\n const declaredBytes = declaredLength(request, 'File uploads')\n if (declaredBytes instanceof Response) return declaredBytes\n const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024)\n if (tooLargeRequest) return tooLargeRequest\n\n const contentType = request.headers.get('content-type') || ''\n let fileData: ArrayBuffer\n let fileName: string\n let rawMimeType: string\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData()\n const file = formData.get('file') as File | null\n if (!file) return fail(400, 'No file provided', 'bad_request')\n const tooBig = overRequestBound(file.size)\n if (tooBig) return tooBig\n fileData = await file.arrayBuffer()\n fileName = formData.get('name')?.toString() || file.name\n rawMimeType = file.type || 'application/octet-stream'\n } else {\n const body = (await request.json()) as { data: string; name: string; mimeType?: string }\n if (!body.data || !body.name) return fail(400, 'Missing data or name', 'bad_request')\n const base64Data = body.data.replace(/^data:[^;]+;base64,/, '')\n // Check the DECODED size before allocating it: base64 carries 3 bytes\n // per 4 characters, so a body inside the Content-Length guard can still\n // decode past the bound.\n const tooBig = overRequestBound(Math.floor((base64Data.length * 3) / 4))\n if (tooBig) return tooBig\n fileData = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)).buffer\n fileName = body.name\n rawMimeType = body.mimeType || 'application/octet-stream'\n }\n\n const mimeType = resolveMimeType(rawMimeType)\n\n const located = locateUpload(url, prefix, excludedPrefixes, fileName)\n if (located instanceof Response) return located\n\n // Only an explicit `?key=` can replace something; generated keys are fresh.\n const replacesKey = url.searchParams.get('key') === null ? null : located.key\n const replaced = replacesKey === null ? null : await bucket.head(replacesKey)\n const replacedBytes = replaced?.size ?? 0\n const admission = await admitStorage(bucket, auth.storage, fileData.byteLength, replacedBytes)\n if (admission instanceof Response) return admission\n\n try {\n const written = await bucket.put(located.key, fileData, {\n // Replacement credit is valid only while the exact object that earned\n // it still exists. A concurrent delete or overwrite loses this CAS and\n // retries from a fresh head instead of landing uncounted bytes.\n ...(replaced ? { onlyIf: { etagMatches: replaced.etag } } : {}),\n httpMetadata: { contentType: mimeType },\n customMetadata: fileMetadata(fileName, auth.userId),\n })\n if (!written) {\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n return fail(\n 409,\n 'The file changed while this upload was being admitted. Retry the upload.',\n 'write_conflict',\n )\n }\n } catch (error) {\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n throw error\n }\n await releaseQuotaMarker(\n bucket,\n auth.storage,\n admission.markerKey,\n admission.markerKey !== null || fileData.byteLength < replacedBytes,\n )\n return storedFileJson(located.key, prefix, fileName, url, scope)\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Upload failed'\n console.error('[handleUpload] Error:', msg, err instanceof Error ? err.stack : '')\n return fail(500, msg, 'upload_failed')\n }\n}\n\n/**\n * Where an upload lands.\n *\n * Deterministic key mode: `?key=<subkey>` overrides the timestamped-filename\n * behavior so callers can upsert at a known location. Without it a unique key\n * is generated per upload, so two uploads of the same name don't clobber each\n * other. Both paths use it, so \"which key\" is decided once.\n */\nfunction locateUpload(\n url: URL,\n prefix: string,\n excludedPrefixes: readonly string[],\n fileName: string,\n generatedId?: string,\n): { key: string; subkey: string } | Response {\n const requested = url.searchParams.get('key')\n const subkey =\n requested ??\n (generatedId\n ? `${generatedId}-${fileName}`\n : `${Date.now()}-${Math.random().toString(36).slice(2, 11)}-${fileName}`)\n const anchored = anchorKey(subkey, prefix, excludedPrefixes)\n return anchored instanceof Response ? anchored : { key: anchored.key, subkey }\n}\n\nfunction fileMetadata(\n fileName: string,\n userId: string | null,\n reservationId?: string,\n declaredBytes?: number,\n): Record<string, string> {\n return {\n originalName: fileName,\n ...(userId ? { uploadedBy: userId } : {}),\n ...(reservationId ? { reservationId } : {}),\n ...(declaredBytes === undefined ? {} : { declaredBytes: String(declaredBytes) }),\n uploadedAt: new Date().toISOString(),\n }\n}\n\n/** The one success shape for a stored file, whichever path stored it. */\nfunction storedFileJson(\n key: string,\n prefix: string,\n name: string,\n url: URL,\n scope: string,\n): Response {\n const fileUrl = new URL(`/api/files/${encodeKeyPath(key)}`, url.origin)\n fileUrl.searchParams.set('scope', scope)\n return Response.json(\n {\n success: true,\n key,\n relativeKey: scopeRelativeKey(key, prefix),\n url: fileUrl.toString(),\n name,\n },\n { headers: CORS_HEADERS },\n )\n}\n\n// ============================================================================\n// Multipart: the transport for anything above one request\n// ============================================================================\n\n/** Control bodies are metadata. 52 parts serialize to ~3 KiB. */\nconst MAX_CONTROL_BODY_BYTES = 64 * 1024\n\n/**\n * Read a small JSON control body.\n *\n * A `Content-Length` is required: every real client sends one for a JSON body,\n * and without it there is nothing to bound the read against before it happens.\n */\nasync function readControlJson(request: Request): Promise<Record<string, unknown> | Response> {\n const declared = declaredLength(request, 'This request')\n if (declared instanceof Response) return declared\n if (declared > MAX_CONTROL_BODY_BYTES) {\n return fail(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, 'too_large')\n }\n let parsed: unknown\n try {\n parsed = await request.json()\n } catch {\n return fail(400, 'Body must be JSON', 'bad_request')\n }\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : fail(400, 'Body must be a JSON object', 'bad_request')\n}\n\nconst REQUEST_ID_RE = /^[A-Za-z0-9_-]{16,80}$/\n\ninterface MultipartSessionRef {\n key: string\n uploadKey: string\n uploadId: string\n reservationId: string\n}\n\n/** Resolve the session a part/complete/abort call names. */\nfunction resolveSession(\n url: URL,\n prefix: string,\n excludedPrefixes: readonly string[],\n): MultipartSessionRef | Response {\n const uploadKey = url.searchParams.get('uploadKey')\n const uploadId = url.searchParams.get('uploadId')\n const reservationId = url.searchParams.get('reservationId')\n if (!uploadKey || !uploadId || !reservationId) {\n return fail(400, 'uploadKey, uploadId, and reservationId are required', 'bad_request')\n }\n if (!REQUEST_ID_RE.test(reservationId)) {\n return fail(400, 'reservationId is invalid', 'bad_request')\n }\n const anchored = anchorKey(uploadKey, prefix, excludedPrefixes)\n return anchored instanceof Response\n ? anchored\n : { key: anchored.key, uploadKey, uploadId, reservationId }\n}\n\nfunction reservationMatches(\n reservation: MultipartQuotaReservation,\n session: MultipartSessionRef,\n): boolean {\n return (\n reservation.admitted &&\n reservation.uploadId === session.uploadId &&\n reservation.objectKey === session.key &&\n reservation.uploadKey === session.uploadKey\n )\n}\n\nfunction multipartSessionJson(\n uploadId: string,\n uploadKey: string,\n reservationId: string,\n): Response {\n return Response.json(\n {\n uploadId,\n uploadKey,\n reservationId,\n partSize: UPLOAD_PART_BYTES,\n maxParts: MAX_UPLOAD_PARTS,\n maxBytes: MAX_APP_FILE_BYTES,\n },\n { headers: CORS_HEADERS },\n )\n}\n\nfunction multipartPartCount(declaredBytes: number): number {\n return Math.ceil(declaredBytes / UPLOAD_PART_BYTES)\n}\n\nfunction expectedMultipartPartBytes(declaredBytes: number, partNumber: number): number | null {\n const partCount = multipartPartCount(declaredBytes)\n if (partNumber < 1 || partNumber > partCount) return null\n return partNumber === partCount\n ? declaredBytes - (partCount - 1) * UPLOAD_PART_BYTES\n : UPLOAD_PART_BYTES\n}\n\nfunction multipartLayoutError(declaredBytes: number): Response {\n return fail(\n 400,\n `This upload must use ${multipartPartCount(declaredBytes)} sequential part(s) at the advertised ${formatBytes(UPLOAD_PART_BYTES)} size, except for the final remainder.`,\n 'bad_part_layout',\n { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) },\n )\n}\n\n/**\n * Stream one part into R2, exactly as the deploy asset transport streams an\n * object: the declared length travels with the bytes through a\n * `FixedLengthStream`, so a truncated or overlong body fails the write instead\n * of landing a part that lies about its size. Only one chunk is ever resident.\n */\nasync function streamPart(\n upload: R2MultipartUpload,\n partNumber: number,\n body: ReadableStream<Uint8Array>,\n expectedBytes: number,\n): Promise<\n { ok: true; part: R2UploadedPart } | { ok: false; lengthMismatch: boolean; error: unknown }\n> {\n let observedBytes = 0\n const sized = new FixedLengthStream(expectedBytes)\n const pumped = body\n .pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n observedBytes += chunk.byteLength\n controller.enqueue(chunk)\n },\n }),\n )\n .pipeTo(sized.writable)\n\n const [uploaded, drained] = await Promise.allSettled([\n upload.uploadPart(partNumber, sized.readable),\n pumped,\n ])\n if (uploaded.status === 'rejected' || drained.status === 'rejected') {\n const reason =\n uploaded.status === 'rejected' ? uploaded.reason : (drained as PromiseRejectedResult).reason\n return { ok: false, lengthMismatch: observedBytes !== expectedBytes, error: reason }\n }\n return { ok: true, part: uploaded.value }\n}\n\nasync function handleMultipart(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n subpath: string,\n): Promise<Response> {\n if (subpath === 'multipart' && request.method === 'POST') {\n return multipartInit(request, url, bucket, prefix, excludedPrefixes, auth)\n }\n if (subpath === 'multipart/part') {\n return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth)\n }\n if (subpath === 'multipart/complete') {\n return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope)\n }\n return multipartAbort(url, bucket, prefix, excludedPrefixes, auth)\n}\n\n/**\n * Begin a chunked upload.\n *\n * Everything checkable is checked here, before a byte of content moves: the\n * declared total against the ceiling, the media type, and the key. The reply\n * carries the part size the client must chunk at — R2 requires every non-final\n * part to be exactly the same size, so the server owning that number is what\n * keeps a client from assembling an upload R2 will refuse.\n */\nasync function multipartInit(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const body = await readControlJson(request)\n if (body instanceof Response) return body\n\n const size = body.size\n if (!Number.isSafeInteger(size) || (size as number) <= 0) {\n return fail(400, 'size must be a positive integer number of bytes', 'bad_request')\n }\n const tooBig = overCeiling(size as number)\n if (tooBig) return tooBig\n\n const name = typeof body.name === 'string' ? body.name.trim() : ''\n if (!name) return fail(400, 'name is required', 'bad_request')\n const requestId = typeof body.requestId === 'string' ? body.requestId : ''\n if (!REQUEST_ID_RE.test(requestId)) {\n return fail(\n 400,\n 'requestId must be 16–80 URL-safe characters and must be reused when retrying init.',\n 'bad_request',\n )\n }\n\n const mimeType = resolveMimeType(typeof body.mimeType === 'string' ? body.mimeType : '')\n\n // A caller-generated request id makes the server-generated key stable across\n // an init retry whose first response was lost.\n const located = locateUpload(url, prefix, excludedPrefixes, name, requestId)\n if (located instanceof Response) return located\n\n // A completed request id is terminal. Reusing it could make a failed\n // completion of a new R2 session look like the old object's successful\n // completion, stranding the new parts without their reservation.\n const completed = await bucket.head(located.key)\n if (completed?.customMetadata?.reservationId === requestId) {\n return fail(\n 409,\n 'requestId already completed an upload; start the new upload with a fresh requestId.',\n 'request_id_completed',\n )\n }\n\n const existing = await readMultipartReservation(bucket, auth.storage, requestId)\n if (existing === 'invalid') {\n return quotaUnavailable('This upload reservation could not be read. Retry shortly.')\n }\n if (existing) {\n const sameRequest =\n existing.objectKey === located.key &&\n existing.uploadKey === located.subkey &&\n existing.declaredBytes === size\n return sameRequest && existing.admitted\n ? multipartSessionJson(existing.uploadId, existing.uploadKey, requestId)\n : sameRequest\n ? quotaUnavailable('This upload is still being initialized. Retry shortly.')\n : fail(\n 409,\n 'requestId already belongs to a different multipart upload.',\n 'request_id_conflict',\n )\n }\n\n let upload: R2MultipartUpload\n try {\n upload = await bucket.createMultipartUpload(located.key, {\n httpMetadata: { contentType: mimeType },\n customMetadata: fileMetadata(name, auth.userId, requestId, size as number),\n })\n } catch (error) {\n return multipartFailure(error)\n }\n\n const marker = auth.storage\n ? {\n key: multipartMarkerKey(auth.storage, requestId),\n body: JSON.stringify({\n version: 1,\n reservationId: requestId,\n uploadId: upload.uploadId,\n objectKey: located.key,\n uploadKey: located.subkey,\n admitted: false,\n declaredBytes: size,\n }),\n metadata: {\n quotaKind: 'multipart',\n active: 'false',\n },\n }\n : undefined\n // Small multipart calls remain part of the plain HTTP contract. Charge at\n // least one R2 minimum part so one-byte sessions cannot create an unbounded\n // number of week-long uploads inside a byte quota.\n const reservedBytes = Math.max(size as number, 5 * 1024 * 1024)\n const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker)\n if (admission instanceof Response) {\n await upload.abort().catch(() => undefined)\n if (admission.status === 409 && auth.storage) {\n const winner = await readMultipartReservation(bucket, auth.storage, requestId)\n if (\n winner &&\n winner !== 'invalid' &&\n winner.objectKey === located.key &&\n winner.uploadKey === located.subkey &&\n winner.declaredBytes === size &&\n winner.admitted\n ) {\n return multipartSessionJson(winner.uploadId, winner.uploadKey, requestId)\n }\n }\n return admission\n }\n if (auth.storage && !(await activateMultipartReservation(bucket, auth.storage, requestId))) {\n await upload.abort().catch(() => undefined)\n await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true)\n return quotaUnavailable('This upload reservation could not be finalized. Retry shortly.')\n }\n return multipartSessionJson(upload.uploadId, located.subkey, requestId)\n}\n\nasync function abandonMultipart(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n auth: ScopedR2Auth,\n reservation: MultipartQuotaReservation | null,\n): Promise<'aborted' | 'absent' | 'failed'> {\n let result: 'aborted' | 'absent' = 'aborted'\n try {\n await bucket.resumeMultipartUpload(session.key, session.uploadId).abort()\n } catch (error) {\n if (r2ErrorCode(error) !== 10024) {\n console.error('[files:multipart] abort:', error instanceof Error ? error.message : error)\n return 'failed'\n }\n result = 'absent'\n }\n if (reservation) {\n if (!(await releaseQuotaMarker(bucket, auth.storage, reservation.key, true))) return 'failed'\n }\n return result\n}\n\n/** Send one part. The body is streamed; it is never held whole. */\nasync function multipartPart(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const refuse = async (response: Response): Promise<Response> => {\n await request.body?.cancel().catch(() => undefined)\n return response\n }\n\n const declaredBytes = declaredLength(request, 'Part uploads')\n if (declaredBytes instanceof Response) return refuse(declaredBytes)\n const tooBig = overPartBound(declaredBytes)\n if (tooBig) return refuse(tooBig)\n\n const partNumber = Number(url.searchParams.get('partNumber'))\n if (!Number.isSafeInteger(partNumber) || partNumber < 1 || partNumber > MAX_UPLOAD_PARTS) {\n return refuse(\n fail(400, `partNumber must be a whole number from 1 to ${MAX_UPLOAD_PARTS}`, 'bad_request'),\n )\n }\n\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return refuse(session)\n if (!request.body) return fail(400, 'A part body is required', 'bad_request')\n\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return refuse(quotaUnavailable('This upload reservation could not be read. Retry shortly.'))\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return refuse(\n fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n ),\n )\n }\n if (auth.storage && !reservation) {\n return refuse(\n fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n ),\n )\n }\n\n if (reservation) {\n const expectedBytes = expectedMultipartPartBytes(reservation.declaredBytes, partNumber)\n if (expectedBytes === null || declaredBytes !== expectedBytes) {\n return refuse(multipartLayoutError(reservation.declaredBytes))\n }\n }\n\n // Refresh the session lease before R2 accepts the stream. The declared total\n // determines every valid part number and length, so no per-part ledger is\n // needed: retries are identical by construction.\n const refreshed = await refreshMultipartReservation(\n bucket,\n auth.storage,\n session.reservationId,\n session.uploadId,\n session.key,\n )\n if (auth.storage && (refreshed === null || refreshed === 'invalid')) {\n return refuse(quotaUnavailable('This upload reservation is missing or invalid.'))\n }\n if (refreshed === 'busy') {\n return refuse(quotaUnavailable('This upload reservation is busy. Retry the part shortly.'))\n }\n\n const upload = bucket.resumeMultipartUpload(session.key, session.uploadId)\n const stored = await streamPart(upload, partNumber, request.body, declaredBytes)\n if (!stored.ok) {\n if (r2ErrorCode(stored.error) === 10024 && reservation) {\n await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n }\n return stored.lengthMismatch\n ? fail(\n 400,\n `Part ${partNumber} declared ${declaredBytes} bytes but its body did not carry them`,\n 'bad_request',\n )\n : multipartFailure(stored.error)\n }\n return Response.json(\n { partNumber: stored.part.partNumber, etag: stored.part.etag },\n { headers: CORS_HEADERS },\n )\n}\n\nasync function completedReservationObject(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n): Promise<R2Object | null> {\n const object = await bucket.head(session.key)\n return object?.customMetadata?.reservationId === session.reservationId ? object : null\n}\n\nasync function finishCompletedMultipart(\n bucket: R2Bucket,\n session: MultipartSessionRef,\n reservation: MultipartQuotaReservation | null,\n object: R2Object,\n auth: ScopedR2Auth,\n url: URL,\n scope: string,\n prefix: string,\n): Promise<Response> {\n const declaredMetadata = Number(object.customMetadata?.declaredBytes)\n const declaredBytes =\n reservation?.declaredBytes ??\n (Number.isSafeInteger(declaredMetadata) && declaredMetadata >= 0\n ? declaredMetadata\n : MAX_APP_FILE_BYTES)\n const refusal =\n overCeiling(object.size) ??\n (object.size > declaredBytes\n ? fail(\n 400,\n `The assembled upload exceeded its declared ${declaredBytes}-byte size and was not kept.`,\n 'declared_size_exceeded',\n )\n : null)\n if (refusal) {\n let remaining: R2Object | null\n try {\n const current = await bucket.head(session.key)\n if (current?.etag === object.etag) await bucket.delete(session.key)\n remaining = await bucket.head(session.key)\n } catch (error) {\n console.error('[files:multipart] invalid object cleanup:', error)\n return quotaUnavailable('The invalid assembled file could not be removed. Retry completion.')\n }\n if (remaining?.etag === object.etag) {\n return quotaUnavailable('The invalid assembled file could not be removed. Retry completion.')\n }\n if (reservation) {\n const cleaned = await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n if (!cleaned) return quotaUnavailable('The upload was removed, but cleanup must be retried.')\n }\n return refusal\n }\n if (reservation) {\n const cleaned = await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n if (!cleaned) {\n return quotaUnavailable('The file was stored, but its reservation cleanup must be retried.')\n }\n }\n const name = object.customMetadata?.originalName ?? session.uploadKey.split('/').pop() ?? 'file'\n return storedFileJson(session.key, prefix, name, url, scope)\n}\n\n/**\n * Assemble the parts. Completion is idempotent: if R2 committed the object but\n * the response or marker cleanup was interrupted, a retry recognizes the\n * reservation id stored on the object and finishes cleanup.\n */\nasync function multipartComplete(\n request: Request,\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n scope: string,\n): Promise<Response> {\n const body = await readControlJson(request)\n if (body instanceof Response) return body\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return session\n\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return quotaUnavailable('This upload reservation could not be read. Retry shortly.')\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return fail(\n 404,\n 'That upload does not exist — it was completed, abandoned, or expired. Start a new one.',\n 'unknown_upload',\n )\n }\n if (auth.storage && !reservation) {\n const completed = await completedReservationObject(bucket, session)\n if (completed) {\n return finishCompletedMultipart(bucket, session, null, completed, auth, url, scope, prefix)\n }\n await abandonMultipart(bucket, session, auth, null)\n return quotaUnavailable('This upload reservation is missing or does not match.')\n }\n\n const raw = body.parts\n if (!Array.isArray(raw) || raw.length === 0) {\n return fail(400, 'parts must be a non-empty array', 'bad_request')\n }\n if (raw.length > MAX_UPLOAD_PARTS) {\n return fail(400, `An upload may have at most ${MAX_UPLOAD_PARTS} parts`, 'bad_request')\n }\n if (reservation && raw.length !== multipartPartCount(reservation.declaredBytes)) {\n return multipartLayoutError(reservation.declaredBytes)\n }\n const parts: R2UploadedPart[] = []\n for (let index = 0; index < raw.length; index++) {\n const { partNumber, etag } = (raw[index] ?? {}) as Record<string, unknown>\n if (\n !Number.isSafeInteger(partNumber) ||\n (partNumber as number) < 1 ||\n (partNumber as number) > MAX_UPLOAD_PARTS\n ) {\n return fail(\n 400,\n `parts[${index}].partNumber must be a whole number from 1 to ${MAX_UPLOAD_PARTS}`,\n 'bad_request',\n )\n }\n if (reservation && partNumber !== index + 1) {\n return multipartLayoutError(reservation.declaredBytes)\n }\n if (typeof etag !== 'string' || !etag) {\n return fail(400, `parts[${index}].etag must be the string uploadPart returned`, 'bad_request')\n }\n parts.push({ partNumber: partNumber as number, etag })\n }\n\n let object: R2Object\n try {\n object = await bucket.resumeMultipartUpload(session.key, session.uploadId).complete(parts)\n } catch (error) {\n const completed = await completedReservationObject(bucket, session)\n if (completed) {\n return finishCompletedMultipart(\n bucket,\n session,\n reservation,\n completed,\n auth,\n url,\n scope,\n prefix,\n )\n }\n if (r2ErrorCode(error) === 10024 && reservation) {\n await releaseQuotaMarker(bucket, auth.storage, reservation.key, true)\n }\n return multipartFailure(error)\n }\n\n return finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope, prefix)\n}\n\n/**\n * Abandon a chunked upload, releasing whatever parts landed.\n *\n * Unconditionally idempotent. The postcondition a caller needs is \"this\n * session is not holding anything of mine\", and a session R2 cannot find is\n * already in that state; one it cannot reach is reaped within 7 days anyway\n * (uncompleted uploads expire). There is no action a caller would take on a\n * failed abort that differs from what it does on a successful one — both\n * clients call this on the failure path and then re-raise the real error.\n * `NoSuchUpload` is already the desired postcondition and still releases the\n * quota marker.\n */\nasync function multipartAbort(\n url: URL,\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n auth: ScopedR2Auth,\n): Promise<Response> {\n const session = resolveSession(url, prefix, excludedPrefixes)\n if (session instanceof Response) return session\n const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId)\n if (reservation === 'invalid') {\n return Response.json({ success: true, aborted: false }, { headers: CORS_HEADERS })\n }\n if (auth.storage && reservation && !reservationMatches(reservation, session)) {\n return Response.json({ success: true, aborted: false }, { headers: CORS_HEADERS })\n }\n const result = await abandonMultipart(bucket, session, auth, reservation || null)\n return Response.json({ success: true, aborted: result === 'aborted' }, { headers: CORS_HEADERS })\n}\n\nasync function handleList(\n bucket: R2Bucket,\n prefix: string,\n excludedPrefixes: readonly string[],\n url: URL,\n scope: string,\n auth: ScopedR2Auth,\n): Promise<Response> {\n const userPrefix = url.searchParams.get('prefix') || ''\n const listPrefix = `${prefix}${userPrefix}`\n const requestedLimit = Number(url.searchParams.get('limit') ?? 100)\n const limit = Number.isSafeInteger(requestedLimit)\n ? Math.min(1000, Math.max(1, requestedLimit))\n : 100\n const requestCursor = url.searchParams.get('cursor')\n if (excludedPrefixes.some((excluded) => listPrefix.startsWith(excluded))) {\n return Response.json({ files: [], truncated: false }, { headers: CORS_HEADERS })\n }\n\n // The default workers-types entry intentionally models the oldest runtime,\n // while every DeepSpace worker uses a post-2022 compatibility date where R2\n // honors `include`. Keep that compatibility boundary local to these calls.\n const withMetadata = (options: R2ListOptions) =>\n ({ ...options, include: ['customMetadata'] }) as R2ListOptions & {\n include: ['customMetadata']\n }\n const listed = await bucket.list(\n withMetadata({\n prefix: listPrefix,\n limit,\n ...(requestCursor ? { cursor: requestCursor } : {}),\n }),\n )\n const visible = listed.objects.filter((object) =>\n isKeyWithinScope(object.key, prefix, excludedPrefixes),\n )\n const seen = new Set(visible.map((object) => object.key))\n // The frontier is whichever list call the response's truncation state\n // comes from — the initial page, or the last jump past an excluded\n // subtree below.\n let frontier: R2Objects = listed\n // If a private subtree occupied the first page, jump past it instead of\n // walking private keys or hiding public keys that sort after it. Only a\n // truncated listing has anything left to jump to: from an exhausted page the\n // jump would rewind to keys earlier cursor pages already returned.\n // The frontier advances across iterations: a second excluded subtree must\n // jump from where the previous jump ended, not from the original page's\n // tail — restarting from the stale tail would rewind startAfter and\n // re-return keys an earlier jump already delivered.\n let frontierKey = listed.objects.at(-1)?.key\n for (const excluded of excludedPrefixes\n .filter((candidate) => candidate.startsWith(listPrefix))\n .sort()) {\n if (!frontier.truncated || visible.length >= limit) break\n const excludedEnd = `${excluded}\\u{10ffff}`\n const after = await bucket.list(\n withMetadata({\n prefix: listPrefix,\n startAfter: frontierKey && frontierKey > excludedEnd ? frontierKey : excludedEnd,\n limit: limit - visible.length,\n }),\n )\n for (const object of after.objects) {\n if (!seen.has(object.key) && isKeyWithinScope(object.key, prefix, excludedPrefixes)) {\n seen.add(object.key)\n visible.push(object)\n }\n }\n frontierKey = after.objects.at(-1)?.key ?? frontierKey\n frontier = after\n }\n const files = visible.slice(0, limit).map((obj) => {\n const fileUrl = new URL(`/api/files/${encodeKeyPath(obj.key)}`, url.origin)\n fileUrl.searchParams.set('scope', scope)\n return {\n key: obj.key,\n relativeKey: scopeRelativeKey(obj.key, prefix),\n size: obj.size,\n uploaded: obj.uploaded.toISOString(),\n url: fileUrl.toString(),\n ...obj.customMetadata,\n }\n })\n // Account totals belong only on the explicitly authorized owner surface.\n // Public app listings do no registry, billing, or quota-repair work.\n let storage: { usedBytes: number; limitBytes?: number } | undefined\n if (auth.storage && auth.includeStorageUsage) {\n const usedBytes = await storageUsage(bucket, auth.storage)\n if (usedBytes !== null) storage = { usedBytes }\n const limitBytes = await auth.storage.limitBytes()\n if (storage && limitBytes !== null) storage.limitBytes = limitBytes\n }\n // The cursor rides with `truncated`: it continues the frontier list call,\n // so `?cursor=` resumes exactly there.\n return Response.json(\n {\n files,\n truncated: frontier.truncated,\n ...(frontier.truncated ? { cursor: frontier.cursor } : {}),\n ...(storage ? { storage } : {}),\n },\n { headers: CORS_HEADERS },\n )\n}\n\nasync function handleDownload(\n request: Request,\n bucket: R2Bucket,\n key: string,\n scope: 'self' | 'app',\n): Promise<Response> {\n const object = await bucket.get(key)\n if (!object) {\n return Response.json({ error: 'File not found' }, { status: 404, headers: CORS_HEADERS })\n }\n const headers = new Headers()\n const storedType = resolveMimeType(object.httpMetadata?.contentType ?? '')\n const mustDownload = DANGEROUS_MIME_TYPES.has(storedType)\n headers.set('Content-Type', mustDownload ? 'application/octet-stream' : storedType)\n headers.set('X-Content-Type-Options', 'nosniff')\n headers.set('ETag', object.httpEtag)\n headers.set(\n 'Cache-Control',\n scope === 'self' ? 'private, no-store' : 'public, max-age=0, must-revalidate',\n )\n headers.set('Access-Control-Allow-Origin', '*')\n const originalName = object.customMetadata?.originalName ?? key.split('/').pop() ?? 'download'\n headers.set(\n 'Content-Disposition',\n contentDisposition(originalName, mustDownload ? 'attachment' : 'inline'),\n )\n if (mustDownload) headers.set('Content-Security-Policy', \"sandbox; default-src 'none'\")\n if (scope === 'app' && request.headers.get('if-none-match') === object.httpEtag) {\n return new Response(null, { status: 304, headers })\n }\n return new Response(object.body, { headers })\n}\n","/**\n * Upstream worker proxy helpers.\n *\n * App workers reach the platform's other workers (api, platform, auth) through\n * one of two transports:\n *\n * 1. **Service binding** (`env.API_WORKER` / `env.PLATFORM_WORKER`) — the\n * preferred path in production. Configured via wrangler `[[services]]`.\n * Cross-worker calls over plain `*.workers.dev` URLs return Cloudflare\n * error 1042 in production, so the binding is the only working path\n * in deployed apps.\n *\n * 2. **HTTPS URL** (`env.API_WORKER_URL` / `env.PLATFORM_WORKER_URL`) — the\n * fallback used in local development. `deepspace dev start` writes these\n * into `.dev.vars`, which `wrangler dev` exposes as env vars. Service\n * bindings don't work cross-process under `wrangler dev` for SDK apps,\n * so the URL is the only working path in dev.\n *\n * The auth-worker has no service binding even in production — its responses\n * carry `Set-Cookie` headers we want preserved verbatim, which we get for\n * free over plain HTTPS. So `authWorkerFetch` is URL-only; the helper exists\n * for surface consistency, not to switch transports.\n *\n * Each helper:\n * - Prefers the binding if present, falls back to the URL otherwise.\n * - Throws an actionable Error if neither is configured. No silent 502s.\n * - Forwards `init` (method/headers/body) verbatim to the upstream worker.\n *\n * History: a previous in-tree helper folded the binding/URL fallback into\n * the AI module's `resolveTransport`. Inline call sites in the starter\n * template (integrations, files, debug) used `c.env.X.fetch(...)` directly,\n * which broke `npx deepspace dev start` for any app calling those routes — the\n * binding is undefined locally, so the fetch threw. These helpers\n * standardize on the same shape `resolveTransport` had, so every upstream\n * call works in both dev and prod.\n */\n\n/**\n * Env shape required by `apiWorkerFetch`. App workers should extend this\n * (the starter template does) so the helper can be called with `c.env`.\n */\nexport interface ApiWorkerEnv {\n /** Cloudflare service binding for the api-worker. Preferred. */\n API_WORKER?: Fetcher\n /** HTTPS URL for the api-worker. Used when the binding is absent (dev). */\n API_WORKER_URL?: string\n}\n\n/** Env shape required by `platformWorkerFetch`. */\nexport interface PlatformWorkerEnv {\n /** Cloudflare service binding for the platform-worker. Preferred. */\n PLATFORM_WORKER?: Fetcher\n /** HTTPS URL for the platform-worker. Used when the binding is absent. */\n PLATFORM_WORKER_URL?: string\n}\n\n/** Env shape required by `authWorkerFetch`. URL-only. */\nexport interface AuthWorkerEnv {\n /** HTTPS URL for the auth-worker. Always required. */\n AUTH_WORKER_URL?: string\n}\n\ntype Transport = { kind: 'binding'; fetcher: Fetcher } | { kind: 'url'; baseUrl: string }\n\n/**\n * Resolve the api-worker transport. Exported for the AI helper, which\n * needs the URL form to rewrite an internal `https://api-worker.internal`\n * placeholder before calling the provider SDK.\n */\nexport function resolveApiTransport(env: ApiWorkerEnv): Transport {\n if (env.API_WORKER) return { kind: 'binding', fetcher: env.API_WORKER }\n if (env.API_WORKER_URL) {\n return { kind: 'url', baseUrl: env.API_WORKER_URL.replace(/\\/$/, '') }\n }\n throw new Error(\n 'apiWorkerFetch: neither env.API_WORKER nor env.API_WORKER_URL is set. ' +\n 'Add a [[services]] binding in wrangler.toml for production, or let ' +\n '`deepspace dev start` write API_WORKER_URL into .dev.vars for local development.',\n )\n}\n\nfunction resolvePlatformTransport(env: PlatformWorkerEnv): Transport {\n if (env.PLATFORM_WORKER) return { kind: 'binding', fetcher: env.PLATFORM_WORKER }\n if (env.PLATFORM_WORKER_URL) {\n return { kind: 'url', baseUrl: env.PLATFORM_WORKER_URL.replace(/\\/$/, '') }\n }\n throw new Error(\n 'platformWorkerFetch: neither env.PLATFORM_WORKER nor env.PLATFORM_WORKER_URL ' +\n 'is set. Add a [[services]] binding in wrangler.toml for production, or let ' +\n '`deepspace dev start` write PLATFORM_WORKER_URL into .dev.vars for local development.',\n )\n}\n\n/**\n * Build the URL/Request to send over a resolved transport.\n *\n * `path` may be a full URL (e.g. `https://api-worker/api/integrations`,\n * which inline call sites used historically) or a path-only string\n * (`/api/integrations`). Either way we extract `pathname + search` and\n * route it through the chosen transport.\n *\n * - binding: keep an internal placeholder host. The Fetcher dispatches\n * by binding, not host, so the host is purely cosmetic.\n * - url: prefix with `transport.baseUrl`.\n */\nfunction buildTargetUrl(transport: Transport, path: string, internalHost: string): string {\n let pathAndSearch: string\n if (path.startsWith('http://') || path.startsWith('https://')) {\n const u = new URL(path)\n pathAndSearch = u.pathname + u.search\n } else if (path.startsWith('/')) {\n pathAndSearch = path\n } else {\n pathAndSearch = '/' + path\n }\n\n if (transport.kind === 'binding') {\n return `https://${internalHost}${pathAndSearch}`\n }\n return `${transport.baseUrl}${pathAndSearch}`\n}\n\n/**\n * Fetch the api-worker. Prefers the `API_WORKER` service binding, falls\n * back to `API_WORKER_URL` over HTTPS.\n *\n * `path` is treated as path-only — any host in a passed-in URL is\n * stripped and replaced. This matches how `c.env.API_WORKER.fetch(...)`\n * already worked in the starter template (the host was always a\n * placeholder like `api-worker`).\n */\nexport function apiWorkerFetch(\n env: ApiWorkerEnv,\n path: string,\n init?: RequestInit,\n): Promise<Response> {\n const transport = resolveApiTransport(env)\n const url = buildTargetUrl(transport, path, 'api-worker')\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, init as RequestInit)\n }\n return fetch(url, init)\n}\n\n/**\n * Fetch the platform-worker. Prefers the `PLATFORM_WORKER` service binding,\n * falls back to `PLATFORM_WORKER_URL` over HTTPS.\n *\n * Accepts a `Request` instance directly so callers can hand off\n * `c.req.raw`-derived requests with their original method/headers/body\n * intact. (`/api/files/*` does this — it forwards the caller's body\n * stream verbatim.)\n */\nexport function platformWorkerFetch(\n env: PlatformWorkerEnv,\n pathOrRequest: string | Request,\n init?: RequestInit,\n): Promise<Response> {\n const transport = resolvePlatformTransport(env)\n\n if (typeof pathOrRequest === 'string') {\n const url = buildTargetUrl(transport, pathOrRequest, 'platform-worker')\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, init as RequestInit)\n }\n return fetch(url, init)\n }\n\n // Request instance: rewrite the URL through the chosen transport,\n // preserve method/headers/body from the original Request.\n const req = pathOrRequest\n const url = buildTargetUrl(transport, req.url, 'platform-worker')\n const rewritten = new Request(url, req)\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(rewritten)\n }\n return fetch(rewritten)\n}\n\n/**\n * Fetch the auth-worker over HTTPS. URL-only — there is no auth-worker\n * service binding, by design (we want plain-HTTP cookie semantics).\n *\n * Kept as a helper for surface symmetry with `apiWorkerFetch` /\n * `platformWorkerFetch`. Throws if `AUTH_WORKER_URL` is unset.\n */\nexport function authWorkerFetch(\n env: AuthWorkerEnv,\n path: string,\n init?: RequestInit,\n): Promise<Response> {\n if (!env.AUTH_WORKER_URL) {\n throw new Error(\n 'authWorkerFetch: env.AUTH_WORKER_URL is not set. `deepspace dev start` should ' +\n 'write this into .dev.vars; for production, set it as a wrangler var or secret.',\n )\n }\n const baseUrl = env.AUTH_WORKER_URL.replace(/\\/$/, '')\n\n let pathAndSearch: string\n if (path.startsWith('http://') || path.startsWith('https://')) {\n const u = new URL(path)\n pathAndSearch = u.pathname + u.search\n } else if (path.startsWith('/')) {\n pathAndSearch = path\n } else {\n pathAndSearch = '/' + path\n }\n\n return fetch(`${baseUrl}${pathAndSearch}`, init)\n}\n","/**\n * Cron System — Server-Side Scheduled Tasks\n *\n * Provides CronContext for miniapp cron handlers and buildCronContext\n * to construct it from worker environment bindings.\n *\n * CronContext gives handlers access to:\n * - records: Query/create/update/delete via RecordRoom tools API\n * - integrations: Call platform integration endpoints, billed to the app owner\n * - ownerUserId: The app owner's user ID\n */\n\nimport { apiWorkerFetch, type ApiWorkerEnv } from './proxies'\n\n/** Context passed to cron handler functions */\nexport interface CronContext {\n /** RecordRoom data access (queries the DO directly via tools API) */\n records: {\n query(\n collection: string,\n opts?: { where?: Record<string, unknown>; limit?: number },\n ): Promise<unknown[]>\n create(collection: string, data: Record<string, unknown>): Promise<unknown>\n update(collection: string, recordId: string, data: Record<string, unknown>): Promise<unknown>\n delete(collection: string, recordId: string): Promise<unknown>\n }\n /**\n * Call a platform integration endpoint (e.g. \"openai/chat-completion\",\n * \"fal/run-model\"). Billed to the app owner using the same\n * APP_OWNER_JWT the rest of the SDK uses for server-side billed calls.\n */\n integrations: {\n call(endpoint: string, params?: Record<string, unknown>): Promise<unknown>\n }\n /** App owner's user ID */\n ownerUserId: string\n}\n\n/** Environment bindings needed by buildCronContext */\ninterface CronEnv extends ApiWorkerEnv {\n RECORD_ROOMS: DurableObjectNamespace\n /**\n * Long-lived app-owner JWT minted at deploy time. Required for\n * `integrations.call()` so the call is billed to the app owner.\n * Optional here so apps that don't use integrations from cron can omit\n * it without a type error; missing-at-call-time throws a clear message.\n */\n APP_OWNER_JWT?: string\n}\n\n/**\n * Build a CronContext from worker environment bindings.\n *\n * @param env - Worker environment with RECORD_ROOMS DO namespace, APP_OWNER_JWT, and an api-worker transport (API_WORKER binding or API_WORKER_URL)\n * @param ownerUserId - App owner's user ID (for RBAC and billing)\n * @param roomId - RecordRoom ID (defaults to 'default')\n */\nexport function buildCronContext(\n env: CronEnv,\n ownerUserId: string,\n roomId = 'default',\n): CronContext {\n // Get the RecordRoom DO stub for direct internal calls\n const roomIdObj = env.RECORD_ROOMS.idFromName(roomId)\n const room = env.RECORD_ROOMS.get(roomIdObj)\n\n /** Execute a tool call against the RecordRoom's tools API */\n async function executeTool(tool: string, params: Record<string, unknown>): Promise<unknown> {\n const response = await room.fetch(\n new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': ownerUserId,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({ tool, params }),\n }),\n )\n const result = (await response.json()) as { success: boolean; data?: unknown; error?: string }\n if (!result.success) {\n throw new Error(`RecordRoom tool ${tool} failed: ${result.error || 'Unknown error'}`)\n }\n return result.data\n }\n\n const records: CronContext['records'] = {\n async query(collection, opts) {\n const params: Record<string, unknown> = { collection }\n if (opts?.where) params.where = opts.where\n if (opts?.limit) params.limit = opts.limit\n const data = await executeTool('records.query', params)\n const records = (data as { records?: unknown[] } | null | undefined)?.records\n return records ?? []\n },\n async create(collection, data) {\n return executeTool('records.create', { collection, data })\n },\n async update(collection, recordId, data) {\n return executeTool('records.update', { collection, recordId, data })\n },\n async delete(collection, recordId) {\n return executeTool('records.delete', { collection, recordId })\n },\n }\n\n const integrations: CronContext['integrations'] = {\n async call(endpoint, params = {}) {\n if (!env.APP_OWNER_JWT) {\n throw new Error(\n 'integrations.call requires env.APP_OWNER_JWT. Deployed apps receive ' +\n 'this automatically; in dev, `deepspace dev start` writes it into .dev.vars.',\n )\n }\n // The api-worker's /api/integrations route is path-shaped as\n // /:name/:endpoint. Pass the same \"name/endpoint\" string the\n // catalog uses (e.g. \"openai/chat-completion\").\n const response = await apiWorkerFetch(env, `/api/integrations/${endpoint}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${env.APP_OWNER_JWT}`,\n },\n body: JSON.stringify(params),\n })\n // Read as text first so a non-JSON error body (e.g. a 502 from an\n // upstream gateway) still yields a useful error message instead of\n // surfacing a JSON parse failure.\n const text = await response.text()\n let result: { success?: boolean; data?: unknown; error?: string; message?: string } = {}\n try {\n result = text ? JSON.parse(text) : {}\n } catch {\n // fall through with empty `result`; the !response.ok branch below\n // will throw using the raw text as the detail.\n }\n if (!response.ok || !result.success) {\n const detail = result.message || result.error || text || `HTTP ${response.status}`\n throw new Error(`Integration call ${endpoint} failed: ${detail}`)\n }\n return result.data\n },\n }\n\n return { records, integrations, ownerUserId }\n}\n","import type { Role } from '../../shared/roles'\n\ninterface AppRoleEnv {\n RECORD_ROOMS: DurableObjectNamespace\n DEEPSPACE_APP_ID: string\n OWNER_USER_ID: string\n}\n\n/** Resolve a user's current role from the app's canonical users collection. */\nexport async function resolveAppRole(env: AppRoleEnv, userId: string): Promise<Role> {\n if (userId === env.OWNER_USER_ID) return 'admin'\n\n const stub = env.RECORD_ROOMS.get(env.RECORD_ROOMS.idFromName(`app:${env.DEEPSPACE_APP_ID}`))\n try {\n const res = await stub.fetch(\n new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': env.OWNER_USER_ID,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({\n tool: 'records.get',\n params: { collection: 'users', recordId: userId },\n }),\n }),\n )\n const json = (await res.json()) as {\n success?: boolean\n data?: { record?: { data?: { role?: unknown } } }\n }\n const role = json.data?.record?.data?.role\n return json.success && (role === 'admin' || role === 'member') ? role : 'viewer'\n } catch {\n return 'viewer'\n }\n}\n","/**\n * AI provider helpers — create Vercel AI SDK providers that route through\n * the DeepSpace API worker proxy for per-user billing.\n *\n * Supported providers: anthropic, openai, cerebras.\n *\n * The API worker can be reached in two ways:\n * - Service binding `env.API_WORKER` (Cloudflare Fetcher) — preferred in\n * production if the app has declared the binding in wrangler.toml.\n * - HTTPS URL `env.API_WORKER_URL` — used in local dev and in production\n * for apps that don't declare the binding. `deepspace dev start` writes this\n * into `.dev.vars` automatically.\n *\n * Auth is automatic by default:\n * - For server-side autonomous calls (cron, DO alarms, background agents),\n * the helper reads the long-lived `env.APP_OWNER_JWT` minted at deploy\n * time (or by `deepspace dev start` in local development) and uses it for the\n * proxy auth header. The owner is billed automatically via the JWT sub.\n * - For user-initiated calls (e.g. an `/api/ai/chat` route handling a\n * browser request), pass `options.authToken` explicitly with the user's\n * own JWT so the call is billed to the user.\n *\n * Usage:\n *\n * // Server-side autonomous — no auth config needed\n * import { createDeepSpaceAI } from 'deepspace/worker'\n * const cerebras = createDeepSpaceAI(env, 'cerebras')\n * const result = await generateText({ model: cerebras('llama-3.3-70b'), ... })\n *\n * // User-initiated (inside a request handler)\n * const jwt = c.req.header('Authorization')!.slice(7)\n * const anthropic = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport type { DeepSpaceAIProvider } from '../../shared/ai-models'\nimport { resolveApiTransport, type ApiWorkerEnv } from './proxies'\nimport type { LanguageModel } from 'ai'\n\n/**\n * Model factory: `(modelId) => LanguageModel`. The explicit return type\n * keeps tsup's DTS build from leaking unportable `.pnpm/@ai-sdk+provider/...`\n * paths into the published `dist/index.d.ts`.\n */\nexport type DeepSpaceModelFactory = (modelId: string) => LanguageModel\n\nexport interface DeepSpaceAIEnv extends ApiWorkerEnv {\n /**\n * Long-lived owner-scoped JWT minted at deploy time (or by `deepspace dev start`).\n * Used as the default proxy auth token when `options.authToken` is absent.\n * Bills the app owner.\n */\n APP_OWNER_JWT?: string\n}\n\nexport interface DeepSpaceAIOptions {\n /**\n * Explicit auth token for this call. Use this for user-initiated flows\n * where the caller's own JWT should be billed. If omitted, the helper\n * falls back to `env.APP_OWNER_JWT` (bills the app owner).\n *\n * Billing is always against the JWT subject — to bill a different user,\n * pass a JWT whose subject is that user. The proxy does not accept any\n * client-supplied billing override.\n */\n authToken?: string\n}\n\nconst ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096\n\nfunction withDefaultAnthropicMaxTokens(body: RequestInit['body']): RequestInit['body'] {\n if (typeof body !== 'string') return body\n\n try {\n const parsed = JSON.parse(body) as Record<string, unknown>\n const hasExplicitLimit =\n typeof parsed.max_tokens === 'number' ||\n typeof parsed.max_completion_tokens === 'number' ||\n typeof parsed.max_output_tokens === 'number'\n\n if (hasExplicitLimit) return body\n\n return JSON.stringify({\n ...parsed,\n max_tokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,\n })\n } catch {\n return body\n }\n}\n\n/**\n * Build an AI SDK provider that routes through the DeepSpace API worker.\n *\n * Resolves the transport (service binding or URL) and the auth token\n * (explicit or `env.APP_OWNER_JWT`) automatically. Throws a clear error if\n * either is unconfigured.\n */\nexport function createDeepSpaceAI(\n env: DeepSpaceAIEnv,\n provider: DeepSpaceAIProvider,\n options: DeepSpaceAIOptions = {},\n): DeepSpaceModelFactory {\n const transport = resolveApiTransport(env)\n const authToken = options.authToken ?? env.APP_OWNER_JWT\n if (!authToken) {\n throw new Error(\n 'createDeepSpaceAI: no auth token available. Either pass `options.authToken` ' +\n 'explicitly (for user-initiated calls), or ensure `env.APP_OWNER_JWT` is set ' +\n '(injected at deploy time or by `deepspace dev start`).',\n )\n }\n\n const proxyFetch: typeof globalThis.fetch = (input, init) => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.toString()\n : (input as Request).url\n\n const headers = new Headers(init?.headers)\n // Strip the AI SDK's provider-auth headers (Authorization for OpenAI/\n // Cerebras, x-api-key for Anthropic). They carry our placeholder\n // apiKey value 'platform-managed' which would collide with the proxy's\n // JWT auth below and fail verifyJwt.\n headers.delete('authorization')\n headers.delete('x-api-key')\n headers.set('X-Auth-Token', authToken)\n\n const body = provider === 'anthropic' ? withDefaultAnthropicMaxTokens(init?.body) : init?.body\n if (body !== init?.body) {\n // The request body length changed, so the runtime must recompute it.\n headers.delete('content-length')\n }\n const nextInit = { ...init, headers, body }\n\n if (transport.kind === 'binding') {\n return transport.fetcher.fetch(url, nextInit as RequestInit)\n }\n\n // URL transport: rewrite the internal host to the real API worker URL.\n const original = new URL(url)\n const rewritten = `${transport.baseUrl}${original.pathname}${original.search}`\n return fetch(rewritten, nextInit)\n }\n\n // Every provider's SDK expects baseURL to end in /v1 — it then appends\n // the provider-specific suffix (/messages for Anthropic, /chat/completions\n // for OpenAI/Groq/Cerebras).\n const baseURL = `https://api-worker.internal/api/proxy/${provider}/v1`\n\n switch (provider) {\n case 'anthropic':\n // Anthropic always returns usage in both streaming and non-streaming\n // responses, so no extra config is needed.\n return createAnthropic({ baseURL, apiKey: 'platform-managed', fetch: proxyFetch })\n\n case 'openai': {\n // Pin to `.chat` — v5's default `openai(modelId)` returns a Responses\n // API model (POST /responses); our proxy speaks /chat/completions.\n const op = createOpenAI({ baseURL, apiKey: 'platform-managed', fetch: proxyFetch })\n return op.chat\n }\n\n case 'cerebras':\n // `createCerebras` doesn't expose `includeUsage`, so use the underlying\n // openai-compatible primitive directly. Same wire format Cerebras's own\n // wrapper produces, but with `include_usage` flipped on so streaming\n // responses carry token counts.\n return createOpenAICompatible({\n name: 'cerebras',\n baseURL,\n apiKey: 'platform-managed',\n fetch: proxyFetch,\n includeUsage: true,\n })\n\n default: {\n const _exhaustive: never = provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n","import {\n stepCountIs,\n streamText,\n type PrepareStepFunction,\n type StreamTextResult,\n type ToolSet,\n} from 'ai'\nimport {\n resolveDeepSpaceAgentModel,\n type DeepSpaceAgentProfileId,\n type ResolvedDeepSpaceAgentModel,\n} from '../../shared/ai-models'\nimport { createDeepSpaceAI, type DeepSpaceAIEnv } from './ai'\n\ntype StreamTextOptions<TOOLS extends ToolSet> = Parameters<typeof streamText<TOOLS>>[0]\n\nexport type DeepSpaceAgentStreamOptions<TOOLS extends ToolSet> = Omit<\n StreamTextOptions<TOOLS>,\n 'model' | 'providerOptions' | 'stopWhen'\n> & {\n profile: DeepSpaceAgentProfileId\n modelId?: unknown\n authToken?: string\n}\n\nexport interface DeepSpaceAgentStream<TOOLS extends ToolSet> {\n selection: ResolvedDeepSpaceAgentModel\n result: StreamTextResult<TOOLS, never>\n}\n\nexport class DeepSpaceAgentModelError extends Error {\n readonly code = 'unsupported_agent_model'\n\n constructor(readonly modelId: unknown, readonly profile: DeepSpaceAgentProfileId) {\n super(\n `Model ${typeof modelId === 'string' ? JSON.stringify(modelId) : String(modelId)} ` +\n `is not compatible with the ${profile} agent profile`,\n )\n this.name = 'DeepSpaceAgentModelError'\n }\n}\n\nexport class DeepSpaceAgentProfileError extends Error {\n readonly code = 'agent_profile_violation'\n\n constructor(readonly profile: DeepSpaceAgentProfileId, readonly toolName: string) {\n super(`The ${profile} agent profile does not allow tool ${JSON.stringify(toolName)}`)\n this.name = 'DeepSpaceAgentProfileError'\n }\n}\n\n/** The sole provider/model/tool-loop implementation for every DeepSpace agent surface. */\nexport function streamDeepSpaceAgent<TOOLS extends ToolSet>(\n env: DeepSpaceAIEnv,\n options: DeepSpaceAgentStreamOptions<TOOLS>,\n): DeepSpaceAgentStream<TOOLS> {\n const { profile, modelId, authToken, ...streamOptions } = options\n const selection = resolveDeepSpaceAgentModel(modelId, profile)\n if (!selection) throw new DeepSpaceAgentModelError(modelId, profile)\n if (selection.profile.allowedTools !== 'application-defined') {\n const allowed = selection.profile.allowedTools as readonly string[]\n for (const toolName of Object.keys(streamOptions.tools ?? {})) {\n if (!allowed.includes(toolName)) {\n throw new DeepSpaceAgentProfileError(profile, toolName)\n }\n }\n }\n\n const provider = createDeepSpaceAI(env, selection.provider, authToken ? { authToken } : {})\n const { prepareStep, tools, ...remainingStreamOptions } = streamOptions\n const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls)\n const result = streamText<TOOLS>({\n ...remainingStreamOptions,\n ...(boundedTools ? { tools: boundedTools } : {}),\n model: provider(selection.modelId),\n stopWhen: stepCountIs(selection.profile.maxSteps),\n prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),\n ...(selection.provider === 'openai'\n ? { providerOptions: { openai: { reasoningEffort: 'none' as const } } }\n : {}),\n } as StreamTextOptions<TOOLS>)\n\n return { selection, result }\n}\n\nfunction enforceToolCallLimit<TOOLS extends ToolSet>(\n prepareStep: PrepareStepFunction<TOOLS> | undefined,\n maxToolCalls: number | undefined,\n): PrepareStepFunction<TOOLS> | undefined {\n if (maxToolCalls === undefined) return prepareStep\n return async (context) => {\n const prepared = await prepareStep?.(context)\n const completedToolCalls = context.steps.reduce(\n (count, step) => count + step.toolCalls.length,\n 0,\n )\n if (completedToolCalls < maxToolCalls) return prepared\n return { ...prepared, activeTools: [], toolChoice: 'none' }\n }\n}\n\nfunction limitToolExecutions<TOOLS extends ToolSet>(\n tools: TOOLS | undefined,\n maxToolCalls: number | undefined,\n): TOOLS | undefined {\n if (!tools || maxToolCalls === undefined) return tools\n let executions = 0\n return Object.fromEntries(\n Object.entries(tools).map(([name, definition]) => {\n if (typeof definition.execute !== 'function') return [name, definition]\n const execute = definition.execute\n return [\n name,\n {\n ...definition,\n execute: (...args: Parameters<typeof execute>) => {\n if (executions >= maxToolCalls) {\n return Promise.resolve({\n error: `The agent tool-call limit of ${maxToolCalls} has been reached`,\n })\n }\n executions++\n return execute(...args)\n },\n },\n ]\n }),\n ) as TOOLS\n}\n\nexport interface DeepSpaceAgentDiagnosticContext {\n profile?: DeepSpaceAgentProfileId\n provider?: string\n modelId?: string\n}\n\n/** Preserve safe structured provider diagnostics in bounded Worker logs. */\nexport function deepSpaceAgentErrorSummary(\n error: unknown,\n context: DeepSpaceAgentDiagnosticContext = {},\n): string {\n const details: string[] = []\n const contextFields = [\n context.profile ? `profile=${context.profile}` : undefined,\n context.provider ? `provider=${safeLogToken(context.provider)}` : undefined,\n context.modelId ? `model=${safeLogToken(context.modelId)}` : undefined,\n ].filter((part): part is string => Boolean(part))\n if (contextFields.length > 0) details.push(contextFields.join(' '))\n const pending: unknown[] = [error]\n const visited = new Set<unknown>()\n while (pending.length > 0 && details.length < 8) {\n const current = pending.shift()\n if (current == null || visited.has(current)) continue\n visited.add(current)\n if (typeof current === 'object') {\n const record = current as Record<string, unknown>\n const parts = [\n typeof record.name === 'string'\n ? `name=${safeLogToken(record.name)}`\n : current instanceof Error\n ? `name=${safeLogToken(current.name)}`\n : undefined,\n safeErrorMessage(current, record),\n typeof record.statusCode === 'number' ? `status=${record.statusCode}` : undefined,\n typeof record.code === 'string' ? `code=${safeLogToken(record.code)}` : undefined,\n typeof record.requestId === 'string'\n ? `requestId=${safeLogToken(record.requestId)}`\n : undefined,\n ...safeProviderResponseMetadata(record.responseBody),\n ].filter((part): part is string => Boolean(part))\n details.push(parts.join(' ') || Object.prototype.toString.call(current))\n if (record.lastError != null) pending.push(record.lastError)\n if (record.cause != null) pending.push(record.cause)\n if (Array.isArray(record.errors)) pending.push(...record.errors)\n continue\n }\n details.push(String(current))\n }\n return details.join(' <- ') || 'Unknown error'\n}\n\nfunction safeErrorMessage(current: object, record: Record<string, unknown>): string | undefined {\n if (record.responseBody !== undefined) return undefined\n const message = current instanceof Error ? current.message : record.message\n if (typeof message !== 'string' || !message.trim()) return undefined\n return `message=${safeLogText(message)}`\n}\n\nfunction safeProviderResponseMetadata(responseBody: unknown): string[] {\n if (typeof responseBody !== 'string') return []\n try {\n const parsed = JSON.parse(responseBody) as Record<string, unknown>\n const error =\n parsed.error && typeof parsed.error === 'object'\n ? (parsed.error as Record<string, unknown>)\n : undefined\n const code =\n typeof error?.code === 'string'\n ? error.code\n : typeof error?.type === 'string'\n ? error.type\n : undefined\n const requestId = typeof parsed.request_id === 'string' ? parsed.request_id : undefined\n return [\n code ? `upstreamCode=${safeLogToken(code)}` : undefined,\n requestId ? `upstreamRequestId=${safeLogToken(requestId)}` : undefined,\n ].filter((part): part is string => Boolean(part))\n } catch {\n return []\n }\n}\n\nfunction safeLogToken(value: string): string {\n return value.replace(/[^a-zA-Z0-9._:/-]/g, '_').slice(0, 120)\n}\n\nfunction safeLogText(value: string): string {\n const withoutControlCharacters = Array.from(value, (character) => {\n const codePoint = character.codePointAt(0) ?? 0\n return codePoint <= 31 || codePoint === 127 ? ' ' : character\n }).join('')\n\n return withoutControlCharacters\n .replace(/\\bBearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')\n .replace(\n /\\b(api[_ -]?key|authorization|password|secret|token)\\b\\s*[:=]\\s*\\S+/gi,\n '$1=[redacted]',\n )\n .trim()\n .slice(0, 300)\n}\n","/**\n * Canonical DeepSpace model catalog and agent policy.\n *\n * Provider catalogs are discovery inputs, not a safe runtime allowlist: a\n * model is promoted here only after its tool loop, transport, pricing, and\n * streaming contract have been verified end to end through the DeepSpace\n * proxy. Every client picker and server agent profile consumes this module.\n */\n\nexport type DeepSpaceAIProvider = 'anthropic' | 'openai' | 'cerebras'\nexport type DeepSpaceAgentProfileId = 'application' | 'documentation'\nexport type DeepSpaceAgentSupport = 'multi-step' | 'single-step' | 'none'\n\nexport interface DeepSpaceAIModel {\n id: string\n label: string\n provider: DeepSpaceAIProvider\n providerLabel: string\n family: string\n /** Whether the model is eligible for DeepSpace's server-owned agent loop. */\n agentSupport: DeepSpaceAgentSupport\n /** Profiles in which the model has passed the complete runtime contract. */\n agentProfiles: readonly DeepSpaceAgentProfileId[]\n /** Provider transport currently used by the DeepSpace proxy adapter. */\n transport: 'messages' | 'chat-completions'\n recommendation: 'frontier' | 'balanced' | 'fast' | 'available' | 'limited'\n note?: string\n}\n\nexport interface DeepSpaceAgentProfile {\n id: DeepSpaceAgentProfileId\n defaultModel: string\n maxSteps: number\n maxToolCalls?: number\n allowedTools: 'application-defined' | readonly string[]\n}\n\n/**\n * Versioned provenance for the provider catalogs used during the latest\n * promotion review. Keeping this in the shipped artifact makes selection\n * policy auditable without allowing a mutable upstream list to change a\n * deployed app underneath a commit.\n */\nexport const DEEPSPACE_MODEL_CATALOG_PROVENANCE = {\n version: '2026-08-04',\n verifiedAt: '2026-08-04',\n sources: {\n anthropic: 'https://platform.claude.com/docs/en/api/beta/models/list',\n openai: 'https://developers.openai.com/api/docs/models/all',\n cerebras: 'https://inference-docs.cerebras.ai/models/overview',\n },\n} as const\n\nconst MULTI_STEP_AGENT_PROFILES = ['application', 'documentation'] as const\n\ntype MultiStepModelInput = Pick<DeepSpaceAIModel, 'id' | 'label' | 'family' | 'recommendation'>\ntype MultiStepProvider = Pick<DeepSpaceAIModel, 'provider' | 'providerLabel' | 'transport'>\n\nfunction createMultiStepModelFactory<const Provider extends MultiStepProvider>(provider: Provider) {\n return <const Model extends MultiStepModelInput>(model: Model) => ({\n ...model,\n ...provider,\n agentSupport: 'multi-step' as const,\n agentProfiles: MULTI_STEP_AGENT_PROFILES,\n })\n}\n\nconst anthropicModel = createMultiStepModelFactory({\n provider: 'anthropic',\n providerLabel: 'Anthropic',\n transport: 'messages',\n})\n\nconst openAIModel = createMultiStepModelFactory({\n provider: 'openai',\n providerLabel: 'OpenAI',\n transport: 'chat-completions',\n})\n\nexport const DEEPSPACE_AI_MODELS = [\n anthropicModel({\n id: 'claude-fable-5',\n label: 'Claude Fable 5',\n family: 'Claude 5',\n recommendation: 'frontier',\n }),\n anthropicModel({\n id: 'claude-opus-5',\n label: 'Claude Opus 5',\n family: 'Claude 5',\n recommendation: 'frontier',\n }),\n anthropicModel({\n id: 'claude-sonnet-5',\n label: 'Claude Sonnet 5',\n family: 'Claude 5',\n recommendation: 'balanced',\n }),\n anthropicModel({\n id: 'claude-haiku-4-5',\n label: 'Claude Haiku 4.5',\n family: 'Claude 4.5',\n recommendation: 'fast',\n }),\n openAIModel({\n id: 'gpt-5.6-sol',\n label: 'GPT-5.6 Sol',\n family: 'GPT-5.6',\n recommendation: 'frontier',\n }),\n openAIModel({\n id: 'gpt-5.6-terra',\n label: 'GPT-5.6 Terra',\n family: 'GPT-5.6',\n recommendation: 'balanced',\n }),\n openAIModel({\n id: 'gpt-5.6-luna',\n label: 'GPT-5.6 Luna',\n family: 'GPT-5.6',\n recommendation: 'fast',\n }),\n {\n id: 'gpt-oss-120b',\n label: 'GPT-OSS 120B',\n provider: 'cerebras',\n providerLabel: 'Cerebras',\n family: 'GPT-OSS',\n agentSupport: 'single-step',\n agentProfiles: [],\n transport: 'chat-completions',\n recommendation: 'limited',\n note: 'Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation.',\n },\n] as const satisfies readonly DeepSpaceAIModel[]\n\nexport const DEEPSPACE_AI_DEFAULTS = {\n agent: 'claude-sonnet-5',\n directGeneration: 'claude-sonnet-5',\n summarization: 'claude-haiku-4-5',\n} as const satisfies Record<string, DeepSpaceAIModelId>\n\nexport const DEEPSPACE_AGENT_PROFILES = {\n application: {\n id: 'application',\n defaultModel: DEEPSPACE_AI_DEFAULTS.agent,\n maxSteps: 21,\n maxToolCalls: 20,\n allowedTools: 'application-defined',\n },\n documentation: {\n id: 'documentation',\n defaultModel: DEEPSPACE_AI_DEFAULTS.agent,\n // Permit up to twenty completed read-only tools plus one final synthesis\n // step. The runner disables tools once the tool-call budget is exhausted.\n maxSteps: 21,\n maxToolCalls: 20,\n allowedTools: ['documentation_search', 'documentation_read'],\n },\n} as const satisfies Record<DeepSpaceAgentProfileId, DeepSpaceAgentProfile>\n\nexport type DeepSpaceAIModelId = (typeof DEEPSPACE_AI_MODELS)[number]['id']\n\nexport function getDeepSpaceAIModel(modelId: unknown): DeepSpaceAIModel | null {\n if (typeof modelId !== 'string') return null\n return DEEPSPACE_AI_MODELS.find((model) => model.id === modelId) ?? null\n}\n\nexport function listDeepSpaceAgentModels(\n profileId: DeepSpaceAgentProfileId = 'application',\n): readonly DeepSpaceAIModel[] {\n const defaultModel = DEEPSPACE_AGENT_PROFILES[profileId].defaultModel\n return DEEPSPACE_AI_MODELS.filter((model) =>\n (model.agentProfiles as readonly DeepSpaceAgentProfileId[]).includes(profileId),\n ).sort((left, right) => Number(right.id === defaultModel) - Number(left.id === defaultModel))\n}\n\nexport interface ResolvedDeepSpaceAgentModel {\n modelId: string\n provider: DeepSpaceAIProvider\n model: DeepSpaceAIModel\n profile: DeepSpaceAgentProfile\n}\n\nexport function resolveDeepSpaceAgentModel(\n modelId: unknown,\n profileId: DeepSpaceAgentProfileId = 'application',\n): ResolvedDeepSpaceAgentModel | null {\n const profile = DEEPSPACE_AGENT_PROFILES[profileId]\n const resolvedId = modelId === undefined ? profile.defaultModel : modelId\n const model = getDeepSpaceAIModel(resolvedId)\n if (!model || !model.agentProfiles.includes(profileId)) return null\n return { modelId: model.id, provider: model.provider, model, profile }\n}\n","/**\n * Composio tools for the Vercel AI SDK.\n *\n * Turns the platform's Composio integration into ready-to-use Vercel AI SDK\n * tools, so an agent (or the DeepSpace AI chat) can take actions on a user's\n * connected apps. Each returned tool's `execute()` calls our own\n * `composio/execute-tool` endpoint through the api-worker proxy, so our auth\n * and per-user billing apply. This is the managed-proxy equivalent of\n * Composio's `@composio/vercel` provider, which talks straight to Composio.\n *\n * `authToken` must be the END USER's JWT: Composio resolves the user's\n * connected accounts by the JWT subject, and that user is billed.\n *\n * Usage (inside a request handler, e.g. /api/ai/chat):\n *\n * import { composioTools, createDeepSpaceAI } from 'deepspace/worker'\n * const jwt = c.req.header('Authorization')!.slice(7)\n * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })\n * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })\n * const result = streamText({ model: ai('claude-sonnet-5'), prompt, tools })\n *\n * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.\n */\n\nimport { tool, jsonSchema, type ToolSet } from 'ai'\nimport { apiWorkerFetch, type ApiWorkerEnv } from './proxies'\n\nexport interface ComposioToolsOptions {\n /** End user's JWT. Required: the tools run as this user and bill them. */\n authToken: string\n /** Restrict to one toolkit (e.g. 'gmail', 'github'). */\n toolkit?: string\n /** Explicit tool slugs to expose (e.g. ['GMAIL_SEND_EMAIL']). */\n tools?: string[]\n /** Natural-language search to pick relevant tools. */\n search?: string\n /** Cap how many tools are exposed (default 20). Keep small: the LLM sees them all. */\n limit?: number\n}\n\ninterface ComposioToolItem {\n slug: string\n name?: string\n description?: string\n input_parameters?: Record<string, unknown>\n}\n\nconst EMPTY_SCHEMA = { type: 'object', properties: {} }\n\n/** POST a Composio endpoint through the api-worker proxy and parse the\n * `{ success, data }` envelope. Text-first so a non-JSON gateway error still\n * yields a useful detail string instead of a parse failure. Never throws. */\nasync function requestComposio(\n env: ApiWorkerEnv,\n endpoint: string,\n params: Record<string, unknown>,\n authToken: string,\n): Promise<{ ok: boolean; success: boolean; data: unknown; detail: string }> {\n const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${authToken}`,\n },\n body: JSON.stringify(params),\n })\n const text = await res.text()\n let parsed: { success?: boolean; data?: unknown; error?: string; message?: string } = {}\n try {\n parsed = text ? JSON.parse(text) : {}\n } catch {\n // non-JSON body; detail falls back to the raw text below\n }\n const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`\n return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail }\n}\n\n/** Composio slugs are already valid tool-name chars; sanitize + cap at 64 only\n * as a guard (Anthropic rejects longer names). The real slug is always used\n * for execution regardless of the key. */\nfunction toolKey(slug: string): string {\n const key = slug.replace(/[^a-zA-Z0-9_-]/g, '_')\n return key.length <= 64 ? key : key.slice(0, 64)\n}\n\n/**\n * Fetch Composio tools for the given scope and return them as a Vercel AI SDK\n * `ToolSet`. One `list-tools` call provides each tool's JSON Schema; each tool\n * executes via `composio/execute-tool` as the authenticated user.\n */\nexport async function composioTools(\n env: ApiWorkerEnv,\n options: ComposioToolsOptions,\n): Promise<ToolSet> {\n const { authToken } = options\n if (!authToken) {\n throw new Error(\n 'composioTools: options.authToken is required. Pass the end user\\'s JWT so the ' +\n 'tools run as that user (Composio resolves their connected accounts by it) and ' +\n 'the calls are billed to them.',\n )\n }\n\n const list = await requestComposio(\n env,\n 'list-tools',\n {\n toolkit: options.toolkit,\n toolSlugs: options.tools?.length ? options.tools.join(',') : undefined,\n search: options.search,\n limit: options.limit ?? 20,\n },\n authToken,\n )\n if (!list.ok || !list.success) {\n throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`)\n }\n\n const listItems = (list.data as { items?: unknown } | undefined)?.items\n const items: ComposioToolItem[] = Array.isArray(listItems) ? (listItems as ComposioToolItem[]) : []\n const tools: ToolSet = {}\n\n for (const item of items) {\n if (!item?.slug) continue\n const slug = item.slug\n const schema = (item.input_parameters ?? EMPTY_SCHEMA) as Parameters<typeof jsonSchema>[0]\n\n tools[toolKey(slug)] = tool({\n description: item.description ?? item.name ?? slug,\n inputSchema: jsonSchema(schema),\n execute: async (args: Record<string, unknown>) => {\n const r = await requestComposio(\n env,\n 'execute-tool',\n { slug, arguments: args ?? {} },\n authToken,\n )\n if (!r.ok || !r.success) {\n // Surface a readable error to the model instead of throwing, so the\n // agent loop can recover or explain to the user.\n return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` }\n }\n const data = r.data\n if (data && typeof data === 'object' && (data as { requiresConnection?: boolean }).requiresConnection) {\n const toolkit = (data as { toolkit?: string }).toolkit\n return {\n requiresConnection: true,\n toolkit,\n message: `The user has not connected ${toolkit ?? 'this app'}. Ask them to connect it before using this tool.`,\n }\n }\n return data\n },\n })\n }\n\n return tools\n}\n","/**\n * App-identity headers for platform calls (api-worker, platform-worker).\n *\n * The pair is `x-app-identity-token` (HMAC(PLATFORM_IDENTITY_SECRET, appId),\n * minted at deploy time and injected as a binding) + `x-app-id`. Pre-first-\n * deploy the token binding is ABSENT — `deepspace dev start` can only fetch it once\n * the app is in the deploy registry. Attaching headers built from an undefined\n * binding sends the literal string \"undefined\", which upstream identity\n * verification rejects as a *tampered* token (401 invalid) instead of the\n * truthful \"missing app identity\" — so this helper fails closed by attaching\n * nothing when the token is unset. The starter's worker.ts proxies follow the\n * same policy (see reassertAppIdentity there).\n */\n\nexport interface AppIdentityEnv {\n /** The app's immutable id (wrangler.toml [vars] DEEPSPACE_APP_ID). */\n DEEPSPACE_APP_ID: string\n /** Absent until the app's first deploy injects it. */\n APP_IDENTITY_TOKEN?: string\n}\n\n/**\n * Append the app's identity pair to `headers`, fail-closed: no token → no\n * identity headers, so the upstream answers \"missing app identity\" rather\n * than rejecting a garbage token.\n */\nexport function appendAppIdentity(headers: Headers, env: AppIdentityEnv): void {\n // Sanitize first so forwarded request headers can never smuggle identity —\n // same policy as the starter worker's reassertAppIdentity.\n headers.delete('x-app-identity-token')\n headers.delete('x-app-id')\n if (!env.APP_IDENTITY_TOKEN) return\n headers.set('x-app-identity-token', env.APP_IDENTITY_TOKEN)\n headers.set('x-app-id', env.DEEPSPACE_APP_ID)\n}\n","/**\n * captureScreenshot — call platform-worker /internal/screenshot.\n *\n * Apps don't ship CF Browser Rendering bindings or puppeteer in their\n * own bundle. The platform holds the binding; consumers call this\n * helper to get PNG bytes for a URL.\n *\n * The platform enforces HTTPS, a DeepSpace host allowlist, a per-app rate\n * limit, and one fixed viewport-only capture profile. Returns `null` on any\n * non-2xx — callers should treat that as \"no preview available\".\n *\n * Auth is the same HMAC-of-appId pattern `/internal/files` uses:\n * x-app-identity-token = hmac(PLATFORM_IDENTITY_SECRET, DEEPSPACE_APP_ID)\n * x-app-id = DEEPSPACE_APP_ID\n *\n * Apps already have both as bindings (APP_IDENTITY_TOKEN + DEEPSPACE_APP_ID),\n * so this helper is a thin wrapper — no extra secrets to manage.\n */\nimport { platformWorkerFetch, type PlatformWorkerEnv } from './proxies'\nimport { appendAppIdentity } from './app-identity'\n\nexport interface ScreenshotEnv extends PlatformWorkerEnv {\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n}\n\nexport interface ScreenshotResult {\n /** PNG bytes. */\n body: ArrayBuffer\n /** `image/png`. */\n contentType: string\n}\n\n/**\n * Capture a screenshot of `url` and return the PNG bytes.\n *\n * Returns null on capture failure (target unreachable, timeout, BR\n * binding misconfigured platform-side). The platform logs a generic failure\n * without target details; callers should surface their own fallback UX.\n */\nexport async function captureScreenshot(\n env: ScreenshotEnv,\n url: string,\n): Promise<ScreenshotResult | null> {\n const headers = new Headers({ 'content-type': 'application/json' })\n appendAppIdentity(headers, env)\n\n const res = await platformWorkerFetch(\n env,\n new Request('https://platform-internal/internal/screenshot', {\n method: 'POST',\n headers,\n body: JSON.stringify({ url }),\n }),\n )\n\n if (!res.ok) {\n console.warn(`[captureScreenshot] platform returned ${res.status}`)\n return null\n }\n\n return {\n body: await res.arrayBuffer(),\n contentType: res.headers.get('content-type') || 'image/png',\n }\n}\n","/**\n * Chat context pipeline — keeps the per-request payload to the LLM bounded.\n *\n * `prepareMessagesWithCompaction` runs before `streamText`: truncate old tool\n * results, apply a cached summary if available, otherwise summarize the older\n * half of history when over budget. Falls back to a sliding window if\n * summarization fails. `capToolResultSize` caps individual tool calls.\n */\n\nimport { generateText, type ModelMessage } from 'ai'\nimport { createDeepSpaceAI, type DeepSpaceAIEnv } from './ai'\n\nexport interface ChatTurn {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n}\n\nexport type Summarizer = (messages: ChatTurn[]) => Promise<string>\n\nexport interface ChatContextConfig {\n contextBudget: number\n toolResultCap: number\n keepRecentToolResults: number\n minKept: number\n}\n\n// contextBudget: 240_000 chars ≈ 60-80K tokens of history.\n// Safe for 200K+ context models (Claude Sonnet/Opus, GPT-4.1/4o).\n// If your app uses a 128K-context model, lower to ~120_000.\n// If your app uses a 32K-context model (some open-weight Cerebras models),\n// lower to ~40_000.\nexport const DEFAULT_CONTEXT_CONFIG: ChatContextConfig = {\n contextBudget: 240_000,\n toolResultCap: 30_000,\n keepRecentToolResults: 5,\n minKept: 10,\n}\n\nfunction sizeOf(m: ChatTurn): number {\n return (m.content ?? '').length + (m.parts ? JSON.stringify(m.parts).length : 0)\n}\n\nexport function totalChars(messages: ChatTurn[]): number {\n return messages.reduce((acc, m) => acc + sizeOf(m), 0)\n}\n\n/**\n * Replace older tool-result payloads with a small marker. Keeps the last\n * `keepRecent` tool results intact. Errors (`success: false`) are preserved —\n * they're small and the agent needs them for reasoning.\n */\nexport function truncateOldToolResults(messages: ChatTurn[], keepRecent: number): ChatTurn[] {\n const assistantIdxs: number[] = []\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === 'assistant') assistantIdxs.push(i)\n }\n const protectedStart =\n assistantIdxs.length > keepRecent ? assistantIdxs[assistantIdxs.length - keepRecent] : 0\n\n return messages.map((msg, idx) => {\n if (msg.role !== 'assistant' || idx >= protectedStart) return msg\n if (!Array.isArray(msg.parts) || msg.parts.length === 0) return msg\n const newParts = msg.parts.map((p) => {\n if (!p || typeof p !== 'object') return p\n const anyP = p as Record<string, unknown>\n if (anyP.type !== 'tool-invocation') return p\n const inv = anyP.toolInvocation as Record<string, unknown> | undefined\n if (!inv || inv.state !== 'result') return p\n const result = inv.result as Record<string, unknown> | undefined\n if (result && result.success === false) return p\n return {\n ...anyP,\n toolInvocation: {\n ...inv,\n result: {\n _truncated: true,\n note: 'Result from an earlier turn omitted to save context. Call this tool again if you need the data.',\n },\n },\n }\n })\n return { ...msg, parts: newParts }\n })\n}\n\n/**\n * Drop oldest messages until total character count is under `charCap`,\n * never going below `minKept` messages. System messages (e.g. compaction\n * summaries) are pinned — dropping them would discard the most condensed\n * context first.\n */\nexport function applySlidingWindow(\n messages: ChatTurn[],\n charCap: number,\n minKept: number,\n): ChatTurn[] {\n let total = totalChars(messages)\n if (total <= charCap) return messages\n const out = [...messages]\n while (out.length > minKept && total > charCap) {\n const idx = out.findIndex((m) => m.role !== 'system')\n if (idx < 0) break\n const dropped = out.splice(idx, 1)[0]\n total -= sizeOf(dropped)\n }\n return out\n}\n\ntype ResultObj = Record<string, unknown>\n\n// Paths (root → array) we know how to trim, in priority order. Covers the\n// built-in tool-result shapes: `{ records: [...] }`, `{ data: { records } }`,\n// and a bare `{ data: [...] }`.\nconst TRIMMABLE_ARRAY_PATHS: string[][] = [\n ['records'],\n ['data', 'records'],\n ['items'],\n ['data', 'items'],\n ['data'],\n]\n\nfunction getPath(root: unknown, path: string[]): unknown {\n let cur: unknown = root\n for (const key of path) {\n if (!cur || typeof cur !== 'object') return undefined\n cur = (cur as ResultObj)[key]\n }\n return cur\n}\n\n// Shallow-clone along `path`, set the leaf to `value`, and merge `flags` into\n// the object that directly holds the leaf. Non-mutating — the original result\n// is untouched.\nfunction setPath(root: unknown, path: string[], value: unknown, flags: ResultObj): unknown {\n const obj = root && typeof root === 'object' ? (root as ResultObj) : {}\n const [head, ...tail] = path\n if (tail.length === 0) {\n return { ...obj, [head]: value, ...flags }\n }\n return { ...obj, [head]: setPath(obj[head], tail, value, flags) }\n}\n\n// Find the first trimmable array in a tool result, so an oversized payload can\n// be degraded to a usable prefix instead of discarded. Returns undefined when\n// the payload carries no array we know how to shorten.\nfunction locateTrimmableArray(result: ResultObj): { path: string[]; items: unknown[] } | undefined {\n for (const path of TRIMMABLE_ARRAY_PATHS) {\n const arr = getPath(result, path)\n if (Array.isArray(arr)) return { path, items: arr }\n }\n // Generic fallbacks: first array prop at the top level, then one level into `data`.\n for (const [key, value] of Object.entries(result)) {\n if (Array.isArray(value)) return { path: [key], items: value }\n }\n const data = result.data\n if (data && typeof data === 'object') {\n for (const [key, value] of Object.entries(data as ResultObj)) {\n if (Array.isArray(value)) return { path: ['data', key], items: value }\n }\n }\n return undefined\n}\n\n/**\n * Keep an individual tool result under `byteCap`.\n *\n * If the payload carries a list of items (e.g. a `records.query` result), it is\n * degraded gracefully: as many leading items as fit under the cap are returned,\n * the `success: true` shape is preserved, and `{ truncated, returned, total }`\n * flags are merged in next to the array so callers can still use the partial\n * data and paginate for the rest.\n *\n * Only when there is no array to trim (or even an empty list still overflows\n * because of oversized sibling fields) does it fall back to replacing the\n * result with an error + small preview telling the agent to narrow its query.\n */\nexport function capToolResultSize(result: unknown, byteCap: number): unknown {\n let serialized: string | undefined\n try {\n serialized = JSON.stringify(result)\n } catch {\n return { success: false, error: 'Tool result could not be serialized.' }\n }\n // JSON.stringify returns `undefined` (not a string) for `undefined`,\n // bare functions, and symbols. Pass those through — caller decides what\n // to do with non-serializable shapes; this function's job is only to\n // cap oversized payloads.\n if (typeof serialized !== 'string') return result\n if (serialized.length <= byteCap) return result\n\n if (result && typeof result === 'object') {\n const located = locateTrimmableArray(result as ResultObj)\n if (located) {\n const { path, items } = located\n const total = items.length\n const build = (k: number) =>\n setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total })\n const fits = (k: number): boolean => {\n try {\n return JSON.stringify(build(k)).length <= byteCap\n } catch {\n return false\n }\n }\n // Only degrade if an empty list actually fits — otherwise the bloat is in\n // sibling fields, not the array, and trimming it can't help.\n if (fits(0)) {\n // Binary search for the largest prefix that still fits under the cap.\n let lo = 0\n let hi = total\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (fits(mid)) lo = mid\n else hi = mid - 1\n }\n // Return a partial page only if at least one item fits. When not even a\n // single record fits (one oversized record), fall through to the error\n // below so the caller gets actionable guidance instead of an empty list\n // that reads like \"no results\".\n if (lo > 0) return build(lo)\n }\n }\n }\n\n return {\n success: false,\n truncated: true,\n error:\n `Tool result exceeded ${byteCap} bytes (was ${serialized.length}). ` +\n `Retry with a narrower query (e.g. add a \\`where\\` filter, reduce \\`limit\\`, ` +\n `or call records.get for a single record).`,\n preview: serialized.slice(0, 2_000),\n }\n}\n\n/**\n * Convert persisted ChatTurns into AI SDK ModelMessages.\n *\n * Persisted assistant rows store `parts` in UI shape (text + tool-invocation,\n * each invocation carrying its own `result`). When fed back to the LLM, the\n * shape MUST match the original multi-step flow: an assistant message\n * containing a `tool_use` block must end with that block, the IMMEDIATELY\n * NEXT message must be a tool/user message containing the matching\n * `tool_result`, and any text the model produced AFTER seeing the tool\n * result belongs in a SEPARATE assistant message after the tool message.\n *\n * Anthropic specifically rejects an assistant message of the form\n * `[text, tool_use, text]` — the trailing text breaks its `tool_use` →\n * `tool_result` pairing check. So we walk the parts in order and split at\n * each tool-invocation boundary, emitting a fresh assistant + tool pair per\n * tool call, and a final trailing assistant message for any post-tool text.\n *\n * Tool-invocation entries with `state: 'call'` (no result — typically an\n * interrupted stream) are dropped on both sides.\n */\nexport function turnsToCoreMessages(turns: ChatTurn[]): ModelMessage[] {\n const out: ModelMessage[] = []\n for (const t of turns) {\n if (t.role === 'system') {\n out.push({ role: 'system', content: t.content })\n continue\n }\n if (t.role === 'user') {\n out.push({ role: 'user', content: t.content })\n continue\n }\n\n if (!Array.isArray(t.parts) || t.parts.length === 0) {\n if (t.content) out.push({ role: 'assistant', content: t.content })\n continue\n }\n\n // Persisted ChatTurn parts use a flat `args`/`result` shape; the SDK\n // wants v5's `input` and `output: { type:'json', value }` wrapper. The\n // `json` tag is the right variant for JSON-serializable tool results.\n type AssistantPart =\n | { type: 'text'; text: string }\n | { type: 'tool-call'; toolCallId: string; toolName: string; input: unknown }\n type ToolResultEntry = {\n type: 'tool-result'\n toolCallId: string\n toolName: string\n output: { type: 'json'; value: unknown }\n }\n\n // Pending text segment + the tool call it terminates with (if any).\n let pending: AssistantPart[] = []\n let emittedAny = false\n\n const flushAssistant = () => {\n if (pending.length === 0) return\n out.push({ role: 'assistant', content: pending } as ModelMessage)\n pending = []\n emittedAny = true\n }\n\n for (const p of t.parts) {\n if (!p || typeof p !== 'object') continue\n const anyP = p as Record<string, unknown>\n if (anyP.type === 'text' && typeof anyP.text === 'string' && anyP.text) {\n pending.push({ type: 'text', text: anyP.text })\n continue\n }\n if (anyP.type !== 'tool-invocation') continue\n const inv = anyP.toolInvocation as Record<string, unknown> | undefined\n const toolCallId = typeof anyP.toolCallId === 'string' ? anyP.toolCallId : undefined\n const toolName = typeof inv?.toolName === 'string' ? (inv.toolName as string) : undefined\n if (!toolCallId || !toolName) continue\n // Drop calls without a paired result — AI SDK errors on unanswered calls.\n if (inv?.state !== 'result') continue\n\n // Close out the current assistant segment with the tool_use block, then\n // emit it followed by a tool message holding the matching tool_result.\n pending.push({ type: 'tool-call', toolCallId, toolName, input: inv?.args })\n flushAssistant()\n const tr: ToolResultEntry = {\n type: 'tool-result',\n toolCallId,\n toolName,\n output: { type: 'json', value: inv.result as unknown },\n }\n out.push({ role: 'tool', content: [tr] } as ModelMessage)\n }\n\n // Trailing text after the last tool call (or the whole turn if no tools).\n flushAssistant()\n\n // Fallback: turn had only orphan tool-calls (all state:'call'); use plain\n // content text so we don't drop the message entirely.\n if (!emittedAny && t.content) {\n out.push({ role: 'assistant', content: t.content })\n }\n }\n\n return out\n}\n\n/**\n * Convert AI SDK response messages into our persisted UI shape (text +\n * tool-invocation parts), pairing each assistant tool-call with its tool-\n * result from the following tool message. Order is chronological.\n *\n * Inverse of `turnsToCoreMessages`: takes the v5 `ModelMessage[]` returned\n * from `streamText`'s `onFinish` and produces the flat `parts` array we\n * persist on `ai-messages` rows. Reads `c.input` / `c.output` (v5 wire\n * names) and unwraps `output`'s tagged-union via `unwrapToolOutput`.\n */\nexport function buildUiParts(responseMessages: ModelMessage[]): unknown[] {\n const toolResults = new Map<string, unknown>()\n for (const msg of responseMessages) {\n if (msg.role !== 'tool' || !Array.isArray(msg.content)) continue\n for (const c of msg.content) {\n if (c.type === 'tool-result') {\n toolResults.set(c.toolCallId, unwrapToolOutput(c.output))\n }\n }\n }\n const parts: unknown[] = []\n for (const msg of responseMessages) {\n if (msg.role !== 'assistant') continue\n if (typeof msg.content === 'string') {\n if (msg.content) parts.push({ type: 'text', text: msg.content })\n continue\n }\n if (!Array.isArray(msg.content)) continue\n for (const c of msg.content) {\n if (c.type === 'text' && typeof c.text === 'string' && c.text) {\n parts.push({ type: 'text', text: c.text })\n } else if (c.type === 'tool-call') {\n const result = toolResults.get(c.toolCallId)\n if (result === undefined) {\n // onFinish only fires on a successful stream, so every tool-call\n // should have a paired tool-result. If one is missing (provider\n // quirk, mid-step failure), drop the half-state invocation —\n // turnsToCoreMessages would drop it on the next turn anyway.\n continue\n }\n parts.push({\n type: 'tool-invocation',\n toolCallId: c.toolCallId,\n toolInvocation: {\n toolName: c.toolName,\n state: 'result',\n args: c.input,\n result,\n },\n })\n }\n }\n }\n return parts\n}\n\n/**\n * Unwrap v5's tagged tool-result `output` to the flat shape we persist.\n * Errors get remapped to `{ success: false, error }` because\n * `truncateOldToolResults` preserves entries with that shape across turns —\n * without the remap, error context would get truncated like a normal result.\n */\nexport function unwrapToolOutput(output: unknown): unknown {\n if (!output || typeof output !== 'object') return output\n const o = output as Record<string, unknown>\n const tag = typeof o.type === 'string' ? o.type : null\n if (tag === 'json' || tag === 'text') {\n return 'value' in o ? o.value : output\n }\n if (tag === 'error-text' || tag === 'error-json') {\n return { success: false, error: 'value' in o ? o.value : 'Tool execution failed' }\n }\n return output\n}\n\nfunction buildSummaryMessage(text: string, throughId: string): ChatTurn {\n return {\n id: `summary-${throughId}`,\n role: 'system',\n content: `Earlier conversation summary:\\n${text}`,\n }\n}\n\n/**\n * Pre-stream pipeline with compaction.\n *\n * 1. Truncate old tool results.\n * 2. If a cached summary covers a known message id, replace prior turns with it.\n * 3. If still over budget, summarize the older half of `working` and return a\n * `newSummary` for persistence — runs even after cached-summary application\n * so a long-running chat can re-summarize on subsequent turns.\n * 4. On summarizer error or missing ids, fall back to a sliding window\n * (which preserves system messages — see `applySlidingWindow`).\n */\nexport async function prepareMessagesWithCompaction(\n messages: ChatTurn[],\n config: ChatContextConfig,\n options: {\n summarizer: Summarizer\n cachedSummary?: { text: string; throughId: string }\n },\n): Promise<{ messages: ChatTurn[]; newSummary?: { text: string; throughId: string } }> {\n const truncated = truncateOldToolResults(messages, config.keepRecentToolResults)\n\n let working = truncated\n if (options.cachedSummary) {\n const idx = truncated.findIndex((m) => m.id === options.cachedSummary!.throughId)\n if (idx >= 0) {\n working = [\n buildSummaryMessage(options.cachedSummary.text, options.cachedSummary.throughId),\n ...truncated.slice(idx + 1),\n ]\n }\n }\n\n if (totalChars(working) <= config.contextBudget) {\n return { messages: working }\n }\n\n // Over budget: summarize the older half of `working`. Runs even when a\n // cached summary was applied — the summarizer prompt rolls prior summaries\n // forward, so the fresh summary subsumes the old one.\n const half = Math.max(1, Math.floor(working.length / 2))\n const older = working.slice(0, half)\n const newer = working.slice(half)\n // Pick the last REAL message id in `older` as the anchor. A previously\n // applied cached summary leaves a synthetic `summary-...` id at index 0;\n // anchoring on it would round-trip through history (which has no such id)\n // and force a re-summarize from scratch every turn — a billing leak.\n let throughId: string | undefined\n for (let i = older.length - 1; i >= 0; i--) {\n const id = older[i].id\n if (id && !id.startsWith('summary-')) {\n throughId = id\n break\n }\n }\n if (throughId) {\n try {\n const text = await options.summarizer(older)\n return {\n messages: [buildSummaryMessage(text, throughId), ...newer],\n newSummary: { text, throughId },\n }\n } catch (err) {\n console.error('prepareMessagesWithCompaction: summarizer failed, falling back to sliding window', err)\n }\n } else {\n console.warn('prepareMessagesWithCompaction: messages lack ids; cannot anchor a summary, falling back to sliding window')\n }\n\n return {\n messages: applySlidingWindow(working, config.contextBudget, config.minKept),\n }\n}\n\nconst SUMMARIZER_SYSTEM_PROMPT = `You are compressing a conversation between a user and an AI assistant into a\nconcise memory summary. The summary will replace the original messages in the\nconversation history; the assistant will read it on subsequent turns to\nmaintain continuity.\n\nPreserve in the summary:\n- User preferences and constraints the user stated (coding style, tone,\n skill level, goals).\n- Decisions made and rejected alternatives (\"we chose X over Y because Z\").\n- Current task state (what's in progress, what's been completed).\n- Any facts the user might reasonably reference later.\n\nIf the messages below contain a prior summary, incorporate its content into\nyour new summary — do not drop information from earlier summaries.\n\nKeep the summary under 2000 tokens. Write as a factual note to future-you,\nnot as a narrative. Use terse bullet points where possible.`\n\nfunction formatTranscript(messages: ChatTurn[]): string {\n return messages\n .map((m) => {\n const label = m.role === 'user' ? 'User' : m.role === 'assistant' ? 'Assistant' : 'System'\n const tools = Array.isArray(m.parts)\n ? m.parts\n .filter((p) => (p as { type?: string } | null)?.type === 'tool-invocation')\n .map((p) => {\n const name = (p as { toolInvocation?: { toolName?: string } }).toolInvocation?.toolName\n return ` [tool: ${name ?? 'unknown'}]`\n })\n .join('\\n')\n : ''\n return `${label}: ${m.content}${tools ? '\\n' + tools : ''}`\n })\n .join('\\n\\n')\n}\n\n/**\n * Build a default summarizer backed by Claude Haiku.\n *\n * Billing: defaults to the app owner via `APP_OWNER_JWT` — summarization is\n * usually infrastructure, not user work. Pass `{ authToken }` to bill a\n * specific user (e.g. the caller's JWT) instead.\n */\nexport function makeDefaultSummarizer(\n env: DeepSpaceAIEnv,\n options: { authToken?: string } = {},\n): Summarizer {\n return async (messages) => {\n const anthropic = createDeepSpaceAI(env, 'anthropic', { authToken: options.authToken })\n const { text } = await generateText({\n // Stable alias — provider bug-fix snapshots roll forward without code change.\n model: anthropic('claude-haiku-4-5'),\n // 2500 gives the model room to finish a sentence after hitting the\n // \"under 2000 tokens\" soft limit in the prompt.\n maxOutputTokens: 2500,\n system: SUMMARIZER_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: formatTranscript(messages) }],\n })\n return text\n }\n}\n","/**\n * Wire protocol constants.\n *\n * The JSON WebSocket protocol uses dotted string identifiers (e.g.\n * `\"records.query\"`) as the `type` discriminator. All message types are\n * grouped under a single `MSG` object so imports stay tidy:\n *\n * import { MSG, dispatch, clientBuild } from 'deepspace'\n *\n * dispatch<ServerMessage>(raw, {\n * })\n *\n * Each key's value is the on-wire string — grep-friendly, self-\n * documenting, and infinite per namespace. Adding a new message is one\n * line here plus one arm in the discriminated union in `./messages.ts`.\n *\n * Yjs binary protocol constants (`MSG_YJS_SYNC`, `MSG_YJS_AWARENESS`) are\n * intentionally kept numeric and separate from `MSG` — they ride a\n * binary WebSocket frame format and aren't part of the JSON dispatcher.\n */\n\nexport const MSG = {\n // ---- Records / CRUD --------------------------------------------------\n SUBSCRIBE: 'core.subscribe',\n UNSUBSCRIBE: 'core.unsubscribe',\n QUERY_RESULT: 'core.query_result',\n RECORD_CHANGE: 'core.record_change',\n PUT: 'core.put',\n DELETE: 'core.delete',\n ERROR: 'core.error',\n\n // ---- Users -----------------------------------------------------------\n USER_INFO: 'user.info',\n USER_LIST: 'user.list',\n SET_ROLE: 'user.set_role',\n USER_UPDATE: 'user.update',\n\n // ---- Auth signal -----------------------------------------------------\n // Server → client one-shot on connect from rooms that enforce role-based\n // writes (Canvas, Game, Cron). Tells the client whether this connection\n // can write so its hook can disable local write APIs and the UI can\n // render controls accordingly. Without this, a viewer's click would\n // round-trip to the server and silently come back as ERROR.\n AUTH: 'auth',\n\n // ---- Yjs JSON control frames (binary sync lives in ./yjs.ts) ---------\n YJS_JOIN: 'yjs.join',\n YJS_LEAVE: 'yjs.leave',\n\n // ---- Records room misc -----------------------------------------------\n ACK: 'records.ack',\n LIST_SCHEMAS: 'records.list_schemas',\n RESUBSCRIBE: 'records.resubscribe',\n\n\n // ---- CanvasRoom ------------------------------------------------------\n CANVAS_SHAPES: 'canvas.shapes',\n CANVAS_ADD: 'canvas.add',\n CANVAS_MOVE: 'canvas.move',\n CANVAS_RESIZE: 'canvas.resize',\n CANVAS_DELETE: 'canvas.delete',\n CANVAS_UPDATE: 'canvas.update',\n CANVAS_VIEWPORT: 'canvas.viewport',\n CANVAS_UNDO: 'canvas.undo',\n CANVAS_REDO: 'canvas.redo',\n\n // ---- CronRoom --------------------------------------------------------\n CRON_TASKS: 'cron.tasks',\n CRON_HISTORY: 'cron.history',\n CRON_TRIGGER: 'cron.trigger',\n CRON_PAUSE: 'cron.pause',\n CRON_RESUME: 'cron.resume',\n CRON_STATUS: 'cron.status',\n // Per-mutation receipt, sent only to the socket whose trigger/pause/\n // resume carried a `requestId` — frames without one stay fire-and-forget.\n CRON_ACK: 'cron.ack',\n\n // ---- JobRoom ---------------------------------------------------------\n // Background-job queue with durable, alarm-driven execution. See\n // server/rooms/job-room.ts. Clients enqueue and subscribe to updates;\n // the room broadcasts JOB_UPDATE for every status transition.\n JOB_ENQUEUE: 'job.enqueue',\n JOB_CANCEL: 'job.cancel',\n JOB_RETRY: 'job.retry',\n JOB_UPDATE: 'job.update',\n\n // ---- PresenceRoom ----------------------------------------------------\n PRESENCE_SYNC: 'presence.sync',\n PRESENCE_JOIN: 'presence.join',\n PRESENCE_LEAVE: 'presence.leave',\n PRESENCE_UPDATE: 'presence.update',\n\n // ---- Gateway multiplexing --------------------------------------------\n GW_SCOPE_CONNECT: 'gateway.scope_connect',\n GW_SCOPE_DISCONNECT: 'gateway.scope_disconnect',\n GW_SCOPE_ERROR: 'gateway.scope_error',\n GW_TOKEN_REFRESH: 'gateway.token_refresh',\n GW_USER_UPDATE: 'gateway.user_update',\n} as const\n\n/**\n * Type of any message type constant — the union of every string literal\n * stored in `MSG`. Useful when declaring functions that accept \"any known\n * message type\" without enumerating all 54 strings by hand.\n */\nexport type MsgType = (typeof MSG)[keyof typeof MSG]\n\n// ---------------------------------------------------------------------------\n// Yjs binary protocol (NOT part of MSG — see header comment)\n// ---------------------------------------------------------------------------\n\n/** Outer envelope id for binary-framed yjs sync messages. Varuint-encoded. */\nexport const MSG_YJS_SYNC = 22\n/** Outer envelope id for binary-framed yjs awareness messages. Varuint-encoded. */\nexport const MSG_YJS_AWARENESS = 23\n\n// ---------------------------------------------------------------------------\n// Role names — not wire messages, just the string values the auth layer uses\n// as role identifiers.\n// ---------------------------------------------------------------------------\n\n/** Role assigned to unauthenticated WebSocket connections */\nexport const ROLE_ANONYMOUS = 'viewer'\n/** Prefix `base-room` mints anonymous socket ids with. */\nexport const ANONYMOUS_USER_ID_PREFIX = 'anon-'\n/** The one test for an anonymous socket identity — `ROLE_ANONYMOUS` cannot be\n * it, since authenticated users can legitimately hold the same role name. */\nexport function isAnonymousUserId(userId: string): boolean {\n return userId.startsWith(ANONYMOUS_USER_ID_PREFIX)\n}\n/** Default role for newly registered authenticated users */\nexport const ROLE_DEFAULT = 'member'\n/** Admin role */\nexport const ROLE_ADMIN = 'admin'\n\n// ---------------------------------------------------------------------------\n// Error strings that cross a room boundary and are matched by the caller\n// ---------------------------------------------------------------------------\n\n/**\n * Reported when a record id resolves to nothing.\n *\n * This crosses the DO boundary as a response body and callers branch on it,\n * so it is a contract rather than prose. `getChat` in particular treats it as\n * \"no such chat\" and returns null — the same answer it gives for a chat owned\n * by someone else, which is what stops chat ids being enumerable. If a room\n * reworded this independently, a miss would start throwing while a cross-user\n * hit kept returning null, and that difference is an existence oracle.\n */\nexport const RECORD_NOT_FOUND = 'Record not found'\n","/**\n * Chat history helpers — wrap RecordRoom's tools API for ai-chats / ai-messages.\n *\n * Trust model: every helper here runs through `executeToolAsApp`, which sends\n * `X-App-Action: 'true'` and therefore **bypasses RecordRoom's per-record\n * RBAC entirely**. The `userId` argument these helpers take is the identity\n * they act *as* — it is NOT an authorization boundary, and RecordRoom will\n * not check it. The worker is the only trust boundary.\n *\n * `getChat` is that boundary for this module: it compares the stored\n * `userId` against the caller and returns null on a mismatch, so the\n * worker's `/api/ai/chat`, `PATCH /api/ai/chats/:id`, and\n * `DELETE /api/ai/chats/:id` routes 404 when a row is missing *or* owned by\n * someone else. Every write helper (`updateChat`, `appendMessage`,\n * `deleteChatCascade`) is reachable only behind that precheck; a new caller\n * that skips it is writing across users, so route new consumers through\n * `getChat` first.\n *\n * `updateChat` and `appendMessage` re-run that precheck themselves and report\n * `false` instead of writing when the chat is gone: `records.update` /\n * `records.create` are upserts, so a write racing the user's delete would\n * resurrect the chat as a ghost row or orphan messages under a chat that no\n * longer exists. The guard lives in the helpers, not the routes, so every copy\n * of the scaffolded chat routes gets it.\n *\n * The tools API returns records as `{ recordId, data, createdAt, updatedAt }`\n * envelopes; helpers below flatten them into ChatRow / ChatMessageRow.\n */\n\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\n\nexport type ChatRow = {\n recordId: string\n userId: string\n title: string\n model?: string\n compactedSummary?: string\n compactedThroughId?: string\n createdAt: string\n updatedAt: string\n}\n\nexport type ChatMessageRow = {\n recordId: string\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n createdAt: string\n}\n\ntype RecordEnvelope<T> = {\n recordId: string\n data: T\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype ChatColumns = {\n userId: string\n title?: string\n model?: string\n compactedSummary?: string\n compactedThroughId?: string\n}\n\ntype MessageColumns = {\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n}\n\ntype ToolResponse<T> = { success: boolean; data?: T; error?: string }\n\nasync function executeToolAsApp<T>(\n stub: DurableObjectStub,\n userId: string,\n tool: string,\n params: Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch(new Request('https://internal/api/tools/execute', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': userId,\n 'X-App-Action': 'true',\n },\n body: JSON.stringify({ tool, params }),\n }))\n const body = await res.json() as ToolResponse<T>\n if (!body.success) {\n throw new Error(`chat-history ${tool} failed: ${body.error ?? 'unknown error'}`)\n }\n return body.data as T\n}\n\nfunction toChatRow(env: RecordEnvelope<ChatColumns>): ChatRow {\n return {\n recordId: env.recordId,\n userId: env.data.userId,\n title: env.data.title ?? '',\n model: env.data.model,\n compactedSummary: env.data.compactedSummary,\n compactedThroughId: env.data.compactedThroughId,\n createdAt: env.createdAt,\n updatedAt: env.updatedAt,\n }\n}\n\nfunction toMessageRow(env: RecordEnvelope<MessageColumns>): ChatMessageRow {\n return {\n recordId: env.recordId,\n chatId: env.data.chatId,\n userId: env.data.userId,\n role: env.data.role,\n content: env.data.content ?? '',\n parts: env.data.parts,\n createdAt: env.createdAt,\n }\n}\n\n/**\n * Fetch a chat the caller owns, or null.\n *\n * This is the module's authorization boundary. `records.get` runs with\n * `X-App-Action`, so RecordRoom applies no per-record RBAC and will happily\n * return another user's row — the ownership comparison below is the only\n * thing standing between a caller and someone else's chat. A miss and a\n * cross-user hit deliberately look identical to the caller (both null, both\n * 404 at the route) so chat ids stay unenumerable.\n */\nexport async function getChat(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<ChatRow | null> {\n try {\n const result = await executeToolAsApp<{ record: RecordEnvelope<ChatColumns> }>(stub, userId, 'records.get', {\n collection: 'ai-chats',\n recordId: chatId,\n })\n if (!result.record) return null\n const chat = toChatRow(result.record)\n return chat.userId === userId ? chat : null\n } catch (err) {\n // Shared constant, not a loose string: a miss and a cross-user hit must\n // keep producing the same null. If a room reworded this on its own, a\n // miss would throw while a foreign chat still returned null, and that\n // asymmetry is exactly the existence oracle the ownership check removes.\n if (err instanceof Error && err.message.includes(RECORD_NOT_FOUND)) return null\n throw err\n }\n}\n\nexport async function createChat(\n stub: DurableObjectStub,\n userId: string,\n opts: { title?: string; model?: string } = {},\n): Promise<ChatRow> {\n const data: Record<string, unknown> = { userId }\n if (opts.title !== undefined) data.title = opts.title\n if (opts.model !== undefined) data.model = opts.model\n const result = await executeToolAsApp<{ record: RecordEnvelope<ChatColumns> }>(stub, userId, 'records.create', {\n collection: 'ai-chats',\n data,\n })\n return toChatRow(result.record)\n}\n\n/**\n * Patch a chat the caller owns. Returns true if the row was written, false if\n * the chat no longer exists (or never belonged to the caller).\n *\n * The `getChat` precheck is not redundant with the route's own: `records.update`\n * is an upsert on the DO — same code path as create — so an unguarded patch of\n * a chat the user deleted mid-stream *recreates* it as a title-less ghost whose\n * messages are already cascaded away. Writing only over a row that still exists\n * is the one place that can be prevented for every caller, so the guard lives\n * here rather than at each call site. A tiny TOCTOU window remains between the\n * read and the write (closing it needs a compare-and-set on the DO); a delete\n * landing inside that window is the same rare race as before, not the routine\n * \"user deleted the chat while the stream ran\" case this closes.\n */\nexport async function updateChat(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n patch: Partial<Pick<ChatRow, 'title' | 'model' | 'compactedSummary' | 'compactedThroughId'>>,\n): Promise<boolean> {\n if (!(await getChat(stub, chatId, userId))) return false\n await executeToolAsApp(stub, userId, 'records.update', {\n collection: 'ai-chats',\n recordId: chatId,\n data: patch,\n })\n return true\n}\n\n/**\n * Messages removed per `records.deleteWhere` call. One subrequest per page,\n * so a chat of any length costs `ceil(messages / PAGE) + 1` subrequests\n * instead of one per message — the old shape ran out of the Workers\n * subrequest budget mid-cascade and orphaned the remainder.\n */\nconst MESSAGE_DELETE_PAGE = 200\n\nexport async function deleteChatCascade(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<void> {\n let messagesError: unknown = null\n try {\n // Each page really removes its rows, so the remaining set strictly shrinks\n // and a short page is the last one.\n let deleted = MESSAGE_DELETE_PAGE\n while (deleted === MESSAGE_DELETE_PAGE) {\n const page = await executeToolAsApp<{ deleted: number }>(stub, userId, 'records.deleteWhere', {\n collection: 'ai-messages',\n where: { chatId },\n limit: MESSAGE_DELETE_PAGE,\n })\n deleted = page.deleted\n }\n } catch (err) {\n messagesError = err\n }\n\n // Always attempt the chat row delete so the row disappears from listings,\n // even if some message rows got orphaned. If it fails too, its error is the\n // one that surfaces — a chat still sitting in the user's list is the visible\n // failure, orphaned messages are not.\n await executeToolAsApp(stub, userId, 'records.delete', {\n collection: 'ai-chats',\n recordId: chatId,\n })\n\n if (messagesError) throw messagesError\n}\n\nexport async function loadMessages(\n stub: DurableObjectStub,\n chatId: string,\n userId: string,\n): Promise<ChatMessageRow[]> {\n // Filter by userId in addition to chatId — defense in depth against any\n // future change that lets a row land in this collection without going\n // through the worker (which already verifies chat ownership).\n const result = await executeToolAsApp<{ records: Array<RecordEnvelope<MessageColumns>> }>(stub, userId, 'records.query', {\n collection: 'ai-messages',\n where: { chatId, userId },\n orderBy: 'createdAt',\n orderDir: 'asc',\n })\n return result.records.map(toMessageRow)\n}\n\n/**\n * Append one message to a chat the caller owns. Returns true if the row was\n * written, false if the chat is gone (or never belonged to the caller).\n *\n * Same write-after-delete guard as `updateChat`, for the same reason: a turn\n * that finishes after the user deleted the chat would otherwise write messages\n * whose parent row no longer exists — invisible in every listing and never\n * cascaded again.\n */\nexport async function appendMessage(\n stub: DurableObjectStub,\n msg: {\n id: string\n chatId: string\n userId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n parts?: unknown[]\n },\n): Promise<boolean> {\n if (!(await getChat(stub, msg.chatId, msg.userId))) return false\n const data: Record<string, unknown> = {\n chatId: msg.chatId,\n userId: msg.userId,\n role: msg.role,\n content: msg.content,\n }\n if (msg.parts !== undefined) data.parts = msg.parts\n await executeToolAsApp(stub, msg.userId, 'records.create', {\n collection: 'ai-messages',\n recordId: msg.id,\n data,\n })\n return true\n}\n","/**\n * Per-binding usage metering — record Vectorize / Workers AI / etc. costs\n * to the auto-attached `USAGE_EVENTS` Analytics Engine dataset.\n *\n * Why: the platform's tail-worker captures per-invocation compute (CPU + wall\n * time + script name) but it can't see which model an AI call hit, how many\n * tokens it embedded, or how many vectors a Vectorize query scanned. Without\n * those signals there's no way to surface per-tenant binding cost on the\n * billing dashboard.\n *\n * The deploy-worker auto-attaches a `USAGE_EVENTS` AE binding to every app\n * (dataset: `deepspace_binding_usage`). Apps don't need to declare it. They\n * just call `meterAi(...)` / `meterVectorize(...)` / `meterUsage(...)` after\n * each call and the dashboard rolls it up by `ownerUserId`.\n *\n * Schema written:\n * indexes: [ownerUserId]\n * blobs: [appName, kind, model_or_index, op]\n * doubles: [units, count]\n *\n * Use:\n * await meterAi(env, '@cf/qwen/qwen3-embedding-0.6b', { inputChars: 5000 })\n * await meterVectorize(env, 'unison-candidates', 'query', { vectors: 1000 })\n * await meterUsage(env, 'custom-thing', { units: 1 })\n */\n/// <reference types=\"@cloudflare/workers-types\" />\n\ninterface MeteringEnv {\n USAGE_EVENTS?: AnalyticsEngineDataset\n OWNER_USER_ID?: string\n DEEPSPACE_RESOURCE_ID?: string\n APP_NAME?: string\n}\n\n/**\n * Generic event recorder. Returns `false` if the binding isn't present\n * (dev / not yet deployed) or if AnalyticsEngine throws — metering must\n * never break the calling code path.\n */\nexport function meterUsage(\n env: MeteringEnv,\n kind: string,\n fields: { id?: string; op?: string; units?: number; count?: number } = {},\n): boolean {\n const ds = env.USAGE_EVENTS\n if (!ds) return false\n try {\n ds.writeDataPoint({\n indexes: [env.OWNER_USER_ID ?? 'unknown'],\n blobs: [\n env.DEEPSPACE_RESOURCE_ID ?? env.APP_NAME ?? 'unknown',\n kind,\n fields.id ?? '',\n fields.op ?? '',\n ],\n doubles: [fields.units ?? 0, fields.count ?? 0],\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Record a Workers AI call.\n *\n * Cloudflare prices input and output tokens at different rates for LLMs\n * (output is typically more expensive); embedding models bill input only.\n * Emits up to two events per call so the dashboard rollup can group by\n * `op` and apply the right per-token rate:\n *\n * op='input' units=inputChars\n * op='output' units=outputChars\n *\n * For a pure embedding call (outputChars=0), only the input event fires.\n * Pass `inputChars` and `outputChars` raw — the rough chars-to-token\n * conversion happens at price time using `COST_RATES.ai.embedInputPerChar`.\n *\n * Note: only embedding-input has an authoritative rate today. LLM-output\n * pricing varies wildly per model so `priceBindingUsageEvent` returns 0\n * for `op='output'` until per-model rates are wired. The events are still\n * recorded so the dashboard can show that the calls happened.\n */\nexport function meterAi(\n env: MeteringEnv,\n model: string,\n fields: { inputChars?: number; outputChars?: number; calls?: number } = {},\n): boolean {\n const calls = fields.calls ?? 1\n const inChars = fields.inputChars ?? 0\n const outChars = fields.outputChars ?? 0\n let ok = true\n if (inChars > 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'input', units: inChars, count: calls }) && ok\n }\n if (outChars > 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'output', units: outChars, count: calls }) && ok\n }\n // No tokens reported: still record the call so we can see the model was\n // invoked in dashboards. Useful for failed/empty responses.\n if (inChars === 0 && outChars === 0) {\n ok = meterUsage(env, 'ai', { id: model, op: 'call', units: 0, count: calls }) && ok\n }\n return ok\n}\n\n/**\n * Record a Vectorize operation.\n *\n * Cloudflare's published model (https://developers.cloudflare.com/vectorize/platform/pricing/):\n *\n * \"If you have 10,000 vectors with 384-dimensions in an index, and make\n * 100 queries against that index, your total queried vector dimensions\n * would sum to 3.878 million ((10000 + 100) * 384).\"\n *\n * So query billing is *additive* — `(stored + queries) * dims` summed\n * across the call, not per-query-multiplied-by-stored. Translating to a\n * per-call meter:\n *\n * op='query': units = (vectors + storedCount) * dims\n * Without `storedCount` we significantly undercount: a single\n * query against a 100K-vector index produces ~100K queried\n * dims, not just `dims`.\n * op='upsert': CF doesn't bill upserts directly; the chargeable delta is\n * the change to stored-vector-month. `units = vectors * dims`\n * approximates the per-call storage delta.\n * op='delete' / 'getByIds': recorded for observability; no direct cost.\n *\n * Edge case: querying an empty index gives `(1 + 0) * dims = dims`, which\n * matches CF's formula (the `+ queries` term is always added, even at 0\n * stored). If CF later changes that and an empty-index query bills 0,\n * adjust here — `metering` is the single place to update the math.\n */\nexport function meterVectorize(\n env: MeteringEnv,\n indexName: string,\n op: 'query' | 'upsert' | 'delete' | 'getByIds',\n fields: { vectors?: number; dims?: number; storedCount?: number } = {},\n): boolean {\n const vectors = fields.vectors ?? 0\n const dims = fields.dims ?? 0\n const stored = fields.storedCount ?? 0\n const units = op === 'query' ? (vectors + stored) * dims : vectors * dims\n return meterUsage(env, 'vectorize', { id: indexName, op, units, count: vectors })\n}\n\n// ---- Cost estimates (USD) — see Cloudflare pricing as of 2026-05 ----\n\n/** English avg; replace with a real tokenizer if accuracy matters. */\nconst CHARS_PER_TOKEN = 4\n\n/** $0.012 per 1M input tokens (bge-m3 + qwen3-embedding-0.6b tier). */\nconst AI_EMBED_USD_PER_M_TOKENS = 0.012\n\n/** $0.01 per 1M queried dimensions. */\nconst VECTORIZE_QUERIED_USD_PER_M_DIMS = 0.01\n\n/** $0.05 per 100M stored dimensions per month. */\nconst VECTORIZE_STORED_USD_PER_100M_DIMS = 0.05\n\n/**\n * Per-`units` USD multipliers, matched to the (`kind`, `op`) the meter\n * helpers above record. Dashboard rollup can multiply\n *\n * SUM(_sample_interval * doubles[1]) -- units\n *\n * by these to surface $-figures without re-querying CF's billing API.\n */\nexport const COST_RATES = {\n ai: {\n /**\n * USD per character of *embedding input* (bge-m3 / qwen3-embedding tier).\n * Renamed from `perChar` to make explicit that this rate does NOT\n * apply to LLM-generation output — see `priceBindingUsageEvent`.\n */\n embedInputPerChar: AI_EMBED_USD_PER_M_TOKENS / 1_000_000 / CHARS_PER_TOKEN,\n },\n vectorize: {\n /** USD per queried dimension (per query, per stored vector compared). */\n queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1_000_000,\n /** USD per stored dimension per month. */\n storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 100_000_000,\n },\n aiSearch: {\n ingestPerToken: 0.75 / 1_000_000,\n ingestImagePerToken: 0.5 / 1_000_000,\n storagePerByteMonth: 2 / 1_000_000_000,\n hybridOrSemanticPerQuery: 0.75 / 1_000,\n fulltextPerQuery: 0.1 / 1_000,\n },\n} as const\n\n/**\n * Price a single rolled-up `deepspace_binding_usage` row. Lives next to\n * `COST_RATES` so the (kind, op) → rate mapping stays paired with the schema\n * the meter helpers write.\n *\n * Returns 0 for combinations without an authoritative per-unit rate; the row\n * still surfaces in dashboards for observability. Notable zeros:\n * - `ai/output`: LLM-output prices vary per model and `meterAi` doesn't\n * carry a model-family signal. Pricing it at the embedding rate would\n * silently under-bill chat-LLM use.\n * - `vectorize.storedPerDimPerMonth`: events are per-call deltas, not\n * monthly snapshots, so a windowed SUM isn't meaningful here.\n */\nexport function priceBindingUsageEvent(kind: string, op: string, units: number): number {\n // A non-finite or negative unit count is a corrupt meter row or a corrupt\n // provider response. Price it at zero rather than letting NaN reach a cost\n // column, where it would be unrecoverable.\n if (!Number.isFinite(units) || units < 0) return 0\n if (kind === 'ai' && op === 'input') {\n return units * COST_RATES.ai.embedInputPerChar\n }\n if (kind === 'vectorize' && op === 'query') {\n return units * COST_RATES.vectorize.queriedPerDim\n }\n // Managed AI Search is billed server-side by the API worker. These raw\n // preview rates are the one published rate source; the ledger derives its\n // distinct binding markup from the row's `source`.\n if (kind === 'ai_search') {\n if (op === 'ingest') return units * COST_RATES.aiSearch.ingestPerToken\n if (op === 'ingest-image') return units * COST_RATES.aiSearch.ingestImagePerToken\n if (op === 'storage') return units * COST_RATES.aiSearch.storagePerByteMonth\n if (op === 'search-hybrid' || op === 'search-semantic')\n return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery\n if (op === 'search-fulltext') return units * COST_RATES.aiSearch.fulltextPerQuery\n }\n return 0\n}\n","import { appendAppIdentity, type AppIdentityEnv } from './app-identity'\nimport { apiWorkerFetch, type ApiWorkerEnv } from './proxies'\n\nconst MAX_UPLOAD_BYTES = 4 * 1024 * 1024\nconst TEXT_MIME = /^(text\\/|application\\/(json|xml|javascript|x-(javascript|typescript)))/iu\n/** Matches the API worker's bound; a search reads a query, not a document. */\nconst MAX_QUERY_CHARS = 4096\n\n/** NUL, CR/LF and friends: these reach the provider as a multipart filename.\n * Checked by codepoint rather than a regex literal, which cannot embed\n * control characters without tripping `no-control-regex`. */\nfunction hasControlChar(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code < 0x20 || code === 0x7f) return true\n }\n return false\n}\nexport interface KnowledgeEnv extends ApiWorkerEnv, AppIdentityEnv {}\nexport type KnowledgeStatus = 'queued' | 'running' | 'completed' | 'error' | 'skipped' | 'outdated'\nexport interface KnowledgeItem {\n id: string\n key: string\n status: KnowledgeStatus\n fileSize?: number\n chunksCount?: number\n createdAt?: string\n updatedAt?: string\n error?: string\n}\nexport interface KnowledgeAddOptions {\n folder?: string\n}\nexport interface KnowledgeListOptions {\n folder?: string\n page?: number\n perPage?: number\n status?: KnowledgeStatus\n search?: string\n}\nexport interface KnowledgeSearchOptions {\n folder?: string\n mode?: 'hybrid' | 'semantic' | 'fulltext'\n limit?: number\n matchThreshold?: number\n queryRewrite?: boolean\n}\nexport interface KnowledgeAddResult {\n items: KnowledgeItem[]\n}\nexport interface KnowledgeListResult {\n items: KnowledgeItem[]\n page: number\n perPage: number\n total?: number\n totalPages?: number\n}\nexport interface KnowledgeSearchChunk {\n id: string\n score: number\n text: string\n key?: string\n filename?: string\n folder?: string\n timestamp?: string\n}\nexport interface KnowledgeSearchResult {\n chunks: KnowledgeSearchChunk[]\n queryKind?: string\n}\nexport interface ScopedKnowledgeClient {\n add(file: File): Promise<KnowledgeAddResult>\n list(options?: Omit<KnowledgeListOptions, 'folder'>): Promise<KnowledgeListResult>\n search(\n query: string,\n options?: Omit<KnowledgeSearchOptions, 'folder'>,\n ): Promise<KnowledgeSearchResult>\n}\nexport interface KnowledgeClient extends ScopedKnowledgeClient {\n add(file: File, options?: KnowledgeAddOptions): Promise<KnowledgeAddResult>\n list(options?: KnowledgeListOptions): Promise<KnowledgeListResult>\n remove(itemId: string): Promise<void>\n search(query: string, options?: KnowledgeSearchOptions): Promise<KnowledgeSearchResult>\n scoped(scope: { folder: string }): ScopedKnowledgeClient\n}\n\nexport class KnowledgeError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n public readonly uploadedItems?: KnowledgeItem[],\n ) {\n super(message)\n this.name = 'KnowledgeError'\n }\n}\n\nexport function normalizeKnowledgeFolder(folder: string): string {\n if (!folder || folder.includes('\\\\') || hasControlChar(folder) || folder.startsWith('/'))\n throw new KnowledgeError(400, 'invalid_folder', 'Folder must be a relative forward-slash path')\n // Accept the normalized form too. This keeps scoped clients idempotent:\n // they capture `docs/` once and can safely pass it through the same\n // validation used by root-client calls.\n const withoutTrailingSlash = folder.endsWith('/') ? folder.slice(0, -1) : folder\n if (!withoutTrailingSlash || withoutTrailingSlash.endsWith('/'))\n throw new KnowledgeError(400, 'invalid_folder', 'Folder cannot contain empty segments')\n const parts = withoutTrailingSlash.split('/')\n if (parts.some((part) => !part || part === '.' || part === '..'))\n throw new KnowledgeError(\n 400,\n 'invalid_folder',\n 'Folder cannot contain empty, dot, or parent segments',\n )\n const normalized = `${parts.join('/')}/`\n if (normalized.length > 128)\n throw new KnowledgeError(400, 'invalid_folder', 'Folder is too long for an AI Search item key')\n return normalized\n}\n\nfunction validateKnowledgeFilename(name: string): void {\n if (\n !name ||\n name === '.' ||\n name === '..' ||\n name.includes('/') ||\n name.includes('\\\\') ||\n hasControlChar(name)\n ) {\n throw new KnowledgeError(\n 400,\n 'invalid_filename',\n 'File name must be a non-empty base name without path separators',\n )\n }\n}\n\nexport function folderFilter(folder: string): { folder: { $gte: string; $lt: string } } {\n const value = normalizeKnowledgeFolder(folder)\n // `/` is immediately followed by `0` in the provider's lexical ordering.\n return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } }\n}\n\nexport function knowledge(env: KnowledgeEnv): KnowledgeClient {\n const add = (file: File, options: KnowledgeAddOptions = {}) => addFile(env, file, options)\n const list = (options: KnowledgeListOptions = {}) => listItems(env, options)\n const search = (query: string, options: KnowledgeSearchOptions = {}) =>\n searchItems(env, query, options)\n return {\n add,\n list,\n search,\n async remove(itemId) {\n if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))\n throw new KnowledgeError(400, 'invalid_item_id', 'Invalid knowledge item id')\n await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: 'DELETE' })\n },\n scoped({ folder }) {\n const fixed = normalizeKnowledgeFolder(folder)\n return {\n add: (file) => add(file, { folder: fixed }),\n list: (options = {}) => list({ ...options, folder: fixed }),\n search: (query, options = {}) => search(query, { ...options, folder: fixed }),\n }\n },\n }\n}\n\nasync function addFile(\n env: KnowledgeEnv,\n file: File,\n options: KnowledgeAddOptions,\n): Promise<KnowledgeAddResult> {\n validateKnowledgeFilename(file.name)\n const folder = options.folder === undefined ? undefined : normalizeKnowledgeFolder(options.folder)\n if ((folder?.length ?? 0) + file.name.length > 128)\n throw new KnowledgeError(\n 400,\n 'invalid_key',\n 'Folder and filename together exceed the 128-character AI Search key limit',\n )\n if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder)\n if (\n !TEXT_MIME.test(file.type) &&\n !/\\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name)\n )\n throw new KnowledgeError(\n 413,\n 'file_too_large',\n 'This file exceeds 4 MB. Split the source document with a format-aware tool before uploading.',\n )\n const bytes = new Uint8Array(await file.arrayBuffer())\n const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES)\n const uploaded: KnowledgeItem[] = []\n try {\n for (let i = 0; i < parts.length; i++) {\n const bytes = parts[i]\n const contents = bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer\n const part = new File([contents], partName(file.name, i + 1), {\n type: file.type || 'text/plain',\n })\n if ((folder?.length ?? 0) + part.name.length > 128)\n throw new KnowledgeError(\n 400,\n 'invalid_key',\n 'Folder and split filename together exceed the 128-character AI Search key limit',\n )\n uploaded.push(...(await upload(env, part, folder)).items)\n }\n } catch (error) {\n if (error instanceof KnowledgeError)\n throw new KnowledgeError(error.status, error.code, error.message, uploaded)\n throw new KnowledgeError(502, 'knowledge_upload_failed', 'Knowledge upload failed', uploaded)\n }\n return { items: uploaded }\n}\n\nfunction splitUtf8(bytes: Uint8Array, max: number): Uint8Array[] {\n const out: Uint8Array[] = []\n for (let start = 0; start < bytes.length; ) {\n let end = Math.min(start + max, bytes.length)\n if (end < bytes.length) {\n let cut = end\n while (cut > start && (bytes[cut] & 0xc0) === 0x80) cut--\n // Start at the final byte already inside this part. Looking at `cut`\n // would inspect the first byte of the next part and could grow this one\n // past the provider's hard limit when that byte is a newline.\n for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)\n if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {\n cut = i + 1\n break\n }\n end = cut > start ? cut : end\n }\n out.push(bytes.slice(start, end))\n start = end\n }\n return out\n}\nfunction partName(name: string, n: number): string {\n const dot = name.lastIndexOf('.')\n return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`\n}\nasync function upload(env: KnowledgeEnv, file: File, folder?: string): Promise<KnowledgeAddResult> {\n const body = new FormData()\n body.append('file', file)\n if (folder) body.append('folder', folder)\n return (await request(env, '/api/knowledge/items', {\n method: 'POST',\n body,\n })) as KnowledgeAddResult\n}\nasync function listItems(\n env: KnowledgeEnv,\n options: KnowledgeListOptions,\n): Promise<KnowledgeListResult> {\n if (options.page !== undefined && (!Number.isInteger(options.page) || options.page < 1))\n throw new KnowledgeError(400, 'invalid_page', 'List page must be a positive integer')\n if (\n options.perPage !== undefined &&\n (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50)\n )\n throw new KnowledgeError(\n 400,\n 'invalid_per_page',\n 'List perPage must be an integer from 1 to 50',\n )\n if (options.search !== undefined && options.search.length > 256)\n throw new KnowledgeError(400, 'invalid_search', 'List search cannot exceed 256 characters')\n if (\n options.status !== undefined &&\n !['queued', 'running', 'completed', 'error', 'skipped', 'outdated'].includes(options.status)\n )\n throw new KnowledgeError(400, 'invalid_status', 'Invalid knowledge item status')\n const q = new URLSearchParams()\n if (options.folder) q.set('folder', normalizeKnowledgeFolder(options.folder))\n if (options.page !== undefined) q.set('page', String(options.page))\n if (options.perPage !== undefined) q.set('perPage', String(options.perPage))\n if (options.status) q.set('status', options.status)\n if (options.search) q.set('search', options.search)\n return (await request(env, `/api/knowledge/items${q.size ? `?${q}` : ''}`)) as KnowledgeListResult\n}\nasync function searchItems(\n env: KnowledgeEnv,\n query: string,\n options: KnowledgeSearchOptions,\n): Promise<KnowledgeSearchResult> {\n const normalizedQuery = query.trim()\n if (!normalizedQuery) throw new KnowledgeError(400, 'invalid_query', 'Search query is required')\n if (normalizedQuery.length > MAX_QUERY_CHARS)\n throw new KnowledgeError(\n 400,\n 'invalid_query',\n `Search query cannot exceed ${MAX_QUERY_CHARS} characters`,\n )\n if (\n options.mode !== undefined &&\n options.mode !== 'hybrid' &&\n options.mode !== 'semantic' &&\n options.mode !== 'fulltext'\n )\n throw new KnowledgeError(400, 'invalid_mode', 'Invalid knowledge search mode')\n if (\n options.limit !== undefined &&\n (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50)\n )\n throw new KnowledgeError(\n 400,\n 'invalid_limit',\n 'Search limit must be an integer from 1 through 50',\n )\n if (\n options.matchThreshold !== undefined &&\n (!Number.isFinite(options.matchThreshold) ||\n options.matchThreshold < 0 ||\n options.matchThreshold > 1)\n )\n throw new KnowledgeError(\n 400,\n 'invalid_match_threshold',\n 'Match threshold must be between 0 and 1',\n )\n if (options.queryRewrite !== undefined && typeof options.queryRewrite !== 'boolean')\n throw new KnowledgeError(\n 400,\n 'invalid_query_rewrite',\n 'queryRewrite must be a boolean when provided',\n )\n const folder = options.folder === undefined ? undefined : normalizeKnowledgeFolder(options.folder)\n return (await request(env, '/api/knowledge/search', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n query: normalizedQuery,\n mode: options.mode ?? 'hybrid',\n limit: options.limit ?? 10,\n queryRewrite: options.queryRewrite ?? false,\n ...(options.matchThreshold !== undefined && {\n matchThreshold: options.matchThreshold,\n }),\n ...(folder && { folder }),\n }),\n })) as KnowledgeSearchResult\n}\nasync function request(env: KnowledgeEnv, path: string, init: RequestInit = {}): Promise<unknown> {\n const headers = new Headers(init.headers)\n appendAppIdentity(headers, env)\n const response = await apiWorkerFetch(env, path, { ...init, headers })\n const text = await response.text()\n let data: unknown\n try {\n data = text ? JSON.parse(text) : undefined\n } catch {\n data = undefined\n }\n if (!response.ok) {\n const body = data && typeof data === 'object' ? (data as Record<string, unknown>) : {}\n throw new KnowledgeError(\n response.status,\n typeof body.code === 'string' ? body.code : 'knowledge_request_failed',\n typeof body.error === 'string' ? body.error : 'Knowledge request failed',\n )\n }\n return data\n}\n","/**\n * Lightweight D1 schema bootstrapping for apps that use auto-provisioned\n * `[[d1_databases]]` bindings.\n *\n * The auto-provisioner gives apps an empty D1; the app needs to create its\n * own tables before using them. This helper runs ordered SQL fragments and\n * tracks which have applied via a `_dpc_migrations` meta-table so re-running\n * is a no-op:\n *\n * ```ts\n * import { runMigrations } from 'deepspace/worker'\n *\n * await runMigrations(env.CARDS_DB, [\n * `CREATE TABLE cards (id INTEGER PRIMARY KEY, json TEXT NOT NULL);`,\n * `CREATE INDEX idx_cards_updated ON cards(updated_at);`,\n * ])\n * ```\n *\n * **SQL formatting:** statements may span multiple lines freely. The runner\n * splits each migration string on `;` and runs each fragment via\n * `prepare().run()`. That sidesteps `db.exec()`'s newline-or-semicolon quirks\n * (it's optimized for migration files, not inline strings) and gets you\n * predictable single-statement semantics. Trailing `;` after the last\n * statement is fine; semicolons inside string literals are not (we use a\n * naive split, since DDL almost never has them).\n *\n * Each entry in the array is one migration. The runner records the index of\n * each successfully-applied migration in `_dpc_migrations`; subsequent calls\n * skip rows already recorded. Adding a new migration means appending to the\n * array; never reorder or delete entries.\n *\n * Why a meta-table instead of `PRAGMA user_version`: D1's SQLite authorizer\n * rejects PRAGMA writes with `SQLITE_AUTH`, even though the same statements\n * work in raw SQLite. A real table works on any D1 database and stays a\n * trivial bootstrap (one CREATE TABLE IF NOT EXISTS).\n *\n * Concurrency: D1 serializes statements per database, but two simultaneous\n * `runMigrations` callers could race the same migration index. The duplicate\n * INSERT collides on the primary key and the second caller sees the failure\n * — but the migration itself uses `IF NOT EXISTS` so the schema is correct\n * either way. Apps invoke this at startup which is single-threaded per\n * worker isolate; the cross-isolate race is rare and self-healing.\n *\n * This is the simplest possible migration story (option 1 in\n * docs/proposals/binding-auto-provisioning.md). Apps that outgrow it can\n * adopt CF's `wrangler d1 migrations apply` directly without breaking the\n * helper.\n */\n\nconst META_TABLE = '_dpc_migrations'\n// Single-statement DDL — runs through prepare().run(), not exec(). exec()\n// is documented as newline-separated statements for migration files; the\n// binding's behavior on a single statement with a trailing ';' has been\n// flaky in practice and prepare()/run() is the right shape anyway.\nconst META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`\n\nexport interface RunMigrationsResult {\n /** Version before this run started. Equals the count of migrations already applied. */\n fromVersion: number\n /** Version after migrations applied. Equals fromVersion if nothing ran. */\n toVersion: number\n /** Number of migrations applied this call. */\n applied: number\n}\n\n/**\n * Apply ordered SQL migrations to a D1 database. Idempotent: the next call\n * with the same array is a no-op until the array grows.\n *\n * Throws on any individual migration failure. The migrations meta-row is\n * only inserted after a migration succeeds, so a partial failure leaves a\n * recoverable state — fix the SQL, redeploy, and the failed migration runs\n * on next startup.\n */\nexport async function runMigrations(\n db: D1Database,\n migrations: readonly string[],\n): Promise<RunMigrationsResult> {\n // Bootstrap the meta-table. Single statement → prepare().run().\n await db.prepare(META_BOOTSTRAP_SQL).run()\n\n const row = await db\n .prepare(`SELECT COUNT(*) AS n FROM ${META_TABLE}`)\n .first<{ n: number }>()\n const fromVersion = row?.n ?? 0\n\n if (fromVersion >= migrations.length) {\n return { fromVersion, toVersion: fromVersion, applied: 0 }\n }\n\n let current = fromVersion\n for (let i = fromVersion; i < migrations.length; i++) {\n const statements = splitStatements(migrations[i])\n for (const stmt of statements) {\n try {\n await db.prepare(stmt).run()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`Migration ${i} failed (db at version=${current}): ${msg}`)\n }\n }\n // Record the applied migration. PK collision (duplicate idx from a\n // concurrent run) surfaces as an error and we abort — but the schema\n // is already in place, so the next attempt sees fromVersion advance\n // and skips it.\n await db\n .prepare(`INSERT INTO ${META_TABLE} (idx, applied_at) VALUES (?, ?)`)\n .bind(i, new Date().toISOString())\n .run()\n current = i + 1\n }\n\n return { fromVersion, toVersion: current, applied: current - fromVersion }\n}\n\n/**\n * Naive `;` split: good enough for DDL where the only semicolons are\n * statement terminators. Strings with embedded `;` inside literals would\n * break this — accept that tradeoff because real migrations rarely contain\n * them and the alternative is shipping a SQL parser.\n */\nfunction splitStatements(sql: string): string[] {\n return sql\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n}\n","/**\n * Identity claims cross from an app Worker to its private room Durable Object\n * in headers. Header values are ByteStrings, while verified profile claims may\n * contain arbitrary Unicode or control characters, so carry each value as\n * UTF-8 base64url rather than writing the claim directly.\n */\n\nconst BASE64URL = /^[A-Za-z0-9_-]*$/\nconst IDENTITY_QUERY_PARAMS = ['userId', 'userName', 'userEmail', 'userImageUrl', 'role'] as const\nconst IDENTITY_HEADERS = [\n 'x-user-id',\n 'x-user-name',\n 'x-user-email',\n 'x-user-image-url',\n 'x-user-role',\n] as const\n\ninterface RoomAuth {\n userId: string\n claims: { name?: string; email?: string; image?: string }\n}\n\nexport function encodeRoomIdentityHeader(value: string): string {\n const bytes = new TextEncoder().encode(value)\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')\n}\n\nexport function decodeRoomIdentityHeader(value: string | null): string | undefined {\n if (value === null || !BASE64URL.test(value) || value.length % 4 === 1) return undefined\n const base64 = value\n .replaceAll('-', '+')\n .replaceAll('_', '/')\n .padEnd(value.length + ((4 - (value.length % 4)) % 4), '=')\n try {\n const binary = atob(base64)\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n } catch {\n return undefined\n }\n}\n\n/** Strip public identity inputs and forward only verified room identity. */\nexport function authenticatedRoomRequest(\n request: Request,\n auth: RoomAuth | null,\n identity?: { role?: string },\n): Request {\n const url = new URL(request.url)\n url.searchParams.delete('token')\n for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key)\n\n const forwarded = new Request(url.toString(), request)\n for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header)\n if (!auth) return forwarded\n\n forwarded.headers.set('x-user-id', encodeRoomIdentityHeader(auth.userId))\n if (auth.claims.name) {\n forwarded.headers.set('x-user-name', encodeRoomIdentityHeader(auth.claims.name))\n }\n if (auth.claims.email) {\n forwarded.headers.set('x-user-email', encodeRoomIdentityHeader(auth.claims.email))\n }\n if (auth.claims.image) {\n forwarded.headers.set('x-user-image-url', encodeRoomIdentityHeader(auth.claims.image))\n }\n if (identity?.role) {\n forwarded.headers.set('x-user-role', encodeRoomIdentityHeader(identity.role))\n }\n return forwarded\n}\n","/**\n * BaseRoom — Abstract base class for all DeepSpace Durable Objects.\n *\n * Provides:\n * - WebSocket upgrade with Cloudflare hibernation API\n * - Connection tracking (WebSocket -> UserAttachment)\n * - Auth: parse JWT-verified user info from internal request headers\n * - Presence: connected users list, awareness on connect/disconnect\n * - Message routing: JSON parse -> dispatch by `type` field, binary hook\n * - Raw SQLite access via this.sql\n * - Broadcast helpers: broadcast(), sendTo()\n * - HTTP fetch handler with WebSocket upgrade detection\n * - Internal control-plane endpoint: POST /internal/disconnect-sockets\n * (force every client to reconnect and resync after out-of-band writes)\n *\n * Subclasses implement lifecycle hooks:\n * onConnect, onMessage, onBinaryMessage, onDisconnect, onRequest, onAlarm\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport { ANONYMOUS_USER_ID_PREFIX } from '../../shared/protocol/constants'\nimport { decodeRoomIdentityHeader } from '../../shared/room-identity-headers'\n\n// ============================================================================\n// User Attachment (survives DO hibernation via WebSocket serialization)\n// ============================================================================\n\nexport interface UserAttachment {\n userId: string\n userName: string\n userEmail: string\n userImageUrl?: string\n /** Subclass-specific data serialized alongside user info */\n [key: string]: unknown\n}\n\n/** Build the hibernation attachment from app-worker-verified headers only. */\nexport function connectionAttachmentFromRequest(request: Request): UserAttachment {\n // The client only ever sends `?token=`; `?userId=` on the internal URL is the\n // pre-0.19 app proxy shape, which rooms no longer read. Say so, because the\n // visible symptom — every signed-in user anonymous — does not.\n if (!request.headers.has('x-user-id') && new URL(request.url).searchParams.has('userId')) {\n console.warn(\n '[BaseRoom] Connection carries identity in the URL (?userId=) but no x-user-id header, ' +\n \"so this user joins as anonymous. The app worker's room proxy predates 0.19 or forwards \" +\n \"the client's URL unstripped: forward with authenticatedRoomRequest() from deepspace/worker.\",\n )\n }\n return {\n userId:\n decodeRoomIdentityHeader(request.headers.get('x-user-id')) ||\n `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,\n userName: decodeRoomIdentityHeader(request.headers.get('x-user-name')) || 'Anonymous',\n userEmail: decodeRoomIdentityHeader(request.headers.get('x-user-email')) || '',\n userImageUrl: decodeRoomIdentityHeader(request.headers.get('x-user-image-url')),\n role: decodeRoomIdentityHeader(request.headers.get('x-user-role')),\n }\n}\n\n// ============================================================================\n// BaseRoom\n// ============================================================================\n\nexport abstract class BaseRoom<E = Record<string, unknown>> {\n protected state: DurableObjectState\n protected env: E\n protected sql: SqlStorage\n\n constructor(state: DurableObjectState, env: unknown) {\n this.state = state\n this.env = (env ?? {}) as E\n this.sql = state.storage.sql\n\n this.state.setWebSocketAutoResponse(new WebSocketRequestResponsePair('ping', 'pong'))\n }\n\n // ==========================================================================\n // HTTP Entry Point\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n // Internal control-plane endpoints. These are reachable ONLY via a DO stub\n // fetch from the app worker's own server-side code (same trust model as the\n // `/api/tools/execute` surface) — the app worker's public `/ws/:roomId`\n // proxy forwards only `/ws/*` paths, so a browser can never reach\n // `/internal/*` through it. Do NOT add a public app-worker route that\n // forwards arbitrary paths to a room DO.\n const internal = await this.handleInternalRequest(request, url)\n if (internal) return internal\n\n // WebSocket upgrade\n if (request.headers.get('Upgrade') === 'websocket') {\n return this.handleWebSocketUpgrade(request, url)\n }\n\n // Delegate non-WS requests to subclass\n if (this.onRequest) {\n return this.onRequest(request)\n }\n\n return new Response('Not Found', { status: 404 })\n }\n\n // ==========================================================================\n // Internal Control-Plane Endpoints\n // ==========================================================================\n\n /**\n * Handle built-in `/internal/*` control-plane routes shared by every room\n * type. Returns a `Response` if the request was an internal route, or `null`\n * to let the caller continue normal dispatch.\n *\n * Currently:\n * - `POST /internal/disconnect-sockets` — close every live WebSocket so\n * clients reconnect and resync. Optional JSON body `{ code?, reason? }`\n * overrides the defaults (1012 / 'state-refresh'). Responds with\n * `{ success: true, closed: <n> }`.\n *\n * See the security note on `fetch()`: this path is only reachable via DO\n * stub fetch from the app worker, never from the public internet.\n */\n protected async handleInternalRequest(request: Request, url: URL): Promise<Response | null> {\n if (request.method === 'POST' && url.pathname === '/internal/disconnect-sockets') {\n let options: { code?: number; reason?: string } | undefined\n try {\n const text = await request.text()\n if (text) options = JSON.parse(text) as { code?: number; reason?: string }\n } catch {\n // Malformed/empty body → fall back to defaults.\n }\n const closed = this.disconnectAllSockets(options)\n return Response.json({ success: true, closed })\n }\n\n return null\n }\n\n // ==========================================================================\n // WebSocket Connection\n // ==========================================================================\n\n private async handleWebSocketUpgrade(request: Request, _url: URL): Promise<Response> {\n const pair = new WebSocketPair()\n const [client, server] = Object.values(pair)\n\n this.state.acceptWebSocket(server)\n\n const attachment = connectionAttachmentFromRequest(request)\n\n // Let subclass augment the attachment and perform setup\n const augmented = (await this.onConnect(server, attachment)) ?? attachment\n server.serializeAttachment(augmented)\n\n return new Response(null, { status: 101, webSocket: client })\n }\n\n // ==========================================================================\n // Hibernation API Handlers\n // ==========================================================================\n\n async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void> {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (!attachment) return\n\n // Binary messages\n if (message instanceof ArrayBuffer) {\n if (this.onBinaryMessage) {\n try {\n await this.onBinaryMessage(ws, attachment, message)\n } catch (e) {\n console.error(`[${this.constructor.name}] Binary message error:`, e)\n }\n }\n return\n }\n\n // JSON messages\n try {\n const msg = JSON.parse(message)\n await this.onMessage(ws, attachment, msg)\n } catch (e) {\n console.error(`[${this.constructor.name}] Message error:`, e)\n }\n }\n\n async webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void> {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (attachment) {\n try {\n await this.onDisconnect(ws, attachment)\n } catch {\n /* best-effort */\n }\n }\n }\n\n async webSocketError(_ws: WebSocket, error: unknown): Promise<void> {\n console.error(`[${this.constructor.name}] webSocketError:`, error)\n }\n\n async alarm(): Promise<void> {\n if (this.onAlarm) {\n await this.onAlarm()\n }\n }\n\n // ==========================================================================\n // Lifecycle Hooks (subclasses override)\n // ==========================================================================\n\n /**\n * Called when a new WebSocket connects (after auth parsing).\n * Return an augmented attachment to serialize on the WebSocket,\n * or void to use the default attachment.\n */\n protected onConnect(\n _ws: WebSocket,\n _user: UserAttachment,\n ): UserAttachment | void | Promise<UserAttachment | void> {\n // Default: no-op\n }\n\n /**\n * Called for each parsed JSON message.\n */\n protected abstract onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): void | Promise<void>\n\n /**\n * Called for binary messages (Yjs, custom protocols).\n */\n protected onBinaryMessage?(\n ws: WebSocket,\n user: UserAttachment,\n data: ArrayBuffer,\n ): void | Promise<void>\n\n /**\n * Called when a WebSocket disconnects.\n */\n protected onDisconnect(_ws: WebSocket, _user: UserAttachment): void | Promise<void> {\n // Default: no-op\n }\n\n /**\n * Called for HTTP requests that are NOT WebSocket upgrades.\n */\n protected onRequest?(request: Request): Response | Promise<Response>\n\n /**\n * Called on DO alarm.\n */\n protected onAlarm?(): void | Promise<void>\n\n // ==========================================================================\n // Utilities\n // ==========================================================================\n\n /**\n * Get all connected WebSockets.\n */\n protected getWebSockets(): WebSocket[] {\n return this.state.getWebSockets()\n }\n\n /**\n * Force every connected client to reconnect and resync by closing all live\n * WebSockets. Returns the number of sockets closed.\n *\n * Use this after an **out-of-band, server-side write** to the room's records\n * — an admin import route, a migration script, a cron job, or a server\n * action — that connected clients have no way to learn about. Without it, a\n * browser tab holding stale in-memory state keeps operating on (and may\n * autosave over) data that changed underneath it. Closing the socket makes\n * the client SDK reconnect and pull fresh query results.\n *\n * The default close code is 1012 (\"service restart\") with reason\n * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect\n * trigger (it does not special-case clean/1000 closes), so on reconnect it\n * re-subscribes every active query and receives fresh `QUERY_RESULT`s —\n * `useQuery` consumers see the new data without re-subscribing.\n *\n * Each close is guarded so one already-closing socket can't abort the sweep.\n *\n * @param options.code WebSocket close code (default 1012).\n * @param options.reason WebSocket close reason (default 'state-refresh').\n * @returns the number of sockets that were closed.\n */\n disconnectAllSockets(options?: { code?: number; reason?: string }): number {\n const code = options?.code ?? 1012\n const reason = options?.reason ?? 'state-refresh'\n let closed = 0\n for (const ws of this.state.getWebSockets()) {\n try {\n ws.close(code, reason)\n closed++\n } catch {\n // Socket already closing/closed — skip it, keep sweeping the rest.\n }\n }\n return closed\n }\n\n /**\n * Get the user attachment for a WebSocket.\n */\n protected getAttachment(ws: WebSocket): UserAttachment | null {\n return ws.deserializeAttachment() as UserAttachment | null\n }\n\n /**\n * Get all currently connected users.\n */\n protected getConnectedUsers(): UserAttachment[] {\n const users: UserAttachment[] = []\n for (const ws of this.state.getWebSockets()) {\n const attachment = ws.deserializeAttachment() as UserAttachment | null\n if (attachment) users.push(attachment)\n }\n return users\n }\n\n /**\n * Send a JSON message to a specific WebSocket.\n *\n * Typed as `ServerMessage` so every room's outbound traffic is\n * compile-checked against the wire protocol contract. Passing\n * `{ type: 'whatever', payload: {...} }` with a non-matching arm fails\n * to compile — that's the whole point of the typed layer. Apps that\n * need to send an app-specific message should override `sendTo` in\n * their subclass with a widened union (`ServerMessage | MyAppMessage`).\n */\n protected sendTo(ws: WebSocket, message: ServerMessage): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n } catch {\n // Ignore send errors on dead sockets\n }\n }\n\n /**\n * Send binary data to a specific WebSocket.\n */\n protected sendBinaryTo(ws: WebSocket, data: Uint8Array | ArrayBuffer): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n // Ignore send errors on dead sockets\n }\n }\n\n /**\n * Broadcast a JSON message to all connected WebSockets.\n * Optionally exclude a specific WebSocket (e.g. the sender).\n *\n * See `sendTo` for the reasoning behind typing as `ServerMessage`.\n */\n protected broadcast(message: ServerMessage, exclude?: WebSocket): void {\n const encoded = JSON.stringify(message)\n for (const ws of this.state.getWebSockets()) {\n if (ws === exclude) continue\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(encoded)\n }\n } catch {\n /* dead socket */\n }\n }\n }\n\n /**\n * Broadcast binary data to all connected WebSockets.\n */\n protected broadcastBinary(data: Uint8Array | ArrayBuffer, exclude?: WebSocket): void {\n for (const ws of this.state.getWebSockets()) {\n if (ws === exclude) continue\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n /* dead socket */\n }\n }\n }\n}\n","/**\n * Collection Schema Definitions & Validation\n *\n * All collections use typed SQL columns. No document-mode / fields-based storage.\n */\n\nimport type {\n CollectionSchema,\n ColumnDefinition,\n ColumnInterpretation,\n PermissionLevel,\n RolePermissions,\n} from '../../shared/types'\n\nexport type {\n CollectionSchema,\n ColumnDefinition,\n ColumnInterpretation,\n PermissionLevel,\n RolePermissions,\n} from '../../shared/types'\n\n// ============================================================================\n// Column Definitions\n// ============================================================================\n\nexport interface ResolvedColumn {\n id: string\n name: string\n storage: 'number' | 'text'\n interpretation: ColumnInterpretation\n expression?: string\n readonly: boolean\n userBound?: boolean\n immutable?: boolean\n required?: boolean\n default?: unknown\n timestampTrigger?: { field: string; value?: unknown }\n}\n\n// ============================================================================\n// Column Helpers\n// ============================================================================\n\nexport function collectionTableName(name: string): string {\n return `c_${name.replace(/[^a-zA-Z0-9_]/g, '_')}`\n}\n\nexport function columnId(name: string): string {\n return `col_${name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`\n}\n\nexport function resolveColumn(col: ColumnDefinition): ResolvedColumn {\n const interp: ColumnInterpretation =\n typeof col.interpretation === 'string'\n ? ({ kind: col.interpretation } as ColumnInterpretation)\n : col.interpretation\n return {\n id: col.id ?? columnId(col.name),\n name: col.name,\n storage: col.storage,\n interpretation: interp,\n expression: col.expression,\n readonly: !!col.expression,\n userBound: col.userBound,\n immutable: col.immutable,\n required: col.required,\n default: col.default,\n timestampTrigger: col.timestampTrigger,\n }\n}\n\nexport function rowToData(\n row: Record<string, unknown>,\n columns: ResolvedColumn[],\n): Record<string, unknown> {\n const data: Record<string, unknown> = {}\n for (const col of columns) {\n if (row[col.id] !== undefined && row[col.id] !== null) {\n let val = row[col.id]\n if (col.interpretation.kind === 'json' && typeof val === 'string') {\n try {\n val = JSON.parse(val as string)\n } catch {\n /* keep as string */\n }\n }\n data[col.name] = val\n }\n }\n return data\n}\n\nexport function dataToColumnValues(\n data: Record<string, unknown>,\n columns: ResolvedColumn[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n for (const col of columns) {\n if (col.expression || col.readonly) continue\n // Record mutations use public schema names only. Accepting physical column\n // ids here lets a second key bypass name-based RBAC/userBound checks.\n const val = data[col.name]\n if (val !== undefined) {\n values[col.id] = coerceValue(val, col.storage, col.interpretation)\n }\n }\n return values\n}\n\nexport function coerceValue(\n value: unknown,\n storage: 'number' | 'text',\n interpretation: ColumnInterpretation,\n): unknown {\n if (value === null || value === undefined) return null\n // An empty string is a value on a text column, not an absence. Folding it\n // into NULL destroyed it on the way in — `rowToData` then dropped the key on\n // the way out — so a field written as `''` read back `undefined` and a\n // `column.default = ''` never materialized at all (it is applied at create\n // and was coerced away one line later). A `number` column has nothing to\n // store for `''`, so there it stays NULL.\n if (value === '') return storage === 'text' ? '' : null\n\n if (storage === 'text') {\n if (interpretation.kind === 'json') {\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value)\n } catch {\n return null\n }\n }\n return typeof value === 'string' ? value : String(value)\n }\n\n if (typeof value === 'number') return isFinite(value) ? value : null\n if (typeof value === 'boolean') return value ? 1 : 0\n if (typeof value !== 'string') return String(value)\n\n const s = value.trim()\n\n if (interpretation.kind === 'boolean') {\n const lower = s.toLowerCase()\n if (lower === 'true' || lower === '1' || lower === 'yes') return 1\n if (lower === 'false' || lower === '0' || lower === 'no') return 0\n return s\n }\n\n if (interpretation.kind === 'percent') {\n const m = s.match(/^(-?\\d+(?:\\.\\d+)?)\\s*%$/)\n if (m) return parseFloat(m[1]) / 100\n }\n\n if (interpretation.kind === 'currency') {\n const cleaned = s.replace(/[^0-9.-]/g, '')\n const n = parseFloat(cleaned)\n if (!isNaN(n)) return n\n return s\n }\n\n if (interpretation.kind === 'date' || interpretation.kind === 'datetime') {\n const d = new Date(s)\n if (!isNaN(d.getTime())) return epochSeconds(d)\n return s\n }\n\n const cleaned = s.replace(/[$€£¥,\\s]/g, '')\n const n = parseFloat(cleaned)\n if (!isNaN(n) && isFinite(n)) return n\n\n return s\n}\n\n/** Canonical numeric representation for date/datetime storage and triggers. */\nexport function epochSeconds(date: Date = new Date()): number {\n return Math.floor(date.getTime() / 1000)\n}\n\nexport function buildTableSelect(collectionName: string, columns: ResolvedColumn[]): string {\n const tbl = collectionTableName(collectionName)\n const parts: string[] = ['_row_id', '_created_by', '_created_at', '_updated_at']\n for (const col of columns) {\n if (col.expression) {\n parts.push(`(${col.expression}) AS \"${col.id}\"`)\n } else {\n parts.push(`\"${col.id}\"`)\n }\n }\n return `SELECT ${parts.join(', ')} FROM \"${tbl}\"`\n}\n\nexport interface User {\n id: string\n email: string\n name: string\n imageUrl?: string\n role: string\n createdAt: string\n lastSeenAt: string\n}\n\nexport interface StoredRecord {\n collection: string\n recordId: string\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\n// ============================================================================\n// Permission Checking\n// ============================================================================\n\nexport interface PermissionContext {\n isTeamMember: (teamId: string, userId: string) => boolean\n}\n\nexport const noopPermissionContext: PermissionContext = {\n isTeamMember: () => false,\n}\n\nexport function getRolePermissions(schema: CollectionSchema, role: string): RolePermissions {\n if (schema.permissions[role]) return schema.permissions[role]\n if (schema.permissions['*']) return schema.permissions['*']\n return { read: false, create: false, update: false, delete: false }\n}\n\nexport function isOwner(\n schema: CollectionSchema,\n record: { data: Record<string, unknown>; createdBy: string },\n userId: string,\n): boolean {\n if (schema.ownerField) return record.data[schema.ownerField] === userId\n return record.createdBy === userId\n}\n\nfunction isCollaborator(\n schema: CollectionSchema,\n record: { data: Record<string, unknown> },\n userId: string,\n): boolean {\n if (!schema.collaboratorsField) return false\n const collab = record.data[schema.collaboratorsField]\n if (typeof collab === 'string') {\n try {\n return (JSON.parse(collab) as string[]).includes(userId)\n } catch {\n return false\n }\n }\n if (Array.isArray(collab)) return collab.includes(userId)\n return false\n}\n\nfunction passesVisibilityCheck(\n schema: CollectionSchema,\n record: { data: Record<string, unknown> },\n): boolean {\n if (!schema.visibilityField) return false\n if (typeof schema.visibilityField === 'string') {\n return record.data[schema.visibilityField] === 'public'\n }\n return record.data[schema.visibilityField.field] === schema.visibilityField.value\n}\n\nfunction checkPermissionLevel(\n level: PermissionLevel,\n schema: CollectionSchema,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n if (level === true) return true\n if (level === false) return false\n if (level === 'own') return isOwner(schema, record, userId)\n if (level === 'unclaimed-or-own') {\n if (schema.ownerField) {\n const ownerVal = record.data[schema.ownerField]\n if (!ownerVal || ownerVal === '') return true\n }\n return isOwner(schema, record, userId)\n }\n if (level === 'collaborator') {\n return isOwner(schema, record, userId) || isCollaborator(schema, record, userId)\n }\n if (level === 'team' || level === 'access') {\n if (isOwner(schema, record, userId)) return true\n if (isCollaborator(schema, record, userId)) return true\n if (schema.teamField) {\n const teamId = record.data[schema.teamField]\n if (typeof teamId === 'string' && ctx.isTeamMember(teamId, userId)) return true\n }\n return false\n }\n if (level === 'published') {\n return isOwner(schema, record, userId) || passesVisibilityCheck(schema, record)\n }\n if (level === 'shared') {\n return (\n isOwner(schema, record, userId) ||\n isCollaborator(schema, record, userId) ||\n passesVisibilityCheck(schema, record)\n )\n }\n return false\n}\n\nexport function canRead(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).read, schema, record, userId, ctx)\n}\n\nexport function canCreate(schema: CollectionSchema, role: string): boolean {\n return getRolePermissions(schema, role).create === true\n}\n\nexport function canUpdate(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).update, schema, record, userId, ctx)\n}\n\nexport function canDelete(\n schema: CollectionSchema,\n role: string,\n record: { data: Record<string, unknown>; createdBy: string; recordId?: string },\n userId: string,\n ctx: PermissionContext = noopPermissionContext,\n): boolean {\n return checkPermissionLevel(getRolePermissions(schema, role).delete, schema, record, userId, ctx)\n}\n\n/** Check if a caller-supplied field write violates writableFields restrictions. */\nexport function checkFieldPermissions(\n schema: CollectionSchema,\n role: string,\n newData: Record<string, unknown>,\n existingData?: Record<string, unknown>,\n): string | null {\n const perms = getRolePermissions(schema, role)\n if (!perms.writableFields) return null // no restrictions\n\n const allowedFields = new Set(perms.writableFields)\n for (const key of Object.keys(newData)) {\n if (existingData && newData[key] === existingData[key]) continue\n if (!allowedFields.has(key)) {\n return `Role '${role}' cannot modify field '${key}'`\n }\n }\n return null\n}\n\n/**\n * `unclaimed-or-own` lets a client claim an empty owner field, but never lets\n * them choose another user's id. Check the post-write record so the invariant\n * covers both creation and updates while still allowing an owner to unclaim.\n */\nexport function checkUnclaimedOwnerTransition(\n schema: CollectionSchema,\n role: string,\n nextData: Record<string, unknown>,\n userId: string,\n): string | null {\n const permissions = getRolePermissions(schema, role)\n if (permissions.update !== 'unclaimed-or-own' || !schema.ownerField) return null\n\n const nextOwner = nextData[schema.ownerField]\n if (nextOwner === undefined || nextOwner === null || nextOwner === '' || nextOwner === userId) {\n return null\n }\n\n return `Role '${role}' can set '${schema.ownerField}' only to their own user id or leave it unclaimed`\n}\n\n// ============================================================================\n// System-Managed Columns (users collection)\n// ============================================================================\n\n/**\n * Columns the system *assigns* — from the identity provider at registration,\n * or from an admin action. A caller who sends one of these has chosen a value\n * and means it, so a record write that would change one is refused loudly\n * rather than silently dropped.\n */\nexport const SYSTEM_ASSIGNED_COLUMNS = new Set(['email', 'name', 'imageUrl', 'role'])\n\n/**\n * Columns the system *maintains* on its own schedule, without telling anyone.\n * `registerUser` bumps `lastSeenAt` on every WS connect and `handleUserUpdate`\n * rewrites it on every 60s presence heartbeat; neither broadcasts, so every\n * client's copy goes stale silently and there is no heal path short of a\n * refetch. Read-modify-write echo (`put(id, { ...rec.data, field })`) is the\n * SDK's own house style, so refusing these would refuse writes over a value the\n * caller never chose and cannot keep current. They are preserved silently.\n */\nexport const SYSTEM_MAINTAINED_COLUMNS = new Set(['createdAt', 'lastSeenAt'])\n\n/** Every `users` column owned by the system rather than by record writes. */\nexport const SYSTEM_MANAGED_COLUMNS = new Set([\n ...SYSTEM_ASSIGNED_COLUMNS,\n ...SYSTEM_MAINTAINED_COLUMNS,\n])\n\n/** Standard user columns. Apps spread these into their users schema. */\nexport const USERS_COLUMNS: ColumnDefinition[] = [\n { name: 'email', storage: 'text', interpretation: 'plain' },\n { name: 'name', storage: 'text', interpretation: 'plain' },\n { name: 'imageUrl', storage: 'text', interpretation: 'plain' },\n { name: 'role', storage: 'text', interpretation: 'plain' },\n { name: 'createdAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'lastSeenAt', storage: 'text', interpretation: { kind: 'datetime' } },\n]\n\nexport const BASE_USERS_SCHEMA: CollectionSchema = {\n name: 'users',\n columns: [...USERS_COLUMNS],\n permissions: {\n '*': { read: 'own', create: false, update: false, delete: false },\n admin: { read: true, create: false, update: true, delete: true },\n },\n}\n\n// ============================================================================\n// Schema Registry\n// ============================================================================\n\n/**\n * Lint a CollectionSchema for declarations that look like they should\n * enforce something but don't, due to interactions between top-level\n * fields (visibilityField, ownerField) and per-role permission levels.\n *\n * The SDK has historically silently accepted schemas that imply more\n * enforcement than they actually deliver — e.g., `visibilityField` set\n * but every role's `read: true` means \"anyone can read everything\"\n * regardless of `visibility`. Warn loudly at registration so app\n * authors notice before shipping a privacy bug.\n *\n * Returns an array of warning messages. Empty = clean.\n */\nexport function lintSchema(schema: CollectionSchema): string[] {\n const warnings: string[] = []\n const VISIBILITY_AWARE: ReadonlyArray<PermissionLevel> = ['published', 'shared']\n\n // Roles that grant unconditional read despite visibilityField being declared.\n if (schema.visibilityField) {\n const offendingRoles = Object.entries(schema.permissions)\n .filter(([, perms]) => perms.read === true)\n .map(([role]) => role)\n if (offendingRoles.length > 0) {\n const usesAware = Object.values(schema.permissions).some(\n (p) => typeof p.read === 'string' && VISIBILITY_AWARE.includes(p.read as PermissionLevel),\n )\n if (!usesAware) {\n warnings.push(\n `[${schema.name}] visibilityField is declared but no role uses read: 'published' or 'shared'. ` +\n `Roles with read: true (${offendingRoles.join(', ')}) will see every row regardless of visibility. ` +\n `Change those to read: 'published' (owner OR public) or 'shared' (owner OR collaborator OR public) ` +\n `to actually enforce the filter, or remove visibilityField if you don't intend to gate reads by it.`,\n )\n }\n }\n }\n\n // An ordinary client role that can create rows must not be able to claim\n // someone else's ownership. Server-written collections legitimately carry\n // a recipient/subject id instead: their non-privileged roles all have\n // create:false, while owner/admin writes are already fully trusted.\n if (schema.ownerField) {\n const col = schema.columns.find((c) => c.name === schema.ownerField)\n const spoofableRoles = Object.entries(schema.permissions)\n .filter(\n ([role, permissions]) =>\n role !== 'owner' &&\n role !== 'admin' &&\n permissions.create === true &&\n permissions.update !== 'unclaimed-or-own',\n )\n .map(([role]) => role)\n if (col && !col.userBound && spoofableRoles.length > 0) {\n warnings.push(\n `[${schema.name}] ownerField is '${schema.ownerField}' but that column is not marked userBound: true. ` +\n `Client role(s) ${spoofableRoles.join(', ')} can create a row with someone else's id in this field, bypassing 'own' permission checks. ` +\n `Add userBound: true (and ideally immutable: true) to the column.`,\n )\n }\n }\n\n // userBound columns that aren't text storage — userBound sets the\n // field to the caller's userId (a string), so non-text storage is a\n // schema mistake that will surface as a coercion error at write time.\n for (const col of schema.columns) {\n if (col.userBound && col.storage !== 'text') {\n warnings.push(\n `[${schema.name}] column '${col.name}' is userBound but storage is '${col.storage}'. ` +\n `userBound stamps the user id (a string); use storage: 'text'.`,\n )\n }\n }\n\n return warnings\n}\n\n/**\n * Lint a whole schema set: every per-schema warning, plus the rules that\n * need to see the set. The `'team'` permission level resolves membership from\n * a `team_members` collection (`teamId`, `userId`, `status`); without one it\n * denies everything, silently — a mistake that presents as a sync bug, not\n * as a schema error, so name it here.\n */\nexport function lintSchemas(schemas: CollectionSchema[]): string[] {\n return [...schemas.flatMap((schema) => lintSchema(schema)), ...lintSchemaSet(schemas)]\n}\n\n/** The rules that need to see the whole schema set (per-schema rules live in\n * `lintSchema`; `lintSchemas` composes both). */\nexport function lintSchemaSet(schemas: CollectionSchema[]): string[] {\n const warnings: string[] = []\n if (!schemas.some((schema) => schema.name === 'team_members')) {\n for (const schema of schemas) {\n const teamRoles = Object.entries(schema.permissions)\n .filter(([, p]) => [p.read, p.update, p.delete].includes('team'))\n .map(([role]) => role)\n if (teamRoles.length > 0) {\n warnings.push(\n `[${schema.name}] role(s) ${teamRoles.join(', ')} use the 'team' permission level, but no ` +\n `'team_members' collection is registered. 'team' resolves membership from team_members ` +\n `(teamId, userId, status), so without it those roles are denied everything — silently. ` +\n `Register a team_members schema or use another level.`,\n )\n }\n }\n }\n return warnings\n}\n\nexport class SchemaRegistry {\n private trusted: Map<string, CollectionSchema> = new Map()\n\n constructor(schemas: CollectionSchema[] = []) {\n // registerTrusted runs the per-schema lint; only the set-level rules are\n // this loop's to add.\n for (const w of lintSchemaSet(schemas)) console.warn(`[schema-lint] ${w}`)\n for (const schema of schemas) this.registerTrusted(schema)\n }\n\n registerTrusted(schema: CollectionSchema): void {\n for (const w of lintSchema(schema)) {\n console.warn(`[schema-lint] ${w}`)\n }\n this.trusted.set(schema.name, schema)\n }\n\n get(name: string): CollectionSchema | undefined {\n return this.trusted.get(name)\n }\n\n has(name: string): boolean {\n return this.trusted.has(name)\n }\n\n all(): CollectionSchema[] {\n return Array.from(this.trusted.values())\n }\n\n names(): string[] {\n return Array.from(this.trusted.keys())\n }\n}\n\n// ============================================================================\n// Permission Analysis\n// ============================================================================\n\nexport type PermissionSource = 'explicit' | 'wildcard' | 'default-deny'\n\nexport interface ResolvedPermission {\n level: PermissionLevel | boolean\n source: PermissionSource\n}\n\nexport interface CollectionPermissionSummary {\n collection: string\n ownerField?: string\n collaboratorsField?: string\n teamField?: string\n columns: ColumnDefinition[]\n permissions: Record<\n string,\n {\n read: ResolvedPermission\n create: ResolvedPermission\n update: ResolvedPermission\n delete: ResolvedPermission\n writableFields?: string[]\n }\n >\n}\n\nexport interface PermissionAnalysis {\n roles: string[]\n collections: CollectionPermissionSummary[]\n}\n\nexport function analyzePermissions(schemas: CollectionSchema[]): PermissionAnalysis {\n const roleSet = new Set<string>()\n for (const schema of schemas) {\n for (const role of Object.keys(schema.permissions)) roleSet.add(role)\n }\n roleSet.delete('*')\n const roles = Array.from(roleSet)\n\n const collections: CollectionPermissionSummary[] = schemas.map((schema) => {\n const permissions: CollectionPermissionSummary['permissions'] = {}\n for (const role of roles) {\n let source: PermissionSource\n let perms: RolePermissions\n if (schema.permissions[role]) {\n source = 'explicit'\n perms = schema.permissions[role]\n } else if (schema.permissions['*']) {\n source = 'wildcard'\n perms = schema.permissions['*']\n } else {\n source = 'default-deny'\n perms = { read: false, create: false, update: false, delete: false }\n }\n permissions[role] = {\n read: { level: perms.read, source },\n create: { level: perms.create, source },\n update: { level: perms.update, source },\n delete: { level: perms.delete, source },\n writableFields: perms.writableFields,\n }\n }\n return {\n collection: schema.name,\n ownerField: schema.ownerField,\n collaboratorsField: schema.collaboratorsField,\n teamField: schema.teamField,\n columns: schema.columns,\n permissions,\n }\n })\n\n return { roles, collections }\n}\n","/**\n * Per-collection SQLite table management.\n *\n * Pure functions over the DO's SqlStorage. Extracted from RecordRoom so the\n * CREATE / ALTER / unique-index paths are testable without spinning up a\n * full DurableObject in miniflare.\n *\n * Behavior:\n * - If the table doesn't exist: CREATE with the system columns + every\n * resolved schema column.\n * - If the table exists: PRAGMA table_info, ALTER TABLE ADD COLUMN for any\n * resolved schema columns missing from the table. Idempotent — re-running\n * with the same schema is a no-op.\n * - Computed/expression columns are skipped (no storage).\n * - uniqueOn produces a UNIQUE INDEX (best-effort; logged-and-skipped on\n * constraint conflicts so a deploy can recover from pre-existing dupes).\n *\n * This is the single source of truth for \"does an alive table match the\n * current schema?\" — call it on every DO cold-start (via\n * `ensureAllCollectionTables`) and column-additive migrations propagate\n * automatically as DOs cycle.\n */\n\nimport {\n collectionTableName,\n resolveColumn,\n columnId,\n type CollectionSchema,\n} from '../schemas/registry'\n\nexport interface MigrationLogger {\n warn(msg: string): void\n}\n\nconst NOOP_LOGGER: MigrationLogger = { warn: () => {} }\n\nexport function ensureCollectionTable(\n sql: SqlStorage,\n schema: CollectionSchema,\n logger: MigrationLogger = NOOP_LOGGER,\n): void {\n if (!schema.columns) return\n\n const tbl = collectionTableName(schema.name)\n const resolved = schema.columns.map(resolveColumn)\n\n const tableExists = sql.exec(\n `SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl\n ).toArray().length > 0\n\n if (!tableExists) {\n const storageCols = resolved\n .filter(c => !c.expression)\n .map(c => `\"${c.id}\" ${c.storage === 'number' ? 'REAL' : 'TEXT'}`)\n .join(', ')\n\n const colClause = storageCols ? `, ${storageCols}` : ''\n sql.exec(`\n CREATE TABLE IF NOT EXISTS \"${tbl}\" (\n _row_id TEXT PRIMARY KEY,\n _created_by TEXT NOT NULL,\n _created_at TEXT NOT NULL,\n _updated_at TEXT NOT NULL${colClause}\n )\n `)\n } else {\n const existingCols = new Set<string>()\n const pragmaRows = sql.exec(`PRAGMA table_info(\"${tbl}\")`).toArray()\n for (const row of pragmaRows) {\n existingCols.add((row as { name: string }).name)\n }\n\n for (const col of resolved) {\n if (col.expression) continue\n if (!existingCols.has(col.id)) {\n const sqlType = col.storage === 'number' ? 'REAL' : 'TEXT'\n sql.exec(`ALTER TABLE \"${tbl}\" ADD COLUMN \"${col.id}\" ${sqlType}`)\n }\n }\n }\n\n if (schema.uniqueOn && schema.uniqueOn.length > 0) {\n const uniqueCols = schema.uniqueOn.map(fieldName => {\n const col = resolved.find(c => c.name === fieldName)\n return col ? `\"${col.id}\"` : `\"${columnId(fieldName)}\"`\n })\n const indexName = `uniq_${tbl}_${schema.uniqueOn.join('_').replace(/[^a-zA-Z0-9_]/g, '_')}`\n try {\n sql.exec(\n `CREATE UNIQUE INDEX IF NOT EXISTS \"${indexName}\" ON \"${tbl}\" (${uniqueCols.join(', ')})`\n )\n } catch (e) {\n logger.warn(\n `[RecordRoom] Cannot create UNIQUE index on ${tbl} (${schema.uniqueOn.join(', ')}): ${\n e instanceof Error ? e.message : e\n }`,\n )\n }\n }\n}\n","/**\n * Yjs collaborative editing handlers for RecordRoom\n */\n\nimport * as Y from 'yjs'\nimport { resolveCollection } from './subscriptions'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type {\n YjsDocKey,\n YjsJoinPayload,\n YjsLeavePayload,\n YjsSubscription,\n} from '../../shared/types'\nimport {\n MSG_YJS_SYNC,\n MSG_YJS_AWARENESS,\n MSG,\n RECORD_NOT_FOUND,\n} from '../../shared/protocol/constants'\nimport {\n MSG_SYNC_STEP1,\n MSG_SYNC_STEP2,\n MSG_SYNC_UPDATE,\n createEncoder,\n createDecoder,\n toUint8Array,\n writeVarUint,\n writeVarUint8Array,\n readVarUint,\n readVarUint8Array,\n} from '../../shared/protocol/yjs'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n canRead,\n canUpdate,\n collectionTableName,\n} from '../schemas/registry'\nimport { getRecord } from './records'\n\n/** Parse a Yjs doc key (collection:recordId:fieldName). recordId may contain colons. */\nfunction parseYjsDocKey(\n docKey: YjsDocKey,\n): { collection: string; recordId: string; fieldName: string } | null {\n const firstColon = docKey.indexOf(':')\n const lastColon = docKey.lastIndexOf(':')\n if (firstColon === -1 || lastColon === -1 || firstColon === lastColon) return null\n return {\n collection: docKey.slice(0, firstColon),\n recordId: docKey.slice(firstColon + 1, lastColon),\n fieldName: docKey.slice(lastColon + 1),\n }\n}\n\nfunction hasYjsSubscription(attachment: ConnectionAttachment, target: YjsSubscription): boolean {\n return attachment.yjsSubscriptions.some(\n (subscription) =>\n subscription.collection === target.collection &&\n subscription.recordId === target.recordId &&\n subscription.fieldName === target.fieldName,\n )\n}\n\n// ============================================================================\n// System Collections (permissive, no schema required)\n// ============================================================================\n\n/** Collections that bypass schema checks (permissive access) */\nexport const SYSTEM_COLLECTIONS = new Set(['canvas-settings'])\n\n/**\n * Schemas for system collections.\n * These have empty columns arrays — they only use system columns\n * (_row_id, _created_by, _created_at, _updated_at).\n * Yjs data is stored in the yjs_docs table, not in the record itself.\n */\nexport const SYSTEM_COLLECTION_SCHEMAS: CollectionSchema[] = [\n {\n name: 'canvas-settings',\n columns: [],\n permissions: {\n '*': { read: true, create: true, update: true, delete: true },\n },\n },\n]\n\nexport interface YjsContext {\n sql: SqlStorage\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n schemaRegistry: SchemaRegistry\n getPermissionContext(): PermissionContext\n send(ws: WebSocket, message: { type: string; payload: unknown }): void\n sendBinary(ws: WebSocket, data: Uint8Array): void\n}\n\n/**\n * Create a Yjs doc key from collection, recordId, and fieldName\n */\nexport function getYjsDocKey(collection: string, recordId: string, fieldName: string): YjsDocKey {\n return `${collection}:${recordId}:${fieldName}`\n}\n\n/**\n * Get or create a Y.Doc for a record field.\n * Loads from database if exists, creates new if not.\n */\nexport function getOrCreateYjsDoc(ctx: YjsContext, docKey: YjsDocKey): Y.Doc {\n // Return cached doc if available\n let doc = ctx.yjsDocs.get(docKey)\n if (doc) return doc\n\n // Create new doc\n doc = new Y.Doc()\n\n // Try to load from database\n const row = ctx.sql.exec(`SELECT state FROM yjs_docs WHERE doc_key = ?`, docKey).toArray()[0] as\n | { state: ArrayBuffer }\n | undefined\n\n if (row?.state) {\n try {\n Y.applyUpdate(doc, new Uint8Array(row.state))\n } catch (e) {\n console.error(`Failed to load Yjs doc ${docKey}:`, e)\n }\n }\n\n // Set up auto-save on updates\n const saveDoc = (): void => {\n saveYjsDoc(ctx.sql, docKey, doc!)\n }\n doc.on('update', saveDoc)\n\n ctx.yjsDocs.set(docKey, doc)\n return doc\n}\n\n/** Destroy and evict a document once no live connection subscribes to it. */\nexport function unloadYjsDocIfUnused(\n ctx: YjsContext,\n docKey: YjsDocKey,\n excludeWs: WebSocket | null = null,\n): boolean {\n const target = parseYjsDocKey(docKey)\n if (!target) return false\n\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment && hasYjsSubscription(attachment, target)) return false\n }\n\n const doc = ctx.yjsDocs.get(docKey)\n if (!doc) return false\n\n ctx.yjsDocs.delete(docKey)\n doc.destroy()\n return true\n}\n\n/**\n * Save Yjs doc state to database.\n */\nexport function saveYjsDoc(sql: SqlStorage, docKey: YjsDocKey, doc: Y.Doc): void {\n const state = Y.encodeStateAsUpdate(doc)\n const now = new Date().toISOString()\n sql.exec(\n `INSERT INTO yjs_docs (doc_key, state, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(doc_key) DO UPDATE SET state = ?, updated_at = ?`,\n docKey,\n state,\n now,\n state,\n now,\n )\n}\n\n/**\n * Handle request to join Yjs sync for a record field.\n * System collections (see SYSTEM_COLLECTIONS) are permissive; others require schema.\n */\nexport function handleYjsJoin(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: YjsJoinPayload,\n): void {\n const { collection, recordId, fieldName } = payload\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n\n const schema = ctx.schemaRegistry.get(collection)\n let record = getRecord(ctx.sql, collection, recordId, schema)\n let hasWriteAccess = isSystem // System collections always have write access\n\n if (isSystem) {\n // Auto-create record for system collections (table-mode)\n if (!record) {\n const now = new Date().toISOString()\n if (schema) {\n const tbl = collectionTableName(collection)\n ctx.sql.exec(\n `INSERT OR IGNORE INTO \"${tbl}\" (_row_id, _created_by, _created_at, _updated_at) VALUES (?, ?, ?, ?)`,\n recordId,\n attachment.userId,\n now,\n now,\n )\n }\n record = getRecord(ctx.sql, collection, recordId, schema)\n }\n } else {\n // Regular collections: require schema and record\n if (!schema) {\n const resolution = resolveCollection(ctx, collection)\n ctx.send(ws, {\n type: MSG.ERROR,\n payload: {\n error: resolution.ok\n ? `Schema not registered for collection: ${collection}`\n : resolution.error,\n },\n })\n return\n }\n if (!record) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: RECORD_NOT_FOUND } })\n return\n }\n const permCtx = ctx.getPermissionContext()\n const recordWithId = { ...record, recordId }\n if (!canRead(schema, attachment.role, recordWithId, attachment.userId, permCtx)) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'Permission denied' } })\n return\n }\n hasWriteAccess = canUpdate(schema, attachment.role, recordWithId, attachment.userId, permCtx)\n }\n\n // Get or create Yjs doc and subscribe\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const doc = getOrCreateYjsDoc(ctx, docKey)\n\n if (!hasYjsSubscription(attachment, payload)) {\n attachment.yjsSubscriptions.push({ collection, recordId, fieldName })\n ws.serializeAttachment(attachment)\n }\n\n // Send sync messages\n const docKeyBytes = new TextEncoder().encode(docKey)\n const encoder1 = createEncoder()\n writeVarUint(encoder1, MSG_YJS_SYNC)\n writeVarUint8Array(encoder1, docKeyBytes)\n writeVarUint(encoder1, MSG_SYNC_STEP1)\n writeVarUint8Array(encoder1, Y.encodeStateVector(doc))\n ctx.sendBinary(ws, toUint8Array(encoder1))\n\n const fullState = Y.encodeStateAsUpdate(doc)\n if (fullState.length > 1) {\n const encoder2 = createEncoder()\n writeVarUint(encoder2, MSG_YJS_SYNC)\n writeVarUint8Array(encoder2, docKeyBytes)\n writeVarUint(encoder2, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder2, fullState)\n ctx.sendBinary(ws, toUint8Array(encoder2))\n }\n\n ctx.send(ws, {\n type: MSG.YJS_JOIN,\n payload: { collection, recordId, fieldName, canWrite: hasWriteAccess },\n })\n}\n\n/**\n * Handle request to leave Yjs sync for a record field.\n */\nexport function handleYjsLeave(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: YjsLeavePayload,\n): void {\n const { collection, recordId, fieldName } = payload\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n\n const clientId = attachment.awarenessClientIds?.[docKey]\n if (clientId !== undefined) {\n broadcastAwarenessClientRemoval(ctx, docKey, clientId)\n delete attachment.awarenessClientIds![docKey]\n }\n\n // Remove from subscriptions\n attachment.yjsSubscriptions = attachment.yjsSubscriptions.filter(\n (s) => !(s.collection === collection && s.recordId === recordId && s.fieldName === fieldName),\n )\n ws.serializeAttachment(attachment)\n unloadYjsDocIfUnused(ctx, docKey, ws)\n}\n\n/**\n * Handle binary Yjs sync messages from clients.\n *\n * Protocol:\n * - MSG_SYNC_STEP1: Client sends state vector → Server responds with SYNC_STEP2 (diff)\n * - MSG_SYNC_STEP2/UPDATE: Client sends update → Server applies and broadcasts\n */\nexport function handleYjsBinaryMessage(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n data: Uint8Array,\n): void {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n\n // Handle awareness messages (ephemeral — relay only, no persistence)\n if (messageType === MSG_YJS_AWARENESS) {\n const docKeyBytes = readVarUint8Array(decoder)\n const docKey = new TextDecoder().decode(docKeyBytes) as YjsDocKey\n const payload = readVarUint8Array(decoder)\n\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n if (!hasYjsSubscription(attachment, parsed)) return\n\n // Capture this document's awareness clientId for leave/disconnect cleanup.\n if (attachment.awarenessClientIds?.[docKey] === undefined) {\n try {\n const pd = createDecoder(payload)\n const count = readVarUint(pd)\n if (count > 0) {\n attachment.awarenessClientIds ??= {}\n attachment.awarenessClientIds[docKey] = readVarUint(pd)\n ws.serializeAttachment(attachment)\n }\n } catch {\n /* best-effort */\n }\n }\n\n broadcastAwareness(ctx, docKey, payload, ws)\n return\n }\n\n if (messageType !== MSG_YJS_SYNC) return\n\n // Parse doc key (format: collection:recordId:fieldName)\n // Note: recordId may contain colons (e.g., \"shape:notepad-xxx\")\n // So we split on first colon for collection, last colon for fieldName\n const docKeyBytes = readVarUint8Array(decoder)\n const docKey = new TextDecoder().decode(docKeyBytes) as YjsDocKey\n\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n const { collection, recordId } = parsed\n\n // Verify subscription\n if (!hasYjsSubscription(attachment, parsed)) return\n\n // Load doc from SQLite if not in memory (handles DO hibernation wake-up)\n const doc = getOrCreateYjsDoc(ctx, docKey)\n\n const syncType = readVarUint(decoder)\n\n switch (syncType) {\n case MSG_SYNC_STEP1: {\n // Client requests sync - respond with diff based on their state vector\n const clientStateVector = readVarUint8Array(decoder)\n const diff = Y.encodeStateAsUpdate(doc, clientStateVector)\n\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_SYNC)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint(encoder, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder, diff)\n ctx.sendBinary(ws, toUint8Array(encoder))\n break\n }\n\n case MSG_SYNC_STEP2:\n case MSG_SYNC_UPDATE: {\n // Client sends update - system collections permissive, others check permission\n if (!SYSTEM_COLLECTIONS.has(collection)) {\n const syncSchema = ctx.schemaRegistry.get(collection)\n if (!syncSchema) return\n const record = getRecord(ctx.sql, collection, recordId, syncSchema)\n if (!record) return\n if (\n !canUpdate(\n syncSchema,\n attachment.role,\n { ...record, recordId },\n attachment.userId,\n ctx.getPermissionContext(),\n )\n )\n return\n }\n\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update, 'client')\n broadcastYjsUpdate(ctx, docKey, update, ws)\n break\n }\n }\n}\n\n/**\n * Broadcast a Yjs update to all subscribers of a doc, except the sender.\n */\nexport function broadcastYjsUpdate(\n ctx: YjsContext,\n docKey: YjsDocKey,\n update: Uint8Array,\n excludeWs: WebSocket | null,\n): void {\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n // Build message once\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_SYNC)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint(encoder, MSG_SYNC_UPDATE)\n writeVarUint8Array(encoder, update)\n const message = toUint8Array(encoder)\n\n // Send to all other subscribers\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n if (hasYjsSubscription(attachment, parsed)) {\n ctx.sendBinary(ws, message)\n }\n }\n}\n\n/**\n * Broadcast an awareness update to all subscribers of a doc, except the sender.\n * Awareness is ephemeral (cursors, presence) — NOT persisted to SQLite.\n */\nexport function broadcastAwareness(\n ctx: YjsContext,\n docKey: YjsDocKey,\n payload: Uint8Array,\n excludeWs: WebSocket | null,\n): void {\n const parsed = parseYjsDocKey(docKey)\n if (!parsed) return\n\n // Build message once\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_YJS_AWARENESS)\n writeVarUint8Array(encoder, new TextEncoder().encode(docKey))\n writeVarUint8Array(encoder, payload)\n const message = toUint8Array(encoder)\n\n // Send to all other subscribers\n for (const ws of ctx.state.getWebSockets()) {\n if (ws === excludeWs) continue\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n if (hasYjsSubscription(attachment, parsed)) {\n ctx.sendBinary(ws, message)\n }\n }\n}\n\n/**\n * Broadcast awareness removal for a disconnected client.\n *\n * When a WebSocket closes without a clean leave, other clients still\n * show the disconnected user's cursor. This constructs a null-state\n * awareness update and broadcasts it to remaining subscribers.\n */\nexport function broadcastAwarenessRemoval(ctx: YjsContext, attachment: ConnectionAttachment): void {\n if (!attachment.awarenessClientIds) return\n\n for (const sub of attachment.yjsSubscriptions) {\n const docKey = getYjsDocKey(sub.collection, sub.recordId, sub.fieldName)\n const clientId = attachment.awarenessClientIds[docKey]\n if (clientId !== undefined) broadcastAwarenessClientRemoval(ctx, docKey, clientId)\n }\n}\n\nfunction broadcastAwarenessClientRemoval(\n ctx: YjsContext,\n docKey: YjsDocKey,\n clientId: number,\n): void {\n const payloadEncoder = createEncoder()\n writeVarUint(payloadEncoder, 1)\n writeVarUint(payloadEncoder, clientId)\n writeVarUint(payloadEncoder, 0xffffff) // high clock to override any prior state\n writeVarUint8Array(payloadEncoder, new TextEncoder().encode('null'))\n const removalPayload = toUint8Array(payloadEncoder)\n broadcastAwareness(ctx, docKey, removalPayload, null)\n}\n\n/** Clean up ephemeral awareness and cached documents for a closed connection. */\nexport function handleYjsDisconnect(\n ctx: YjsContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n): void {\n broadcastAwarenessRemoval(ctx, attachment)\n\n const docKeys = new Set(\n attachment.yjsSubscriptions.map((subscription) =>\n getYjsDocKey(subscription.collection, subscription.recordId, subscription.fieldName),\n ),\n )\n for (const docKey of docKeys) {\n unloadYjsDocIfUnused(ctx, docKey, ws)\n }\n}\n","/**\n * Yjs Sync Protocol Implementation\n * \n * This implements the y-protocols sync and awareness protocols without\n * depending on y-websocket internal paths. Makes it easier to bundle\n * and template.\n * \n * Protocol spec: https://github.com/yjs/y-protocols\n */\n\nimport * as Y from 'yjs'\n\n// Message type constants\nexport const MSG_SYNC = 0\nexport const MSG_AWARENESS = 1\n\n// Sync message subtypes\nexport const MSG_SYNC_STEP1 = 0 // Send state vector\nexport const MSG_SYNC_STEP2 = 1 // Send diff based on received state vector\nexport const MSG_SYNC_UPDATE = 2 // Send incremental update\n\n// ============================================================================\n// Binary Encoding Utilities (lib0 compatible)\n// ============================================================================\n\nexport function createEncoder(): { data: number[] } {\n return { data: [] }\n}\n\nexport function toUint8Array(encoder: { data: number[] }): Uint8Array {\n return new Uint8Array(encoder.data)\n}\n\nexport function writeVarUint(encoder: { data: number[] }, num: number): void {\n while (num > 0x7f) {\n encoder.data.push((num & 0x7f) | 0x80)\n num = Math.floor(num / 128)\n }\n encoder.data.push(num & 0x7f)\n}\n\nexport function writeVarUint8Array(encoder: { data: number[] }, arr: Uint8Array): void {\n writeVarUint(encoder, arr.length)\n for (let i = 0; i < arr.length; i++) {\n encoder.data.push(arr[i])\n }\n}\n\nexport function createDecoder(data: Uint8Array): { data: Uint8Array; pos: number } {\n return { data, pos: 0 }\n}\n\nexport function readVarUint(decoder: { data: Uint8Array; pos: number }): number {\n let num = 0\n let mult = 1\n const len = decoder.data.length\n while (decoder.pos < len) {\n const byte = decoder.data[decoder.pos++]\n num += (byte & 0x7f) * mult\n mult *= 128\n if (byte < 0x80) {\n return num\n }\n }\n throw new Error('Unexpected end of buffer')\n}\n\nexport function readVarUint8Array(decoder: { data: Uint8Array; pos: number }): Uint8Array {\n const len = readVarUint(decoder)\n const arr = decoder.data.subarray(decoder.pos, decoder.pos + len)\n decoder.pos += len\n return arr\n}\n\nexport function hasContent(decoder: { data: Uint8Array; pos: number }): boolean {\n return decoder.pos < decoder.data.length\n}\n\n// ============================================================================\n// Sync Protocol\n// ============================================================================\n\n/**\n * Encode sync step 1: our state vector\n */\nexport function encodeSyncStep1(doc: Y.Doc): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_STEP1)\n writeVarUint8Array(encoder, Y.encodeStateVector(doc))\n return toUint8Array(encoder)\n}\n\n/**\n * Encode sync step 2: diff based on received state vector\n */\nexport function encodeSyncStep2(doc: Y.Doc, stateVector: Uint8Array): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_STEP2)\n writeVarUint8Array(encoder, Y.encodeStateAsUpdate(doc, stateVector))\n return toUint8Array(encoder)\n}\n\n/**\n * Encode an incremental update\n */\nexport function encodeUpdate(update: Uint8Array): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_SYNC)\n writeVarUint(encoder, MSG_SYNC_UPDATE)\n writeVarUint8Array(encoder, update)\n return toUint8Array(encoder)\n}\n\nexport interface SyncResult {\n type: 'step1' | 'step2' | 'update'\n response?: Uint8Array\n stateVector?: Uint8Array\n update?: Uint8Array\n}\n\n/**\n * Handle an incoming sync message\n * Returns response to send (if any) and whether this was an update\n */\nexport function handleSyncMessage(doc: Y.Doc, data: Uint8Array): SyncResult {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n \n if (messageType !== MSG_SYNC) {\n throw new Error(`Expected sync message (${MSG_SYNC}), got ${messageType}`)\n }\n \n const syncType = readVarUint(decoder)\n \n switch (syncType) {\n case MSG_SYNC_STEP1: {\n // Received state vector, send our diff\n const stateVector = readVarUint8Array(decoder)\n const response = encodeSyncStep2(doc, stateVector)\n return { type: 'step1', response, stateVector }\n }\n \n case MSG_SYNC_STEP2: {\n // Received diff, apply it\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update)\n return { type: 'step2', update }\n }\n \n case MSG_SYNC_UPDATE: {\n // Received incremental update, apply it\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update)\n return { type: 'update', update }\n }\n \n default:\n throw new Error(`Unknown sync type: ${syncType}`)\n }\n}\n\n// ============================================================================\n// Awareness Protocol\n// ============================================================================\n\nexport interface AwarenessState {\n [key: string]: unknown\n}\n\nexport interface AwarenessStates {\n [clientId: number]: AwarenessState\n}\n\n/**\n * Simple awareness implementation for presence/cursors\n */\nexport class Awareness {\n doc: Y.Doc\n clientID: number\n states: Map<number, AwarenessState> = new Map()\n private meta: Map<number, { clock: number; lastUpdated: number }> = new Map()\n private updateListeners: Set<(changes: { added: number[]; updated: number[]; removed: number[] }) => void> = new Set()\n private changeListeners: Set<(changes: { added: number[]; updated: number[]; removed: number[] }) => void> = new Set()\n\n constructor(doc: Y.Doc) {\n this.doc = doc\n this.clientID = doc.clientID\n }\n \n getStates(): Map<number, AwarenessState> {\n return this.states\n }\n \n getLocalState(): AwarenessState | null {\n return this.states.get(this.clientID) || null\n }\n \n setLocalState(state: AwarenessState | null): void {\n const clientID = this.clientID\n const prevState = this.states.get(clientID)\n \n if (state === null) {\n this.states.delete(clientID)\n this.meta.delete(clientID)\n if (prevState) {\n this.emit({ added: [], updated: [], removed: [clientID] })\n }\n } else {\n const meta = this.meta.get(clientID) || { clock: 0, lastUpdated: Date.now() }\n meta.clock++\n meta.lastUpdated = Date.now()\n this.meta.set(clientID, meta)\n this.states.set(clientID, state)\n this.emit({\n added: prevState ? [] : [clientID],\n updated: prevState ? [clientID] : [],\n removed: [],\n })\n }\n }\n \n setLocalStateField(field: string, value: unknown): void {\n const state = this.getLocalState() || {}\n state[field] = value\n this.setLocalState(state)\n }\n \n on(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[] }) => void): void {\n if (event === 'update') {\n this.updateListeners.add(handler)\n } else if (event === 'change') {\n this.changeListeners.add(handler)\n }\n }\n\n off(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[] }) => void): void {\n if (event === 'update') {\n this.updateListeners.delete(handler)\n } else if (event === 'change') {\n this.changeListeners.delete(handler)\n }\n }\n\n private emit(changes: { added: number[]; updated: number[]; removed: number[] }): void {\n for (const handler of this.updateListeners) {\n handler(changes)\n }\n if (changes.added.length > 0 || changes.updated.length > 0 || changes.removed.length > 0) {\n for (const handler of this.changeListeners) {\n handler(changes)\n }\n }\n }\n \n /**\n * Encode awareness update for specific clients\n */\n encodeUpdate(clientIds?: number[]): Uint8Array {\n const ids = clientIds || Array.from(this.states.keys())\n const encoder = createEncoder()\n writeVarUint(encoder, ids.length)\n \n for (const clientId of ids) {\n const state = this.states.get(clientId)\n const meta = this.meta.get(clientId) || { clock: 0, lastUpdated: Date.now() }\n \n writeVarUint(encoder, clientId)\n writeVarUint(encoder, meta.clock)\n \n const stateStr = state ? JSON.stringify(state) : 'null'\n const stateBytes = new TextEncoder().encode(stateStr)\n writeVarUint8Array(encoder, stateBytes)\n }\n \n return toUint8Array(encoder)\n }\n \n /**\n * Apply awareness update from another client\n */\n applyUpdate(data: Uint8Array): { added: number[]; updated: number[]; removed: number[] } {\n const decoder = createDecoder(data)\n const len = readVarUint(decoder)\n \n const added: number[] = []\n const updated: number[] = []\n const removed: number[] = []\n \n for (let i = 0; i < len; i++) {\n const clientId = readVarUint(decoder)\n const clock = readVarUint(decoder)\n const stateBytes = readVarUint8Array(decoder)\n const stateStr = new TextDecoder().decode(stateBytes)\n const state = stateStr === 'null' ? null : JSON.parse(stateStr) as AwarenessState\n \n const prevMeta = this.meta.get(clientId)\n const prevState = this.states.get(clientId)\n \n // Only apply if this is a newer update\n if (!prevMeta || prevMeta.clock < clock) {\n this.meta.set(clientId, { clock, lastUpdated: Date.now() })\n \n if (state === null) {\n this.states.delete(clientId)\n if (prevState) {\n removed.push(clientId)\n }\n } else {\n this.states.set(clientId, state)\n if (prevState) {\n updated.push(clientId)\n } else {\n added.push(clientId)\n }\n }\n }\n }\n \n if (added.length > 0 || updated.length > 0 || removed.length > 0) {\n this.emit({ added, updated, removed })\n }\n \n return { added, updated, removed }\n }\n \n /**\n * Remove awareness states for disconnected clients\n */\n removeStates(clientIds: number[]): void {\n const removed: number[] = []\n for (const clientId of clientIds) {\n if (this.states.has(clientId)) {\n this.states.delete(clientId)\n this.meta.delete(clientId)\n removed.push(clientId)\n }\n }\n if (removed.length > 0) {\n this.emit({ added: [], updated: [], removed })\n }\n }\n\n /** Release all state and listeners owned by this awareness instance. */\n destroy(): void {\n this.setLocalState(null)\n this.states.clear()\n this.meta.clear()\n this.updateListeners.clear()\n this.changeListeners.clear()\n }\n}\n\n/**\n * Encode a full awareness message\n */\nexport function encodeAwarenessMessage(awareness: Awareness, clientIds?: number[]): Uint8Array {\n const encoder = createEncoder()\n writeVarUint(encoder, MSG_AWARENESS)\n writeVarUint8Array(encoder, awareness.encodeUpdate(clientIds))\n return toUint8Array(encoder)\n}\n\n/**\n * Decode and apply awareness message\n */\nexport function handleAwarenessMessage(\n awareness: Awareness, \n data: Uint8Array\n): { added: number[]; updated: number[]; removed: number[] } {\n const decoder = createDecoder(data)\n const messageType = readVarUint(decoder)\n \n if (messageType !== MSG_AWARENESS) {\n throw new Error(`Expected awareness message (${MSG_AWARENESS}), got ${messageType}`)\n }\n \n const update = readVarUint8Array(decoder)\n return awareness.applyUpdate(update)\n}\n\n/**\n * Get message type from raw data\n */\nexport function getMessageType(data: Uint8Array): number {\n const decoder = createDecoder(data)\n return readVarUint(decoder)\n}\n","/**\n * Typed wire-protocol layer — discriminated unions, typed builders, and a\n * type-safe dispatcher for every `MSG.*` the SDK understands.\n *\n * Why this exists\n * ---------------\n *\n * The string `MSG.*` constants in `./constants.ts` are the authoritative\n * wire protocol, but using them directly is error-prone: a typo picks the\n * wrong message with the wrong payload shape and fails silently at\n * runtime. This module pairs every constant with its payload type, so\n * that:\n *\n * 1. Building a message with `clientBuild.gameInput(...)` is payload-\n * checked at the call site — the compiler refuses to ship a wrong\n * shape.\n *\n * 2. Parsing an inbound message via `dispatch(raw, handlers)` narrows the\n * payload type inside each handler automatically, replacing the\n * unsafe `switch (msg.type) { case MSG.X: (payload as any).foo }`\n * pattern.\n *\n * 3. Tightening `BaseRoom.sendTo` / `BaseRoom.broadcast` /\n * `HandlerContext.send` / `SubscriptionContext.send` to accept\n * `ServerMessage` turns the type layer into enforcement: any room\n * that ships a payload inconsistent with its declared arm fails to\n * compile. Without that, the discriminated union is documentation,\n * not contract.\n *\n * 4. Adding a new `MSG.*` is localized: one entry in the discriminated\n * union, one builder function, one handler key in every dispatcher\n * that cares. No grep-and-fix across the codebase.\n *\n * 5. Apps can extend the SDK protocol without forking: `dispatch<M>` is\n * generic over any `M extends ProtocolMessage`, and builders are\n * plain objects so apps compose via spread (`{ ...clientBuild,\n * myMessage: ... }`).\n *\n * Direction split\n * ---------------\n *\n * Some message types carry different payloads depending on who's sending.\n * `MSG.RECORDS_RESUBSCRIBE`, for example, is `{}` when the client asks to\n * but `{ state, tick }` when the server broadcasts the start event.\n * `MSG.CANVAS_ADD` is a flat shape dict on the way in and a `{ shape }`\n * wrapper on the way out. Modelling these with one union would force\n * handlers to juggle a union payload — clunky and error-prone. Instead we\n * split by direction:\n *\n * - `ClientMessage` — what the client sends to the server\n * - `ServerMessage` — what the server sends to the client\n * - `ProtocolMessage = ClientMessage | ServerMessage` (for code that\n * really doesn't care — avoid when possible)\n *\n * Each side gets its own builder (`clientBuild` / `serverBuild`) and each\n * side's dispatcher is parameterised with the union it expects.\n *\n * Payload strictness\n * ------------------\n *\n * Where payload shapes are stable + narrow (ids, flags), we type them\n * precisely. Where they're opaque or escape the protocol layer (record\n * data blobs, Yjs binary frames, game-engine state), we use `unknown` and\n * defer narrowing to the caller. This is intentional: over-typing opaque\n * payloads would require the protocol layer to import application types\n * and defeat the \"thin wire contract\" goal.\n */\n\nimport { MSG } from './constants'\n\n// =============================================================================\n// Envelope + helpers\n// =============================================================================\n\n/**\n * The outer shape of every wire message. `T` is the string discriminator\n * (e.g. `\"game.input\"`) — keeping it as a generic literal type lets the\n * discriminated-union narrowing in `dispatch()` pick the right payload.\n *\n * Callers extending the protocol should pass a string-literal type for\n * `T`, not the widened `string`. `BaseMessage<string, P>` collapses the\n * discriminated union and handler-map key inference falls back to a\n * single untyped `string` key, losing all narrowing.\n */\nexport interface BaseMessage<T extends string, P> {\n type: T\n payload: P\n}\n\n/** Matches when a payload is intentionally empty — `{}` on the wire. */\ntype EmptyPayload = Record<string, never>\n\n/**\n * Frozen empty payload reused by every \"no args\" builder. Frozen so a\n * runaway consumer mutating `.payload` can't corrupt the shared instance\n * that every other call site sees.\n */\nconst EMPTY: EmptyPayload = Object.freeze({}) as EmptyPayload\n\n// =============================================================================\n// Client → Server messages\n// =============================================================================\n\n/**\n * Every message the client can legitimately send. Extend in app code with\n * a string-literal union arm:\n *\n * type MyClientMessage = ClientMessage | BaseMessage<'myapp.foo', { x: number }>\n */\nexport type ClientMessage =\n // ---- Records / CRUD ---------------------------------------------------\n | BaseMessage<typeof MSG.SUBSCRIBE, { subscriptionId: string; query: unknown }>\n | BaseMessage<typeof MSG.UNSUBSCRIBE, { subscriptionId: string }>\n | BaseMessage<\n typeof MSG.PUT,\n { collection: string; recordId: string; data: Record<string, unknown>; requestId?: string }\n >\n | BaseMessage<\n typeof MSG.DELETE,\n { collection: string; recordId: string; requestId?: string }\n >\n // ---- Users ------------------------------------------------------------\n | BaseMessage<typeof MSG.USER_LIST, EmptyPayload>\n | BaseMessage<typeof MSG.SET_ROLE, { userId: string; role: string }>\n | BaseMessage<typeof MSG.USER_UPDATE, EmptyPayload>\n | BaseMessage<typeof MSG.LIST_SCHEMAS, EmptyPayload>\n // ---- Yjs --------------------------------------------------------------\n | BaseMessage<typeof MSG.YJS_JOIN, { collection: string; recordId: string; fieldName: string }>\n | BaseMessage<typeof MSG.YJS_LEAVE, { collection: string; recordId: string; fieldName: string }>\n // Binary yjs sync/awareness frames use numeric `MSG_YJS_SYNC` /\n // `MSG_YJS_AWARENESS` envelope ids from `./constants.ts` and ride the\n // binary WebSocket channel. They bypass this typed layer entirely.\n // ---- Game -------------------------------------------------------------\n // ---- Canvas -----------------------------------------------------------\n // `CANVAS_ADD` sends a flat shape dict as the payload (not `{ shape }`);\n // the server wraps it in `{ shape }` on its rebroadcast. Keep the types\n // aligned with the real wire or `clientBuild` lies. See\n // server/rooms/canvas-room.ts for the server side of this asymmetry.\n // `unknown` here (rather than `Record<string, unknown>`) so closed\n // concrete shape types pass assignability without index signatures.\n | BaseMessage<typeof MSG.CANVAS_ADD, unknown>\n | BaseMessage<typeof MSG.CANVAS_MOVE, { shapeId: string; x: number; y: number }>\n | BaseMessage<\n typeof MSG.CANVAS_RESIZE,\n { shapeId: string; width: number; height: number; x?: number; y?: number }\n >\n | BaseMessage<typeof MSG.CANVAS_DELETE, { shapeId: string }>\n | BaseMessage<\n typeof MSG.CANVAS_UPDATE,\n { shapeId: string; props: Record<string, unknown> }\n >\n // Flat payload in the client→server direction, matching `CANVAS_ADD`.\n | BaseMessage<typeof MSG.CANVAS_VIEWPORT, unknown>\n | BaseMessage<typeof MSG.CANVAS_UNDO, EmptyPayload>\n | BaseMessage<typeof MSG.CANVAS_REDO, EmptyPayload>\n // ---- Cron -------------------------------------------------------------\n // Optional `requestId` opts the mutation into a `CRON_ACK` receipt;\n // frames without one keep the fire-and-forget contract.\n | BaseMessage<typeof MSG.CRON_TRIGGER, { taskName: string; requestId?: string }>\n | BaseMessage<typeof MSG.CRON_PAUSE, { taskName: string; requestId?: string }>\n | BaseMessage<typeof MSG.CRON_RESUME, { taskName: string; requestId?: string }>\n // ---- Jobs -------------------------------------------------------------\n // `JOB_ENQUEUE` uses requestId/JOB_UPDATE-ack so the caller learns the\n // newly-assigned jobId.\n | BaseMessage<\n typeof MSG.JOB_ENQUEUE,\n {\n requestId: string\n type: string\n payload?: unknown\n maxAttempts?: number\n }\n >\n | BaseMessage<typeof MSG.JOB_CANCEL, { jobId: string }>\n | BaseMessage<typeof MSG.JOB_RETRY, { jobId: string }>\n // ---- Presence ---------------------------------------------------------\n // Flat state object — the server merges incoming fields into the peer's\n // stored state; there's no `{ state }` wrapper in the client direction.\n | BaseMessage<typeof MSG.PRESENCE_UPDATE, Record<string, unknown>>\n // ---- Gateway ----------------------------------------------------------\n | BaseMessage<typeof MSG.GW_SCOPE_CONNECT, { scopeType: string; scopeId: string }>\n | BaseMessage<typeof MSG.GW_SCOPE_DISCONNECT, { scopeType: string; scopeId: string }>\n | BaseMessage<typeof MSG.GW_TOKEN_REFRESH, { token: string }>\n\n// =============================================================================\n// Server → Client messages\n// =============================================================================\n\n/**\n * Every message the server can send. Room and handler `send` / `broadcast`\n * signatures are tightened to this union so outbound payloads are\n * compile-checked against the wire contract. As with `ClientMessage`,\n * extend via a string-literal union arm in app code when adding new\n * server-side broadcasts.\n */\nexport type ServerMessage =\n // ---- Records / CRUD ---------------------------------------------------\n | BaseMessage<\n typeof MSG.QUERY_RESULT,\n { subscriptionId: string; records: unknown[] }\n >\n | BaseMessage<\n typeof MSG.RECORD_CHANGE,\n {\n collection: string\n record: unknown\n // Real wire values are `create`/`update`/`delete`, not the\n // underlying storage op name. See `server/handlers/subscriptions.ts`.\n changeType: 'create' | 'update' | 'delete'\n }\n >\n // `subscriptionId` is optional because generic transport errors (bad\n // envelope, missing connection) have no subscription context. Specific\n // query failures include the id so the client can correlate.\n | BaseMessage<typeof MSG.ERROR, { error: string; subscriptionId?: string }>\n // ACK is a mutation ack — it carries the correlation `requestId` from\n // the inbound PUT/DELETE plus a success flag. Server/handlers/records.ts\n // is the authoritative send site.\n | BaseMessage<\n typeof MSG.ACK,\n | { requestId: string; success: true; recordId?: string }\n | { requestId: string; success: false; error: string }\n >\n | BaseMessage<typeof MSG.RESUBSCRIBE, EmptyPayload>\n | BaseMessage<typeof MSG.LIST_SCHEMAS, { schemas: unknown }>\n // ---- Auth -------------------------------------------------------------\n // Emitted once per connection by rooms that enforce role-based writes\n // (Canvas, Game, Cron). Client hooks store `canWrite` in state and\n // disable write APIs when false. See useCanvas /\n // useCronMonitor for the consumer side.\n | BaseMessage<typeof MSG.AUTH, { canWrite: boolean }>\n // ---- Users ------------------------------------------------------------\n // `USER_INFO` carries an app-specific user object (column-shape varies\n // per deployment), so the wire type is opaque. Narrow at the consumer.\n | BaseMessage<typeof MSG.USER_INFO, unknown>\n | BaseMessage<typeof MSG.USER_LIST, { users: unknown[] }>\n // ---- Yjs (JSON frames; binary sync/awareness bypass this layer) -------\n | BaseMessage<\n typeof MSG.YJS_JOIN,\n { collection: string; recordId: string; fieldName: string; canWrite: boolean }\n >\n // ---- Game -------------------------------------------------------------\n // ---- Canvas -----------------------------------------------------------\n // `CANVAS_SHAPES` carries both the shape list and the viewports snapshot\n // so new connections can restore a multi-user view in one message. See\n // server/rooms/canvas-room.ts:onConnect.\n | BaseMessage<\n typeof MSG.CANVAS_SHAPES,\n { shapes: unknown[]; viewports: unknown[] }\n >\n | BaseMessage<typeof MSG.CANVAS_ADD, { shape: unknown }>\n | BaseMessage<typeof MSG.CANVAS_MOVE, { shapeId: string; x: number; y: number }>\n | BaseMessage<\n typeof MSG.CANVAS_RESIZE,\n { shapeId: string; width: number; height: number; x?: number; y?: number }\n >\n | BaseMessage<typeof MSG.CANVAS_DELETE, { shapeId: string }>\n | BaseMessage<\n typeof MSG.CANVAS_UPDATE,\n { shapeId: string; props: Record<string, unknown> }\n >\n | BaseMessage<\n typeof MSG.CANVAS_VIEWPORT,\n { viewport: unknown } | { userId: string; removed: true }\n >\n // ---- Cron -------------------------------------------------------------\n // `CRON_STATUS` server broadcasts carry both tasks and recent history;\n // see server/rooms/cron-room.ts.\n | BaseMessage<typeof MSG.CRON_TASKS, { tasks: unknown }>\n | BaseMessage<typeof MSG.CRON_HISTORY, { history: unknown }>\n | BaseMessage<typeof MSG.CRON_STATUS, { tasks: unknown; recentHistory: unknown }>\n // `CRON_ACK` is the per-mutation receipt for a trigger/pause/resume that\n // carried a `requestId`. Triggers execute synchronously in the DO before\n // the ack is sent, so an ok trigger receipt means the run completed\n // (execution detail rides the history broadcasts). Failures carry a\n // machine-readable `reason` so clients never parse error strings —\n // `read_only` covers the role-downgrade race the client-side gate\n // cannot see.\n | BaseMessage<\n typeof MSG.CRON_ACK,\n | { requestId: string; taskName: string; ok: true }\n | {\n requestId: string\n taskName?: string\n ok: false\n reason: 'read_only' | 'unknown_task' | 'failed'\n error?: string\n }\n >\n // ---- Jobs -------------------------------------------------------------\n // `JOB_UPDATE` is the single push channel — same payload shape carries\n // initial-snapshot, enqueue, progress, success, failure, cancel, and\n // retry events. `kind` discriminates client-side. See useJobs.\n | BaseMessage<\n typeof MSG.JOB_UPDATE,\n {\n kind: 'snapshot' | 'enqueued' | 'progress' | 'succeeded' | 'failed' | 'canceled' | 'retried'\n job?: unknown\n jobs?: unknown[]\n /** Echoes the enqueue requestId when the update is the enqueue ack. */\n requestId?: string\n }\n >\n // ---- Presence ---------------------------------------------------------\n | BaseMessage<typeof MSG.PRESENCE_SYNC, { peers: unknown[] }>\n | BaseMessage<typeof MSG.PRESENCE_JOIN, { peer: unknown }>\n | BaseMessage<typeof MSG.PRESENCE_LEAVE, { userId: string }>\n | BaseMessage<\n typeof MSG.PRESENCE_UPDATE,\n { userId: string; state: Record<string, unknown> }\n >\n // ---- Gateway ----------------------------------------------------------\n | BaseMessage<\n typeof MSG.GW_SCOPE_ERROR,\n { scopeType: string; scopeId: string; error: string }\n >\n | BaseMessage<typeof MSG.GW_USER_UPDATE, unknown>\n\n/**\n * Any wire message, regardless of direction. Use this only when the code\n * path genuinely doesn't care — otherwise pick `ClientMessage` or\n * `ServerMessage` so the handler's payload type is narrower.\n */\nexport type ProtocolMessage = ClientMessage | ServerMessage\n\n// =============================================================================\n// Typed builders\n// =============================================================================\n//\n// Each builder returns a `{ type, payload }` object typed as a specific arm\n// of `ClientMessage` / `ServerMessage`. Call sites that hand the result to\n// `JSON.stringify` ship exactly what the wire protocol expects.\n//\n// `as const` on every return preserves the literal string `type` value\n// (e.g. `\"game.input\"`), which is what the discriminated-union narrowing\n// in `dispatch` keys on. Without it TS widens to `string` and the handler\n// map collapses to a single untyped key.\n// =============================================================================\n\n/** Factories for every client → server message. */\nexport const clientBuild = {\n // Records / CRUD\n subscribe: (subscriptionId: string, query: unknown) =>\n ({ type: MSG.SUBSCRIBE, payload: { subscriptionId, query } }) as const,\n unsubscribe: (subscriptionId: string) =>\n ({ type: MSG.UNSUBSCRIBE, payload: { subscriptionId } }) as const,\n put: (\n collection: string,\n recordId: string,\n data: Record<string, unknown>,\n requestId?: string,\n ) =>\n ({\n type: MSG.PUT,\n payload: { collection, recordId, data, requestId },\n }) as const,\n remove: (collection: string, recordId: string, requestId?: string) =>\n ({ type: MSG.DELETE, payload: { collection, recordId, requestId } }) as const,\n // Users\n userList: () => ({ type: MSG.USER_LIST, payload: EMPTY }) as const,\n setRole: (userId: string, role: string) =>\n ({ type: MSG.SET_ROLE, payload: { userId, role } }) as const,\n userUpdate: () => ({ type: MSG.USER_UPDATE, payload: EMPTY }) as const,\n listSchemas: () => ({ type: MSG.LIST_SCHEMAS, payload: EMPTY }) as const,\n // Yjs\n yjsJoin: (collection: string, recordId: string, fieldName: string) =>\n ({ type: MSG.YJS_JOIN, payload: { collection, recordId, fieldName } }) as const,\n yjsLeave: (collection: string, recordId: string, fieldName: string) =>\n ({ type: MSG.YJS_LEAVE, payload: { collection, recordId, fieldName } }) as const,\n // Canvas — `CANVAS_ADD` / `CANVAS_VIEWPORT` payloads are flat (the shape\n // or viewport object is the whole payload).\n canvasAdd: (shape: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_ADD, payload: shape }) as const,\n canvasMove: (shapeId: string, x: number, y: number) =>\n ({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }) as const,\n canvasResize: (\n shapeId: string,\n width: number,\n height: number,\n x?: number,\n y?: number,\n ) =>\n ({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }) as const,\n canvasDelete: (shapeId: string) =>\n ({ type: MSG.CANVAS_DELETE, payload: { shapeId } }) as const,\n canvasUpdate: (shapeId: string, props: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }) as const,\n canvasViewport: (viewport: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_VIEWPORT, payload: viewport }) as const,\n canvasUndo: () => ({ type: MSG.CANVAS_UNDO, payload: EMPTY }) as const,\n canvasRedo: () => ({ type: MSG.CANVAS_REDO, payload: EMPTY }) as const,\n // Cron\n cronTrigger: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_TRIGGER, payload: { taskName, requestId } }) as const,\n cronPause: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_PAUSE, payload: { taskName, requestId } }) as const,\n cronResume: (taskName: string, requestId?: string) =>\n ({ type: MSG.CRON_RESUME, payload: { taskName, requestId } }) as const,\n // Jobs\n jobEnqueue: (\n requestId: string,\n type: string,\n payload?: unknown,\n maxAttempts?: number,\n ) =>\n ({\n type: MSG.JOB_ENQUEUE,\n payload: { requestId, type, payload, maxAttempts },\n }) as const,\n jobCancel: (jobId: string) =>\n ({ type: MSG.JOB_CANCEL, payload: { jobId } }) as const,\n jobRetry: (jobId: string) =>\n ({ type: MSG.JOB_RETRY, payload: { jobId } }) as const,\n // Presence\n presenceUpdate: (state: Record<string, unknown>) =>\n ({ type: MSG.PRESENCE_UPDATE, payload: state }) as const,\n // Gateway\n gwScopeConnect: (scopeType: string, scopeId: string) =>\n ({ type: MSG.GW_SCOPE_CONNECT, payload: { scopeType, scopeId } }) as const,\n gwScopeDisconnect: (scopeType: string, scopeId: string) =>\n ({ type: MSG.GW_SCOPE_DISCONNECT, payload: { scopeType, scopeId } }) as const,\n gwTokenRefresh: (token: string) =>\n ({ type: MSG.GW_TOKEN_REFRESH, payload: { token } }) as const,\n}\n\n/** Factories for every server → client message. Used by room DOs and\n * handler modules so server broadcasts are payload-checked too. Opaque\n * slots (`record`, `shape`, `peer`, etc.) take `unknown` so concrete\n * domain types — `RecordResult`, `CanvasShape`, `MediaPeer` — can be\n * passed without casts. */\nexport const serverBuild = {\n // Records / CRUD\n queryResult: (subscriptionId: string, records: unknown[]) =>\n ({ type: MSG.QUERY_RESULT, payload: { subscriptionId, records } }) as const,\n recordChange: (\n collection: string,\n record: unknown,\n changeType: 'create' | 'update' | 'delete',\n ) =>\n ({ type: MSG.RECORD_CHANGE, payload: { collection, record, changeType } }) as const,\n error: (error: string, subscriptionId?: string) =>\n ({ type: MSG.ERROR, payload: { error, subscriptionId } }) as const,\n ackSuccess: (requestId: string, recordId?: string) =>\n ({\n type: MSG.ACK,\n payload: { requestId, success: true as const, recordId },\n }) as const,\n ackFailure: (requestId: string, error: string) =>\n ({\n type: MSG.ACK,\n payload: { requestId, success: false as const, error },\n }) as const,\n resubscribe: () => ({ type: MSG.RESUBSCRIBE, payload: EMPTY }) as const,\n schemas: (schemas: unknown) =>\n ({ type: MSG.LIST_SCHEMAS, payload: { schemas } }) as const,\n // Users\n userInfo: (user: unknown) =>\n ({ type: MSG.USER_INFO, payload: user }) as const,\n userList: (users: unknown[]) =>\n ({ type: MSG.USER_LIST, payload: { users } }) as const,\n // Yjs\n yjsJoin: (collection: string, recordId: string, fieldName: string, canWrite: boolean) =>\n ({\n type: MSG.YJS_JOIN,\n payload: { collection, recordId, fieldName, canWrite },\n }) as const,\n // Canvas\n canvasShapes: (shapes: unknown[], viewports: unknown[]) =>\n ({ type: MSG.CANVAS_SHAPES, payload: { shapes, viewports } }) as const,\n canvasAdd: (shape: unknown) =>\n ({ type: MSG.CANVAS_ADD, payload: { shape } }) as const,\n canvasMove: (shapeId: string, x: number, y: number) =>\n ({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }) as const,\n canvasResize: (\n shapeId: string,\n width: number,\n height: number,\n x?: number,\n y?: number,\n ) =>\n ({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }) as const,\n canvasDelete: (shapeId: string) =>\n ({ type: MSG.CANVAS_DELETE, payload: { shapeId } }) as const,\n canvasUpdate: (shapeId: string, props: Record<string, unknown>) =>\n ({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }) as const,\n canvasViewport: (viewport: unknown) =>\n ({ type: MSG.CANVAS_VIEWPORT, payload: { viewport } }) as const,\n canvasViewportRemoved: (userId: string) =>\n ({\n type: MSG.CANVAS_VIEWPORT,\n payload: { userId, removed: true as const },\n }) as const,\n // Cron\n cronTasks: (tasks: unknown) =>\n ({ type: MSG.CRON_TASKS, payload: { tasks } }) as const,\n cronHistory: (history: unknown) =>\n ({ type: MSG.CRON_HISTORY, payload: { history } }) as const,\n cronStatus: (tasks: unknown, recentHistory: unknown) =>\n ({ type: MSG.CRON_STATUS, payload: { tasks, recentHistory } }) as const,\n cronAckSuccess: (requestId: string, taskName: string) =>\n ({\n type: MSG.CRON_ACK,\n payload: { requestId, taskName, ok: true as const },\n }) as const,\n cronAckFailure: (\n requestId: string,\n reason: 'read_only' | 'unknown_task' | 'failed',\n error?: string,\n taskName?: string,\n ) =>\n ({\n type: MSG.CRON_ACK,\n payload: { requestId, taskName, ok: false as const, reason, error },\n }) as const,\n // Jobs\n jobSnapshot: (jobs: unknown[]) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'snapshot' as const, jobs } }) as const,\n jobEnqueued: (job: unknown, requestId?: string) =>\n ({\n type: MSG.JOB_UPDATE,\n payload: { kind: 'enqueued' as const, job, requestId },\n }) as const,\n jobProgress: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'progress' as const, job } }) as const,\n jobSucceeded: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'succeeded' as const, job } }) as const,\n jobFailed: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'failed' as const, job } }) as const,\n jobCanceled: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'canceled' as const, job } }) as const,\n jobRetried: (job: unknown) =>\n ({ type: MSG.JOB_UPDATE, payload: { kind: 'retried' as const, job } }) as const,\n // Presence\n presenceSync: (peers: unknown[]) =>\n ({ type: MSG.PRESENCE_SYNC, payload: { peers } }) as const,\n presenceJoin: (peer: unknown) =>\n ({ type: MSG.PRESENCE_JOIN, payload: { peer } }) as const,\n presenceLeave: (userId: string) =>\n ({ type: MSG.PRESENCE_LEAVE, payload: { userId } }) as const,\n presenceUpdate: (userId: string, state: Record<string, unknown>) =>\n ({ type: MSG.PRESENCE_UPDATE, payload: { userId, state } }) as const,\n // Gateway\n gwScopeError: (scopeType: string, scopeId: string, error: string) =>\n ({\n type: MSG.GW_SCOPE_ERROR,\n payload: { scopeType, scopeId, error },\n }) as const,\n gwUserUpdate: (user: unknown) =>\n ({ type: MSG.GW_USER_UPDATE, payload: user }) as const,\n}\n\n// =============================================================================\n// Typed dispatch\n// =============================================================================\n//\n// `dispatch` parses a raw WebSocket frame (or takes an already-parsed\n// object) and routes it to the right handler. Handlers are indexed by the\n// message's string `type`, and TS narrows the `payload` argument to the\n// correct arm of the message union automatically thanks to the\n// discriminated-union lookup.\n//\n// The generic `M` defaults to `ProtocolMessage` so ad-hoc uses get the\n// full SDK vocabulary for free, but apps can pass a narrower (or extended)\n// union:\n//\n// dispatch<ServerMessage>(raw, {\n// })\n//\n// // With an app-defined message (use a STRING LITERAL for the type\n// // parameter — `string` widens the union and kills narrowing):\n// type MyMsg =\n// | ServerMessage\n// | BaseMessage<'myapp.boss_spawn', { hp: number }>\n// dispatch<MyMsg>(raw, {\n// 'myapp.boss_spawn': (p) => { console.log(p.hp) },\n// })\n//\n// =============================================================================\n\n/**\n * Handler map keyed by a message's string `type`. Every key maps to the\n * payload type of the matching arm in `M`, so handlers get automatic\n * payload narrowing with no runtime checks.\n *\n * Narrowing requires each `M` arm to use a string-literal `T`; widening\n * `M` to `BaseMessage<string, unknown>` collapses all keys to a single\n * untyped `string` and the map becomes a plain `Record<string, (p: unknown)\n * => void>`.\n */\nexport type MessageHandlers<M extends BaseMessage<string, unknown> = ProtocolMessage> = {\n [K in M as K['type']]?: (payload: K['payload']) => void\n}\n\n/**\n * Parse and route an incoming wire message. Accepts either the raw JSON\n * string (directly from `ws.onmessage`'s `event.data`) or an already-\n * parsed object. Returns `true` if a handler ran, `false` otherwise\n * (unrecognised type, parse failure, or malformed envelope).\n *\n * Defaults `M` to `ProtocolMessage` so callers who don't care about\n * direction can just do `dispatch(raw, handlers)`; narrow with\n * `dispatch<ClientMessage>` / `dispatch<ServerMessage>` to restrict which\n * arms the handler map is allowed to cover.\n */\nexport function dispatch<M extends BaseMessage<string, unknown> = ProtocolMessage>(\n raw: unknown,\n handlers: MessageHandlers<M>,\n): boolean {\n let msg: { type?: unknown; payload?: unknown }\n if (typeof raw === 'string') {\n try {\n msg = JSON.parse(raw) as { type?: unknown; payload?: unknown }\n } catch {\n return false\n }\n } else if (raw && typeof raw === 'object') {\n msg = raw as { type?: unknown; payload?: unknown }\n } else {\n return false\n }\n if (typeof msg.type !== 'string') return false\n const handler = (handlers as Record<string, ((payload: unknown) => void) | undefined>)[\n msg.type\n ]\n if (!handler) return false\n handler(msg.payload)\n return true\n}\n\n/**\n * Serialise a typed-built message for `ws.send`. A tiny wrapper around\n * `JSON.stringify` kept around so call sites read as\n * `ws.send(encode(clientBuild.gameInput(...)))` rather than paying\n * attention to stringify arguments.\n */\nexport function encode<M extends BaseMessage<string, unknown>>(message: M): string {\n return JSON.stringify(message)\n}\n","/**\n * Record operation handlers for RecordRoom (PUT/DELETE)\n *\n * All collections use table-mode storage (c_* tables with typed columns).\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { RecordResult, PutPayload, DeletePayload } from '../../shared/types'\nimport type { ToolResult } from '../utils/tools'\nimport { DEFAULT_DELETE_WHERE_LIMIT, MAX_DELETE_WHERE_LIMIT } from '../utils/tools'\nimport { serverBuild } from '../../shared/protocol/messages'\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\nimport {\n type CollectionSchema,\n type ResolvedColumn,\n canCreate,\n canUpdate,\n canDelete,\n canRead,\n checkFieldPermissions,\n checkUnclaimedOwnerTransition,\n SYSTEM_ASSIGNED_COLUMNS,\n SYSTEM_MANAGED_COLUMNS,\n coerceValue,\n resolveColumn,\n columnId,\n collectionTableName,\n rowToData,\n dataToColumnValues,\n buildTableSelect,\n epochSeconds,\n} from '../schemas/registry'\nimport {\n broadcastChange,\n executeQuery,\n resolveCollection,\n type SubscriptionContext,\n} from './subscriptions'\n\nexport interface RecordContext extends SubscriptionContext {\n state: DurableObjectState\n}\n\n/**\n * Get resolved columns for a schema.\n */\nfunction getResolvedColumns(schema: CollectionSchema): ResolvedColumn[] {\n return (schema.columns ?? []).map(resolveColumn)\n}\n\n/**\n * \"Now\", in the representation this column's own `storage` declares: epoch\n * seconds for a `number` column, an ISO string for a `text` one.\n *\n * The trigger used to write ISO whatever the storage was, and `coerceValue`\n * then ran `parseFloat('2026-08-18T…')` over it — so a `storage: 'number'`\n * column silently stored the year, `2026`, with tsc, eslint and the schema\n * lint all clean. Writing the declared representation removes the failure\n * mode instead of warning about it.\n */\nfunction triggerTimestamp(col: ResolvedColumn): string | number {\n return col.storage === 'number' ? epochSeconds() : new Date().toISOString()\n}\n\n/** Refusal text for a record write that tries to set a system-assigned column. */\nfunction systemManagedColumnError(column: string, isUpdate: boolean): string {\n const roleHint =\n column === 'role' ? ', or the admin-gated useUsers().setRole() to change a role' : ''\n // On an update there is a current value to echo, and echoing it succeeds. On\n // a create there is none, and no value at all is accepted — pointing at a\n // \"current value\" would send the caller looking for something that does not\n // exist. The row itself is registration's to make.\n const remedy = isUpdate\n ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.`\n : `A users row is created by registration, so no value for '${column}' is accepted here — omit it.`\n return (\n `FIELD ERROR: '${column}' is system-managed and cannot be set by a record write — ` +\n `nothing was written. Use tools.registerUser({ userId, isAdmin })${roleHint}. ${remedy}`\n )\n}\n\n// ============================================================================\n// Team Membership Change Detection\n// ============================================================================\n\n/**\n * When a team_members record is created/updated/deleted, the affected user's\n * team-scoped subscriptions (teams, tasks, projects, etc.) are stale — the\n * initial query was filtered by their old team list. Send MSG.RESUBSCRIBE to\n * tell the client to re-subscribe all active queries with fresh team data.\n */\nfunction notifyTeamMembershipChange(\n ctx: RecordContext,\n collection: string,\n record: RecordResult,\n): void {\n if (collection !== 'team_members') return\n\n const affectedUserId = record.data.UserId as string\n if (!affectedUserId) return\n\n const webSockets = ctx.state.getWebSockets()\n for (const ws of webSockets) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n if (attachment.userId === affectedUserId) {\n ctx.send(ws, serverBuild.resubscribe())\n }\n }\n}\n\n// ============================================================================\n// Row ↔ Data Mapping\n// ============================================================================\n\ninterface TableRow {\n _row_id: string\n _created_by: string\n _created_at: string\n _updated_at: string\n [key: string]: unknown\n}\n\nfunction tableRowToRecord(\n row: TableRow,\n columns: ResolvedColumn[],\n): {\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n} {\n return {\n data: rowToData(row, columns),\n createdBy: row._created_by,\n createdAt: row._created_at,\n updatedAt: row._updated_at,\n }\n}\n\n// ============================================================================\n// Get Record\n// ============================================================================\n\n/**\n * Get a single record from its c_* table.\n */\nexport function getRecord(\n sql: SqlStorage,\n collection: string,\n recordId: string,\n schema?: CollectionSchema,\n): {\n data: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n} | null {\n if (!schema || !schema.columns) {\n // System collections with empty columns — query with just system columns\n const tbl = collectionTableName(collection)\n try {\n const cursor = sql.exec(`SELECT * FROM \"${tbl}\" WHERE _row_id = ?`, recordId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return {\n data: {},\n createdBy: row._created_by,\n createdAt: row._created_at,\n updatedAt: row._updated_at,\n }\n } catch {\n return null\n }\n }\n\n const columns = getResolvedColumns(schema)\n const selectSql = buildTableSelect(collection, columns) + ` WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, recordId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return tableRowToRecord(row, columns)\n}\n\n/**\n * Handle PUT (create/update) record request via WebSocket.\n * Thin wrapper around putRecord() — translates ToolResult errors to WS messages.\n */\nexport function handlePut(\n ctx: RecordContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: PutPayload,\n): void {\n const { collection, recordId, data, requestId } = payload\n const result = putRecord(ctx, collection, recordId, data, attachment.userId, attachment.role)\n\n if (requestId) {\n ctx.send(\n ws,\n result.success\n ? serverBuild.ackSuccess(requestId, recordId)\n : serverBuild.ackFailure(requestId, result.error),\n )\n } else if (!result.success) {\n ctx.send(ws, serverBuild.error(result.error))\n }\n}\n\n/**\n * Handle DELETE record request via WebSocket.\n * Thin wrapper around deleteRecord() — translates ToolResult errors to WS messages.\n */\nexport function handleDelete(\n ctx: RecordContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: DeletePayload,\n): void {\n const { collection, recordId, requestId } = payload\n const result = deleteRecord(ctx, collection, recordId, attachment.userId, attachment.role)\n\n if (requestId) {\n ctx.send(\n ws,\n result.success\n ? serverBuild.ackSuccess(requestId)\n : serverBuild.ackFailure(requestId, result.error),\n )\n } else if (!result.success) {\n ctx.send(ws, serverBuild.error(result.error))\n }\n}\n\n// ============================================================================\n// Shared Record Operations (used by both WebSocket handlers and Tools API)\n// ============================================================================\n\n/**\n * Put (create/update) a record. Returns ToolResult instead of sending WS messages.\n * Performs schema validation, RBAC checks, and broadcasts changes.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions\n * that have already been authorized at the app level.\n * @param systemUpdate - When true, also skip system-managed field stripping.\n * Used for server-initiated updates to system fields (e.g. user profile sync).\n */\nexport function putRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n data: Record<string, unknown>,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n systemUpdate = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n\n // A write needs declared columns, so a schema-less system collection is not\n // writable through this path even though it resolves.\n const schema = resolved.schema\n if (!schema) {\n return { success: false, error: `Schema not registered for collection: ${collection}` }\n }\n\n const columns = getResolvedColumns(schema)\n for (const col of columns) {\n if (col.id !== col.name && Object.hasOwn(data, col.id)) {\n return {\n success: false,\n error: `FIELD ERROR: physical column id '${col.id}' is not a writable field; use '${col.name}'`,\n }\n }\n }\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n const isUpdate = existing !== null\n\n const mergedData = isUpdate ? { ...existing!.data, ...data } : { ...data }\n\n // Enforce column-level field behaviors\n for (const col of columns) {\n if (col.readonly) continue\n\n if (!isUpdate) {\n // CREATE: apply defaults, userBound, required checks\n if (col.default !== undefined && mergedData[col.name] === undefined) {\n mergedData[col.name] = col.default\n }\n if (col.userBound) {\n mergedData[col.name] = userId\n }\n if (\n col.required &&\n (mergedData[col.name] === undefined ||\n mergedData[col.name] === null ||\n mergedData[col.name] === '')\n ) {\n return { success: false, error: `Required field '${col.name}' is missing` }\n }\n } else {\n // UPDATE: enforce immutable, preserve userBound\n if (\n col.immutable &&\n data[col.name] !== undefined &&\n data[col.name] !== existing!.data[col.name]\n ) {\n return { success: false, error: `Cannot modify immutable field '${col.name}'` }\n }\n if (col.userBound && data[col.name] !== undefined) {\n mergedData[col.name] = existing!.data[col.name] // preserve original\n }\n }\n\n // timestampTrigger: auto-set timestamp when trigger field changes\n if (col.timestampTrigger) {\n const { field: triggerField, value: triggerValue } = col.timestampTrigger\n if (!isUpdate) {\n // CREATE: set timestamp if trigger field is present and matches value (if specified)\n if (mergedData[triggerField] !== undefined && mergedData[triggerField] !== null) {\n if (triggerValue === undefined || mergedData[triggerField] === triggerValue) {\n mergedData[col.name] = triggerTimestamp(col)\n }\n }\n } else {\n // UPDATE: set timestamp if trigger field changed (and optionally to specified value)\n const oldVal = existing!.data[triggerField]\n const newVal = mergedData[triggerField]\n if (newVal !== oldVal) {\n if (triggerValue === undefined || newVal === triggerValue) {\n mergedData[col.name] = triggerTimestamp(col)\n }\n }\n }\n }\n }\n\n const finalData = { ...mergedData }\n\n // RBAC checks\n if (!skipUserRbac) {\n const permCtx = ctx.getPermissionContext()\n if (isUpdate) {\n if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {\n return {\n success: false,\n error: `UPDATE DENIED: role=${userRole}, collection=${collection}`,\n }\n }\n } else {\n if (!canCreate(schema, userRole)) {\n return {\n success: false,\n error: `CREATE DENIED: role=${userRole}, collection=${collection}`,\n }\n }\n }\n\n // Check only caller-supplied fields. Defaults, user-bound values, and\n // timestamp triggers are server-owned and must not trip writableFields.\n const fieldError = checkFieldPermissions(\n schema,\n userRole,\n data,\n isUpdate ? existing.data : undefined,\n )\n if (fieldError) {\n return { success: false, error: `FIELD ERROR: ${fieldError}` }\n }\n\n const ownerError = checkUnclaimedOwnerTransition(schema, userRole, finalData, userId)\n if (ownerError) {\n return { success: false, error: `FIELD ERROR: ${ownerError}` }\n }\n }\n\n // System-managed columns on `users` belong to registerUser, not to ordinary\n // record writes, so none of them is ever written from here. How a supplied\n // value is answered depends on whether the caller could have chosen it:\n //\n // - SYSTEM_ASSIGNED (email, name, imageUrl, role): a different value is a\n // change the caller means and will not get. Refuse loudly instead of\n // returning `{ success: true }` over a silently discarded write.\n // - SYSTEM_MAINTAINED (createdAt, lastSeenAt): the server moves these on\n // its own without broadcasting, so a stale echo is an artifact of\n // read-modify-write, not an attempted change. Preserve silently.\n //\n // \"Different\" means different *once written*: `coerceValue` is what decides\n // the stored column, and it maps undefined and null alike to NULL — the two\n // spellings of \"absent\" are one state, so comparing anything but the coerced\n // values refuses writes that would not change a byte. `''` is not one of\n // them on a text column: it is a value, and setting a system-assigned column\n // to it is a change the caller means and will not get.\n if (!systemUpdate && schema.name === 'users') {\n for (const col of columns) {\n if (!SYSTEM_MANAGED_COLUMNS.has(col.name)) continue\n const current = existing?.data[col.name]\n if (\n SYSTEM_ASSIGNED_COLUMNS.has(col.name) &&\n Object.hasOwn(data, col.name) &&\n coerceValue(data[col.name], col.storage, col.interpretation) !==\n coerceValue(current, col.storage, col.interpretation)\n ) {\n return { success: false, error: systemManagedColumnError(col.name, isUpdate) }\n }\n if (current !== undefined)\n finalData[col.name] = current // preserve existing\n else delete finalData[col.name]\n }\n }\n\n const colValues = dataToColumnValues(finalData, columns)\n const now = new Date().toISOString()\n const tbl = collectionTableName(collection)\n\n // Check the same resolved columns and coerced values as the UNIQUE index so\n // every write path returns a normal ToolResult instead of leaking a SQLite\n // constraint exception. Updates exclude the row being written.\n if (schema.uniqueOn && schema.uniqueOn.length > 0) {\n const uniqueColumns = schema.uniqueOn.map((fieldName) => {\n const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName)\n return { id, value: colValues[id] ?? null }\n })\n const uniqueWhere = uniqueColumns.map(({ id }) => `\"${id}\" = ?`)\n const uniqueParams = uniqueColumns.map(({ value }) => value)\n if (isUpdate) uniqueParams.push(recordId)\n const conflict = ctx.sql.exec(\n `SELECT _row_id FROM \"${tbl}\" WHERE ${uniqueWhere.join(' AND ')}${isUpdate ? ' AND _row_id <> ?' : ''} LIMIT 1`,\n ...uniqueParams,\n )\n if (conflict.toArray().length > 0) {\n const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? 'null'}`).join(', ')\n return {\n success: false,\n error: `Duplicate: a record with ${fields} already exists in ${collection}`,\n }\n }\n }\n\n if (isUpdate) {\n const setClauses: string[] = [`_updated_at = ?`]\n const params: unknown[] = [now]\n for (const [colId, val] of Object.entries(colValues)) {\n setClauses.push(`\"${colId}\" = ?`)\n params.push(val)\n }\n params.push(recordId)\n ctx.sql.exec(`UPDATE \"${tbl}\" SET ${setClauses.join(', ')} WHERE _row_id = ?`, ...params)\n } else {\n const colIds = Object.keys(colValues)\n const allCols = [\n '_row_id',\n '_created_by',\n '_created_at',\n '_updated_at',\n ...colIds.map((c) => `\"${c}\"`),\n ]\n const placeholders = allCols.map(() => '?').join(', ')\n const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])]\n ctx.sql.exec(`INSERT INTO \"${tbl}\" (${allCols.join(', ')}) VALUES (${placeholders})`, ...params)\n }\n\n // Read back to get computed columns and canonical values\n const saved = getRecord(ctx.sql, collection, recordId, schema)!\n const record: RecordResult = {\n recordId,\n data: saved.data,\n createdBy: saved.createdBy,\n createdAt: saved.createdAt,\n updatedAt: saved.updatedAt,\n }\n broadcastChange(ctx, ctx.state, collection, record, isUpdate ? 'update' : 'create')\n notifyTeamMembershipChange(ctx, collection, record)\n\n return { success: true, data: { recordId, record } }\n}\n\n/**\n * Delete a record. Returns ToolResult instead of sending WS messages.\n * Performs RBAC check and broadcasts the deletion.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function deleteRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n if (!existing) {\n return { success: false, error: RECORD_NOT_FOUND }\n }\n\n if (\n !skipUserRbac &&\n schema &&\n !canDelete(schema, userRole, { ...existing, recordId }, userId, ctx.getPermissionContext())\n ) {\n return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` }\n }\n\n const tbl = collectionTableName(collection)\n ctx.sql.exec(`DELETE FROM \"${tbl}\" WHERE _row_id = ?`, recordId)\n\n const record: RecordResult = {\n recordId,\n data: existing.data,\n createdBy: existing.createdBy,\n createdAt: existing.createdAt,\n updatedAt: existing.updatedAt,\n }\n broadcastChange(ctx, ctx.state, collection, record, 'delete')\n notifyTeamMembershipChange(ctx, collection, record)\n\n return { success: true, data: { deleted: true } }\n}\n\n/**\n * The one `where` validator for the tools API's filtered reads and deletes.\n * `executeQuery` is lenient — a key that names no column is silently dropped\n * — which for a delete truncates and for a read over-returns (a typo'd\n * filter hands the whole readable collection back with `success: true`, and\n * an assistant or action then reasons over it as the filtered set). A `where`\n * must be a plain object; every key must be `recordId`, `createdBy`, or a\n * schema column; every value a primitive (equality only). `null` = nothing to\n * refuse.\n *\n * The shape check lives here rather than at each caller because an array\n * `where` degrades differently at every one of them: `[]` passed the query\n * path's emptiness test and filtered nothing, `['a']` was reported as an\n * unknown field named \"0\", and only `deleteWhere` named the real problem.\n *\n * `schema` is undefined only for a schemaless system collection — and its\n * query path (`executeSystemQuery`) ignores `where` entirely, so ANY filter\n * there would over-return the whole collection as if filtered. Refuse every\n * non-empty `where` on it and say why.\n */\nexport function refuseUnknownWhere(\n collection: string,\n schema: CollectionSchema | undefined,\n where: unknown,\n): ToolResult | null {\n const columns = (schema?.columns ?? []).map(resolveColumn)\n const filterable = ['recordId', 'createdBy', ...columns.map((c) => c.name)]\n if (typeof where !== 'object' || where === null || Array.isArray(where)) {\n const got = where === null ? 'null' : Array.isArray(where) ? 'an array' : typeof where\n return {\n success: false,\n error:\n `where for \"${collection}\" must be an object of field=value pairs, not ${got} — ` +\n `filterable fields: ${filterable.join(', ')}`,\n }\n }\n if (!schema && Object.keys(where).length > 0) {\n return {\n success: false,\n error: `\"${collection}\" has no schema, so it has no filterable fields — query it without \\`where\\``,\n }\n }\n const unknown = Object.keys(where).filter(\n (key) =>\n key !== 'recordId' &&\n key !== 'createdBy' &&\n !columns.some((c) => c.id === columnId(key) || c.name === key),\n )\n if (unknown.length > 0) {\n return {\n success: false,\n error: `Unknown field(s) in where for \"${collection}\": ${unknown.join(', ')} — filterable fields: ${filterable.join(', ')}`,\n }\n }\n const nonPrimitive = Object.entries(where).filter(([, v]) => v !== null && typeof v === 'object')\n if (nonPrimitive.length > 0) {\n return {\n success: false,\n error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(', ')}`,\n }\n }\n return null\n}\n\n/**\n * Delete every record matching `where`, capped at one bounded page.\n *\n * The batch primitive behind cascading deletes: a caller that deleted row by\n * row spent one DO subrequest per row and ran out of subrequest budget on a\n * large set, orphaning the rest. One call handles a page; the caller repeats\n * it while `deleted` equals the page size (each page really removes rows, so\n * the loop terminates).\n *\n * Authorization is exactly `records.delete`'s: every matched record is checked\n * with `canDelete` *before* anything is removed, so a denied row refuses the\n * whole page rather than leaving a half-applied batch.\n *\n * `where` is validated strictly here because `executeQuery` is lenient — it\n * silently drops a key that names no column, and ignores `where` altogether on\n * a schemaless system collection. A read that ignores a filter over-returns; a\n * delete that ignores one truncates. So every key must name a real field\n * (`recordId`, `createdBy`, or a schema column), the collection must have a\n * schema, and `limit` must be a finite number — anything else is refused, and\n * collection-wide truncation is not reachable through this tool.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function deleteWhere(\n ctx: RecordContext,\n collection: string,\n where: unknown,\n limit: unknown,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n if (!schema) {\n return {\n success: false,\n error: `records.deleteWhere: collection \"${collection}\" has no schema, so it has no filterable fields — delete its rows by recordId with records.delete`,\n }\n }\n\n const whereRefusal = refuseUnknownWhere(collection, schema, where)\n if (whereRefusal) return whereRefusal\n if (Object.keys(where as Record<string, unknown>).length === 0) {\n return {\n success: false,\n error: 'Missing required param: where (must match at least one field)',\n }\n }\n\n if (limit !== undefined && !(typeof limit === 'number' && Number.isFinite(limit))) {\n return { success: false, error: 'limit must be a finite number' }\n }\n const page = Math.min(\n Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),\n MAX_DELETE_WHERE_LIMIT,\n )\n // Candidate selection is storage-only. A delete permission must not inherit\n // the role's read permission; every candidate is authorized exactly once by\n // canDelete below, matching records.delete.\n const matched = executeQuery(\n ctx,\n { collection, where: where as Record<string, unknown>, limit: page },\n userId,\n userRole,\n true,\n )\n\n if (!skipUserRbac) {\n const permCtx = ctx.getPermissionContext()\n for (const record of matched) {\n if (!canDelete(schema, userRole, record, userId, permCtx)) {\n return {\n success: false,\n error: `DELETE DENIED: role=${userRole}, collection=${collection}`,\n }\n }\n }\n }\n\n let deleted = 0\n for (const record of matched) {\n // Authorization for the whole page completed above. Do not run read or\n // delete policy a second time while applying the already-approved set.\n if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {\n deleted++\n }\n }\n if (deleted < matched.length) {\n // A short page must mean \"no more matches\", never \"some deletes failed\" —\n // a drain loop reading it as exhaustion would silently orphan the rest.\n return {\n success: false,\n error: `Deleted ${deleted} of ${matched.length} matched record(s) in \"${collection}\"; the rest failed — retry`,\n }\n }\n return { success: true, data: { deleted } }\n}\n\n/**\n * Read a single record with RBAC check. Returns ToolResult.\n *\n * @param skipUserRbac - When true, skip user role checks. Used by server actions.\n */\nexport function readRecord(\n ctx: RecordContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n skipUserRbac = false,\n): ToolResult {\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n const schema = resolved.schema\n\n const existing = getRecord(ctx.sql, collection, recordId, schema)\n if (!existing) {\n return { success: false, error: RECORD_NOT_FOUND }\n }\n\n if (\n !skipUserRbac &&\n schema &&\n !canRead(schema, userRole, { ...existing, recordId }, userId, ctx.getPermissionContext())\n ) {\n return { success: false, error: `READ DENIED: role=${userRole}, collection=${collection}` }\n }\n\n const record: RecordResult = {\n recordId,\n data: existing.data,\n createdBy: existing.createdBy,\n createdAt: existing.createdAt,\n updatedAt: existing.updatedAt,\n }\n\n return { success: true, data: { record } }\n}\n","/**\n * Subscription handlers for RecordRoom\n *\n * All collections use table-mode storage (c_* tables with typed columns).\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { Query, RecordResult, SubscribePayload, UnsubscribePayload } from '../../shared/types'\nimport { MSG } from '../../shared/protocol/constants'\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n canRead,\n getRolePermissions,\n resolveColumn,\n buildTableSelect,\n rowToData,\n columnId,\n collectionTableName,\n} from '../schemas/registry'\nimport { SYSTEM_COLLECTIONS } from './yjs'\n\nexport interface SubscriptionContext {\n sql: SqlStorage\n schemaRegistry: SchemaRegistry\n state: DurableObjectState // Needed to get all connected WebSockets\n /**\n * The scope this room was addressed by — `app:{appId}`, `chat:{channelId}`,\n * `conv:{convId}`, `dir:{appId}`, … Records are sharded across rooms\n * by scope, so the scope is what names the room in an unknown-collection\n * failure. Undefined only for rooms addressed by a unique id rather than a\n * name (`idFromName` populates `DurableObjectId.name`).\n */\n scopeId?: string\n getPermissionContext(): PermissionContext\n /** Typed against `ServerMessage` so outbound broadcasts are\n * compile-checked against the wire contract. */\n send(ws: WebSocket, message: ServerMessage): void\n}\n\n/**\n * Outcome of resolving a collection name against the room's schema set.\n * `schema` is undefined only for a system collection registered without\n * columns, which the query path serves from system columns alone.\n */\nexport type CollectionResolution =\n | { ok: true; schema: CollectionSchema | undefined; isSystem: boolean }\n | { ok: false; error: string }\n\n/**\n * Resolve a collection name against the collections this room actually knows.\n *\n * The single place that decides whether a room can serve a collection at all.\n * Records are sharded across Durable Object rooms by scope, so asking a room\n * for a collection it was never given is a routing mistake, not an empty\n * result — the failure names the collection, the room, and what that room\n * does serve so the caller can fix the scope instead of debugging silence.\n *\n * The `Schema not registered for collection: <name>` prefix is load-bearing:\n * the scaffolded Yjs route probes for an optional `documents` collection with\n * `startsWith` on it. Extend the message, never reword the prefix.\n */\nexport function resolveCollection(\n ctx: SubscriptionContext,\n collection: string,\n): CollectionResolution {\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n const schema = ctx.schemaRegistry.get(collection)\n\n if (!schema && !isSystem) {\n const known = ctx.schemaRegistry.names().sort().join(', ') || '(none)'\n const room = ctx.scopeId ? `Room '${ctx.scopeId}'` : 'This room'\n return {\n ok: false,\n error:\n `Schema not registered for collection: ${collection} — ` +\n `${room} serves only: ${known}. Records are sharded by scope, so ` +\n `address the room that owns '${collection}', or add it to the ` +\n `schemas this room is constructed with.`,\n }\n }\n\n return { ok: true, schema, isSystem }\n}\n\n/**\n * Handle a new subscription request.\n *\n * We no longer store subscriptions server-side - broadcasts go to all clients\n * and they filter locally. This avoids hibernation issues.\n */\nexport function handleSubscribe(\n ctx: SubscriptionContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: SubscribePayload\n): void {\n const { subscriptionId, query } = payload\n\n // Execute query and send initial results\n const records = executeQuery(ctx, query, attachment.userId, attachment.role)\n ctx.send(ws, { type: MSG.QUERY_RESULT, payload: { subscriptionId, records } })\n}\n\n/**\n * Handle unsubscribe request.\n *\n * Since we no longer store subscriptions server-side, this is a no-op.\n * The client handles unsubscription locally.\n */\nexport function handleUnsubscribe(\n _ctx: SubscriptionContext,\n _ws: WebSocket,\n _attachment: ConnectionAttachment,\n _payload: UnsubscribePayload\n): void {\n // No-op - we don't store subscriptions server-side anymore\n}\n\n/**\n * Execute a query and return matching records.\n * All collections use table-mode (c_* tables).\n *\n * `skipUserRbac` lets a server-action caller (i.e. the app itself, via\n * `X-App-Action`) bypass the per-user read filter for parity with the other\n * `tools.*` operations (`get`, `create`, `update`, `remove`).\n */\n/** The scan budget for one limited, row-filtered query call — the bound that\n * keeps `limit` a bound on work when unreadable rows dominate a table. The\n * scan stops on the first batch boundary at or past this, so the exact worst\n * case is `MAX + batch − 1` rows where `batch = max(limit, 200)` — i.e.\n * O(cap + limit), and a `limit` above the cap is honored as asked. */\nexport const MAX_FILTERED_SCAN_ROWS = 5_000\n\n/** Out-param for {@link executeQuery}: `capped` is set when a limited,\n * row-filtered scan stopped on {@link MAX_FILTERED_SCAN_ROWS} rather than on\n * exhausting the rows — the one case a short result does NOT mean \"no more\n * matches\". */\nexport interface QueryScanState {\n capped: boolean\n}\n\nexport function executeQuery(\n ctx: SubscriptionContext,\n query: Query,\n userId: string,\n userRole: string,\n skipUserRbac: boolean = false,\n scanState?: QueryScanState,\n): RecordResult[] {\n const resolved = resolveCollection(ctx, query.collection)\n\n // Row-returning callers (WS subscribe, the debug query view) contract on an\n // array, so an unknown collection degrades to \"no rows\" here. The surfaces\n // that report success/failure to a caller — the `records.*` tools and the\n // record write/read operations — reject it at `resolveCollection` first, so\n // no tool call reaches this line with a collection the room does not know.\n if (!resolved.ok) return []\n\n if (!resolved.schema) {\n // System collection without schema — query the c_* table directly\n return executeSystemQuery(ctx, query)\n }\n\n return executeTableQuery(\n ctx,\n query,\n resolved.schema,\n userId,\n userRole,\n resolved.isSystem,\n skipUserRbac,\n scanState,\n )\n}\n\n/**\n * Query a system collection (no schema, just system columns).\n */\nfunction executeSystemQuery(\n ctx: SubscriptionContext,\n query: Query,\n): RecordResult[] {\n const tbl = collectionTableName(query.collection)\n let sql = `SELECT _row_id, _created_by, _created_at, _updated_at FROM \"${tbl}\"`\n const params: unknown[] = []\n\n sql += ` ORDER BY _created_at DESC`\n\n if (query.limit) {\n sql += ` LIMIT ?`\n params.push(query.limit)\n }\n\n try {\n const cursor = ctx.sql.exec(sql, ...params)\n return cursor.toArray().map((row: unknown) => {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string }\n return {\n recordId: r._row_id,\n data: {},\n createdBy: r._created_by,\n createdAt: r._created_at,\n updatedAt: r._updated_at,\n }\n })\n } catch {\n return []\n }\n}\n\n/**\n * Preload the current user's team IDs from the c_team_members collection table.\n * Returns null if no team_members collection schema is registered (rooms without teams).\n */\nfunction preloadUserTeamIds(ctx: SubscriptionContext, userId: string): string[] | null {\n const tmSchema = ctx.schemaRegistry.get('team_members')\n if (!tmSchema?.columns?.length) return null\n\n const cursor = ctx.sql.exec(\n `SELECT col_teamid FROM c_team_members WHERE col_userid = ? AND (col_status = 'active' OR col_status IS NULL)`,\n userId\n )\n return cursor.toArray().map(r => (r as Record<string, unknown>).col_teamid as string)\n}\n\nfunction executeTableQuery(\n ctx: SubscriptionContext,\n query: Query,\n schema: CollectionSchema,\n userId: string,\n userRole: string,\n isSystem: boolean,\n skipUserRbac: boolean = false,\n scanState?: QueryScanState,\n): RecordResult[] {\n const columns = (schema.columns ?? []).map(resolveColumn)\n const perms = getRolePermissions(schema, userRole)\n\n let sql = buildTableSelect(query.collection, columns)\n const params: unknown[] = []\n const whereClauses: string[] = []\n\n // SQL pushdown for team-scoped reads: filter at the SQL level instead\n // of fetching all rows and checking team membership per-record.\n // Skipped entirely when the caller is an app action bypassing user RBAC.\n let skipPerRecordCheck = skipUserRbac\n if (!skipUserRbac && schema.teamField && perms.read === 'team') {\n // '_rowId' is a sentinel: the record's own ID is the team ID (e.g. teams table)\n // Push down only when the field resolves to a real column — a synthesized\n // id for a misdeclared field would throw `no such column` out of SQL,\n // where the per-row check merely (and correctly) matches nothing.\n const filterCol = schema.teamField === '_rowId'\n ? '_row_id'\n : columns.find(c => c.name === schema.teamField)?.id\n const teamIds = filterCol ? preloadUserTeamIds(ctx, userId) : null\n\n if (filterCol) {\n if (teamIds && teamIds.length > 0) {\n const placeholders = teamIds.map(() => '?').join(', ')\n whereClauses.push(`(\"${filterCol}\" IN (${placeholders}) OR _created_by = ?)`)\n params.push(...teamIds, userId)\n } else {\n whereClauses.push(`_created_by = ?`)\n params.push(userId)\n }\n skipPerRecordCheck = true\n }\n } else if (!skipUserRbac && perms.read === 'own') {\n // Same shape as the team pushdown. 'own' is exactly `isOwner`: the\n // declared ownerField when there is one, else the row's creator — so the\n // SQL predicate is the whole check and the per-row pass can be skipped.\n const ownerCol = schema.ownerField\n ? columns.find(c => c.name === schema.ownerField)?.id\n : '_created_by'\n if (ownerCol) {\n whereClauses.push(`\"${ownerCol}\" = ?`)\n params.push(userId)\n skipPerRecordCheck = true\n }\n } else if (!skipUserRbac && perms.read === true) {\n // Unconditional read: the per-row check cannot refuse anything, and\n // skipping it makes `limit` safe to push into SQL.\n skipPerRecordCheck = true\n }\n\n if (query.where) {\n for (const [fieldName, value] of Object.entries(query.where)) {\n const colId = columnId(fieldName)\n const col = columns.find(c => c.id === colId || c.name === fieldName)\n if (col) {\n whereClauses.push(`\"${col.id}\" = ?`)\n params.push(value)\n } else if (fieldName === 'recordId') {\n whereClauses.push(`_row_id = ?`)\n params.push(value)\n } else if (fieldName === 'createdBy') {\n whereClauses.push(`_created_by = ?`)\n params.push(value)\n }\n }\n }\n\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`\n }\n\n if (query.orderBy) {\n const dir = query.orderDir === 'asc' ? 'ASC' : 'DESC'\n if (query.orderBy === 'createdAt') {\n sql += ` ORDER BY _created_at ${dir}`\n } else if (query.orderBy === 'updatedAt') {\n sql += ` ORDER BY _updated_at ${dir}`\n } else {\n const colId = columnId(query.orderBy)\n const col = columns.find(c => c.id === colId || c.name === query.orderBy)\n if (col) {\n sql += ` ORDER BY \"${col.id}\" ${dir}`\n }\n }\n } else {\n sql += ` ORDER BY _created_at DESC`\n }\n\n const readPage = (limitClause: string, limitParams: number[]): { records: RecordResult[]; scanned: number } => {\n const cursor = ctx.sql.exec(sql + limitClause, ...params, ...limitParams)\n const rows = cursor.toArray()\n const records: RecordResult[] = []\n for (const row of rows) {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string; [key: string]: unknown }\n const record: RecordResult = {\n recordId: r._row_id,\n data: rowToData(r, columns),\n createdBy: r._created_by,\n createdAt: r._created_at,\n updatedAt: r._updated_at,\n }\n if (isSystem || skipPerRecordCheck || canRead(schema, userRole, { data: record.data, createdBy: record.createdBy, recordId: record.recordId }, userId, ctx.getPermissionContext())) {\n records.push(record)\n }\n }\n return { records, scanned: rows.length }\n }\n\n // `limit` counts the records the caller receives. When the SQL predicates\n // are the whole check it pushes straight into SQL; when a per-row read\n // filter still applies, a SQL LIMIT would end the result early (a page of\n // unreadable rows reads as \"no more matches\"), so scan in bounded batches\n // until the limit is met, the rows run out, or the scan cap is hit — the\n // cap keeps `limit` a bound on work, and `scanState.capped` makes hitting\n // it observable so a caller's drain loop cannot read a capped short page\n // as exhaustion.\n if (!query.limit) return readPage('', []).records\n const rowFiltered = !isSystem && !skipPerRecordCheck\n if (!rowFiltered) return readPage(' LIMIT ?', [query.limit]).records\n\n const results: RecordResult[] = []\n const batch = Math.max(query.limit, 200)\n for (let offset = 0; ; offset += batch) {\n const { records, scanned } = readPage(' LIMIT ? OFFSET ?', [batch, offset])\n for (const record of records) {\n results.push(record)\n if (results.length >= query.limit) return results\n }\n if (scanned < batch) return results\n if (offset + batch >= MAX_FILTERED_SCAN_ROWS) {\n // A full final page leaves \"capped\" and \"exhausted exactly on the\n // boundary\" indistinguishable; one single-row probe settles it, so\n // `capped` never fires on a table that simply ended here.\n if (scanState && readPage(' LIMIT ? OFFSET ?', [1, offset + batch]).scanned > 0) {\n scanState.capped = true\n }\n return results\n }\n }\n}\n\n/**\n * Check if a record matches a subscription's query and permissions\n */\nexport function recordMatchesSubscription(\n record: { recordId: string; data: Record<string, unknown>; createdBy: string },\n collection: string,\n query: Query,\n userId: string,\n userRole: string,\n schema: CollectionSchema,\n ctx: PermissionContext\n): boolean {\n // Collection must match\n if (query.collection !== collection) return false\n\n // Permission check (includes collaborator/team)\n if (!canRead(schema, userRole, { data: record.data, createdBy: record.createdBy, recordId: record.recordId }, userId, ctx)) {\n return false\n }\n\n // Filter check (equality only)\n if (query.where) {\n for (const [field, value] of Object.entries(query.where)) {\n if (record.data[field] !== value) return false\n }\n }\n\n return true\n}\n\n/**\n * Broadcast a record change to all connected clients who can read it.\n *\n * Instead of matching subscriptions (which are lost on hibernation),\n * we broadcast to ALL clients and include the collection name.\n * The client filters based on its local subscriptions.\n */\nexport function broadcastChange(\n ctx: SubscriptionContext,\n state: DurableObjectState,\n collection: string,\n record: RecordResult,\n changeType: 'create' | 'update' | 'delete'\n): void {\n const isSystem = SYSTEM_COLLECTIONS.has(collection)\n const schema = ctx.schemaRegistry.get(collection)\n\n if (!schema && !isSystem) return // Schema not loaded yet\n\n const webSockets = state.getWebSockets()\n\n for (const ws of webSockets) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n\n // System collections broadcast to all; others check permissions\n const canBroadcast = isSystem || recordMatchesSubscription(\n record, collection, { collection }, attachment.userId, attachment.role, schema!, ctx.getPermissionContext()\n )\n\n if (canBroadcast) {\n ctx.send(ws, { type: MSG.RECORD_CHANGE, payload: { collection, record, changeType } })\n }\n }\n}\n","/**\n * User management handlers for RecordRoom\n *\n * Users are stored in the c_users table (table-mode).\n * System-managed fields (email, name, role, etc.) can only be set by registerUser().\n */\n\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { CollectionSchema, SetRolePayload } from '../../shared/types'\nimport { MSG, isAnonymousUserId } from '../../shared/protocol/constants'\nimport {\n canRead,\n getRolePermissions,\n type PermissionContext,\n type User,\n type ResolvedColumn,\n SchemaRegistry,\n resolveColumn,\n buildTableSelect,\n rowToData,\n} from '../schemas/registry'\nimport { putRecord, type RecordContext } from './records'\n\nexport interface UserContext {\n sql: SqlStorage\n state: DurableObjectState\n schemaRegistry: SchemaRegistry\n getPermissionContext(): PermissionContext\n send(ws: WebSocket, message: { type: string; payload: unknown }): void\n}\n\n/**\n * User data stored in the users collection.\n * Core fields are system-managed, apps can add their own fields.\n */\ninterface UserRecord {\n // System-managed fields\n email: string\n name: string\n imageUrl?: string\n role: string\n createdAt: string\n lastSeenAt: string\n // App-specific fields are dynamic\n [key: string]: unknown\n}\n\ninterface TableRow {\n _row_id: string\n _created_by: string\n _created_at: string\n _updated_at: string\n [key: string]: unknown\n}\n\n/**\n * Get resolved columns for the users schema.\n */\nfunction getUsersColumns(schemaRegistry: SchemaRegistry): ResolvedColumn[] {\n const schema = schemaRegistry.get('users')\n return (schema?.columns ?? []).map(resolveColumn)\n}\n\n/**\n * Convert a c_users row to User type (core fields only).\n */\nfunction rowToUser(row: TableRow, columns: ResolvedColumn[]): User {\n const data = rowToData(row, columns)\n return {\n id: row._row_id,\n email: (data.email as string) || '',\n name: (data.name as string) || '',\n imageUrl: data.imageUrl as string | undefined,\n role: (data.role as string) || 'viewer',\n createdAt: (data.createdAt as string) || row._created_at,\n lastSeenAt: (data.lastSeenAt as string) || row._updated_at,\n }\n}\n\n/**\n * Convert a c_users row to full UserRecord (including app-specific fields).\n */\nfunction rowToUserRecord(row: TableRow, columns: ResolvedColumn[]): UserRecord {\n const data = rowToData(row, columns)\n return {\n email: (data.email as string) || '',\n name: (data.name as string) || '',\n imageUrl: data.imageUrl as string | undefined,\n role: (data.role as string) || 'viewer',\n createdAt: (data.createdAt as string) || row._created_at,\n lastSeenAt: (data.lastSeenAt as string) || row._updated_at,\n ...data,\n }\n}\n\n/**\n * Get a single user by ID from the c_users table.\n */\nexport function getUser(sql: SqlStorage, userId: string, schemaRegistry?: SchemaRegistry): User | null {\n // If we have the schema registry, use table-mode columns for proper field mapping\n if (schemaRegistry) {\n const columns = getUsersColumns(schemaRegistry)\n const selectSql = buildTableSelect('users', columns) + ` WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, userId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return rowToUser(row, columns)\n }\n // Fallback: direct column query with known core column names\n const cursor = sql.exec(\n `SELECT * FROM c_users WHERE _row_id = ?`,\n userId\n )\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n return {\n id: row._row_id,\n email: (row.col_email as string) || '',\n name: (row.col_name as string) || '',\n imageUrl: row.col_imageurl as string | undefined,\n role: (row.col_role as string) || 'viewer',\n createdAt: (row.col_createdat as string) || row._created_at,\n lastSeenAt: (row.col_lastseenat as string) || row._updated_at,\n }\n}\n\n/**\n * Get full user record including app-specific fields.\n */\nexport function getUserRecord(sql: SqlStorage, userId: string, schemaRegistry?: SchemaRegistry): { data: UserRecord; createdBy: string } | null {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` WHERE _row_id = ?`\n : `SELECT * FROM c_users WHERE _row_id = ?`\n const cursor = sql.exec(selectSql, userId)\n const row = cursor.toArray()[0] as unknown as TableRow | undefined\n if (!row) return null\n\n return {\n data: columns.length > 0 ? rowToUserRecord(row, columns) : {\n email: (row.col_email as string) || '',\n name: (row.col_name as string) || '',\n imageUrl: row.col_imageurl as string | undefined,\n role: (row.col_role as string) || 'viewer',\n createdAt: (row.col_createdat as string) || row._created_at,\n lastSeenAt: (row.col_lastseenat as string) || row._updated_at,\n },\n createdBy: row._created_by,\n }\n}\n\n/**\n * Get all users from the c_users table.\n */\nexport function getAllUsers(sql: SqlStorage, schemaRegistry?: SchemaRegistry): User[] {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM c_users ORDER BY _created_at DESC`\n const cursor = sql.exec(selectSql)\n return cursor.toArray().map((row: unknown) => {\n const r = row as TableRow\n if (columns.length > 0) return rowToUser(r, columns)\n return {\n id: r._row_id,\n email: (r.col_email as string) || '',\n name: (r.col_name as string) || '',\n imageUrl: r.col_imageurl as string | undefined,\n role: (r.col_role as string) || 'viewer',\n createdAt: (r.col_createdat as string) || r._created_at,\n lastSeenAt: (r.col_lastseenat as string) || r._updated_at,\n }\n })\n}\n\n/**\n * Get all user records including app-specific fields.\n */\nexport function getAllUserRecords(sql: SqlStorage, schemaRegistry?: SchemaRegistry): Array<{ recordId: string; data: UserRecord; createdBy: string }> {\n const columns = schemaRegistry ? getUsersColumns(schemaRegistry) : []\n const selectSql = columns.length > 0\n ? buildTableSelect('users', columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM c_users ORDER BY _created_at DESC`\n const cursor = sql.exec(selectSql)\n return cursor.toArray().map((row: unknown) => {\n const r = row as TableRow\n return {\n recordId: r._row_id,\n createdBy: r._created_by,\n data: columns.length > 0 ? rowToUserRecord(r, columns) : {\n email: (r.col_email as string) || '',\n name: (r.col_name as string) || '',\n imageUrl: r.col_imageurl as string | undefined,\n role: (r.col_role as string) || 'viewer',\n createdAt: (r.col_createdat as string) || r._created_at,\n lastSeenAt: (r.col_lastseenat as string) || r._updated_at,\n },\n }\n })\n}\n\n/**\n * Register or update a user in the c_users table.\n *\n * This is the ONLY way to set system-managed fields (email, name, role, etc.).\n * Normal mutations via handlePut will reject changes to system-managed fields.\n *\n * Role derivation (in order of priority):\n * 1. isAdmin=true (global admin, canvas owner, or app owner) → always 'admin'\n * 2. Existing role in users collection (preserved)\n * 3. Default role (configurable per-app, defaults to 'member')\n *\n * This allows each miniapp to define its own role hierarchy while\n * ensuring admins and owners always have full access.\n */\nexport async function registerUser(\n sql: SqlStorage,\n userId: string,\n name: string,\n email: string,\n imageUrl: string | undefined,\n isAdmin: boolean,\n defaultRole: string = 'viewer',\n schemaRegistry?: SchemaRegistry\n): Promise<User> {\n const now = new Date().toISOString()\n\n // Get existing user record\n const existing = getUserRecord(sql, userId, schemaRegistry)\n\n // Derive role:\n // 1. DeepSpace global admins are ALWAYS admin\n // 2. Otherwise, preserve existing role\n // 3. Fall back to default role\n let role: string\n if (isAdmin) {\n role = 'admin'\n } else if (existing?.data.role) {\n role = existing.data.role\n } else {\n role = defaultRole\n }\n\n if (existing) {\n // Update existing user - only update system-managed columns.\n // When connecting with token-only (no profile), name='Anonymous' and email=''.\n // Don't overwrite real values with defaults.\n const updatedEmail = email || existing.data.email\n const updatedName = (name && name !== 'Anonymous') ? name : existing.data.name\n const updatedImageUrl = imageUrl ?? existing.data.imageUrl\n\n sql.exec(\n `UPDATE c_users SET col_email = ?, col_name = ?, col_imageurl = ?, col_role = ?, col_lastseenat = ?, _updated_at = ? WHERE _row_id = ?`,\n updatedEmail, updatedName, updatedImageUrl ?? null, role, now, now, userId\n )\n\n return {\n id: userId,\n email: updatedEmail,\n name: updatedName,\n imageUrl: updatedImageUrl,\n role,\n createdAt: existing.data.createdAt,\n lastSeenAt: now\n }\n }\n\n // Create new user record\n sql.exec(\n `INSERT INTO c_users (_row_id, _created_by, _created_at, _updated_at, col_email, col_name, col_imageurl, col_role, col_createdat, col_lastseenat) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n userId, userId, now, now, email, name, imageUrl ?? null, role, now, now\n )\n\n return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now }\n}\n\ntype UserRow = { recordId: string; data: UserRecord; createdBy: string }\ntype PublicIdentity = {\n id: string\n name: string\n imageUrl?: string\n role: string\n /** Presence is a public fact — `usePresence().isOnline` derives from it, so\n * dropping it from the projection left presence dead for every non-admin. */\n lastSeenAt: string\n}\n\n/**\n * What one caller may see of the roster. Anonymous callers and roles whose\n * users policy is switched off (`read: false`, or no `read` at all — the same\n * \"deny\" `canRead` uses) get nothing: that is a policy statement, not a row\n * filter. Admins get whole rows — email and any app-defined column — so their\n * read still goes through the schema's row policy. Everyone else gets the\n * public-identity projection of the whole roster: a row-level policy (`'own'`,\n * `'team'`, a predicate) guards full-row reads on the records/query path, and\n * nothing it protects survives this projection — applying it here left every\n * member of a `read: 'own'` room (every `chat:*` room, since messaging ships\n * no users schema) with a roster of just themselves and peers rendered as\n * \"Unknown\".\n */\nfunction rosterFor(\n ctx: UserContext,\n schema: CollectionSchema,\n records: UserRow[],\n publicRoster: PublicIdentity[],\n userId: string,\n role: string,\n): Array<Record<string, unknown>> {\n // No identity at all (the tools API without `X-User-Id`) is the same\n // statement as an anonymous socket: not someone the directory is for.\n if (!userId || isAnonymousUserId(userId)) return []\n if (role === 'admin') {\n const permissionContext = ctx.getPermissionContext()\n return records\n .filter((record) => canRead(schema, 'admin', record, userId, permissionContext))\n .map((record) => ({ id: record.recordId, ...record.data }))\n }\n if (!getRolePermissions(schema, role).read) return []\n // `roster: 'read-policy'` opts a tenant/team-scoped app out of the shared\n // roster: only the rows the caller's read policy grants, still projected.\n if (schema.roster === 'read-policy') {\n const permissionContext = ctx.getPermissionContext()\n const readable = new Set(\n records\n .filter((record) => canRead(schema, role, record, userId, permissionContext))\n .map((record) => record.recordId),\n )\n return publicRoster.filter((user) => readable.has(user.id))\n }\n return publicRoster\n}\n\nfunction publicIdentities(records: UserRow[]): PublicIdentity[] {\n return records.map(({ recordId, data }) => ({\n id: recordId,\n name: data.name,\n ...(data.imageUrl ? { imageUrl: data.imageUrl } : {}),\n role: data.role,\n lastSeenAt: data.lastSeenAt,\n }))\n}\n\n/**\n * The roster this caller may see, read fresh.\n *\n * The socket's `user.list` request and the `user.list` tool are one question\n * asked over two transports, so both answer from here. A tool that read the\n * users table directly handed every member the directory the room's users\n * policy — `read: false`, `roster: 'read-policy'` — exists to withhold.\n *\n * Public rooms support anonymous sockets, but that must not turn the users\n * table into a public directory; a room with no users schema has none.\n */\nexport function rosterForCaller(\n ctx: UserContext,\n userId: string,\n role: string,\n): Array<Record<string, unknown>> {\n const schema = ctx.schemaRegistry.get('users')\n if (!schema) return []\n const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry)\n return rosterFor(ctx, schema, records, publicIdentities(records), userId, role)\n}\n\n/** Handle user list requests without bypassing the users collection's privacy boundary. */\nexport function handleUserList(\n ctx: UserContext,\n ws: WebSocket,\n attachment: ConnectionAttachment\n): void {\n const users = rosterForCaller(ctx, attachment.userId, attachment.role)\n ctx.send(ws, { type: MSG.USER_LIST, payload: { users } })\n}\n\n/**\n * Push the current roster to every connected socket (except `except`). The\n * client asks for `user.list` once per connection and never again, so anything\n * that changes what the roster shows — a user registering, a rename, a new\n * avatar, a role change — must push, or every open tab keeps rendering the\n * peer it has never seen as \"Unknown\" until it reconnects. The roster is read\n * and projected once; each socket then gets its own view of it.\n */\nexport function broadcastUserList(\n ctx: UserContext,\n except?: WebSocket | ReadonlySet<WebSocket>,\n): void {\n const schema = ctx.schemaRegistry.get('users')\n if (!schema) return\n const skip = except instanceof Set ? except : new Set(except ? [except] : [])\n const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry)\n const publicRoster = publicIdentities(records)\n for (const other of ctx.state.getWebSockets()) {\n if (skip.has(other)) continue\n const attachment = other.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) continue\n ctx.send(other, {\n type: MSG.USER_LIST,\n payload: {\n users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role),\n },\n })\n }\n}\n\n/**\n * Handle user profile update.\n *\n * Called when the client's profile loads after the initial WS connection.\n * Updates the user's name/email/imageUrl in c_users and pushes the updated\n * roster to every connected client so names refresh in real time.\n */\nexport interface UserUpdatePayload {\n name?: string\n email?: string\n imageUrl?: string\n}\n\nexport function handleUserUpdate(\n ctx: RecordContext,\n _ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: UserUpdatePayload\n): void {\n const existing = getUserRecord(ctx.sql, attachment.userId, ctx.schemaRegistry)\n if (!existing) return\n\n const data: Record<string, unknown> = { lastSeenAt: new Date().toISOString() }\n if (payload.name && payload.name !== 'Anonymous') data.name = payload.name\n if (payload.email) data.email = payload.email\n if (payload.imageUrl !== undefined) data.imageUrl = payload.imageUrl || undefined\n\n // systemUpdate=true bypasses system-managed field stripping so we can\n // write to name/email/imageUrl. broadcastChange fires automatically,\n // updating any useQuery('users') subscriptions on connected clients; the\n // `user.list` roster (`useUsers()`) is a separate request/response and\n // needs the explicit push.\n putRecord(ctx, 'users', attachment.userId, data, attachment.userId, 'admin', true, true)\n const rosterChanged =\n (data.name !== undefined && data.name !== existing.data.name) ||\n (data.imageUrl !== undefined && data.imageUrl !== existing.data.imageUrl)\n if (rosterChanged) broadcastUserList(ctx)\n}\n\n/**\n * Handle set role request (admin only).\n * Updates the role field in the c_users table.\n */\nexport async function handleSetRole(\n ctx: UserContext,\n ws: WebSocket,\n attachment: ConnectionAttachment,\n payload: SetRolePayload\n): Promise<void> {\n if (attachment.role !== 'admin') {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'Admin access required' } })\n return\n }\n\n const existing = getUserRecord(ctx.sql, payload.userId, ctx.schemaRegistry)\n if (!existing) {\n ctx.send(ws, { type: MSG.ERROR, payload: { error: 'User not found' } })\n return\n }\n\n // Update role in the c_users table\n const now = new Date().toISOString()\n ctx.sql.exec(\n `UPDATE c_users SET col_role = ?, _updated_at = ? WHERE _row_id = ?`,\n payload.role, now, payload.userId\n )\n\n // Close the changed user's live sockets. Reconnect runs the ordinary role\n // lookup again, so no connection keeps permissions from before the change.\n const closing = new Set<WebSocket>()\n for (const otherWs of ctx.state.getWebSockets()) {\n const otherAttachment = otherWs.deserializeAttachment() as ConnectionAttachment | null\n if (otherAttachment?.userId !== payload.userId) continue\n closing.add(otherWs)\n try {\n otherWs.close(1008, 'role-changed')\n } catch {\n // Already closing.\n }\n }\n\n // Then push the roster to everyone else — one read, each socket its own\n // projection — so their tabs see the new role instead of holding the stale\n // one until reconnect. The closed sockets are skipped by identity rather\n // than trusted to drop the frame.\n broadcastUserList(ctx, closing)\n}\n","/**\n * Tools API HTTP handlers for RecordRoom\n *\n * Provides an HTTP interface for agent tool calls (records, schemas, users).\n *\n * Caller identity is supplied via HTTP headers:\n *\n * X-User-Id: <userId> — identifies the caller (required for any\n * tool that touches user-bound data).\n * X-App-Action: 'true' — bypass user RBAC because the app's\n * server-side code is already the trust\n * boundary. Used by server actions and\n * cron jobs; unsafe to pass from clients.\n *\n * The userId is looked up in the users collection to derive the caller's\n * role. All operations go through the same RBAC checks as the WebSocket\n * path unless flagged as an app action.\n */\n\nimport * as Y from 'yjs'\nimport type { ToolResult } from '../utils/tools'\nimport { BUILT_IN_TOOLS } from '../utils/tools'\nimport type { YjsDocKey } from '../../shared/types'\nimport { RECORD_NOT_FOUND } from '../../shared/protocol/constants'\nimport { executeQuery, resolveCollection, type SubscriptionContext } from './subscriptions'\nimport {\n getRecord,\n putRecord,\n deleteRecord,\n deleteWhere,\n refuseUnknownWhere,\n readRecord,\n type RecordContext,\n} from './records'\nimport { getUser, getAllUsers, registerUser, rosterForCaller } from './users'\nimport {\n getOrCreateYjsDoc,\n unloadYjsDocIfUnused,\n broadcastYjsUpdate,\n getYjsDocKey,\n SYSTEM_COLLECTIONS,\n type YjsContext,\n} from './yjs'\nimport { canRead, canUpdate } from '../schemas/registry'\n\nexport interface ToolsApiContext extends SubscriptionContext {\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n sendBinary: (ws: WebSocket, data: Uint8Array) => void\n ownerUserId?: string\n}\n\nconst CORS_HEADERS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, X-User-Id, X-App-Action',\n 'Content-Type': 'application/json',\n}\n\n/**\n * Handle /tools/ API requests.\n * Called from handleApiRequest when path starts with 'tools/'.\n */\nexport async function handleToolsRequest(\n ctx: ToolsApiContext,\n request: Request,\n path: string,\n): Promise<Response> {\n if (request.method === 'OPTIONS') {\n return new Response(null, { headers: CORS_HEADERS })\n }\n\n // GET /tools/list — return all tool schemas\n if (path === 'tools/list') {\n return Response.json({ tools: BUILT_IN_TOOLS }, { headers: CORS_HEADERS })\n }\n\n // GET /tools/describe/:toolName — return single tool schema\n if (path.startsWith('tools/describe/')) {\n const toolName = path.replace('tools/describe/', '')\n const tool = BUILT_IN_TOOLS.find((t) => t.name === toolName)\n if (!tool) {\n return Response.json(\n { error: `Tool not found: ${toolName}` },\n { status: 404, headers: CORS_HEADERS },\n )\n }\n return Response.json({ tool }, { headers: CORS_HEADERS })\n }\n\n // POST /tools/execute — execute a tool\n // Path may include a trailing roomId (e.g., tools/execute/{roomId}) from the call script,\n // but the DO doesn't need it since the outer worker already routed to the correct DO.\n if (path.startsWith('tools/execute') && request.method === 'POST') {\n return handleToolExecute(ctx, request)\n }\n\n return Response.json({ error: 'Not found' }, { status: 404, headers: CORS_HEADERS })\n}\n\n/**\n * POST /tools/execute\n *\n * Body: { tool: string, params: Record<string, unknown> }\n * Headers: X-User-Id (required for identity-bound tools)\n * X-App-Action: 'true' (optional, bypasses user RBAC)\n */\nasync function handleToolExecute(ctx: ToolsApiContext, request: Request): Promise<Response> {\n let body: { tool: string; params: Record<string, unknown> }\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json(\n { success: false, error: 'Invalid JSON body' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n const { tool, params = {} } = body\n\n if (!tool) {\n return Response.json(\n { success: false, error: 'Missing \"tool\" in request body' },\n { status: 400, headers: CORS_HEADERS },\n )\n }\n\n // Caller identity is an HTTP header. Missing header ⇒ anonymous viewer.\n const userId = request.headers.get('x-user-id') ?? ''\n const isAppAction = request.headers.get('x-app-action')?.toLowerCase() === 'true'\n\n // Look up user role for RBAC. If the caller is the deploy-time owner, grant\n // admin — mirrors the WebSocket path which registers the owner explicitly.\n // The HTTP tools API doesn't go through WebSocket, so the owner may not\n // exist in the users table yet.\n let userRole = 'viewer'\n if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {\n userRole = 'admin'\n } else if (userId) {\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (user) {\n userRole = user.role\n }\n }\n\n // Yjs tools have a dedicated dispatcher because they need Yjs-specific context.\n if (tool.startsWith('yjs.')) {\n const result = executeYjsTool(ctx, tool, params, userId, userRole)\n const status = result.success ? 200 : result.error?.includes('not found') ? 404 : 400\n return Response.json(result, { status, headers: CORS_HEADERS })\n }\n\n const recordCtx: RecordContext = {\n sql: ctx.sql,\n schemaRegistry: ctx.schemaRegistry,\n scopeId: ctx.scopeId,\n state: ctx.state,\n getPermissionContext: ctx.getPermissionContext,\n send: ctx.send,\n }\n\n const result = await executeTool(recordCtx, tool, params, userId, userRole, isAppAction)\n const status = result.success ? 200 : result.error?.includes('not found') ? 404 : 400\n return Response.json(result, { status, headers: CORS_HEADERS })\n}\n\n/**\n * Dispatch a tool call to the appropriate handler.\n */\nasync function executeTool(\n ctx: RecordContext,\n toolName: string,\n params: Record<string, unknown>,\n userId: string,\n userRole: string,\n skipUserRbac: boolean,\n): Promise<ToolResult> {\n switch (toolName) {\n // ---- Records ----\n case 'records.query': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n // An unknown collection is a wrong-room mistake, not an empty table. The\n // other `records.*` tools inherit this from putRecord/readRecord/\n // deleteRecord; the query path is row-returning, so it asks directly.\n const resolved = resolveCollection(ctx, collection)\n if (!resolved.ok) return { success: false, error: resolved.error }\n // A `where` key that names no field is refused, not dropped: dropped, it\n // returned the whole readable collection as if filtered (the same class\n // `records.deleteWhere` refuses). `refuseUnknownWhere` is the one place\n // that decides what a `where` may be — shape included.\n if (params.where !== undefined) {\n const refusal = refuseUnknownWhere(collection, resolved.schema, params.where)\n if (refusal) return refusal\n }\n // Validated above: a plain object of field=value pairs, or absent.\n const where = params.where as Record<string, unknown> | undefined\n // No default `limit` here: this dispatch is the SDK's general record-read\n // path (chat history, cron, app `actions.query`), which must return every\n // row. The assistant's page-size default is applied upstream in the AI\n // tool layer (`applyAiToolDefaults` in `buildTools`).\n const query = {\n collection,\n where,\n orderBy: params.orderBy as string | undefined,\n orderDir: params.orderDir as 'asc' | 'desc' | undefined,\n limit: params.limit as number | undefined,\n }\n const records = executeQuery(ctx, query, userId, userRole, skipUserRbac)\n return { success: true, data: { records, count: records.length } }\n }\n\n case 'records.get': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n return readRecord(ctx, collection, recordId, userId, userRole, skipUserRbac)\n }\n\n case 'records.create': {\n const collection = params.collection as string\n const data = params.data as Record<string, unknown>\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!data) return { success: false, error: 'Missing required param: data' }\n const recordId = (params.recordId as string) || generateId()\n return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac)\n }\n\n case 'records.update': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n const data = params.data as Record<string, unknown>\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n if (!data) return { success: false, error: 'Missing required param: data' }\n return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac)\n }\n\n case 'records.delete': {\n const collection = params.collection as string\n const recordId = params.recordId as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n if (!recordId) return { success: false, error: 'Missing required param: recordId' }\n return deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac)\n }\n\n // Bounded batch delete — one subrequest per page instead of one per row.\n // Same authorization posture as `records.delete`; `deleteWhere` itself\n // validates `where` (every key must name a real field) and `limit`, so no\n // dispatch path can reach a collection-wide truncation.\n case 'records.deleteWhere': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n return deleteWhere(ctx, collection, params.where, params.limit, userId, userRole, skipUserRbac)\n }\n\n // ---- Users ----\n // Bootstrap or refresh the `users` row for a given userId. Same path\n // the SDK uses on WS connect via registerUser, exposed for server\n // contexts (CLI-only callers, action handlers) where there's no WS\n // connection to trigger the natural registration. System-managed\n // columns (email, name, imageUrl, role) are written through\n // registerUser's privileged path, bypassing the SYSTEM_MANAGED\n // stripping that blocks naive tools.create / tools.update.\n case 'users.register': {\n const targetUserId = (params.userId as string) || userId\n if (!targetUserId) return { success: false, error: 'Missing userId' }\n const name = typeof params.name === 'string' ? params.name : ''\n const email = typeof params.email === 'string' ? params.email : ''\n const imageUrl = typeof params.imageUrl === 'string' ? params.imageUrl : undefined\n const isAdmin = params.isAdmin === true\n const user = await registerUser(\n ctx.sql,\n targetUserId,\n name,\n email,\n imageUrl,\n isAdmin,\n 'member',\n ctx.schemaRegistry,\n )\n return { success: true, data: { user } }\n }\n\n // ---- Schema ----\n case 'schema.list': {\n const schemas = ctx.schemaRegistry.all()\n return {\n success: true,\n data: {\n schemas: schemas.map((s) => ({\n name: s.name,\n columns: s.columns,\n permissions: s.permissions,\n ...(s.ownerField ? { ownerField: s.ownerField } : {}),\n ...(s.collaboratorsField ? { collaboratorsField: s.collaboratorsField } : {}),\n ...(s.teamField ? { teamField: s.teamField } : {}),\n })),\n },\n }\n }\n\n case 'schema.describe': {\n const collection = params.collection as string\n if (!collection) return { success: false, error: 'Missing required param: collection' }\n const schema = ctx.schemaRegistry.get(collection)\n if (!schema) return { success: false, error: `Schema not found: ${collection}` }\n return { success: true, data: { schema } }\n }\n\n // ---- Users ----\n case 'user.current': {\n if (!userId) return { success: false, error: 'No userId provided' }\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (!user) return { success: false, error: 'User not found' }\n return { success: true, data: { user } }\n }\n\n // The same roster the socket's `user.list` returns, for the same caller.\n // Reading the users table directly here handed any member every peer's\n // email regardless of the room's users policy, while their own socket\n // roster and `useQuery('users')` obeyed it.\n //\n // An app action (`X-App-Action`) is the app's own server-side code, which\n // is already the trust boundary and reads every other collection RBAC-off;\n // it keeps the full rows.\n case 'user.list': {\n const users = skipUserRbac\n ? getAllUsers(ctx.sql, ctx.schemaRegistry)\n : rosterForCaller(ctx, userId, userRole)\n return { success: true, data: { users } }\n }\n\n default:\n return { success: false, error: `Unknown tool: ${toolName}` }\n }\n}\n\n// ============================================================================\n// Yjs Tool Execution\n// ============================================================================\n\n/**\n * Check RBAC permission for Yjs doc access.\n * System collections are permissive (same as handleYjsJoin).\n * Returns null if permitted, or an error ToolResult if denied.\n */\nfunction checkYjsPermission(\n ctx: ToolsApiContext,\n collection: string,\n recordId: string,\n userId: string,\n userRole: string,\n action: 'read' | 'write',\n): ToolResult | null {\n if (SYSTEM_COLLECTIONS.has(collection)) return null\n\n const resolution = resolveCollection(ctx, collection)\n if (!resolution.ok) return { success: false, error: resolution.error }\n const schema = resolution.schema\n if (!schema)\n return { success: false, error: `Schema not registered for collection: ${collection}` }\n\n const record = getRecord(ctx.sql, collection, recordId, schema)\n if (!record) return { success: false, error: `${RECORD_NOT_FOUND}: ${collection}/${recordId}` }\n\n const recordWithId = { ...record, recordId }\n const permCtx = ctx.getPermissionContext()\n if (action === 'read' && !canRead(schema, userRole, recordWithId, userId, permCtx)) {\n return { success: false, error: 'Permission denied: cannot read this record' }\n }\n if (action === 'write' && !canUpdate(schema, userRole, recordWithId, userId, permCtx)) {\n return { success: false, error: 'Permission denied: cannot update this record' }\n }\n\n return null\n}\n\n/**\n * Build a YjsContext from ToolsApiContext for calling Yjs helpers.\n */\nfunction buildYjsCtx(ctx: ToolsApiContext): YjsContext {\n return {\n sql: ctx.sql,\n state: ctx.state,\n yjsDocs: ctx.yjsDocs,\n schemaRegistry: ctx.schemaRegistry,\n getPermissionContext: ctx.getPermissionContext,\n send: ctx.send,\n sendBinary: ctx.sendBinary,\n }\n}\n\n/** Use a cached document for one synchronous operation, then release it if idle. */\nfunction withYjsDoc<T>(ctx: YjsContext, docKey: YjsDocKey, operation: (doc: Y.Doc) => T): T {\n const doc = getOrCreateYjsDoc(ctx, docKey)\n try {\n return operation(doc)\n } finally {\n unloadYjsDocIfUnused(ctx, docKey)\n }\n}\n\n/** Execute a Yjs tool (yjs.list, yjs.getText, yjs.setText). */\nfunction executeYjsTool(\n ctx: ToolsApiContext,\n toolName: string,\n params: Record<string, unknown>,\n userId: string,\n userRole: string,\n): ToolResult {\n switch (toolName) {\n case 'yjs.list': {\n const rows = ctx.sql\n .exec(`SELECT doc_key, updated_at FROM yjs_docs ORDER BY updated_at DESC`)\n .toArray() as Array<{ doc_key: string; updated_at: string }>\n\n const docs = rows.map((row) => {\n const firstColon = row.doc_key.indexOf(':')\n const lastColon = row.doc_key.lastIndexOf(':')\n return {\n docKey: row.doc_key,\n collection: row.doc_key.slice(0, firstColon),\n recordId: row.doc_key.slice(firstColon + 1, lastColon),\n fieldName: row.doc_key.slice(lastColon + 1),\n updatedAt: row.updated_at,\n }\n })\n return { success: true, data: { docs, count: docs.length } }\n }\n\n case 'yjs.getText': {\n const { collection, recordId, fieldName } = params as {\n collection: string\n recordId: string\n fieldName: string\n }\n if (!collection || !recordId || !fieldName) {\n return { success: false, error: 'Missing required params: collection, recordId, fieldName' }\n }\n\n const permError = checkYjsPermission(ctx, collection, recordId, userId, userRole, 'read')\n if (permError) return permError\n\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const yjsCtx = buildYjsCtx(ctx)\n return withYjsDoc(yjsCtx, docKey, (doc) => {\n const ytext = doc.getText(fieldName)\n const text = ytext.toString()\n\n const sharedTypes: Record<string, string> = {}\n for (const [key, type] of doc.share) {\n if (type instanceof Y.Text) {\n sharedTypes[key || '(default)'] = `Y.Text (${type.length} chars)`\n } else if (type instanceof Y.Map) {\n sharedTypes[key] = `Y.Map (${(type as Y.Map<unknown>).size} entries)`\n } else if (type instanceof Y.Array) {\n sharedTypes[key] = `Y.Array (${(type as Y.Array<unknown>).length} items)`\n }\n }\n\n return { success: true, data: { docKey, text, sharedTypes } }\n })\n }\n\n case 'yjs.setText': {\n const { collection, recordId, fieldName, text } = params as {\n collection: string\n recordId: string\n fieldName: string\n text: string\n }\n if (!collection || !recordId || !fieldName) {\n return { success: false, error: 'Missing required params: collection, recordId, fieldName' }\n }\n if (text === undefined || text === null) {\n return { success: false, error: 'Missing required param: text' }\n }\n\n const permError = checkYjsPermission(ctx, collection, recordId, userId, userRole, 'write')\n if (permError) return permError\n\n const docKey = getYjsDocKey(collection, recordId, fieldName)\n const yjsCtx = buildYjsCtx(ctx)\n return withYjsDoc(yjsCtx, docKey, (doc) => {\n const ytext = doc.getText(fieldName)\n let capturedUpdate: Uint8Array | null = null\n const updateHandler = (update: Uint8Array) => {\n capturedUpdate = update\n }\n doc.on('update', updateHandler)\n\n try {\n doc.transact(() => {\n ytext.delete(0, ytext.length)\n ytext.insert(0, text)\n })\n } finally {\n doc.off('update', updateHandler)\n }\n\n if (capturedUpdate) {\n broadcastYjsUpdate(yjsCtx, docKey, capturedUpdate, null)\n }\n\n return { success: true, data: { docKey, length: text.length } }\n })\n }\n\n default:\n return { success: false, error: `Unknown Yjs tool: ${toolName}` }\n }\n}\n\nfunction generateId(): string {\n return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\n}\n","/**\n * HTTP Debug API handlers for RecordRoom\n */\n\nimport * as Y from 'yjs'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type { Query, YjsDocKey } from '../../shared/types'\nimport {\n resolveColumn,\n buildTableSelect,\n rowToData,\n collectionTableName,\n} from '../schemas/registry'\nimport { executeQuery, type SubscriptionContext } from './subscriptions'\nimport { getAllUsers, getUser } from './users'\nimport { handleToolsRequest } from './tools-api'\n\nexport interface DebugApiContext extends SubscriptionContext {\n state: DurableObjectState\n yjsDocs: Map<YjsDocKey, Y.Doc>\n sendBinary: (ws: WebSocket, data: Uint8Array) => void\n}\n\nconst CORS_HEADERS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n 'Content-Type': 'application/json',\n}\n\n/**\n * Handle HTTP API requests (for debugging)\n */\nexport async function handleApiRequest(\n ctx: DebugApiContext,\n request: Request,\n url: URL\n): Promise<Response> {\n if (request.method === 'OPTIONS') {\n return new Response(null, { headers: CORS_HEADERS })\n }\n\n const path = url.pathname.replace('/api/', '')\n\n // Tools API endpoints\n if (path.startsWith('tools/')) {\n return handleToolsRequest(ctx, request, path)\n }\n\n // Debug status endpoint\n if (path === 'debug/status') {\n return handleDebugStatus(ctx)\n }\n\n // Debug SQL - run arbitrary SQL queries\n if (path === 'debug/sql') {\n return handleDebugSql(ctx, request, url)\n }\n\n // Debug user-role by email endpoint\n if (path === 'debug/user-role') {\n return handleDebugUserRole(ctx, url)\n }\n\n // Debug set-role endpoint — POST { email, role } or { userId, role }\n if (path === 'debug/set-role' && request.method === 'POST') {\n return handleDebugSetRole(ctx, request)\n }\n\n // Debug records endpoint\n if (path.startsWith('debug/records/')) {\n return handleDebugRecords(ctx, path)\n }\n\n // Debug query endpoint\n if (path === 'debug/query') {\n return handleDebugQuery(ctx, url)\n }\n\n return Response.json({ error: 'Not found' }, { status: 404, headers: CORS_HEADERS })\n}\n\n/**\n * GET /api/debug/user-role - Get user role info by email or userId\n *\n * Usage:\n * /api/debug/user-role?email=ctc@gmail.com\n * /api/debug/user-role?userId=user_39JIhFoZkRsPtj5u3dprEOYKgVR\n */\nfunction handleDebugUserRole(ctx: DebugApiContext, url: URL): Response {\n const email = url.searchParams.get('email')\n const userId = url.searchParams.get('userId')\n\n if (!email && !userId) {\n return Response.json({ error: 'email or userId parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n\n const user = userId\n ? users.find(u => u.id === userId)\n : users.find(u => u.email === email)\n\n if (!user) {\n return Response.json({\n error: `User not found`,\n registeredUsers: users.map(u => ({ id: u.id, email: u.email, role: u.role })),\n }, { status: 404, headers: CORS_HEADERS })\n }\n\n return Response.json({\n userId: user.id,\n email: user.email,\n name: user.name,\n role: user.role,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * POST /api/debug/set-role - Change a user's role\n *\n * Body: { email: string, role: string } or { userId: string, role: string }\n *\n * Usage:\n * POST /api/debug/set-role { \"email\": \"ctc@gmail.com\", \"role\": \"viewer\" }\n * POST /api/debug/set-role { \"userId\": \"user_39JIh...\", \"role\": \"admin\" }\n */\nasync function handleDebugSetRole(ctx: DebugApiContext, request: Request): Promise<Response> {\n const body = await request.json() as { email?: string; userId?: string; role?: string }\n\n if (!body.role) {\n return Response.json({ error: 'role is required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!body.email && !body.userId) {\n return Response.json({ error: 'email or userId is required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n const user = body.userId\n ? users.find(u => u.id === body.userId)\n : users.find(u => u.email === body.email)\n\n if (!user) {\n return Response.json({\n error: 'User not found',\n registeredUsers: users.map(u => ({ id: u.id, email: u.email, role: u.role })),\n }, { status: 404, headers: CORS_HEADERS })\n }\n\n // Update role in c_users table\n const now = new Date().toISOString()\n ctx.sql.exec(\n `UPDATE c_users SET col_role = ?, _updated_at = ? WHERE _row_id = ?`,\n body.role, now, user.id\n )\n\n return Response.json({\n userId: user.id,\n email: user.email,\n previousRole: user.role,\n newRole: body.role,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * GET /api/debug/status - Get server status\n */\nfunction handleDebugStatus(ctx: DebugApiContext): Response {\n const users = getAllUsers(ctx.sql, ctx.schemaRegistry)\n const schemas = ctx.schemaRegistry.all()\n const connections: Array<{ userId: string; role: string; subscriptions: number }> = []\n \n for (const ws of ctx.state.getWebSockets()) {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment) {\n connections.push({\n userId: attachment.userId,\n role: attachment.role,\n subscriptions: attachment.subscriptions.length,\n })\n }\n }\n\n return Response.json(\n { users, schemas: schemas.map(s => s.name), connections },\n { headers: CORS_HEADERS }\n )\n}\n\n/**\n * GET /api/debug/records/:collection - Get records in a collection\n */\nfunction handleDebugRecords(ctx: DebugApiContext, path: string): Response {\n const collection = path.replace('debug/records/', '')\n const schema = ctx.schemaRegistry.get(collection)\n const columns = (schema?.columns ?? []).map(resolveColumn)\n\n try {\n const selectSql = columns.length > 0\n ? buildTableSelect(collection, columns) + ` ORDER BY _created_at DESC`\n : `SELECT * FROM \"${collectionTableName(collection)}\" ORDER BY _created_at DESC`\n\n const cursor = ctx.sql.exec(selectSql)\n const records = cursor.toArray().map((row: unknown) => {\n const r = row as { _row_id: string; _created_by: string; _created_at: string; _updated_at: string; [key: string]: unknown }\n return {\n recordId: r._row_id,\n data: columns.length > 0 ? rowToData(r, columns) : {},\n createdBy: r._created_by,\n createdAt: r._created_at,\n }\n })\n return Response.json({ collection, records }, { headers: CORS_HEADERS })\n } catch (error) {\n return Response.json({\n error: error instanceof Error ? error.message : 'Query failed',\n collection,\n }, { status: 400, headers: CORS_HEADERS })\n }\n}\n\n/**\n * GET /api/debug/query - Test query as a specific user\n */\nfunction handleDebugQuery(ctx: DebugApiContext, url: URL): Response {\n const collection = url.searchParams.get('collection')\n const userId = url.searchParams.get('userId')\n const role = url.searchParams.get('role')\n\n if (!collection) {\n return Response.json({ error: 'collection parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!userId) {\n return Response.json({ error: 'userId parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n if (!role) {\n return Response.json({ error: 'role parameter required' }, { status: 400, headers: CORS_HEADERS })\n }\n\n // Get user info\n const user = getUser(ctx.sql, userId, ctx.schemaRegistry)\n if (!user) {\n return Response.json({ error: `User not found: ${userId}` }, { status: 404, headers: CORS_HEADERS })\n }\n\n // Build query from URL params\n const query: Query = { collection }\n \n // Parse where filters from URL (e.g., where.userId=xxx)\n const where: Record<string, unknown> = {}\n for (const [key, value] of url.searchParams) {\n if (key.startsWith('where.')) {\n const field = key.replace('where.', '')\n where[field] = value\n }\n }\n if (Object.keys(where).length > 0) {\n query.where = where\n }\n\n // Execute with specified role (override user's actual role for testing)\n const records = executeQuery(ctx, query, userId, role)\n\n return Response.json({\n query,\n asUser: { id: userId, name: user.name, actualRole: user.role, testingAsRole: role },\n recordCount: records.length,\n records,\n }, { headers: CORS_HEADERS })\n}\n\n/**\n * GET/POST /api/debug/sql - Run arbitrary SQL queries\n * \n * GET: /api/debug/sql?q=SELECT * FROM users\n * POST: { \"sql\": \"SELECT * FROM users\", \"params\": [] }\n * \n * Special queries:\n * - \"SHOW TABLES\" or \".tables\" - list all tables\n * - \"DESCRIBE <table>\" or \".schema <table>\" - show table schema\n */\nasync function handleDebugSql(\n ctx: DebugApiContext,\n request: Request,\n url: URL\n): Promise<Response> {\n let sql: string\n let params: unknown[] = []\n\n if (request.method === 'POST') {\n const body = await request.json() as { sql: string; params?: unknown[] }\n sql = body.sql\n params = body.params ?? []\n } else {\n sql = url.searchParams.get('q') ?? ''\n }\n\n if (!sql.trim()) {\n return Response.json({ \n error: 'SQL query required',\n usage: {\n get: '/api/debug/sql?q=SELECT * FROM users',\n post: '{ \"sql\": \"SELECT * FROM users\", \"params\": [] }',\n special: [\n 'SHOW TABLES - list all tables',\n 'DESCRIBE <table> - show table schema',\n ],\n },\n }, { status: 400, headers: CORS_HEADERS })\n }\n\n const sqlUpper = sql.trim().toUpperCase()\n\n try {\n // Handle special commands\n if (sqlUpper === 'SHOW TABLES' || sqlUpper === '.TABLES') {\n const cursor = ctx.sql.exec(\n `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`\n )\n const tables = cursor.toArray().map((r: unknown) => (r as { name: string }).name)\n return Response.json({ tables }, { headers: CORS_HEADERS })\n }\n\n if (sqlUpper.startsWith('DESCRIBE ') || sqlUpper.startsWith('.SCHEMA ')) {\n const tableName = sql.trim().split(/\\s+/)[1]\n const cursor = ctx.sql.exec(`PRAGMA table_info(${tableName})`)\n const columns = cursor.toArray().map((r: unknown) => {\n const col = r as { cid: number; name: string; type: string; notnull: number; dflt_value: string | null; pk: number }\n return {\n name: col.name,\n type: col.type,\n notNull: col.notnull === 1,\n defaultValue: col.dflt_value,\n primaryKey: col.pk === 1,\n }\n })\n return Response.json({ table: tableName, columns }, { headers: CORS_HEADERS })\n }\n\n // Execute the query\n const cursor = ctx.sql.exec(sql, ...params)\n const rows = cursor.toArray()\n\n return Response.json({ \n sql,\n rowCount: rows.length,\n rows,\n }, { headers: CORS_HEADERS })\n } catch (error) {\n return Response.json({ \n error: error instanceof Error ? error.message : 'Query failed',\n sql,\n }, { status: 400, headers: CORS_HEADERS })\n }\n}\n","/**\n * RecordRoom Durable Object\n *\n * SQLite-based storage with query-based real-time subscriptions.\n * Extends BaseRoom for WebSocket/connection infrastructure.\n *\n * Architecture:\n * - Data stored in SQLite (single `records` table)\n * - Clients subscribe to QUERIES, not collections\n * - On record change, server evaluates which subscriptions match\n * - Only matching subscribers receive updates\n *\n * Protocol:\n * - SUBSCRIBE { subscriptionId, query } → QUERY_RESULT { subscriptionId, records }\n * - UNSUBSCRIBE { subscriptionId }\n * - PUT { collection, recordId, data } → broadcasts RECORD_CHANGE to matching\n * - DELETE { collection, recordId } → broadcasts RECORD_CHANGE to matching\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport type { ConnectionAttachment } from '../../shared/protocol/types'\nimport type {\n YjsDocKey,\n SubscribePayload,\n UnsubscribePayload,\n PutPayload,\n DeletePayload,\n SetRolePayload,\n YjsJoinPayload,\n YjsLeavePayload,\n} from '../../shared/types'\nimport { ROLE_ANONYMOUS, MSG, isAnonymousUserId } from '../../shared/protocol/constants'\nimport type { ServerMessage } from '../../shared/protocol/messages'\nimport {\n type CollectionSchema,\n type PermissionContext,\n SchemaRegistry,\n BASE_USERS_SCHEMA,\n} from '../schemas/registry'\nimport { ensureCollectionTable as ensureCollectionTableImpl } from './collection-table-migration'\nimport {\n handleSubscribe,\n handleUnsubscribe,\n handlePut,\n handleDelete,\n handleUserList,\n handleUserUpdate,\n handleSetRole,\n registerUser,\n getUser,\n broadcastUserList,\n handleYjsJoin,\n handleYjsLeave,\n handleYjsBinaryMessage,\n handleApiRequest,\n} from '../handlers'\nimport { SYSTEM_COLLECTION_SCHEMAS, handleYjsDisconnect } from '../handlers/yjs'\n\n/**\n * RecordRoom configuration options\n */\nexport interface RecordRoomConfig {\n /**\n * User ID of the app owner.\n * This user automatically gets 'admin' role on connect.\n */\n ownerUserId?: string\n}\n\n/**\n * RecordRoom Durable Object\n */\nexport class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private schemaRegistry: SchemaRegistry\n private initPromise: Promise<void> | null = null\n /** Yjs docs loaded in memory (key: collection:recordId:fieldName) */\n private yjsDocs: Map<YjsDocKey, Y.Doc> = new Map()\n /** Owner user ID — gets admin role automatically */\n private ownerUserId: string | null\n /** True until the first fetch() completes — detects hibernation wake-up */\n private freshConstruct = true\n\n /**\n * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so\n * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any\n * truthy value on the worker to opt in). Off by default.\n */\n private get perfLogEnabled(): boolean {\n return !!(this.env as { DEEPSPACE_DO_PERF?: unknown })?.DEEPSPACE_DO_PERF\n }\n\n /**\n * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes\n * with no auth of its own, so it is gated here at the DO's single ingress.\n * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`\n * (`deepspace dev start`/`test run` set it automatically). Deployments holding shared\n * data override this to always return false.\n */\n protected get debugRoutesEnabled(): boolean {\n return (this.env as { ALLOW_DEBUG_ROUTES?: unknown })?.ALLOW_DEBUG_ROUTES === 'true'\n }\n\n constructor(\n state: DurableObjectState,\n env: unknown,\n schemas: CollectionSchema[] = [],\n config: RecordRoomConfig = {},\n ) {\n super(state, env)\n this.schemaRegistry = new SchemaRegistry([\n ...SYSTEM_COLLECTION_SCHEMAS,\n BASE_USERS_SCHEMA,\n ...schemas,\n ])\n this.ownerUserId = config.ownerUserId ?? null\n\n // Log hibernation wake-up (WebSockets preserved but memory cleared)\n const wsCount = this.state.getWebSockets().length\n if (wsCount > 0) {\n console.log('[DO] Woke from hibernation:', wsCount, 'connections')\n }\n }\n\n // ============================================================================\n // Permission Context\n // ============================================================================\n\n private getPermissionContext(): PermissionContext {\n const teamMembersSchema = this.schemaRegistry.get('team_members')\n const hasTeamMembers = !!teamMembersSchema?.columns?.length\n\n return {\n isTeamMember: (teamId: string, userId: string): boolean => {\n if (!hasTeamMembers) return false\n const cursor = this.sql.exec(\n `SELECT 1 FROM c_team_members WHERE col_teamid = ? AND col_userid = ? AND (col_status = 'active' OR col_status IS NULL) LIMIT 1`,\n teamId,\n userId,\n )\n return cursor.toArray().length > 0\n },\n }\n }\n\n // ============================================================================\n // HTTP Entry Point (overrides BaseRoom.fetch)\n // ============================================================================\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n // Internal control-plane endpoints (e.g. POST /internal/disconnect-sockets)\n // are shared by every room type via BaseRoom. Handle them first — before\n // the DB-init + WS path — since closing sockets needs no schema init.\n // Only reachable via DO stub fetch from the app worker (see BaseRoom.fetch).\n if (url.pathname.startsWith('/internal/')) {\n const internal = await this.handleInternalRequest(request, url)\n if (internal) return internal\n }\n\n const fetchStart = Date.now()\n const isColdStart = this.freshConstruct\n const needsInit = !this.initPromise\n this.freshConstruct = false\n\n await this.ensureInitialized()\n const initMs = Date.now() - fetchStart\n\n // HTTP API endpoints (tools, debug, etc.)\n if (url.pathname.startsWith('/api/')) {\n if (url.pathname.startsWith('/api/debug/') && !this.debugRoutesEnabled) {\n return new Response('Not Found', { status: 404 })\n }\n return handleApiRequest(this.createHandlerContext(), request, url)\n }\n\n // WebSocket upgrade — delegate to BaseRoom\n if (request.headers.get('Upgrade') === 'websocket') {\n // Store timing info for logging in onConnect\n this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs }\n return super.fetch(request)\n }\n\n return new Response('Not Found', { status: 404 })\n }\n\n /** Timing info from the current fetch(), used by onConnect for logging */\n private _fetchTiming: {\n fetchStart: number\n isColdStart: boolean\n needsInit: boolean\n initMs: number\n } | null = null\n\n // ============================================================================\n // Database\n // ============================================================================\n\n private ensureInitialized(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this.initializeDatabase()\n }\n return this.initPromise\n }\n\n private async initializeDatabase(): Promise<void> {\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS yjs_docs (\n doc_key TEXT PRIMARY KEY,\n state BLOB NOT NULL,\n updated_at TEXT NOT NULL\n );\n `)\n\n this.ensureAllCollectionTables()\n }\n\n // ============================================================================\n // Table-Mode Schema Management\n // ============================================================================\n\n private ensureCollectionTable(schema: CollectionSchema): void {\n ensureCollectionTableImpl(this.sql, schema, console)\n }\n\n private ensureAllCollectionTables(): void {\n for (const schema of this.schemaRegistry.all()) {\n this.ensureCollectionTable(schema)\n }\n }\n\n // ============================================================================\n // BaseRoom Lifecycle Hooks\n // ============================================================================\n\n protected async onConnect(ws: WebSocket, user: UserAttachment): Promise<ConnectionAttachment> {\n await this.ensureInitialized()\n\n const timing = this._fetchTiming ?? {\n fetchStart: Date.now(),\n isColdStart: false,\n needsInit: false,\n initMs: 0,\n }\n\n const userId = user.userId\n const userName = user.userName\n const userEmail = user.userEmail\n const userImageUrl = user.userImageUrl\n const isAuthenticated = !isAnonymousUserId(userId)\n\n let attachment: ConnectionAttachment\n\n if (isAuthenticated) {\n const isOwner = this.ownerUserId != null && userId === this.ownerUserId\n const regStart = Date.now()\n const defaultRole = this.schemaRegistry.get('users')?.defaultRole ?? 'member'\n const before = getUser(this.sql, userId, this.schemaRegistry)\n const registeredUser = await registerUser(\n this.sql,\n userId,\n userName,\n userEmail,\n userImageUrl,\n isOwner,\n defaultRole,\n this.schemaRegistry,\n )\n const regMs = Date.now() - regStart\n // A first registration (or a changed name/avatar/role) changes what\n // every other tab's roster should show — push it, or peers who joined\n // after a tab connected render as \"Unknown\" there until it reconnects.\n if (\n !before ||\n before.name !== registeredUser.name ||\n before.imageUrl !== registeredUser.imageUrl ||\n before.role !== registeredUser.role\n ) {\n broadcastUserList(this.createUserContext(), ws)\n }\n\n attachment = {\n userId: registeredUser.id,\n userName,\n userEmail,\n userImageUrl,\n role: registeredUser.role,\n subscriptions: [],\n yjsSubscriptions: [],\n }\n\n this.send(ws, { type: MSG.USER_INFO, payload: registeredUser })\n\n const totalMs = Date.now() - timing.fetchStart\n if (this.perfLogEnabled)\n console.log(\n `[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`,\n )\n } else {\n attachment = {\n userId,\n userName: 'Anonymous',\n userEmail: '',\n role: ROLE_ANONYMOUS,\n subscriptions: [],\n yjsSubscriptions: [],\n }\n\n this.send(ws, {\n type: MSG.USER_INFO,\n payload: {\n id: userId,\n name: 'Anonymous',\n email: '',\n role: ROLE_ANONYMOUS,\n createdAt: new Date().toISOString(),\n lastSeenAt: new Date().toISOString(),\n },\n })\n\n const totalMs = Date.now() - timing.fetchStart\n if (this.perfLogEnabled)\n console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`)\n }\n\n this._fetchTiming = null\n return attachment\n }\n\n /**\n * Required to satisfy BaseRoom's abstract contract, but never invoked:\n * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),\n * so the BaseRoom dispatch path that would call this never runs. All real\n * message handling lives in `handleRecordMessage`.\n */\n protected onMessage(): void {\n throw new Error('RecordRoom.onMessage is unreachable; use webSocketMessage')\n }\n\n // ============================================================================\n // Hibernation API — RecordRoom overrides these directly (rather than using\n // BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run\n // ensureInitialized() before every message after a hibernation wake-up.\n // ============================================================================\n\n async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void> {\n await this.ensureInitialized()\n\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (!attachment) {\n this.send(ws, { type: MSG.ERROR, payload: { error: 'Connection not found' } })\n return\n }\n\n // Handle binary messages (Yjs sync)\n if (message instanceof ArrayBuffer) {\n try {\n await handleYjsBinaryMessage(\n this.createYjsContext(),\n ws,\n attachment,\n new Uint8Array(message),\n )\n } catch (e) {\n console.error('[RecordRoom] Yjs binary message error:', e)\n }\n return\n }\n\n // Handle JSON messages\n try {\n const msg = JSON.parse(message)\n await this.handleRecordMessage(ws, attachment, msg)\n } catch (e) {\n const errMsg = e instanceof Error ? e.message : String(e)\n const msgPreview = typeof message === 'string' ? message.slice(0, 200) : '(non-string)'\n console.error(`[RecordRoom] Message handler error: ${errMsg}`, { message: msgPreview })\n this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } })\n }\n }\n\n async webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void> {\n console.log(`[RecordRoom] webSocketClose code=${code} reason=${reason}`)\n try {\n const attachment = ws.deserializeAttachment() as ConnectionAttachment | null\n if (attachment) {\n handleYjsDisconnect(this.createYjsContext(), ws, attachment)\n }\n } catch {\n /* best-effort */\n }\n }\n\n async webSocketError(_ws: WebSocket, error: unknown): Promise<void> {\n console.error(`[RecordRoom] webSocketError:`, error)\n }\n\n // ============================================================================\n // Message Routing\n // ============================================================================\n\n private async handleRecordMessage(\n ws: WebSocket,\n attachment: ConnectionAttachment,\n msg: { type: string; payload: unknown },\n ): Promise<void> {\n const { type, payload } = msg\n const ctx = this.createHandlerContext()\n const recordCtx = this.createRecordContext()\n const userCtx = this.createUserContext()\n const yjsCtx = this.createYjsContext()\n\n switch (type) {\n case MSG.SUBSCRIBE:\n handleSubscribe(ctx, ws, attachment, payload as SubscribePayload)\n break\n\n case MSG.UNSUBSCRIBE:\n handleUnsubscribe(ctx, ws, attachment, payload as UnsubscribePayload)\n break\n\n case MSG.PUT:\n await handlePut(recordCtx, ws, attachment, payload as PutPayload)\n break\n\n case MSG.DELETE:\n await handleDelete(recordCtx, ws, attachment, payload as DeletePayload)\n break\n\n case MSG.USER_LIST:\n handleUserList(userCtx, ws, attachment)\n break\n\n case MSG.USER_UPDATE:\n handleUserUpdate(\n recordCtx,\n ws,\n attachment,\n payload as { name?: string; email?: string; imageUrl?: string },\n )\n break\n\n case MSG.SET_ROLE:\n await handleSetRole(userCtx, ws, attachment, payload as SetRolePayload)\n break\n\n case MSG.YJS_JOIN:\n await handleYjsJoin(yjsCtx, ws, attachment, payload as YjsJoinPayload)\n break\n\n case MSG.YJS_LEAVE:\n handleYjsLeave(yjsCtx, ws, attachment, payload as YjsLeavePayload)\n break\n\n case MSG.LIST_SCHEMAS:\n this.handleListSchemas(ws)\n break\n\n default:\n this.send(ws, { type: MSG.ERROR, payload: { error: `Unknown message type: ${type}` } })\n }\n }\n\n // ============================================================================\n // Schema Discovery\n // ============================================================================\n\n private handleListSchemas(ws: WebSocket): void {\n const schemas = this.schemaRegistry.all()\n this.send(ws, {\n type: MSG.LIST_SCHEMAS,\n payload: { schemas },\n })\n }\n\n // ============================================================================\n // Context Factory Methods\n // ============================================================================\n\n private createHandlerContext() {\n return {\n sql: this.sql,\n state: this.state,\n schemaRegistry: this.schemaRegistry,\n // Every record room stub is addressed with `idFromName(scopeId)`, so the\n // DO's own name is the scope. Handlers use it to name the room when a\n // caller asks it for a collection that lives in a different scope.\n scopeId: this.state.id.name,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n sendBinary: (ws: WebSocket, data: Uint8Array) => this.sendBinaryHelper(ws, data),\n yjsDocs: this.yjsDocs,\n ownerUserId: this.ownerUserId ?? undefined,\n }\n }\n\n private createRecordContext() {\n return {\n ...this.createHandlerContext(),\n state: this.state,\n }\n }\n\n private createUserContext() {\n return {\n sql: this.sql,\n state: this.state,\n schemaRegistry: this.schemaRegistry,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n }\n }\n\n private createYjsContext() {\n return {\n sql: this.sql,\n state: this.state,\n yjsDocs: this.yjsDocs,\n schemaRegistry: this.schemaRegistry,\n getPermissionContext: () => this.getPermissionContext(),\n send: (ws: WebSocket, msg: ServerMessage) => this.send(ws, msg),\n sendBinary: (ws: WebSocket, data: Uint8Array) => this.sendBinaryHelper(ws, data),\n }\n }\n\n // ============================================================================\n // Utilities (preserves original API that handlers expect)\n // ============================================================================\n\n private send(ws: WebSocket, message: ServerMessage): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n } catch {\n // Ignore send errors\n }\n }\n\n private sendBinaryHelper(ws: WebSocket, data: Uint8Array): void {\n try {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data)\n }\n } catch {\n // Ignore send errors\n }\n }\n}\n","/**\n * YjsRoom — Lightweight Durable Object for collaborative Yjs documents.\n * Extends BaseRoom for WebSocket/connection infrastructure.\n *\n * Unlike RecordRoom (schemas, RBAC, queries, user state), YjsRoom is\n * purpose-built for Yjs: sync, relay, persist. One DO per document.\n *\n * Architecture (SOTA for Yjs + Cloudflare DOs):\n * - Auth verified at the worker edge, role passed to DO via URL params\n * - DO is a thin Yjs sync relay: receive → apply → persist → broadcast\n * - Viewers can observe but not write; members/admins can write\n * - State persisted as a single binary blob in SQLite\n *\n * Uses the shared yjs-protocol.ts encoding utilities — no duplication.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport {\n MSG_SYNC,\n MSG_AWARENESS,\n MSG_SYNC_STEP1,\n MSG_SYNC_STEP2,\n MSG_SYNC_UPDATE,\n createEncoder,\n createDecoder,\n toUint8Array,\n writeVarUint,\n writeVarUint8Array,\n readVarUint,\n readVarUint8Array,\n} from '../../shared/protocol/yjs'\n\n// ============================================================================\n// Connection attachment (extends UserAttachment, survives hibernation)\n// ============================================================================\n\ninterface YjsAttachment extends UserAttachment {\n role: string\n canWrite: boolean\n awarenessClientId: number | null\n}\n\ninterface AwarenessEntry {\n clock: number\n stateBytes: Uint8Array\n}\n\n// ============================================================================\n// YjsRoom Durable Object\n// ============================================================================\n\nexport class YjsRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private doc: Y.Doc | null = null\n private initialized = false\n private awarenessStates = new Map<number, AwarenessEntry>()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n // --------------------------------------------------------------------------\n // Initialization & persistence\n // --------------------------------------------------------------------------\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS yjs_state (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n doc BLOB NOT NULL,\n updated_at TEXT NOT NULL\n )\n `)\n }\n\n private getDoc(): Y.Doc {\n if (this.doc) return this.doc\n\n this.ensureInitialized()\n this.doc = new Y.Doc()\n\n // Load persisted state\n const rows = this.sql.exec('SELECT doc FROM yjs_state WHERE id = 1').toArray()\n if (rows.length > 0 && rows[0].doc) {\n Y.applyUpdate(this.doc, new Uint8Array(rows[0].doc as ArrayBuffer))\n }\n\n // Auto-save on every update\n this.doc.on('update', () => this.persistDoc())\n\n return this.doc\n }\n\n private persistDoc(): void {\n if (!this.doc) return\n const state = Y.encodeStateAsUpdate(this.doc)\n const now = new Date().toISOString()\n this.sql.exec(\n `INSERT INTO yjs_state (id, doc, updated_at) VALUES (1, ?, ?)\n ON CONFLICT(id) DO UPDATE SET doc = ?, updated_at = ?`,\n state,\n now,\n state,\n now,\n )\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle Hooks\n // --------------------------------------------------------------------------\n\n protected onConnect(ws: WebSocket, user: UserAttachment): YjsAttachment {\n const role = ((user as Record<string, unknown>).role as string) ?? 'viewer'\n const canWrite = role === 'member' || role === 'admin'\n\n const attachment: YjsAttachment = {\n ...user,\n role,\n canWrite,\n awarenessClientId: null,\n }\n\n // Initial sync: send our state vector (STEP1) + full state (STEP2)\n const doc = this.getDoc()\n\n const step1 = createEncoder()\n writeVarUint(step1, MSG_SYNC)\n writeVarUint(step1, MSG_SYNC_STEP1)\n writeVarUint8Array(step1, Y.encodeStateVector(doc))\n ws.send(toUint8Array(step1).buffer)\n\n const fullState = Y.encodeStateAsUpdate(doc)\n if (fullState.length > 1) {\n const step2 = createEncoder()\n writeVarUint(step2, MSG_SYNC)\n writeVarUint(step2, MSG_SYNC_STEP2)\n writeVarUint8Array(step2, fullState)\n ws.send(toUint8Array(step2).buffer)\n }\n\n // Tell client their write access\n ws.send(JSON.stringify({ type: 'auth', canWrite }))\n this.sendAwarenessSnapshot(ws)\n\n return attachment\n }\n\n protected onMessage(\n _ws: WebSocket,\n _user: UserAttachment,\n _message: { type: string; [key: string]: unknown },\n ): void {\n // YjsRoom only handles binary messages; JSON messages are ignored\n // (ping/pong handled by auto-response)\n }\n\n protected onBinaryMessage(ws: WebSocket, _user: UserAttachment, data: ArrayBuffer): void {\n const bytes = new Uint8Array(data)\n const decoder = createDecoder(bytes)\n const messageType = readVarUint(decoder)\n\n if (messageType === MSG_SYNC) {\n this.handleSync(ws, decoder, bytes)\n } else if (messageType === MSG_AWARENESS) {\n this.handleAwareness(ws, decoder, bytes)\n }\n }\n\n protected onDisconnect(ws: WebSocket, _user: UserAttachment): void {\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (attachment?.awarenessClientId == null) return\n\n const clientId = attachment.awarenessClientId\n const prevClock = this.awarenessStates.get(clientId)?.clock ?? 0\n this.awarenessStates.delete(clientId)\n\n const msg = this.encodeAwarenessMessage([\n {\n clientId,\n clock: prevClock + 1,\n stateBytes: new TextEncoder().encode('null'),\n },\n ])\n for (const peer of this.state.getWebSockets()) {\n if (peer === ws) continue\n try {\n peer.send(msg.buffer)\n } catch {\n /* dead socket */\n }\n }\n }\n\n // --------------------------------------------------------------------------\n // Sync protocol\n // --------------------------------------------------------------------------\n\n private handleSync(\n ws: WebSocket,\n decoder: { data: Uint8Array; pos: number },\n rawMessage: Uint8Array,\n ): void {\n const syncType = readVarUint(decoder)\n const doc = this.getDoc()\n\n switch (syncType) {\n case MSG_SYNC_STEP1: {\n const clientStateVector = readVarUint8Array(decoder)\n const diff = Y.encodeStateAsUpdate(doc, clientStateVector)\n const enc = createEncoder()\n writeVarUint(enc, MSG_SYNC)\n writeVarUint(enc, MSG_SYNC_STEP2)\n writeVarUint8Array(enc, diff)\n ws.send(toUint8Array(enc).buffer)\n break\n }\n case MSG_SYNC_STEP2:\n case MSG_SYNC_UPDATE: {\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (!attachment?.canWrite) return\n const update = readVarUint8Array(decoder)\n Y.applyUpdate(doc, update, 'remote')\n this.broadcastRaw(ws, rawMessage)\n break\n }\n }\n }\n\n private handleAwareness(\n ws: WebSocket,\n decoder: { data: Uint8Array; pos: number },\n rawMessage: Uint8Array,\n ): void {\n const updates = this.readAwarenessUpdates(decoder)\n if (updates.length === 0) return\n\n const attachment = ws.deserializeAttachment() as YjsAttachment | null\n if (attachment && attachment.awarenessClientId === null) {\n attachment.awarenessClientId = updates[0].clientId\n ws.serializeAttachment(attachment)\n }\n\n for (const update of updates) {\n const existing = this.awarenessStates.get(update.clientId)\n if (existing && existing.clock >= update.clock) continue\n\n if (new TextDecoder().decode(update.stateBytes) === 'null') {\n this.awarenessStates.delete(update.clientId)\n } else {\n this.awarenessStates.set(update.clientId, {\n clock: update.clock,\n stateBytes: update.stateBytes,\n })\n }\n }\n\n this.broadcastRaw(ws, rawMessage)\n }\n\n private readAwarenessUpdates(decoder: {\n data: Uint8Array\n pos: number\n }): Array<AwarenessEntry & { clientId: number }> {\n const update = readVarUint8Array(decoder)\n const updateDecoder = createDecoder(update)\n const count = readVarUint(updateDecoder)\n const updates: Array<AwarenessEntry & { clientId: number }> = []\n\n for (let i = 0; i < count; i++) {\n const clientId = readVarUint(updateDecoder)\n const clock = readVarUint(updateDecoder)\n const stateBytes = readVarUint8Array(updateDecoder)\n updates.push({ clientId, clock, stateBytes })\n }\n\n return updates\n }\n\n private encodeAwarenessMessage(\n updates: Array<AwarenessEntry & { clientId: number }>,\n ): Uint8Array {\n const inner = createEncoder()\n writeVarUint(inner, updates.length)\n for (const update of updates) {\n writeVarUint(inner, update.clientId)\n writeVarUint(inner, update.clock)\n writeVarUint8Array(inner, update.stateBytes)\n }\n\n const outer = createEncoder()\n writeVarUint(outer, MSG_AWARENESS)\n writeVarUint8Array(outer, toUint8Array(inner))\n return toUint8Array(outer)\n }\n\n private sendAwarenessSnapshot(ws: WebSocket): void {\n if (this.awarenessStates.size === 0) return\n\n const updates = Array.from(this.awarenessStates, ([clientId, entry]) => ({\n clientId,\n clock: entry.clock,\n stateBytes: entry.stateBytes,\n }))\n const msg = this.encodeAwarenessMessage(updates)\n try {\n ws.send(msg.buffer)\n } catch {\n /* dead socket */\n }\n }\n\n // --------------------------------------------------------------------------\n // Broadcasting\n // --------------------------------------------------------------------------\n\n private broadcastRaw(sender: WebSocket, rawMessage: Uint8Array): void {\n for (const ws of this.state.getWebSockets()) {\n if (ws === sender) continue\n try {\n ws.send(rawMessage.buffer)\n } catch {\n /* dead socket */\n }\n }\n }\n}\n","/**\n * CanvasRoom — Spatial canvas Durable Object (tldraw-style).\n *\n * Extends BaseRoom with Yjs-backed spatial operations.\n * Each shape is a Y.Map entry, enabling multi-user concurrent editing.\n *\n * Features:\n * - Shape CRUD (add, move, resize, delete, update properties)\n * - Viewport awareness (each user's visible region)\n * - Per-user undo/redo stacks\n *\n * Message types: canvas.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport * as Y from 'yjs'\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\nimport { ROLES } from '../../shared/roles'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CanvasShape {\n id: string\n type: string\n x: number\n y: number\n width: number\n height: number\n rotation?: number\n props: Record<string, unknown>\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\nexport interface Viewport {\n userId: string\n x: number\n y: number\n width: number\n height: number\n zoom: number\n}\n\ninterface CanvasAttachment extends UserAttachment {\n viewport: Viewport | null\n /** True for member/admin roles; false for viewers and unauthenticated anon. */\n canWrite: boolean\n}\n\n// Message types that mutate shared canvas state. Anything not in this set\n// (viewport broadcasts, the implicit shape snapshot on connect) is\n// presence-like and intentionally open to viewers.\nconst CANVAS_WRITE_TYPES: ReadonlySet<string> = new Set([\n MSG.CANVAS_ADD,\n MSG.CANVAS_MOVE,\n MSG.CANVAS_RESIZE,\n MSG.CANVAS_DELETE,\n MSG.CANVAS_UPDATE,\n MSG.CANVAS_UNDO,\n MSG.CANVAS_REDO,\n])\n\ninterface UndoEntry {\n type: 'add' | 'delete' | 'update'\n shapeId: string\n before?: Record<string, unknown>\n after?: Record<string, unknown>\n}\n\n// ============================================================================\n// CanvasRoom\n// ============================================================================\n\nexport class CanvasRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private doc: Y.Doc | null = null\n private initialized = false\n private viewports: Map<string, Viewport> = new Map()\n private undoStacks: Map<string, UndoEntry[]> = new Map()\n private redoStacks: Map<string, UndoEntry[]> = new Map()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n // --------------------------------------------------------------------------\n // Initialization & persistence\n // --------------------------------------------------------------------------\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS canvas_state (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n doc BLOB NOT NULL,\n updated_at TEXT NOT NULL\n )\n `)\n }\n\n private getDoc(): Y.Doc {\n if (this.doc) return this.doc\n\n this.ensureInitialized()\n this.doc = new Y.Doc()\n\n const rows = this.sql.exec('SELECT doc FROM canvas_state WHERE id = 1').toArray()\n if (rows.length > 0 && rows[0].doc) {\n Y.applyUpdate(this.doc, new Uint8Array(rows[0].doc as ArrayBuffer))\n }\n\n this.doc.on('update', () => this.persistDoc())\n return this.doc\n }\n\n private persistDoc(): void {\n if (!this.doc) return\n const state = Y.encodeStateAsUpdate(this.doc)\n const now = new Date().toISOString()\n this.sql.exec(\n `INSERT INTO canvas_state (id, doc, updated_at) VALUES (1, ?, ?)\n ON CONFLICT(id) DO UPDATE SET doc = ?, updated_at = ?`,\n state, now, state, now,\n )\n }\n\n private getShapesMap(): Y.Map<Record<string, unknown>> {\n return this.getDoc().getMap('shapes') as Y.Map<Record<string, unknown>>\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle\n // --------------------------------------------------------------------------\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected onConnect(ws: WebSocket, user: UserAttachment): CanvasAttachment {\n this.ensureInitialized()\n\n const role = (user.role as string | undefined) ?? ROLES.VIEWER\n const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN\n\n const attachment: CanvasAttachment = {\n ...user,\n viewport: null,\n canWrite,\n }\n\n // Tell the client whether their role grants write access, so the hook\n // can short-circuit local writes and the UI can render disabled\n // controls instead of bouncing every click off the server.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n\n // Send current shapes\n const shapes = this.getAllShapes()\n this.sendTo(ws, {\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes,\n viewports: Array.from(this.viewports.values()),\n },\n })\n\n return attachment\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown }\n ): Promise<void> {\n this.ensureInitialized()\n const { type, payload } = message as { type: string; payload: Record<string, unknown> }\n\n // Viewers and unauthenticated anon connections can observe but not\n // mutate. Reject write attempts at the gate so a bug in any single\n // handler can't accidentally re-open the hole.\n if (CANVAS_WRITE_TYPES.has(type) && !(user as CanvasAttachment).canWrite) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Write access denied: viewer role cannot modify the canvas' },\n })\n return\n }\n\n switch (type) {\n case MSG.CANVAS_ADD: {\n const shape = payload as unknown as Partial<CanvasShape>\n const id = shape.id ?? crypto.randomUUID()\n const now = new Date().toISOString()\n\n const newShape: CanvasShape = {\n id,\n type: shape.type ?? 'rect',\n x: shape.x ?? 0,\n y: shape.y ?? 0,\n width: shape.width ?? 100,\n height: shape.height ?? 100,\n rotation: shape.rotation,\n props: shape.props ?? {},\n createdBy: user.userId,\n createdAt: now,\n updatedAt: now,\n }\n\n this.getShapesMap().set(id, newShape as unknown as Record<string, unknown>)\n\n this.pushUndo(user.userId, { type: 'add', shapeId: id, after: newShape as unknown as Record<string, unknown> })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: newShape } })\n break\n }\n\n case MSG.CANVAS_MOVE: {\n const { shapeId, x, y } = payload as { shapeId: string; x: number; y: number }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = { ...existing, x, y, updatedAt: new Date().toISOString() }\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_MOVE, payload: { shapeId, x, y } }, ws)\n break\n }\n\n case MSG.CANVAS_RESIZE: {\n const { shapeId, width, height, x, y } = payload as { shapeId: string; width: number; height: number; x?: number; y?: number }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = { ...existing, width, height, updatedAt: new Date().toISOString() }\n if (x !== undefined) (updated as Record<string, unknown>).x = x\n if (y !== undefined) (updated as Record<string, unknown>).y = y\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_RESIZE, payload: { shapeId, width, height, x, y } }, ws)\n break\n }\n\n case MSG.CANVAS_DELETE: {\n const { shapeId } = payload as { shapeId: string }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n shapesMap.delete(shapeId)\n\n this.pushUndo(user.userId, { type: 'delete', shapeId, before: existing })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId } })\n break\n }\n\n case MSG.CANVAS_UPDATE: {\n const { shapeId, props } = payload as { shapeId: string; props: Record<string, unknown> }\n const shapesMap = this.getShapesMap()\n const existing = shapesMap.get(shapeId)\n if (!existing) break\n\n const before = { ...existing }\n const updated = {\n ...existing,\n props: { ...(existing.props as Record<string, unknown> ?? {}), ...props },\n updatedAt: new Date().toISOString(),\n }\n shapesMap.set(shapeId, updated)\n\n this.pushUndo(user.userId, { type: 'update', shapeId, before, after: updated })\n this.clearRedo(user.userId)\n\n this.broadcast({ type: MSG.CANVAS_UPDATE, payload: { shapeId, props } }, ws)\n break\n }\n\n case MSG.CANVAS_VIEWPORT: {\n const viewport = payload as unknown as Viewport\n viewport.userId = user.userId\n this.viewports.set(user.userId, viewport)\n this.broadcast({ type: MSG.CANVAS_VIEWPORT, payload: { viewport } }, ws)\n break\n }\n\n case MSG.CANVAS_UNDO: {\n this.handleUndo(user.userId)\n break\n }\n\n case MSG.CANVAS_REDO: {\n this.handleRedo(user.userId)\n break\n }\n\n default:\n this.sendTo(ws, { type: MSG.ERROR, payload: { error: `Unknown canvas message type: ${type}` } })\n }\n }\n\n protected onDisconnect(_ws: WebSocket, user: UserAttachment): void {\n this.viewports.delete(user.userId)\n this.undoStacks.delete(user.userId)\n this.redoStacks.delete(user.userId)\n this.broadcast({ type: MSG.CANVAS_VIEWPORT, payload: { userId: user.userId, removed: true } })\n }\n\n // --------------------------------------------------------------------------\n // Undo / Redo\n // --------------------------------------------------------------------------\n\n private pushUndo(userId: string, entry: UndoEntry): void {\n if (!this.undoStacks.has(userId)) this.undoStacks.set(userId, [])\n const stack = this.undoStacks.get(userId)!\n stack.push(entry)\n if (stack.length > 100) stack.shift()\n }\n\n private clearRedo(userId: string): void {\n this.redoStacks.set(userId, [])\n }\n\n private handleUndo(userId: string): void {\n const stack = this.undoStacks.get(userId)\n if (!stack || stack.length === 0) return\n\n const entry = stack.pop()!\n const shapesMap = this.getShapesMap()\n\n if (!this.redoStacks.has(userId)) this.redoStacks.set(userId, [])\n this.redoStacks.get(userId)!.push(entry)\n\n switch (entry.type) {\n case 'add':\n shapesMap.delete(entry.shapeId)\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId: entry.shapeId } })\n break\n case 'delete':\n if (entry.before) {\n shapesMap.set(entry.shapeId, entry.before)\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: entry.before } })\n }\n break\n case 'update':\n if (entry.before) {\n shapesMap.set(entry.shapeId, entry.before)\n this.broadcast({\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes: this.getAllShapes(),\n viewports: Array.from(this.viewports.values()),\n },\n })\n }\n break\n }\n }\n\n private handleRedo(userId: string): void {\n const stack = this.redoStacks.get(userId)\n if (!stack || stack.length === 0) return\n\n const entry = stack.pop()!\n const shapesMap = this.getShapesMap()\n\n if (!this.undoStacks.has(userId)) this.undoStacks.set(userId, [])\n this.undoStacks.get(userId)!.push(entry)\n\n switch (entry.type) {\n case 'add':\n if (entry.after) {\n shapesMap.set(entry.shapeId, entry.after)\n this.broadcast({ type: MSG.CANVAS_ADD, payload: { shape: entry.after } })\n }\n break\n case 'delete':\n shapesMap.delete(entry.shapeId)\n this.broadcast({ type: MSG.CANVAS_DELETE, payload: { shapeId: entry.shapeId } })\n break\n case 'update':\n if (entry.after) {\n shapesMap.set(entry.shapeId, entry.after)\n this.broadcast({\n type: MSG.CANVAS_SHAPES,\n payload: {\n shapes: this.getAllShapes(),\n viewports: Array.from(this.viewports.values()),\n },\n })\n }\n break\n }\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n private getAllShapes(): CanvasShape[] {\n const shapesMap = this.getShapesMap()\n const shapes: CanvasShape[] = []\n shapesMap.forEach((value, key) => {\n shapes.push({ id: key, ...value } as unknown as CanvasShape)\n })\n return shapes\n }\n}\n","/**\n * Standard DeepSpace role constants.\n *\n * Every DeepSpace app uses the same three roles.\n * Apps can import these instead of defining them locally.\n */\n\nexport const ROLES = {\n VIEWER: 'viewer',\n MEMBER: 'member',\n ADMIN: 'admin',\n} as const\n\nexport type Role = (typeof ROLES)[keyof typeof ROLES]\n\nexport const ROLE_CONFIG: Record<Role, { title: string; badgeVariant: string; description: string }> = {\n viewer: { title: 'Viewer', badgeVariant: 'secondary', description: 'Read-only access' },\n member: { title: 'Member', badgeVariant: 'default', description: 'Can create and edit own content' },\n admin: { title: 'Admin', badgeVariant: 'warning', description: 'Full access to all features' },\n}\n","/**\n * PresenceRoom — Ephemeral presence-tracking Durable Object.\n *\n * Extends BaseRoom. No SQLite — purely in-memory presence state.\n * Tracks who is present in a given scope (canvas, doc, thread, etc.)\n * and broadcasts join/leave/state-update events to all connected peers.\n *\n * Each scope ID maps to its own DO instance. Clients connect via\n * /ws/presence/:scopeId and receive real-time presence for that scope.\n *\n * Peers can attach small ephemeral state (cursor position, typing indicator,\n * viewport, selection, etc.) via MSG.PRESENCE_UPDATE.\n *\n * Message types: presence.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PresencePeer {\n userId: string\n userName: string\n joinedAt: string\n /** Small per-user state (cursor, typing, viewport, etc.) */\n state: Record<string, unknown>\n}\n\ninterface PresenceAttachment extends UserAttachment {\n joinedAt: string\n}\n\nconst MAX_PRESENCE_STATE_BYTES = 16 * 1024\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction stateSize(state: Record<string, unknown>): number {\n return new TextEncoder().encode(JSON.stringify(state)).byteLength\n}\n\n// ============================================================================\n// PresenceRoom\n// ============================================================================\n\nexport class PresenceRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private peers: Map<string, PresencePeer> = new Map()\n private peerSockets: Map<string, WebSocket> = new Map()\n\n constructor(state: DurableObjectState, env: unknown) {\n super(state, env)\n }\n\n /**\n * Durable Objects can hibernate and clear heap while Cloudflare keeps\n * WebSocket connections. Deserialize attachments from already-connected\n * sockets so `peers` matches reality before we send PRESENCE_SYNC.\n */\n private hydratePeersFromLiveSockets(connectingWs: WebSocket): void {\n for (const existing of this.state.getWebSockets()) {\n if (existing === connectingWs) continue\n const att = existing.deserializeAttachment() as PresenceAttachment | null\n if (!att?.userId) continue\n if (this.peers.has(att.userId)) continue\n\n const joinedAt = typeof att.joinedAt === 'string' ? att.joinedAt : new Date().toISOString()\n\n const recovered: PresencePeer = {\n userId: att.userId,\n userName: att.userName,\n joinedAt,\n state: {},\n }\n this.peers.set(att.userId, recovered)\n this.peerSockets.set(att.userId, existing)\n }\n }\n\n // --------------------------------------------------------------------------\n // BaseRoom Lifecycle\n // --------------------------------------------------------------------------\n\n protected onConnect(ws: WebSocket, user: UserAttachment): PresenceAttachment {\n this.hydratePeersFromLiveSockets(ws)\n\n const now = new Date().toISOString()\n\n const peer: PresencePeer = {\n userId: user.userId,\n userName: user.userName,\n joinedAt: now,\n state: {},\n }\n\n // Send existing peers to the new connection (before adding self)\n this.sendTo(ws, {\n type: MSG.PRESENCE_SYNC,\n payload: { peers: Array.from(this.peers.values()) },\n })\n\n // Now add self and notify others\n this.peers.set(user.userId, peer)\n this.peerSockets.set(user.userId, ws)\n this.broadcast({ type: MSG.PRESENCE_JOIN, payload: { peer } }, ws)\n\n const attachment: PresenceAttachment = {\n ...user,\n joinedAt: now,\n }\n\n return attachment\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n const { type, payload } = message as { type: string; payload: Record<string, unknown> }\n\n switch (type) {\n case MSG.PRESENCE_UPDATE: {\n let peer = this.peers.get(user.userId)\n if (!peer) {\n const att = user as PresenceAttachment\n const joinedAt =\n typeof att.joinedAt === 'string' ? att.joinedAt : new Date().toISOString()\n peer = {\n userId: user.userId,\n userName: user.userName,\n joinedAt,\n state: {},\n }\n this.peers.set(user.userId, peer)\n this.peerSockets.set(user.userId, ws)\n }\n\n if (!isPlainObject(payload)) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Presence state must be an object' },\n })\n return\n }\n const nextState = { ...peer.state, ...payload }\n if (stateSize(nextState) > MAX_PRESENCE_STATE_BYTES) {\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: 'Presence state exceeds 16 KiB' },\n })\n return\n }\n\n peer.state = nextState\n this.peers.set(user.userId, peer)\n\n // Broadcast the update to all other peers\n this.broadcast(\n {\n type: MSG.PRESENCE_UPDATE,\n payload: { userId: user.userId, state: peer.state },\n },\n ws,\n )\n break\n }\n\n default:\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: `Unknown presence message type: ${type}` },\n })\n }\n }\n\n protected onDisconnect(ws: WebSocket, user: UserAttachment): void {\n this.peers.delete(user.userId)\n this.peerSockets.delete(user.userId)\n this.broadcast({ type: MSG.PRESENCE_LEAVE, payload: { userId: user.userId } }, ws)\n }\n}\n","/** Pure validation and evaluation for CronRoom schedules. */\n\nexport interface CronTask {\n name: string\n /** Interval in minutes (interval mode) — mutually exclusive with `schedule`. */\n intervalMinutes?: number\n /** 5-field cron expression (cron mode) — requires `timezone`. */\n schedule?: string\n /** IANA timezone string (e.g. \"America/New_York\"). Required with `schedule`. */\n timezone?: string\n /** Whether the task starts paused. */\n paused?: boolean\n}\n\nconst TASK_NAME_RE = /^[a-z0-9-]+$/\n\n/** Reject invalid task configuration before CronRoom mutates storage. */\nexport function validateTask(task: CronTask): CronTask {\n if (!task.name || typeof task.name !== 'string') {\n throw new Error('CronTask.name is required')\n }\n if (task.name.length > 64 || !TASK_NAME_RE.test(task.name)) {\n throw new Error(\n `CronTask.name \"${task.name}\" must be lowercase alphanumeric with hyphens, 1-64 chars`,\n )\n }\n\n const hasInterval = task.intervalMinutes != null\n const hasSchedule = task.schedule != null\n const hasTimezone = task.timezone != null\n if (hasInterval && (hasSchedule || hasTimezone)) {\n throw new Error(`CronTask \"${task.name}\" cannot mix intervalMinutes with schedule/timezone`)\n }\n if (!hasInterval && !(hasSchedule && hasTimezone)) {\n throw new Error(\n `CronTask \"${task.name}\" must declare either intervalMinutes or schedule+timezone`,\n )\n }\n if (\n hasInterval &&\n (!Number.isInteger(task.intervalMinutes) ||\n task.intervalMinutes! < 1 ||\n task.intervalMinutes! > 10080)\n ) {\n throw new Error(`CronTask \"${task.name}\" intervalMinutes must be an integer 1..10080`)\n }\n if (hasSchedule) {\n parseCronExpression(task.schedule!)\n if (typeof task.timezone !== 'string' || task.timezone.length === 0) {\n throw new Error(`CronTask \"${task.name}\" timezone must be a non-empty IANA string`)\n }\n try {\n new Intl.DateTimeFormat('en-US', { timeZone: task.timezone })\n } catch {\n throw new Error(\n `CronTask \"${task.name}\" timezone \"${task.timezone}\" is not a valid IANA timezone`,\n )\n }\n }\n return task\n}\n\ninterface ParsedCron {\n minute: Set<number>\n hour: Set<number>\n dayOfMonth: Set<number>\n month: Set<number>\n dayOfWeek: Set<number>\n}\n\n/** Parse a 5-field expression with literals, lists, ranges, and steps. */\nexport function parseCronExpression(expression: string): ParsedCron {\n const fields = expression.trim().split(/\\s+/)\n if (fields.length !== 5) {\n throw new Error(\n `Cron expression must have exactly 5 fields, got ${fields.length}: \"${expression}\"`,\n )\n }\n const [minute, hour, dayOfMonth, month, dayOfWeek] = fields\n return {\n minute: parseField(minute, 0, 59, 'minute'),\n hour: parseField(hour, 0, 23, 'hour'),\n dayOfMonth: parseField(dayOfMonth, 1, 31, 'day-of-month'),\n month: parseField(month, 1, 12, 'month'),\n dayOfWeek: parseField(dayOfWeek, 0, 6, 'day-of-week'),\n }\n}\n\nfunction parseField(field: string, min: number, max: number, label: string): Set<number> {\n const result = new Set<number>()\n\n for (const part of field.split(',')) {\n const value = part.trim()\n if (value === '*') {\n for (let i = min; i <= max; i++) result.add(i)\n continue\n }\n\n const allStep = value.match(/^\\*\\/(\\d+)$/)\n if (allStep) {\n const step = Number.parseInt(allStep[1], 10)\n if (step <= 0) throw new Error(`Cron ${label} step must be positive: \"${value}\"`)\n for (let i = min; i <= max; i += step) result.add(i)\n continue\n }\n\n const range = value.match(/^(\\d+)-(\\d+)(?:\\/(\\d+))?$/)\n if (range) {\n const start = Number.parseInt(range[1], 10)\n const end = Number.parseInt(range[2], 10)\n const step = range[3] ? Number.parseInt(range[3], 10) : 1\n if (start < min || end > max || start > end || step <= 0) {\n throw new Error(`Cron ${label} range out of bounds: \"${value}\" (${min}..${max})`)\n }\n for (let i = start; i <= end; i += step) result.add(i)\n continue\n }\n\n const literal = Number.parseInt(value, 10)\n if (Number.isNaN(literal) || literal < min || literal > max || String(literal) !== value) {\n throw new Error(`Cron ${label} value invalid: \"${value}\" (${min}..${max})`)\n }\n result.add(literal)\n }\n\n if (result.size === 0) {\n throw new Error(`Cron ${label} field \"${field}\" produced no valid values`)\n }\n return result\n}\n\n/**\n * Find the next UTC minute whose wall-clock fields match in `timezone`.\n * The one-year bound rejects schedules that are not useful to this room.\n */\nexport function nextCronFire(expression: string, timezone: string, from: Date): Date | null {\n const parsed = parseCronExpression(expression)\n const start = Math.floor(from.getTime() / 60_000) * 60_000 + 60_000\n const limit = start + 366 * 24 * 60 * 60_000\n for (let timestamp = start; timestamp < limit; timestamp += 60_000) {\n const candidate = new Date(timestamp)\n if (cronMatches(parsed, timezone, candidate)) return candidate\n }\n return null\n}\n\nfunction cronMatches(parsed: ParsedCron, timezone: string, date: Date): boolean {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n weekday: 'short',\n hour12: false,\n })\n const parts = Object.fromEntries(\n formatter.formatToParts(date).map((part) => [part.type, part.value]),\n )\n const minute = Number.parseInt(parts.minute, 10)\n const hour = Number.parseInt(parts.hour, 10) % 24\n const dayOfMonth = Number.parseInt(parts.day, 10)\n const month = Number.parseInt(parts.month, 10)\n const weekday: Record<string, number> = {\n Sun: 0,\n Mon: 1,\n Tue: 2,\n Wed: 3,\n Thu: 4,\n Fri: 5,\n Sat: 6,\n }\n const dayOfWeek = weekday[parts.weekday] ?? 0\n return (\n parsed.minute.has(minute) &&\n parsed.hour.has(hour) &&\n parsed.dayOfMonth.has(dayOfMonth) &&\n parsed.month.has(month) &&\n parsed.dayOfWeek.has(dayOfWeek)\n )\n}\n","/**\n * CronRoom — Per-app scheduled task execution Durable Object.\n *\n * Extends BaseRoom. One DO per app shards cron work and avoids the\n * dispatch-worker's global KV-poll bottleneck. The DO alarm triggers\n * `onTask(name)` on the configured cadence; each execution is recorded\n * to a per-app `cron_history` table. Subscribers (admin clients via the\n * `useCronMonitor` hook) get pushes over the WebSocket.\n *\n * Tasks declare *either* `intervalMinutes` (run every N minutes) *or*\n * `schedule` + `timezone` (5-field cron expression evaluated against an\n * IANA timezone via `Intl.DateTimeFormat`). Cron mode is DST-aware\n * because the wall-clock comparison happens after the timezone shift,\n * not before.\n *\n * Message types: cron.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { nextCronFire, validateTask, type CronTask } from './cron-schedule'\nimport { MSG } from '../../shared/protocol/constants'\nimport { serverBuild } from '../../shared/protocol/messages'\nimport { ROLES } from '../../shared/roles'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CronRoomConfig {\n tasks: CronTask[]\n}\n\nexport interface CronExecution {\n taskName: string\n startedAt: string\n completedAt: string | null\n success: boolean\n durationMs: number\n error?: string\n}\n\ninterface CronAttachment extends UserAttachment {\n /** True for member/admin roles; false for viewers and unauthenticated anon. */\n canWrite: boolean\n}\n\n/**\n * `GET /internal/cron/tasks` on the room's stub — the request `armCronRoom`\n * sends. Reachable only by DO-stub fetch from the app worker (see the security\n * note on `BaseRoom.fetch`); answers `{ tasks }` in the `cron.tasks` shape.\n */\nexport const CRON_TASKS_INTERNAL_PATH = '/internal/cron/tasks'\n\n// Cron commands that mutate scheduler state. Read-only messages\n// (CRON_TASKS, CRON_HISTORY) are allowed for any connected client per the\n// SDK contract that viewers can observe but not write.\nconst CRON_WRITE_TYPES: ReadonlySet<string> = new Set([\n MSG.CRON_TRIGGER,\n MSG.CRON_PAUSE,\n MSG.CRON_RESUME,\n])\n\n// ============================================================================\n// CronRoom\n// ============================================================================\n\nexport abstract class CronRoom<E = Record<string, unknown>> extends BaseRoom<E> {\n private tasks: CronTask[]\n private initialized = false\n\n constructor(state: DurableObjectState, env: unknown, config: CronRoomConfig) {\n super(state, env)\n // Validate at construction time — bad configs should never reach DB.\n this.tasks = config.tasks.map(validateTask)\n }\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS cron_tasks (\n name TEXT PRIMARY KEY,\n interval_minutes INTEGER,\n schedule TEXT,\n timezone TEXT,\n paused INTEGER NOT NULL DEFAULT 0,\n last_run_at TEXT,\n next_run_at TEXT\n )\n `)\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS cron_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_name TEXT NOT NULL,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n success INTEGER NOT NULL DEFAULT 0,\n duration_ms INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL\n )\n `)\n\n // Sync configured tasks. Insert new ones; refresh schedule/interval/timezone\n // on existing ones (so editing src/cron.ts and re-deploying picks up the\n // change without a manual reset). Preserve last_run_at / paused.\n for (const task of this.tasks) {\n const existing = this.sql\n .exec(`SELECT name FROM cron_tasks WHERE name = ?`, task.name)\n .toArray()\n if (existing.length === 0) {\n this.sql.exec(\n `INSERT INTO cron_tasks (name, interval_minutes, schedule, timezone, paused)\n VALUES (?, ?, ?, ?, ?)`,\n task.name,\n task.intervalMinutes ?? null,\n task.schedule ?? null,\n task.timezone ?? null,\n task.paused ? 1 : 0,\n )\n } else {\n this.sql.exec(\n `UPDATE cron_tasks\n SET interval_minutes = ?, schedule = ?, timezone = ?\n WHERE name = ?`,\n task.intervalMinutes ?? null,\n task.schedule ?? null,\n task.timezone ?? null,\n task.name,\n )\n }\n }\n\n // Remove tasks no longer in config\n const configNames = new Set(this.tasks.map((t) => t.name))\n const dbTasks = this.sql.exec(`SELECT name FROM cron_tasks`).toArray()\n for (const row of dbTasks) {\n if (!configNames.has((row as { name: string }).name)) {\n this.sql.exec(`DELETE FROM cron_tasks WHERE name = ?`, (row as { name: string }).name)\n }\n }\n\n // Schedule the next alarm\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // BaseRoom Lifecycle\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected async handleInternalRequest(request: Request, url: URL): Promise<Response | null> {\n if (request.method === 'GET' && url.pathname === CRON_TASKS_INTERNAL_PATH) {\n return Response.json({ tasks: this.getTaskStates() })\n }\n return super.handleInternalRequest(request, url)\n }\n\n protected onConnect(ws: WebSocket, user: UserAttachment): CronAttachment {\n this.ensureInitialized()\n\n const role = (user.role as string | undefined) ?? ROLES.VIEWER\n const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN\n\n // Tell the client whether trigger / pause / resume will be accepted,\n // so the monitor UI can render those controls as disabled for viewers\n // instead of trying and surfacing an ERROR after the fact.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n\n // Send current task list and recent history\n this.sendTo(ws, {\n type: MSG.CRON_TASKS,\n payload: { tasks: this.getTaskStates() },\n })\n\n this.sendTo(ws, {\n type: MSG.CRON_HISTORY,\n payload: { history: this.getRecentHistory(50) },\n })\n\n return { ...user, canWrite }\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n this.ensureInitialized()\n // Default `payload` so a payload-less frame reaches the typed reject\n // paths below instead of throwing into BaseRoom's catch-all logger.\n const { type, payload = {} } = message as { type: string; payload?: Record<string, unknown> }\n\n if (CRON_WRITE_TYPES.has(type) && !(user as CronAttachment).canWrite) {\n const error = 'Write access denied: viewer role cannot modify cron tasks'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName: payload.taskName as string | undefined,\n reason: 'read_only',\n error,\n })\n return\n }\n\n try {\n await this.dispatchMessage(ws, type, payload)\n } catch (error) {\n // A mid-operation throw (storage, alarm scheduling, broadcast) must\n // still settle the caller's receipt — an unacked requestId would leave\n // the client's mutation promise pending until unmount.\n try {\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName: payload.taskName as string | undefined,\n reason: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n } catch {\n // Socket already dead — the client's onclose drains its pendings.\n }\n throw error\n }\n }\n\n private async dispatchMessage(\n ws: WebSocket,\n type: string,\n payload: Record<string, unknown>,\n ): Promise<void> {\n switch (type) {\n case MSG.CRON_TRIGGER: {\n const taskName = payload.taskName as string\n if (!taskName) {\n const error = 'Missing taskName'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, { ok: false, reason: 'failed', error })\n return\n }\n const execution = await this.executeTask(taskName)\n if (!execution) {\n const error = `Unknown cron task: ${taskName}`\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName,\n reason: 'unknown_task',\n error,\n })\n break\n }\n this.ackMutation(\n ws,\n payload.requestId,\n execution.success\n ? { ok: true, taskName }\n : { ok: false, taskName, reason: 'failed', error: execution.error },\n )\n break\n }\n\n case MSG.CRON_PAUSE:\n case MSG.CRON_RESUME: {\n const taskName = payload.taskName as string\n if (!taskName) {\n const error = 'Missing taskName'\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, { ok: false, reason: 'failed', error })\n return\n }\n // Same rejection contract as CRON_TRIGGER: a name the scheduler does\n // not know must never ack ok — the UPDATE below would match zero rows\n // and the receipt would report a state flip that never happened.\n const known =\n this.sql.exec(`SELECT name FROM cron_tasks WHERE name = ?`, taskName).toArray().length > 0\n if (!known) {\n const error = `Unknown cron task: ${taskName}`\n this.sendTo(ws, { type: MSG.ERROR, payload: { error } })\n this.ackMutation(ws, payload.requestId, {\n ok: false,\n taskName,\n reason: 'unknown_task',\n error,\n })\n break\n }\n const paused = type === MSG.CRON_PAUSE\n this.sql.exec(`UPDATE cron_tasks SET paused = ? WHERE name = ?`, paused ? 1 : 0, taskName)\n if (!paused) this.scheduleNextAlarm()\n this.broadcastStatus()\n this.ackMutation(ws, payload.requestId, { ok: true, taskName })\n break\n }\n\n case MSG.CRON_TASKS: {\n this.sendTo(ws, {\n type: MSG.CRON_TASKS,\n payload: { tasks: this.getTaskStates() },\n })\n break\n }\n\n case MSG.CRON_HISTORY: {\n const limit = (payload.limit as number) ?? 50\n this.sendTo(ws, {\n type: MSG.CRON_HISTORY,\n payload: { history: this.getRecentHistory(limit) },\n })\n break\n }\n\n default:\n this.sendTo(ws, {\n type: MSG.ERROR,\n payload: { error: `Unknown cron message type: ${type}` },\n })\n }\n }\n\n protected async onAlarm(): Promise<void> {\n this.ensureInitialized()\n const now = new Date()\n\n // Find all tasks due to run\n const tasks = this.sql\n .exec(\n `SELECT * FROM cron_tasks WHERE paused = 0 AND (next_run_at IS NULL OR next_run_at <= ?)`,\n now.toISOString(),\n )\n .toArray()\n\n for (const row of tasks) {\n const task = row as {\n name: string\n interval_minutes: number | null\n schedule: string | null\n timezone: string | null\n }\n await this.executeTask(task.name)\n }\n\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // Task Execution\n // ==========================================================================\n\n private async executeTask(taskName: string): Promise<CronExecution | null> {\n // Resolve the configured task before invoking application code. A manual\n // trigger must not turn an arbitrary caller-provided name into a reported\n // successful execution.\n const taskRow = this.sql\n .exec(`SELECT interval_minutes, schedule, timezone FROM cron_tasks WHERE name = ?`, taskName)\n .toArray()[0] as\n | { interval_minutes: number | null; schedule: string | null; timezone: string | null }\n | undefined\n if (!taskRow) return null\n\n const startedAt = new Date().toISOString()\n const start = Date.now()\n let success = true\n let error: string | undefined\n\n let thrown: unknown\n try {\n await this.onTask(taskName)\n } catch (e) {\n success = false\n thrown = e\n error = e instanceof Error ? e.message : String(e)\n }\n\n const durationMs = Date.now() - start\n const completedAt = new Date().toISOString()\n\n // One line per run, so `deepspace logs --search cron` finds every run by\n // task name. Workers Logs renders an Error object passed to console.* as\n // its stack frames only — the message never arrives (verified against\n // production rows, 2026-08-18) — so the message goes in the line itself\n // and the stack rides along as a plain string.\n if (success) {\n console.log(`[cron] ${taskName} ok ${durationMs}ms`)\n } else {\n const stack = thrown instanceof Error ? thrown.stack : undefined\n console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...(stack ? [stack] : []))\n }\n\n // Record execution\n this.sql.exec(\n `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n taskName,\n startedAt,\n completedAt,\n success ? 1 : 0,\n durationMs,\n error ?? null,\n completedAt,\n )\n\n // Update last_run_at and compute next_run_at\n const nextRunAt = computeNextRunAt(taskRow, new Date(completedAt))\n this.sql.exec(\n `UPDATE cron_tasks SET last_run_at = ?, next_run_at = ? WHERE name = ?`,\n completedAt,\n nextRunAt,\n taskName,\n )\n\n // Trim history to last 500 entries\n this.sql.exec(\n `DELETE FROM cron_history WHERE id NOT IN (SELECT id FROM cron_history ORDER BY id DESC LIMIT 500)`,\n )\n\n // Broadcast update to monitors\n this.broadcastStatus()\n return { taskName, startedAt, completedAt, success, durationMs, error }\n }\n\n // ==========================================================================\n // Scheduling\n // ==========================================================================\n\n private scheduleNextAlarm(): void {\n const now = Date.now()\n let earliestMs = Infinity\n\n const tasks = this.sql\n .exec(\n `SELECT interval_minutes, schedule, timezone, next_run_at FROM cron_tasks WHERE paused = 0`,\n )\n .toArray()\n\n for (const row of tasks) {\n const t = row as {\n interval_minutes: number | null\n schedule: string | null\n timezone: string | null\n next_run_at: string | null\n }\n let nextMs: number | null = null\n if (t.next_run_at) {\n nextMs = new Date(t.next_run_at).getTime()\n } else if (t.interval_minutes) {\n // Cold-start interval task — run on the next alarm.\n nextMs = now\n } else if (t.schedule && t.timezone) {\n // Cold-start cron task — find the next minute boundary that matches.\n const next = nextCronFire(t.schedule, t.timezone, new Date(now))\n if (next) nextMs = next.getTime()\n }\n if (nextMs != null && nextMs < earliestMs) {\n earliestMs = nextMs\n }\n }\n\n if (earliestMs < Infinity) {\n const alarmTime = Math.max(earliestMs, now + 1000) // At least 1s from now\n this.state.storage.setAlarm(alarmTime)\n }\n }\n\n // ==========================================================================\n // Helpers\n // ==========================================================================\n\n private getTaskStates(): Record<string, unknown>[] {\n return this.sql\n .exec(`SELECT * FROM cron_tasks`)\n .toArray()\n .map((row) => {\n const r = row as Record<string, unknown>\n return {\n name: r.name,\n intervalMinutes: r.interval_minutes,\n schedule: r.schedule,\n timezone: r.timezone,\n paused: r.paused === 1,\n lastRunAt: r.last_run_at,\n nextRunAt: r.next_run_at,\n }\n })\n }\n\n private getRecentHistory(limit: number): CronExecution[] {\n return this.sql\n .exec(`SELECT * FROM cron_history ORDER BY id DESC LIMIT ?`, limit)\n .toArray()\n .map((row) => {\n const r = row as Record<string, unknown>\n return {\n taskName: r.task_name as string,\n startedAt: r.started_at as string,\n completedAt: r.completed_at as string | null,\n success: r.success === 1,\n durationMs: r.duration_ms as number,\n error: r.error as string | undefined,\n }\n })\n }\n\n private broadcastStatus(): void {\n this.broadcast({\n type: MSG.CRON_STATUS,\n payload: {\n tasks: this.getTaskStates(),\n recentHistory: this.getRecentHistory(10),\n },\n })\n }\n\n /**\n * Receipt for a mutation frame, addressed only to its sender. Receipts\n * are opt-in by correlation id: frames without a `requestId` keep the\n * fire-and-forget contract, so the (untyped) `requestId` from the wire\n * is the single gate here rather than a check at every call site.\n * Frames go through the shared `serverBuild` constructors so the wire\n * shape is enforced by the protocol types, not re-spelled here.\n */\n private ackMutation(\n ws: WebSocket,\n requestId: unknown,\n result:\n | { ok: true; taskName: string }\n | {\n ok: false\n taskName?: string\n reason: 'read_only' | 'unknown_task' | 'failed'\n error?: string\n },\n ): void {\n if (typeof requestId !== 'string') return\n this.sendTo(\n ws,\n result.ok\n ? serverBuild.cronAckSuccess(requestId, result.taskName)\n : serverBuild.cronAckFailure(requestId, result.reason, result.error, result.taskName),\n )\n }\n\n // ==========================================================================\n // Lifecycle Hook (subclass implements)\n // ==========================================================================\n\n /**\n * Execute a scheduled task by name.\n * Called both by the alarm scheduler and manual trigger.\n */\n protected abstract onTask(taskName: string): void | Promise<void>\n}\n\n/** Rooms this isolate has already woken — the wake is idempotent, so once is enough. */\nconst armedCronRooms = new Set<string>()\n\n/**\n * Wake the app's CronRoom so its alarm is armed.\n *\n * A Durable Object does not exist until something fetches it, and CronRoom\n * arms its alarm inside that first fetch — so a deployed schedule that no\n * client has opened yet runs nothing, and nothing reports it. The app worker\n * calls this from its request path (once per isolate is enough); the first\n * request the worker handles after a deploy then arms the schedule. A no-op\n * when the app declares no tasks. The wake runs under `ctx.waitUntil`, so it\n * never delays the response.\n */\nexport function armCronRoom(\n // Structural on purpose: Hono's `c.executionCtx` and workers-types'\n // `ExecutionContext` disagree on members the wake never uses.\n ctx: { waitUntil(promise: Promise<unknown>): void },\n namespace: DurableObjectNamespace,\n roomId: string,\n tasks: readonly CronTask[],\n): void {\n if (tasks.length === 0 || armedCronRooms.has(roomId)) return\n armedCronRooms.add(roomId)\n ctx.waitUntil(\n namespace\n .get(namespace.idFromName(roomId))\n .fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`)\n .then((response) => {\n // `fetch` resolves for HTTP failures. Only a successful internal route\n // proves initialization ran far enough to arm the room.\n if (!response.ok) armedCronRooms.delete(roomId)\n return response\n })\n // A transport failure is not \"armed\" either: let the next request retry.\n .catch(() => {\n armedCronRooms.delete(roomId)\n }),\n )\n}\n\n/**\n * Compute the next ISO timestamp the given task should run at, using\n * `from` as the reference moment.\n */\nfunction computeNextRunAt(\n task: { interval_minutes: number | null; schedule: string | null; timezone: string | null },\n from: Date,\n): string | null {\n if (task.interval_minutes) {\n return new Date(from.getTime() + task.interval_minutes * 60 * 1000).toISOString()\n }\n if (task.schedule && task.timezone) {\n const next = nextCronFire(task.schedule, task.timezone, from)\n return next ? next.toISOString() : null\n }\n return null\n}\n","/**\n * JobRoom — Per-app durable background-job execution Durable Object.\n *\n * Solves the \"long job dies when the response goes out\" problem on\n * Cloudflare Workers. `ctx.waitUntil` only gets 30s after a response;\n * jobs that need minutes-to-hours live here instead: rows in SQLite,\n * picked up by DO alarms (15-min wall budget per tick), resumable\n * across ticks via `ctx.continue` for the rare longer cases.\n *\n * Lifecycle: queued → running → succeeded | failed | canceled\n *\n * Crash recovery: if an isolate is recycled mid-run, the row stays at\n * `running`. On next init, rows older than ~16 min are either retried\n * (if attempts left) or marked failed.\n *\n * See the abstract `onJob` method below for the subclass contract.\n * Message types: job.*\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nimport { BaseRoom, type UserAttachment } from './base-room'\nimport { MSG } from '../../shared/protocol/constants'\nimport { ROLES } from '../../shared/roles'\nimport { serverBuild, type ServerMessage } from '../../shared/protocol/messages'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type JobStatus =\n | 'queued'\n | 'running'\n | 'succeeded'\n | 'failed'\n | 'canceled'\n\n/**\n * Job record exposed to `onJob` handlers and clients. Payloads are typed\n * as `unknown` at the SDK layer; subclasses narrow via a generic param.\n */\nexport interface Job<P = unknown> {\n id: string\n type: string\n status: JobStatus\n payload: P\n result?: unknown\n error?: string\n progress?: number\n progressMessage?: string\n attempts: number\n maxAttempts: number\n enqueuedAt: string\n startedAt?: string | null\n completedAt?: string | null\n enqueuedBy?: string | null\n /** Set when a previous run called `ctx.continue(state)`. */\n resumeFrom?: unknown\n}\n\nexport interface JobContext {\n /** Set progress (0..1) with an optional message. Broadcasts. */\n progress(value: number, message?: string): void\n /**\n * Save a resumable checkpoint and re-run `onJob` on the next alarm\n * with `job.resumeFrom = state`. The return value of `onJob` is\n * ignored once `continue` has been called.\n */\n continue(state: unknown, options?: { afterMs?: number }): void\n /**\n * Fires when a JOB_CANCEL arrives while this job is running in this\n * isolate. Cross-isolate cancels still mark the row `canceled` but\n * can't fire this signal.\n */\n signal: AbortSignal\n}\n\nexport interface JobRoomConfig {\n /** Default for jobs enqueued without explicit `maxAttempts`. Default 1 (no auto-retry). */\n defaultMaxAttempts?: number\n /** TTL for terminal rows (succeeded/failed/canceled) in ms. Default 24h. */\n retentionMs?: number\n /** Job count in the initial snapshot to a new subscriber. Default 100. */\n snapshotLimit?: number\n /** Delay before a failed attempt is retried, in ms. Default 1000. */\n retryBackoffMs?: number\n /** Re-check whether a connected user may mutate the queue. Defaults to member/admin roles. */\n authorizeWrite?: (user: UserAttachment) => boolean | Promise<boolean>\n /** Re-check whether a connected user may observe the queue. Defaults to authorizeWrite. */\n authorizeRead?: (user: UserAttachment) => boolean | Promise<boolean>\n}\n\ninterface JobAttachment extends UserAttachment {\n canWrite: boolean\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** 1 min past CF's 15-min alarm wall — anything older was killed, not running. */\nconst RUNNING_STALE_MS = 16 * 60 * 1000\nconst DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1000\nconst DEFAULT_SNAPSHOT_LIMIT = 100\n\n// ============================================================================\n// JobRoom\n// ============================================================================\n\nexport abstract class JobRoom<\n E = Record<string, unknown>,\n P = unknown,\n R = unknown,\n> extends BaseRoom<E> {\n private initialized = false\n private readonly defaultMaxAttempts: number\n private readonly retentionMs: number\n private readonly snapshotLimit: number\n private readonly retryBackoffMs: number\n private readonly authorizeWrite: (user: UserAttachment) => boolean | Promise<boolean>\n private readonly authorizeRead: (user: UserAttachment) => boolean | Promise<boolean>\n private broadcastQueue: Promise<void> = Promise.resolve()\n\n /** In-flight jobs in this isolate; lets same-isolate JOB_CANCEL fire the signal. */\n private readonly inFlight = new Map<string, AbortController>()\n\n /** Set by `ctx.continue` inside `executeJob`; reset before every run. */\n private continueState: { checkpoint: unknown; afterMs: number } | null = null\n\n constructor(state: DurableObjectState, env: unknown, config: JobRoomConfig = {}) {\n super(state, env)\n this.defaultMaxAttempts = Math.max(1, config.defaultMaxAttempts ?? 1)\n this.retentionMs = Math.max(0, config.retentionMs ?? DEFAULT_RETENTION_MS)\n this.snapshotLimit = Math.max(1, config.snapshotLimit ?? DEFAULT_SNAPSHOT_LIMIT)\n this.retryBackoffMs = Math.max(0, config.retryBackoffMs ?? 1000)\n this.authorizeWrite =\n config.authorizeWrite ?? ((user) => user.role === ROLES.MEMBER || user.role === ROLES.ADMIN)\n this.authorizeRead = config.authorizeRead ?? this.authorizeWrite\n }\n\n private ensureInitialized(): void {\n if (this.initialized) return\n this.initialized = true\n\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n status TEXT NOT NULL,\n payload TEXT,\n result TEXT,\n error TEXT,\n progress REAL,\n progress_message TEXT,\n checkpoint TEXT,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 1,\n enqueued_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n enqueued_by TEXT,\n next_run_at TEXT\n )\n `)\n\n this.sql.exec(\n `CREATE INDEX IF NOT EXISTS jobs_status_next_run ON jobs (status, next_run_at)`,\n )\n this.sql.exec(\n `CREATE INDEX IF NOT EXISTS jobs_completed_at ON jobs (completed_at)`,\n )\n\n this.recoverStuckRunning()\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // BaseRoom Lifecycle\n // ==========================================================================\n\n async fetch(request: Request): Promise<Response> {\n this.ensureInitialized()\n return super.fetch(request)\n }\n\n protected async onConnect(ws: WebSocket, user: UserAttachment): Promise<JobAttachment> {\n this.ensureInitialized()\n const { canRead, canWrite } = await this.resolveAccess(user)\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n this.sendTo(ws, serverBuild.jobSnapshot(canRead ? this.getRecentJobs(this.snapshotLimit) : []))\n return { ...user, canWrite }\n }\n\n protected async onMessage(\n ws: WebSocket,\n user: UserAttachment,\n message: { type: string; [key: string]: unknown },\n ): Promise<void> {\n this.ensureInitialized()\n const { type, payload } = message as {\n type: string\n payload: Record<string, unknown>\n }\n\n if (type === MSG.JOB_ENQUEUE || type === MSG.JOB_CANCEL || type === MSG.JOB_RETRY) {\n const attachment = user as JobAttachment\n const { canWrite } = await this.resolveAccess(user)\n if (attachment.canWrite !== canWrite) {\n attachment.canWrite = canWrite\n ws.serializeAttachment(attachment)\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } })\n } else if (!canWrite) {\n // Repeat the denial so a newly attempted enqueue rejects immediately.\n this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite: false } })\n }\n if (!canWrite) {\n this.sendTo(ws, serverBuild.error('Write access denied: job queue requires member access'))\n return\n }\n }\n\n switch (type) {\n case MSG.JOB_ENQUEUE: {\n const requestId = String(payload.requestId ?? '')\n const jobType = String(payload.type ?? '')\n if (!jobType) {\n this.sendTo(ws, serverBuild.error('JOB_ENQUEUE missing type'))\n return\n }\n const job = this.insertJob({\n type: jobType,\n payload: payload.payload,\n maxAttempts:\n typeof payload.maxAttempts === 'number'\n ? Math.max(1, Math.floor(payload.maxAttempts))\n : undefined,\n enqueuedBy: user.userId,\n })\n // Send the requestId-echoing ack only to the enqueuer (so their\n // pending promise resolves) and broadcast the same job *without*\n // the requestId to all other subscribers (so they upsert into\n // their `jobs` list without seeing somebody else's correlation id).\n this.sendTo(ws, serverBuild.jobEnqueued(job, requestId))\n this.broadcast(serverBuild.jobEnqueued(job), ws)\n this.scheduleNextAlarm()\n break\n }\n\n case MSG.JOB_CANCEL: {\n const jobId = String(payload.jobId ?? '')\n if (!jobId) {\n this.sendTo(ws, serverBuild.error('JOB_CANCEL missing jobId'))\n return\n }\n this.cancelJob(jobId)\n break\n }\n\n case MSG.JOB_RETRY: {\n const jobId = String(payload.jobId ?? '')\n if (!jobId) {\n this.sendTo(ws, serverBuild.error('JOB_RETRY missing jobId'))\n return\n }\n this.retryJob(jobId)\n break\n }\n\n default:\n this.sendTo(ws, serverBuild.error(`Unknown job message type: ${type}`))\n }\n }\n\n protected async onAlarm(): Promise<void> {\n this.ensureInitialized()\n // A recycled isolate can leave a recently-started row in `running`.\n // Initialization arms a recovery alarm for the exact stale boundary;\n // re-check here so that row becomes runnable even if no request wakes the\n // room in the meantime.\n this.recoverStuckRunning()\n await this.drainDueJobs()\n this.pruneExpired()\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // HTTP — internal enqueue path for cross-isolate callers\n // ==========================================================================\n\n /**\n * One internal endpoint, `POST /enqueue`, used by the `enqueueJob`\n * helper at the bottom of this file. The room is not meant to be\n * reachable from the public internet; callers route through the app\n * worker (or call `this.enqueue(...)` directly from a subclass).\n *\n * Subclasses overriding `onRequest` should call `super.onRequest(req)`\n * for paths they don't handle.\n */\n protected async onRequest(request: Request): Promise<Response> {\n this.ensureInitialized()\n const url = new URL(request.url)\n if (request.method !== 'POST' || !url.pathname.endsWith('/enqueue')) {\n return new Response('Not Found', { status: 404 })\n }\n const body = (await request.json().catch(() => null)) as\n | { type?: unknown; payload?: unknown; maxAttempts?: unknown; enqueuedBy?: unknown }\n | null\n if (!body || typeof body.type !== 'string' || !body.type) {\n return new Response(JSON.stringify({ error: 'type required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const job = this.insertJob({\n type: body.type,\n payload: body.payload,\n maxAttempts:\n typeof body.maxAttempts === 'number'\n ? Math.max(1, Math.floor(body.maxAttempts))\n : undefined,\n enqueuedBy: typeof body.enqueuedBy === 'string' ? body.enqueuedBy : undefined,\n })\n this.broadcast(serverBuild.jobEnqueued(job))\n this.scheduleNextAlarm()\n return new Response(JSON.stringify({ job }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // ==========================================================================\n // Public enqueue surface (server-side callers in the same isolate)\n // ==========================================================================\n\n /**\n * Synchronously persist a new job and arm the alarm. Returns the new\n * job. This is the in-isolate path — useful for a subclass `onJob`\n * that wants to chain follow-up work without paying a fetch hop:\n *\n * protected async onJob(job: Job, ctx: JobContext) {\n * // ... do work ...\n * if (needsFollowup) this.enqueue('followup', { parent: job.id })\n * }\n *\n * Workers code that lives outside the DO isolate (HTTP routes, cron\n * tasks, server actions) cannot call this directly — they must go\n * through the `enqueueJob` helper at the bottom of this file, which\n * routes via the DO's `/enqueue` HTTP endpoint.\n */\n public enqueue(\n type: string,\n payload?: unknown,\n options?: { maxAttempts?: number; enqueuedBy?: string },\n ): Job {\n this.ensureInitialized()\n const job = this.insertJob({\n type,\n payload,\n maxAttempts: options?.maxAttempts,\n enqueuedBy: options?.enqueuedBy,\n })\n this.broadcast(serverBuild.jobEnqueued(job))\n this.scheduleNextAlarm()\n return job\n }\n\n // ==========================================================================\n // Mutation helpers\n // ==========================================================================\n\n private insertJob(input: {\n type: string\n payload?: unknown\n maxAttempts?: number\n enqueuedBy?: string\n }): Job {\n const id = crypto.randomUUID()\n const enqueuedAt = new Date().toISOString()\n const maxAttempts = Math.max(1, input.maxAttempts ?? this.defaultMaxAttempts)\n const payloadJson = input.payload === undefined ? null : safeStringify(input.payload)\n\n this.sql.exec(\n `INSERT INTO jobs (id, type, status, payload, attempts, max_attempts, enqueued_at, enqueued_by, next_run_at)\n VALUES (?, ?, 'queued', ?, 0, ?, ?, ?, ?)`,\n id,\n input.type,\n payloadJson,\n maxAttempts,\n enqueuedAt,\n input.enqueuedBy ?? null,\n enqueuedAt,\n )\n\n return this.getJobById(id)!\n }\n\n private cancelJob(jobId: string): void {\n const row = this.sql\n .exec(`SELECT status FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as { status: JobStatus } | undefined\n if (!row) return\n // Terminal statuses are already resolved.\n if (row.status === 'succeeded' || row.status === 'failed' || row.status === 'canceled') {\n return\n }\n const completedAt = new Date().toISOString()\n // We intentionally do NOT clear `progress` / `progress_message` here:\n // \"canceled at 60%\" is useful diagnostic info, unlike succeeded/failed\n // where the terminal state already implies completion. `checkpoint`\n // is internal scaffolding that has no purpose after cancellation.\n this.sql.exec(\n `UPDATE jobs\n SET status = 'canceled',\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL\n WHERE id = ?`,\n completedAt,\n jobId,\n )\n // Best-effort: fire the AbortSignal if the job is running in this isolate.\n const controller = this.inFlight.get(jobId)\n if (controller) controller.abort()\n const job = this.getJobById(jobId)\n if (job) this.broadcast(serverBuild.jobCanceled(job))\n }\n\n private retryJob(jobId: string): void {\n const row = this.sql\n .exec(`SELECT status, attempts, max_attempts FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as\n | { status: JobStatus; attempts: number; max_attempts: number }\n | undefined\n if (!row) return\n // Retry is only meaningful for terminal failures / cancels. Live jobs\n // are already on track; succeeded jobs should be re-enqueued fresh.\n if (row.status !== 'failed' && row.status !== 'canceled') return\n\n // Lift max_attempts so the manual retry actually has a budget to run\n // even if the previous attempts exhausted the original limit.\n const newMax = Math.max(row.max_attempts, row.attempts + 1)\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n max_attempts = ?,\n error = NULL,\n result = NULL,\n completed_at = NULL,\n next_run_at = ?\n WHERE id = ?`,\n newMax,\n new Date().toISOString(),\n jobId,\n )\n const job = this.getJobById(jobId)\n if (job) this.broadcast(serverBuild.jobRetried(job))\n this.scheduleNextAlarm()\n }\n\n // ==========================================================================\n // Alarm / execution\n // ==========================================================================\n\n /**\n * Drain due jobs in FIFO order. All due jobs share the alarm's\n * 15-min wall budget (a CF property, not a per-job allocation).\n */\n private async drainDueJobs(): Promise<void> {\n // Freeze the cutoff at the start: rows whose `next_run_at` is\n // written during this drain (retries, `ctx.continue`) fall out\n // and pick up on the next alarm with a fresh budget. Without\n // this, `ctx.continue({ afterMs: 0 })` re-enters the same tick.\n const cutoff = new Date().toISOString()\n while (true) {\n const row = this.sql\n .exec(\n `SELECT * FROM jobs\n WHERE status = 'queued'\n AND (next_run_at IS NULL OR next_run_at <= ?)\n ORDER BY enqueued_at ASC\n LIMIT 1`,\n cutoff,\n )\n .toArray()[0] as unknown as JobRow | undefined\n if (!row) return\n await this.executeJob(row)\n }\n }\n\n private async executeJob(row: JobRow): Promise<void> {\n const startedAt = row.started_at ?? new Date().toISOString()\n const attemptsBefore = row.attempts\n const attempt = attemptsBefore + 1\n\n this.sql.exec(\n `UPDATE jobs SET status = 'running', started_at = ?, attempts = ? WHERE id = ?`,\n startedAt,\n attempt,\n row.id,\n )\n\n const controller = new AbortController()\n this.inFlight.set(row.id, controller)\n this.continueState = null\n\n const job: Job = {\n ...rowToJobView(row),\n status: 'running',\n attempts: attempt,\n startedAt,\n }\n\n let outcome: 'succeeded' | 'failed' | 'continued' = 'succeeded'\n let result: unknown\n let errorMessage: string | undefined\n\n try {\n const ctx = this.makeContext(row.id, controller.signal)\n result = await this.onJob(job as Job<P>, ctx)\n if (this.continueState) outcome = 'continued'\n } catch (e) {\n outcome = 'failed'\n errorMessage = e instanceof Error ? e.message : String(e)\n // Message in the line, stack as a string: Workers Logs renders an Error\n // object passed to console.* as its frames only (see CronRoom.executeTask).\n const stack = e instanceof Error ? e.stack : undefined\n console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...(stack ? [stack] : []))\n } finally {\n this.inFlight.delete(row.id)\n }\n\n // Cancellation racing the finish: if a cancel landed while we were\n // running, the row is now 'canceled' and we should not overwrite it.\n const currentStatus = this.readStatus(row.id)\n if (currentStatus === 'canceled') {\n this.continueState = null\n return\n }\n\n if (outcome === 'continued') {\n const state = this.continueState!\n this.continueState = null\n const nextRunAt = new Date(Date.now() + Math.max(0, state.afterMs)).toISOString()\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n checkpoint = ?,\n next_run_at = ?\n WHERE id = ?`,\n safeStringify(state.checkpoint),\n nextRunAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobProgress(updated))\n return\n }\n\n const completedAt = new Date().toISOString()\n\n if (outcome === 'succeeded') {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'succeeded',\n result = ?,\n completed_at = ?,\n error = NULL,\n checkpoint = NULL,\n next_run_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n result === undefined ? null : safeStringify(result),\n completedAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobSucceeded(updated))\n return\n }\n\n // Failure path. Retry if we have attempts left.\n if (attempt < row.max_attempts) {\n // Back off the retry by `retryBackoffMs` so a tight crash-loop\n // doesn't burn the alarm wall budget. Clear progress so the next\n // attempt starts fresh — a job that died at 80% should not show\n // \"queued at 80%\" until the retry handler reports its own progress.\n const nextRunAt = new Date(Date.now() + this.retryBackoffMs).toISOString()\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n error = ?,\n next_run_at = ?,\n started_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n errorMessage ?? 'unknown error',\n nextRunAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobRetried(updated))\n return\n }\n\n this.sql.exec(\n `UPDATE jobs\n SET status = 'failed',\n error = ?,\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL,\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n errorMessage ?? 'unknown error',\n completedAt,\n row.id,\n )\n const updated = this.getJobById(row.id)\n if (updated) this.broadcast(serverBuild.jobFailed(updated))\n }\n\n private makeContext(jobId: string, signal: AbortSignal): JobContext {\n return {\n progress: (value: number, message?: string) => {\n const clamped = Math.max(0, Math.min(1, value))\n this.sql.exec(\n `UPDATE jobs SET progress = ?, progress_message = ? WHERE id = ?`,\n clamped,\n message ?? null,\n jobId,\n )\n const updated = this.getJobById(jobId)\n if (updated) this.broadcast(serverBuild.jobProgress(updated))\n },\n continue: (state: unknown, options?: { afterMs?: number }) => {\n // Last call wins if invoked multiple times, matching the intuition\n // that handlers can revise their checkpoint as work progresses.\n this.continueState = {\n checkpoint: state,\n afterMs: Math.max(0, options?.afterMs ?? 0),\n }\n },\n signal,\n }\n }\n\n private readStatus(jobId: string): JobStatus | null {\n const row = this.sql\n .exec(`SELECT status FROM jobs WHERE id = ?`, jobId)\n .toArray()[0] as { status: JobStatus } | undefined\n return row?.status ?? null\n }\n\n /** Reauthorize every live event so role revocation does not leave a stale reader. */\n protected broadcast(message: ServerMessage, exclude?: WebSocket): void {\n this.broadcastQueue = this.broadcastQueue.then(async () => {\n await Promise.all(\n this.getWebSockets().map(async (ws) => {\n if (ws === exclude) return\n try {\n const user = this.getAttachment(ws)\n if (!user) return\n if (await this.authorizeRead(user)) this.sendTo(ws, message)\n } catch {\n // Authorization failures deny this event without poisoning the queue.\n }\n }),\n )\n })\n this.state.waitUntil(this.broadcastQueue)\n }\n\n private async resolveAccess(\n user: UserAttachment,\n ): Promise<{ canRead: boolean; canWrite: boolean }> {\n const canRead = await this.authorizeRead(user)\n if (!canRead) return { canRead: false, canWrite: false }\n const canWrite = this.authorizeRead === this.authorizeWrite || (await this.authorizeWrite(user))\n return { canRead: true, canWrite }\n }\n\n // ==========================================================================\n // Scheduling / recovery / pruning\n // ==========================================================================\n\n private scheduleNextAlarm(): void {\n const queued = this.sql\n .exec(\n `SELECT next_run_at FROM jobs\n WHERE status = 'queued'\n ORDER BY (next_run_at IS NULL) ASC, next_run_at ASC\n LIMIT 1`,\n )\n .toArray()[0] as { next_run_at: string | null } | undefined\n\n const now = Date.now()\n const targets: number[] = []\n if (queued) {\n const queuedAt = queued.next_run_at ? new Date(queued.next_run_at).getTime() : now\n targets.push(Number.isFinite(queuedAt) ? Math.max(now, queuedAt) : now)\n }\n\n const running = this.sql\n .exec(\n `SELECT started_at FROM jobs\n WHERE status = 'running'\n ORDER BY started_at ASC\n LIMIT 1`,\n )\n .toArray()[0] as { started_at: string | null } | undefined\n if (running) {\n const startedAt = running.started_at ? new Date(running.started_at).getTime() : Number.NaN\n targets.push(Number.isFinite(startedAt) ? Math.max(now, startedAt + RUNNING_STALE_MS) : now)\n }\n\n if (targets.length === 0) return\n // 50ms floor so we don't thrash the alarm with sub-tick re-arms.\n this.state.storage.setAlarm(Math.max(Math.min(...targets), now + 50))\n }\n\n private recoverStuckRunning(): void {\n const cutoff = new Date(Date.now() - RUNNING_STALE_MS).toISOString()\n const stuck = this.sql\n .exec(\n `SELECT id, attempts, max_attempts FROM jobs\n WHERE status = 'running' AND (started_at IS NULL OR started_at <= ?)`,\n cutoff,\n )\n .toArray() as { id: string; attempts: number; max_attempts: number }[]\n\n for (const r of stuck) {\n if (r.attempts >= r.max_attempts) {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'failed',\n error = 'Isolate recycled or wall time exceeded',\n completed_at = ?,\n checkpoint = NULL,\n next_run_at = NULL\n WHERE id = ?`,\n new Date().toISOString(),\n r.id,\n )\n } else {\n this.sql.exec(\n `UPDATE jobs\n SET status = 'queued',\n started_at = NULL,\n next_run_at = ?,\n error = 'Previous attempt killed mid-run; retrying',\n progress = NULL,\n progress_message = NULL\n WHERE id = ?`,\n new Date().toISOString(),\n r.id,\n )\n }\n }\n }\n\n private pruneExpired(): void {\n if (this.retentionMs === 0) return\n const cutoff = new Date(Date.now() - this.retentionMs).toISOString()\n this.sql.exec(\n `DELETE FROM jobs\n WHERE status IN ('succeeded', 'failed', 'canceled')\n AND completed_at IS NOT NULL\n AND completed_at < ?`,\n cutoff,\n )\n }\n\n // ==========================================================================\n // Read helpers\n // ==========================================================================\n\n private getRecentJobs(limit: number): Job[] {\n const rows = this.sql\n .exec(\n `SELECT * FROM jobs\n ORDER BY (status IN ('queued','running')) DESC, enqueued_at DESC\n LIMIT ?`,\n limit,\n )\n .toArray() as unknown as JobRow[]\n return rows.map(rowToJobView)\n }\n\n private getJobById(id: string): Job | null {\n const row = this.sql.exec(`SELECT * FROM jobs WHERE id = ?`, id).toArray()[0] as unknown as\n | JobRow\n | undefined\n return row ? rowToJobView(row) : null\n }\n\n // ==========================================================================\n // Handler — subclass implements\n // ==========================================================================\n\n /**\n * Run a single job. Return a value for success; throw for failure\n * (retried until attempts >= maxAttempts). Use `ctx.progress` for\n * progress, `ctx.continue(state)` to span multiple alarms, and\n * `ctx.signal` to honor client cancellations.\n */\n protected abstract onJob(job: Job<P>, ctx: JobContext): Promise<R | void> | R | void\n}\n\n// ============================================================================\n// Cross-isolate enqueue helper\n// ============================================================================\n\n/**\n * Enqueue a job from worker code that has the DO namespace. Routed by\n * `roomId` (typically `app:<APP_NAME>`; pass different ids for sharded\n * queues). Returns the new jobId; throws on failure.\n *\n * const jobId = await enqueueJob(\n * env.JOB_ROOMS,\n * `app:${env.APP_NAME}`,\n * 'ai-summarize',\n * { text },\n * { maxAttempts: 3 },\n * )\n */\nexport async function enqueueJob(\n namespace: DurableObjectNamespace,\n roomId: string,\n type: string,\n payload?: unknown,\n options?: { maxAttempts?: number; enqueuedBy?: string },\n): Promise<string> {\n const stub = namespace.get(namespace.idFromName(roomId))\n const res = await stub.fetch(\n new Request('https://job-room.internal/enqueue', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n type,\n payload,\n maxAttempts: options?.maxAttempts,\n enqueuedBy: options?.enqueuedBy,\n }),\n }),\n )\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`enqueueJob failed: ${res.status} ${text}`)\n }\n const data = (await res.json()) as { job?: { id?: string } }\n if (!data.job?.id) throw new Error('enqueueJob: malformed response')\n return data.job.id\n}\n\n// ============================================================================\n// Internals\n// ============================================================================\n\ninterface JobRow {\n id: string\n type: string\n status: JobStatus\n payload: string | null\n result: string | null\n error: string | null\n progress: number | null\n progress_message: string | null\n checkpoint: string | null\n attempts: number\n max_attempts: number\n enqueued_at: string\n started_at: string | null\n completed_at: string | null\n enqueued_by: string | null\n next_run_at: string | null\n}\n\nfunction rowToJobView(row: JobRow): Job {\n return {\n id: row.id,\n type: row.type,\n status: row.status,\n payload: row.payload == null ? undefined : JSON.parse(row.payload),\n result: row.result == null ? undefined : JSON.parse(row.result),\n error: row.error ?? undefined,\n progress: row.progress ?? undefined,\n progressMessage: row.progress_message ?? undefined,\n attempts: row.attempts,\n maxAttempts: row.max_attempts,\n enqueuedAt: row.enqueued_at,\n startedAt: row.started_at,\n completedAt: row.completed_at,\n enqueuedBy: row.enqueued_by,\n resumeFrom: row.checkpoint == null ? undefined : JSON.parse(row.checkpoint),\n }\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n // Fallback so cyclic payloads don't break the insert.\n return JSON.stringify({ __unserializable__: String(value) })\n }\n}\n","/**\n * DO Manifest — Dynamic Durable Object binding declarations.\n *\n * Apps export a `__DO_MANIFEST__` array in their worker.ts.\n * The CLI extracts it and sends it to the deploy worker,\n * which uses it to generate dynamic CF API bindings and migrations.\n */\n\n/// <reference types=\"@cloudflare/workers-types\" />\n\nexport interface DOManifestEntry {\n /** CF binding name, e.g. 'RECORD_ROOMS' */\n binding: string\n /** Exported class name, e.g. 'AppRecordRoom' */\n className: string\n /** Whether this DO uses SQLite storage */\n sqlite: boolean\n}\n\nexport type DOManifest = DOManifestEntry[]\n\n/**\n * Utility type: auto-generates Env bindings from a manifest.\n *\n * @example\n * const manifest = [\n * { binding: 'RECORD_ROOMS', className: 'AppRecordRoom', sqlite: true },\n * ] as const satisfies DOManifest\n *\n * type Env = BaseEnv & DOBindings<typeof manifest>\n * // => { RECORD_ROOMS: DurableObjectNamespace }\n */\nexport type DOBindings<T extends readonly DOManifestEntry[]> = {\n [K in T[number]['binding']]: DurableObjectNamespace\n}\n\n/** Default manifest for apps that don't declare one */\nexport const DEFAULT_DO_MANIFEST: DOManifest = [\n { binding: 'RECORD_ROOMS', className: 'AppRecordRoom', sqlite: true },\n { binding: 'YJS_ROOMS', className: 'AppYjsRoom', sqlite: true },\n]\n\n/**\n * Shape-validate a DO manifest received over the wire (e.g. from the CLI's\n * deploy form-field). Without this, malformed input gets passed straight to\n * `deployToWfP`'s `.filter(...).map(...)` chain and crashes the route mid-deploy.\n *\n * Mirrors the contract of `validateBindingManifest` for non-DO bindings.\n */\nexport function validateDoManifest(\n manifest: unknown,\n): { valid: true; manifest: DOManifest } | { valid: false; reason: string } {\n if (!Array.isArray(manifest)) {\n return { valid: false, reason: 'doManifest must be an array' }\n }\n const seen = new Set<string>()\n for (const entry of manifest) {\n if (!entry || typeof entry !== 'object') {\n return { valid: false, reason: `doManifest entry must be an object (got ${entry === null ? 'null' : typeof entry})` }\n }\n const e = entry as Record<string, unknown>\n if (typeof e.binding !== 'string' || !e.binding) {\n return { valid: false, reason: `doManifest entry missing 'binding' (string)` }\n }\n if (typeof e.className !== 'string' || !e.className) {\n return { valid: false, reason: `doManifest entry \"${e.binding}\" missing 'className' (string)` }\n }\n if (typeof e.sqlite !== 'boolean') {\n return { valid: false, reason: `doManifest entry \"${e.binding}\" 'sqlite' must be a boolean` }\n }\n if (seen.has(e.binding)) {\n return { valid: false, reason: `doManifest has duplicate binding \"${e.binding}\"` }\n }\n seen.add(e.binding)\n }\n return { valid: true, manifest: manifest as DOManifest }\n}\n","/**\n * Binding Manifest — non-DO bindings declared by an app's wrangler.toml that\n * the deploy-worker should pass through to Cloudflare's WfP upload API.\n *\n * Apps don't export a `__BINDING_MANIFEST__`; the CLI extracts these from the\n * normalized vite/wrangler output config at deploy time. This file just owns\n * the types + validation so both sides (CLI client and deploy-worker server)\n * agree on the shape.\n */\n/// <reference types=\"@cloudflare/workers-types\" />\n\n/**\n * A single non-DO binding the app declares. Mirrors CF's WfP binding API.\n *\n * Provisionable resources (d1, kv_namespace, vectorize, r2_bucket, queue) accept\n * the literal string `\"auto\"` in their ID field to request platform-side\n * provisioning at deploy time. When `\"auto\"` is used, the deploy-worker creates\n * the resource on the platform CF account, persists the resulting CF ID in the\n * app registry, and substitutes the real ID before forwarding to WfP. The\n * sentinel sticks around in this type because:\n * 1. `wrangler` parsing requires a non-empty string in the id field\n * 2. The CLI passes the unresolved manifest through to the deploy-worker\n * 3. The deploy-worker is the only side with CF API credentials\n *\n * Companion fields (`database_name`, `title`, `dimensions`, `metric`) are only\n * used when `\"auto\"` is set — they tell the provisioner how to create the\n * resource. After provisioning these fields are still present on the wire but\n * ignored by WfP.\n */\nexport type CustomBinding =\n | { type: 'plain_text'; name: string; text: string }\n | { type: 'json'; name: string; json: BindingJsonValue }\n | {\n /** Managed by DeepSpace; never forwarded into the customer Worker env. */\n type: 'ai_search'\n name: string\n /** Only the untrusted `auto` sentinel is accepted from wrangler.toml. */\n instance_name: string\n }\n | {\n type: 'vectorize'\n name: string\n /** Either a pre-existing index name or the literal `\"auto\"`. */\n index_name: string\n /** Required when `index_name === \"auto\"`. */\n dimensions?: number\n /** Required when `index_name === \"auto\"`. */\n metric?: 'cosine' | 'euclidean' | 'dot-product'\n }\n | { type: 'ai'; name: string }\n | {\n type: 'r2_bucket'\n name: string\n /** Either a pre-existing bucket name or the literal `\"auto\"`. */\n bucket_name: string\n }\n | {\n type: 'kv_namespace'\n name: string\n /** Either a pre-existing KV namespace ID or the literal `\"auto\"`. */\n namespace_id: string\n /** Required when `namespace_id === \"auto\"`. Human-readable namespace title. */\n title?: string\n }\n | {\n type: 'd1'\n name: string\n /** Either a pre-existing D1 database UUID or the literal `\"auto\"`. */\n id: string\n /** Required when `id === \"auto\"`. Human-readable database name. */\n database_name?: string\n }\n | {\n type: 'queue'\n name: string\n /** Either a pre-existing queue name or the literal `\"auto\"`. */\n queue_name: string\n }\n | { type: 'browser_rendering'; name: string }\n | { type: 'analytics_engine'; name: string; dataset?: string }\n | { type: 'hyperdrive'; name: string; id: string }\n | {\n type: 'ratelimit'\n name: string\n namespace_id: string\n simple: { limit: number; period: 10 | 60 }\n }\n\nexport type CustomBindingManifest = CustomBinding[]\n\n/** Values Wrangler accepts for JSON environment-variable bindings. */\nexport type BindingJsonValue =\n | null\n | boolean\n | number\n | string\n | BindingJsonValue[]\n | { [key: string]: BindingJsonValue }\n\n/** Cloudflare's maximum UTF-8 payload for one environment variable. */\nexport const MAX_ENV_VAR_BYTES = 5 * 1024\n\n/** Sentinel string in an ID field that requests platform-side provisioning. */\nexport const AUTO_PROVISION_SENTINEL = 'auto'\n\n/** Binding types whose ID field accepts the `\"auto\"` sentinel for provisioning. */\nexport const AUTO_PROVISIONABLE_TYPES = new Set([\n 'd1',\n 'kv_namespace',\n 'vectorize',\n 'r2_bucket',\n 'queue',\n 'ai_search',\n])\n\n/** Vectorize metric values accepted by CF when creating an index. */\nconst VECTORIZE_METRICS = new Set(['cosine', 'euclidean', 'dot-product'])\n\n/**\n * True if a binding has the `\"auto\"` sentinel in its primary ID field. Used by\n * the deploy-worker to decide which entries need provisioning and by the\n * validator to enforce companion-field requirements.\n */\nexport function isAutoProvision(b: CustomBinding): boolean {\n switch (b.type) {\n case 'd1':\n return b.id === AUTO_PROVISION_SENTINEL\n case 'kv_namespace':\n return b.namespace_id === AUTO_PROVISION_SENTINEL\n case 'vectorize':\n return b.index_name === AUTO_PROVISION_SENTINEL\n case 'r2_bucket':\n return b.bucket_name === AUTO_PROVISION_SENTINEL\n case 'queue':\n return b.queue_name === AUTO_PROVISION_SENTINEL\n case 'ai_search':\n return b.instance_name === AUTO_PROVISION_SENTINEL\n default:\n return false\n }\n}\n\n/** Binding `type` values an app is allowed to declare. */\nexport const ALLOWED_BINDING_TYPES = new Set([\n 'plain_text',\n 'json',\n 'vectorize',\n 'ai',\n 'r2_bucket',\n 'kv_namespace',\n 'd1',\n 'queue',\n 'browser_rendering',\n 'analytics_engine',\n 'hyperdrive',\n 'ratelimit',\n 'ai_search',\n])\n\n/**\n * Binding NAMES the SDK reserves on every app — apps may not redeclare them.\n *\n * Includes:\n * - Static-asset + service bindings the platform sets up automatically.\n * - SDK-managed env (auth, identity, owner JWT).\n * - The auto-attached cost-tracking AE dataset (`USAGE_EVENTS`).\n *\n * DO binding names (RECORD_ROOMS, YJS_ROOMS, etc.) are NOT in this set\n * because they live in a separate manifest (`__DO_MANIFEST__`).\n */\nexport const RESERVED_BINDING_NAMES = new Set([\n 'ASSETS',\n 'PLATFORM_WORKER',\n 'API_WORKER',\n 'DEEPSPACE_APP_ID',\n 'DEEPSPACE_RESOURCE_ID',\n 'DEEPSPACE_RELEASE_OPERATION_ID',\n 'APP_NAME',\n 'OWNER_USER_ID',\n 'AUTH_JWT_PUBLIC_KEY',\n 'AUTH_JWT_ISSUER',\n 'AUTH_WORKER_URL',\n 'APP_IDENTITY_TOKEN',\n 'APP_OWNER_JWT',\n 'USAGE_EVENTS',\n])\n\n/**\n * Per-binding validation error. `binding` is undefined for top-level\n * shape failures (e.g. manifest is not an array).\n */\nexport interface ValidationError {\n binding?: CustomBinding\n reason: string\n}\n\n/**\n * Validate a binding manifest. Returns errors; an empty array means valid.\n *\n * Used both client-side (CLI) for friendly fail-fast and server-side\n * (deploy-worker) as a security boundary — apps can't sneak in reserved\n * binding names by editing the wire format.\n */\nexport function validateBindingManifest(\n manifest: unknown,\n): { valid: true; bindings: CustomBindingManifest } | { valid: false; errors: ValidationError[] } {\n if (!Array.isArray(manifest)) {\n return { valid: false, errors: [{ reason: 'Manifest must be an array' }] }\n }\n const errors: ValidationError[] = []\n const seenNames = new Set<string>()\n let aiSearchCount = 0\n for (const entry of manifest) {\n if (!entry || typeof entry !== 'object') {\n errors.push({\n reason: `Entry must be an object (got ${entry === null ? 'null' : typeof entry})`,\n })\n continue\n }\n const e = entry as Record<string, unknown>\n const type = e.type\n const name = e.name\n if (typeof type !== 'string' || !ALLOWED_BINDING_TYPES.has(type)) {\n errors.push({\n reason: `Disallowed binding type: ${typeof type === 'string' ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(', ')}`,\n })\n continue\n }\n if (typeof name !== 'string' || !name) {\n errors.push({ reason: `Binding of type \"${type}\" missing 'name' (string)` })\n continue\n }\n if (RESERVED_BINDING_NAMES.has(name)) {\n errors.push({ reason: `Binding name \"${name}\" is reserved by the SDK` })\n continue\n }\n if (seenNames.has(name)) {\n errors.push({ reason: `Duplicate binding name \"${name}\"` })\n continue\n }\n seenNames.add(name)\n // Now safely typed: type is in ALLOWED_BINDING_TYPES + name is a string.\n const binding = entry as CustomBinding\n if (binding.type === 'ai_search') aiSearchCount++\n const fieldErr = requiredFieldError(binding)\n if (fieldErr) errors.push({ binding, reason: fieldErr })\n }\n if (aiSearchCount > 1) {\n errors.push({ reason: 'Only one ai_search binding may be declared per app' })\n }\n if (errors.length) return { valid: false, errors }\n return { valid: true, bindings: manifest as CustomBindingManifest }\n}\n\nfunction requiredFieldError(b: CustomBinding): string | null {\n switch (b.type) {\n case 'plain_text':\n if (typeof b.text !== 'string') {\n return `plain_text binding \"${b.name}\" missing text (string)`\n }\n return envVarSizeError(b.name, b.text)\n case 'json': {\n if (!isBindingJsonValue(b.json)) {\n return `json binding \"${b.name}\" must contain a JSON-serializable value`\n }\n return envVarSizeError(b.name, JSON.stringify(b.json))\n }\n case 'ai_search':\n if (b.instance_name !== AUTO_PROVISION_SENTINEL) {\n return `ai_search binding \"${b.name}\" must set instance_name = \"auto\"; managed instances cannot be selected directly`\n }\n return null\n case 'vectorize': {\n if (!b.index_name) return `vectorize binding \"${b.name}\" missing index_name (or \"auto\")`\n if (b.index_name === AUTO_PROVISION_SENTINEL) {\n // Auto-provisioning needs the index shape upfront — CF requires both\n // dimensions and metric at index-create time.\n if (typeof b.dimensions !== 'number' || b.dimensions <= 0) {\n return `vectorize binding \"${b.name}\" with index_name=\"auto\" requires \"dimensions\" (positive number)`\n }\n if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {\n return `vectorize binding \"${b.name}\" with index_name=\"auto\" requires \"metric\" (one of: ${[...VECTORIZE_METRICS].join(', ')})`\n }\n }\n return null\n }\n case 'r2_bucket':\n return b.bucket_name ? null : `r2_bucket binding \"${b.name}\" missing bucket_name (or \"auto\")`\n case 'kv_namespace': {\n if (!b.namespace_id)\n return `kv_namespace binding \"${b.name}\" missing namespace_id (or \"auto\")`\n if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {\n return `kv_namespace binding \"${b.name}\" with namespace_id=\"auto\" requires \"title\"`\n }\n return null\n }\n case 'd1': {\n if (!b.id) return `d1 binding \"${b.name}\" missing id (or \"auto\")`\n if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {\n return `d1 binding \"${b.name}\" with id=\"auto\" requires \"database_name\"`\n }\n return null\n }\n case 'queue':\n return b.queue_name ? null : `queue binding \"${b.name}\" missing queue_name (or \"auto\")`\n case 'hyperdrive':\n // Hyperdrive auto-provisioning is out of scope for v1 (needs upstream\n // connection-string management). Only pre-existing IDs accepted.\n if (!b.id) return `hyperdrive binding \"${b.name}\" missing id`\n if (b.id === AUTO_PROVISION_SENTINEL) {\n return `hyperdrive binding \"${b.name}\": auto-provisioning not yet supported (provide a real id)`\n }\n return null\n case 'ratelimit':\n if (!/^\\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {\n return `ratelimit binding \"${b.name}\" requires a positive integer namespace_id string`\n }\n if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {\n return `ratelimit binding \"${b.name}\" requires simple.limit (positive integer)`\n }\n if (b.simple.period !== 10 && b.simple.period !== 60) {\n return `ratelimit binding \"${b.name}\" requires simple.period (10 or 60)`\n }\n return null\n case 'ai':\n case 'browser_rendering':\n case 'analytics_engine':\n return null // no required fields beyond `name`\n default: {\n // Exhaustiveness guard — adding a new variant without a case fails here.\n const _exhaustive: never = b\n void _exhaustive\n return null\n }\n }\n}\n\n/**\n * Convert vite/wrangler's normalized config (from `.wrangler/deploy/config.json`)\n * into a CustomBindingManifest.\n *\n * Vite normalizes wrangler.toml into object/array structures with shapes like\n * `{ ai: { binding: 'AI' } }`, `{ vectorize: [{ binding, index_name }] }`,\n * etc. We extract each known shape with explicit field plucks (no broad\n * `as` casts) and return a flat array.\n */\nexport function bindingManifestFromOutputConfig(\n outputConfig: Record<string, unknown>,\n): CustomBindingManifest {\n // Let Wrangler finish resolving the selected named environment first, then\n // reject this capability explicitly. Namespace access would let customer\n // runtime code address sibling instances and breaks managed isolation.\n const aiSearchNamespaces = outputConfig.ai_search_namespaces\n const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces)\n ? aiSearchNamespaces.length > 0\n : aiSearchNamespaces !== undefined && aiSearchNamespaces !== null\n if (declaresAiSearchNamespace) {\n throw new Error(\n 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = \"auto\" instead',\n )\n }\n const out: CustomBindingManifest = []\n\n // Wrangler resolves top-level or selected-environment `vars` into one map.\n // The two scaffold vars are authoritative platform bindings at runtime, so\n // do not duplicate their build-time values in the custom manifest. Every\n // other var is app-owned and must reach the live Worker exactly as Wrangler\n // represents it: strings as plain_text, structured values as json.\n const vars = isObject(outputConfig.vars) ? outputConfig.vars : null\n for (const [name, value] of Object.entries(vars ?? {})) {\n if (name === 'DEEPSPACE_APP_ID' || name === 'APP_NAME') continue\n if (typeof value === 'string') {\n out.push({ type: 'plain_text', name, text: value })\n continue\n }\n if (!isBindingJsonValue(value)) {\n throw new Error(`Wrangler var \"${name}\" is not a JSON-serializable value`)\n }\n out.push({ type: 'json', name, json: value })\n }\n\n // ai: single object\n const aiBinding = pluckString(outputConfig.ai, 'binding')\n if (aiBinding) out.push({ type: 'ai', name: aiBinding })\n\n // vectorize: array of { binding, index_name, dimensions?, metric? }\n // `dimensions` and `metric` are only required when index_name === \"auto\"\n // (auto-provisioning); the validator enforces that, so we just pluck both\n // here and let it decide.\n for (const v of asObjectArray(outputConfig.vectorize)) {\n const name = pluckString(v, 'binding')\n const index_name = pluckString(v, 'index_name')\n if (!name || !index_name) continue\n const dimensions = pluckNumber(v, 'dimensions')\n const metric = pluckString(v, 'metric')\n out.push({\n type: 'vectorize',\n name,\n index_name,\n ...(dimensions != null && { dimensions }),\n ...(metric &&\n (metric === 'cosine' || metric === 'euclidean' || metric === 'dot-product') && { metric }),\n })\n }\n\n // ai_search: Wrangler's array form. `ai_search_namespaces` deliberately\n // remains unextracted: it grants runtime code access to arbitrary sibling\n // instances and is forbidden for managed knowledge.\n for (const a of asObjectArray(outputConfig.ai_search)) {\n const name = pluckString(a, 'binding')\n const instance_name = pluckString(a, 'instance_name')\n if (name && instance_name) out.push({ type: 'ai_search', name, instance_name })\n }\n\n // r2_buckets: array of { binding, bucket_name }\n for (const r of asObjectArray(outputConfig.r2_buckets)) {\n const name = pluckString(r, 'binding')\n const bucket_name = pluckString(r, 'bucket_name')\n if (name && bucket_name) out.push({ type: 'r2_bucket', name, bucket_name })\n }\n\n // kv_namespaces: array of { binding, id, title? }\n // `title` is only required when id === \"auto\"; pluck both here and let\n // the validator enforce the dependency.\n for (const k of asObjectArray(outputConfig.kv_namespaces)) {\n const name = pluckString(k, 'binding')\n const namespace_id = pluckString(k, 'id')\n if (!name || !namespace_id) continue\n const title = pluckString(k, 'title')\n out.push({\n type: 'kv_namespace',\n name,\n namespace_id,\n ...(title && { title }),\n })\n }\n\n // d1_databases: array of { binding, database_id, database_name? }\n // `database_name` is only required when database_id === \"auto\"; pluck both\n // and let the validator enforce.\n for (const d of asObjectArray(outputConfig.d1_databases)) {\n const name = pluckString(d, 'binding')\n const id = pluckString(d, 'database_id')\n if (!name || !id) continue\n const database_name = pluckString(d, 'database_name')\n out.push({\n type: 'd1',\n name,\n id,\n ...(database_name && { database_name }),\n })\n }\n\n // queues.producers: array of { binding, queue }\n const queues = isObject(outputConfig.queues) ? outputConfig.queues : null\n for (const q of asObjectArray(queues?.producers)) {\n const name = pluckString(q, 'binding')\n const queue_name = pluckString(q, 'queue')\n if (name && queue_name) out.push({ type: 'queue', name, queue_name })\n }\n\n // browser: { binding } (Cloudflare names this `browser` not `browser_rendering`)\n const browserBinding = pluckString(outputConfig.browser, 'binding')\n if (browserBinding) out.push({ type: 'browser_rendering', name: browserBinding })\n\n // analytics_engine_datasets: array of { binding, dataset? }\n for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {\n const name = pluckString(a, 'binding')\n const dataset = pluckString(a, 'dataset')\n if (name) out.push({ type: 'analytics_engine', name, dataset: dataset ?? undefined })\n }\n\n // hyperdrive: array of { binding, id }\n for (const h of asObjectArray(outputConfig.hyperdrive)) {\n const name = pluckString(h, 'binding')\n const id = pluckString(h, 'id')\n if (name && id) out.push({ type: 'hyperdrive', name, id })\n }\n\n // Wrangler resolves its native `ratelimits` table into the Vite build\n // output. Translate it to the Workers upload binding only at this boundary.\n for (const r of asObjectArray(outputConfig.ratelimits)) {\n const name = pluckString(r, 'name')\n const namespace_id = pluckString(r, 'namespace_id')\n const simple = isObject(r.simple) ? r.simple : null\n const limit = pluckNumber(simple, 'limit')\n const period = pluckNumber(simple, 'period')\n if (!name || !namespace_id || limit === null || (period !== 10 && period !== 60)) continue\n out.push({ type: 'ratelimit', name, namespace_id, simple: { limit, period } })\n }\n\n return out\n}\n\n// ---- internal helpers (no broad `as` casts) ----\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\nfunction isBindingJsonValue(\n value: unknown,\n ancestors = new Set<object>(),\n): value is BindingJsonValue {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean' ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n return true\n }\n if (typeof value !== 'object') return false\n if (ancestors.has(value)) return false\n\n if (!Array.isArray(value)) {\n const prototype = Object.getPrototypeOf(value)\n if (prototype !== Object.prototype && prototype !== null) return false\n }\n\n ancestors.add(value)\n const valid = Array.isArray(value)\n ? value.every((entry) => isBindingJsonValue(entry, ancestors))\n : Object.values(value).every((entry) => isBindingJsonValue(entry, ancestors))\n ancestors.delete(value)\n return valid\n}\n\nfunction envVarSizeError(name: string, serializedValue: string): string | null {\n const size = new TextEncoder().encode(serializedValue).byteLength\n return size <= MAX_ENV_VAR_BYTES\n ? null\n : `Environment variable binding \"${name}\" is ${size} bytes; maximum is ${MAX_ENV_VAR_BYTES}`\n}\n\nfunction pluckString(v: unknown, key: string): string | null {\n if (!isObject(v)) return null\n const val = v[key]\n return typeof val === 'string' && val.length > 0 ? val : null\n}\n\nfunction pluckNumber(v: unknown, key: string): number | null {\n if (!isObject(v)) return null\n const val = v[key]\n return typeof val === 'number' && Number.isFinite(val) ? val : null\n}\n\nfunction asObjectArray(v: unknown): Record<string, unknown>[] {\n if (!Array.isArray(v)) return []\n return v.filter(isObject)\n}\n","/**\n * Pure helpers for computing Cloudflare Durable Object migrations from a\n * declared manifest + the bindings already registered on a deployed script.\n *\n * Lives in the SDK (not deploy-worker) so the logic is testable with vitest\n * and reusable from other CF deploy paths if we ever add them.\n */\n\nimport type { DOManifestEntry } from './do-manifest'\n\n/** Subset of CF's `bindings` API response we read from. */\nexport interface ExistingDOBinding {\n /** Binding name in `env`, e.g. 'RECORD_ROOMS' */\n name: string\n /** Always `'durable_object_namespace'` for DO bindings. */\n type: string\n /** SDK class name, e.g. 'AppRecordRoom' */\n class_name?: string\n}\n\n/** What goes in the CF script-upload `migrations` block. */\nexport interface DoMigrationDirective {\n tag: string\n new_sqlite_classes?: string[]\n deleted_classes?: string[]\n}\n\nexport interface DoMigrationPlan {\n /** New SQLite classes to register (present in manifest, absent in existing). */\n newSqliteClasses: string[]\n /** Classes to delete (present in existing, absent in manifest). */\n deletedClasses: string[]\n /** True when there's actual delta — only then should the migrations block be sent. */\n needsMigration: boolean\n /** The full directive to splat into the CF script-upload metadata. Null if no migration is needed. */\n directive: DoMigrationDirective | null\n}\n\nexport interface ComputeDoMigrationOptions {\n /**\n * Override for the timestamp baked into the migration tag. Tests pass a\n * fixed value to assert determinism; production omits this and gets\n * `Date.now()`, which guarantees lifetime tag uniqueness even across\n * cycles like `[A]→[A,B]→[A]→[A,B]`.\n */\n now?: number\n}\n\n/**\n * Compute the migration plan for a deploy.\n *\n * manifest: what the app declares now\n * existing: what CF currently has registered for this script\n *\n * Behavior:\n * - new_sqlite_classes ← in manifest, not in existing, sqlite=true\n * - deleted_classes ← in existing, not in manifest\n * - needsMigration ← either of the above is non-empty\n * - tag ← content-addressed by (add, remove) so:\n * - identical re-deploy → unchanged tag → no-op (and\n * `needsMigration` is false anyway)\n * - any class change → unique tag → CF processes\n *\n * Bug history: an earlier version computed `tag = v${count}`. Removing a class\n * dropped the count, the migration block was skipped (no NEW classes), and CF\n * retained the orphaned class registration with its SQLite storage. The\n * `deleted_classes` path closes that gap; the content-addressed tag prevents\n * tag collisions when class sets are added and removed in different orders.\n */\nexport function computeDoMigration(\n manifest: readonly DOManifestEntry[],\n existing: readonly ExistingDOBinding[],\n options?: ComputeDoMigrationOptions,\n): DoMigrationPlan {\n const existingNames = new Set(\n existing.filter((b) => b.type === 'durable_object_namespace').map((b) => b.name),\n )\n const manifestBindingNames = new Set(manifest.map((e) => e.binding))\n\n const newSqliteClasses = manifest\n .filter((e) => e.sqlite && !existingNames.has(e.binding))\n .map((e) => e.className)\n .sort()\n\n const deletedClasses = [...existingNames]\n .filter((name) => !manifestBindingNames.has(name))\n .map((name) => {\n // We need the className for `deleted_classes`. CF's bindings API returns\n // `class_name` per binding; fall back to the binding name when the API\n // didn't include it (older responses, or non-DO entries that slipped\n // through the type filter).\n const m = existing.find((b) => b.name === name)\n return m?.class_name ?? name\n })\n .sort()\n\n const needsMigration = newSqliteClasses.length > 0 || deletedClasses.length > 0\n const directive: DoMigrationDirective | null = needsMigration\n ? {\n tag: migrationTag(newSqliteClasses, deletedClasses, options),\n ...(newSqliteClasses.length ? { new_sqlite_classes: newSqliteClasses } : {}),\n ...(deletedClasses.length ? { deleted_classes: deletedClasses } : {}),\n }\n : null\n\n return { newSqliteClasses, deletedClasses, needsMigration, directive }\n}\n\n/**\n * Unique-per-deploy migration tag.\n *\n * CF requires tags to be unique *across the script's lifetime* — reusing a\n * tag rejects the deploy. Pure content-addressing (hash of add+remove) is\n * insufficient because cycles produce identical content: `[A] → [A,B] → [A]\n * → [A,B]` step 4's delta `(add=[B], remove=[])` matches step 2's verbatim.\n * Even adding the prior state doesn't help — the cycle returns to the same\n * prior state, same delta, same tag, CF rejects.\n *\n * Format: `m-<base36-millis>-<delta-hash>`\n * - base36 timestamp guarantees lifetime uniqueness (CF deploys can't\n * collide on the millisecond)\n * - delta-hash suffix is for human debuggability — eyeballing \"ah, that\n * migration removed ClassFoo\" is easier than reading raw timestamps\n *\n * Tests inject `now` so they can assert deterministic tag values; production\n * uses `Date.now()`.\n */\nfunction migrationTag(\n addClasses: string[],\n removeClasses: string[],\n options?: ComputeDoMigrationOptions,\n): string {\n const ts = (options?.now ?? Date.now()).toString(36)\n const delta = fnv1a32(\n JSON.stringify({ add: [...addClasses].sort(), remove: [...removeClasses].sort() }),\n )\n return `m-${ts}-${delta}`\n}\n\nfunction fnv1a32(s: string): string {\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193) >>> 0\n }\n return h.toString(16).padStart(8, '0')\n}\n","/**\n * App-name validation + sanitization helpers.\n *\n * Strategy: validate strictly so we have a precise definition of \"valid\",\n * but DON'T reject non-conforming names — sanitize them, warn the user, and\n * proceed. Hard rejection would break apps whose `wrangler.toml name` was\n * something like `My_App` (previously deployed as `my-app` via silent\n * server-side sanitization). The new behavior preserves \"still deploys,\"\n * but the CLI now surfaces a warning so the user can fix the name when\n * convenient instead of being silently surprised by their hostname.\n *\n * Rules track Cloudflare's WfP script-name constraints (RFC 1035 host label,\n * no consecutive dashes) plus our 2-char minimum so subdomains read sensibly.\n */\n\nexport const APP_NAME_RULES = {\n /** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */\n pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,\n minLength: 2,\n maxLength: 63,\n} as const\n\nexport type AppNameValidation =\n | { valid: true; name: string }\n | { valid: false; reason: string }\n\n/**\n * Strict validation: returns valid only if the name already conforms.\n * Useful as a precondition test or for CI lints.\n */\nexport function validateAppName(raw: unknown): AppNameValidation {\n if (typeof raw !== 'string') {\n return { valid: false, reason: 'App name must be a string' }\n }\n const name = raw.trim()\n if (!name) {\n return { valid: false, reason: 'App name is required' }\n }\n if (name.length < APP_NAME_RULES.minLength) {\n return { valid: false, reason: `App name \"${name}\" is too short (min ${APP_NAME_RULES.minLength} chars)` }\n }\n if (name.length > APP_NAME_RULES.maxLength) {\n return { valid: false, reason: `App name \"${name}\" is too long (max ${APP_NAME_RULES.maxLength} chars)` }\n }\n if (!APP_NAME_RULES.pattern.test(name)) {\n return {\n valid: false,\n reason:\n `App name \"${name}\" is invalid. Use lowercase letters, digits, and single hyphens only ` +\n `(must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`,\n }\n }\n return { valid: true, name }\n}\n\nexport type AppNameResolution =\n | { ok: true; name: string; warning?: string }\n | { ok: false; reason: string }\n\n/**\n * Resolve an app name for deploy: prefer the input as-is if valid, otherwise\n * sanitize and warn. Hard-fail only if even sanitization can't produce a\n * valid name (empty, all-non-alphanumeric, too short, too long).\n *\n * The intent is \"what previously worked still works, with a friendly warning\n * about non-conforming names.\"\n */\nexport function resolveAppName(raw: unknown): AppNameResolution {\n const strict = validateAppName(raw)\n if (strict.valid) return { ok: true, name: strict.name }\n\n if (typeof raw !== 'string') return { ok: false, reason: strict.reason }\n const trimmed = raw.trim()\n if (!trimmed) return { ok: false, reason: 'App name is required' }\n\n const sanitized = sanitizeAppName(trimmed)\n const sanitizedStrict = validateAppName(sanitized)\n if (!sanitizedStrict.valid) {\n return {\n ok: false,\n reason: `App name \"${trimmed}\" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`,\n }\n }\n return {\n ok: true,\n name: sanitized,\n warning:\n `App name \"${trimmed}\" doesn't conform to deploy rules — using \"${sanitized}\" instead. ` +\n `Update wrangler.toml's \\`name\\` to silence this warning.`,\n }\n}\n\n/**\n * Lossy normalization that mirrors what the deploy-worker historically did\n * server-side. Keep only [a-z0-9-], collapse consecutive dashes, trim leading\n * and trailing dashes.\n */\nfunction sanitizeAppName(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n","/**\n * AI Chat Schemas\n *\n * Pre-built collection schemas for DO-backed AI chat history.\n * The worker is the only writer; the client reads via `useQuery`.\n */\n\nimport type { CollectionSchema } from './registry'\n\n// member.create is false on both collections: writes flow exclusively through\n// the worker's /api/ai/* routes, which use X-App-Action to bypass RBAC.\n// Allowing direct WS creates would let a user PUT fake `role: 'assistant'`\n// rows into their own chats and have the next turn's loadMessages feed them\n// to the LLM as if they were real assistant outputs.\nexport const AI_CHATS_SCHEMA: CollectionSchema = {\n name: 'ai-chats',\n columns: [\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true, required: true },\n { name: 'title', storage: 'text', interpretation: 'plain' },\n { name: 'model', storage: 'text', interpretation: 'plain' },\n { name: 'compactedSummary', storage: 'text', interpretation: 'plain' },\n { name: 'compactedThroughId', storage: 'text', interpretation: 'plain' },\n ],\n ownerField: 'userId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'own', create: false, update: 'own', delete: 'own' },\n viewer: { read: 'own', create: false, update: false, delete: false },\n },\n}\n\nexport const AI_MESSAGES_SCHEMA: CollectionSchema = {\n name: 'ai-messages',\n columns: [\n { name: 'chatId', storage: 'text', interpretation: 'plain', immutable: true, required: true },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true, required: true },\n { name: 'role', storage: 'text', interpretation: 'plain', required: true },\n { name: 'content', storage: 'text', interpretation: 'plain' },\n { name: 'parts', storage: 'text', interpretation: { kind: 'json' } },\n ],\n ownerField: 'userId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: 'own', create: false, update: 'own', delete: 'own' },\n viewer: { read: 'own', create: false, update: false, delete: false },\n },\n}\n","/**\n * Messaging Schemas\n *\n * Pre-built collection schemas for messaging functionality.\n * Any app can import these to add channels, messages, reactions, etc.\n *\n * @example\n * ```typescript\n * import { CHANNELS_SCHEMA, MESSAGES_SCHEMA, REACTIONS_SCHEMA } from 'deepspace/worker'\n * export const schemas = [usersSchema, CHANNELS_SCHEMA, MESSAGES_SCHEMA, REACTIONS_SCHEMA]\n * ```\n */\n\nimport type { CollectionSchema } from './registry'\n\nexport const CHANNELS_SCHEMA: CollectionSchema = {\n name: 'channels',\n columns: [\n { name: 'name', storage: 'text', interpretation: 'plain' },\n { name: 'description', storage: 'text', interpretation: 'plain' },\n {\n name: 'type',\n storage: 'text',\n interpretation: { kind: 'select', options: ['public', 'private', 'dm'] },\n },\n {\n name: 'createdBy',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n { name: 'archived', storage: 'number', interpretation: { kind: 'boolean' } },\n ],\n ownerField: 'createdBy',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: false },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const MESSAGES_SCHEMA: CollectionSchema = {\n name: 'messages',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'content', storage: 'text', interpretation: 'plain' },\n {\n name: 'authorId',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n { name: 'parentMessageId', storage: 'text', interpretation: 'plain' },\n { name: 'edited', storage: 'number', interpretation: { kind: 'boolean' } },\n { name: 'editedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n // Soft-delete flag. Slack-style messaging keeps the row + parent IDs\n // intact (so threads remain coherent) and renders a tombstone instead.\n // Hard-delete via remove() still works if an app wants it.\n { name: 'deleted', storage: 'number', interpretation: { kind: 'boolean' } },\n ],\n ownerField: 'authorId',\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const REACTIONS_SCHEMA: CollectionSchema = {\n name: 'reactions',\n columns: [\n { name: 'messageId', storage: 'text', interpretation: 'plain' },\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'emoji', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n ],\n ownerField: 'userId',\n uniqueOn: ['messageId', 'emoji', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: false, delete: true },\n member: { read: true, create: true, update: false, delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const CHANNEL_MEMBERS_SCHEMA: CollectionSchema = {\n name: 'channel-members',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n { name: 'joinedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n ownerField: 'userId',\n uniqueOn: ['channelId', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: false, delete: true },\n member: { read: true, create: true, update: false, delete: 'own' },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const CHANNEL_INVITATIONS_SCHEMA: CollectionSchema = {\n name: 'channel-invitations',\n columns: [\n {\n name: 'channelId',\n storage: 'text',\n interpretation: 'plain',\n required: true,\n immutable: true,\n },\n {\n name: 'invitedUserId',\n storage: 'text',\n interpretation: 'plain',\n required: true,\n immutable: true,\n },\n {\n name: 'invitedBy',\n storage: 'text',\n interpretation: 'plain',\n userBound: true,\n immutable: true,\n },\n {\n name: 'status',\n storage: 'text',\n interpretation: { kind: 'select', options: ['pending', 'accepted', 'declined'] },\n required: true,\n },\n ],\n uniqueOn: ['channelId', 'invitedUserId'],\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: true, delete: false },\n viewer: { read: true, create: false, update: false, delete: false },\n },\n}\n\nexport const READ_RECEIPTS_SCHEMA: CollectionSchema = {\n name: 'read-receipts',\n columns: [\n { name: 'channelId', storage: 'text', interpretation: 'plain' },\n { name: 'userId', storage: 'text', interpretation: 'plain', userBound: true, immutable: true },\n { name: 'lastReadAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n ownerField: 'userId',\n uniqueOn: ['channelId', 'userId'],\n permissions: {\n admin: { read: true, create: true, update: true, delete: true },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n viewer: { read: false, create: false, update: false, delete: false },\n },\n}\n","/**\n * Shared conversation schemas for RecordRoom-based conversations.\n *\n * These define the collections inside a per-conversation RecordRoom DO.\n * Used by apps that have messaging/conversation features (slack-clone,\n * helpdesk, mail, reddit-clone, etc.).\n *\n * Each conversation gets its own RecordRoom DO keyed by `conv:{id}`.\n */\n\nimport type { CollectionSchema } from './registry'\n\n// Re-export data interfaces from types (single source of truth)\nexport type {\n ConvMessageData,\n ConvReactionData,\n ConvMemberData,\n ConvReadCursorData,\n ConvVoteData,\n} from '../../shared/types'\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nexport const CONVERSATION_SCHEMAS: CollectionSchema[] = [\n {\n name: 'conv_messages',\n columns: [\n { name: 'Content', storage: 'text', interpretation: 'plain' },\n { name: 'AuthorId', storage: 'text', interpretation: 'plain' },\n { name: 'ParentId', storage: 'text', interpretation: 'plain' },\n { name: 'Edited', storage: 'number', interpretation: 'boolean' },\n { name: 'MessageType', storage: 'text', interpretation: 'plain' },\n { name: 'Metadata', storage: 'text', interpretation: 'json' },\n ],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'AuthorId',\n },\n {\n name: 'conv_reactions',\n columns: [\n { name: 'MessageId', storage: 'text', interpretation: 'plain' },\n { name: 'Emoji', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['MessageId', 'Emoji', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: false, delete: 'own' },\n admin: { read: true, create: true, update: false, delete: true },\n },\n ownerField: 'UserId',\n },\n {\n name: 'conv_members',\n columns: [\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'UserName', storage: 'text', interpretation: 'plain' },\n { name: 'Role', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n {\n name: 'conv_read_cursors',\n columns: [\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'LastReadAt', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n]\n\n/**\n * Voting schemas for Reddit-style apps.\n * Add these alongside CONVERSATION_SCHEMAS for apps that need voting.\n */\nexport const VOTING_SCHEMAS: CollectionSchema[] = [\n {\n name: 'conv_votes',\n columns: [\n { name: 'TargetId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'Direction', storage: 'number', interpretation: 'plain' },\n ],\n uniqueOn: ['TargetId', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n },\n]\n","/**\n * Directory DO Schemas\n *\n * Purpose-built collections for the `dir:{appName}` global DO type.\n * This is the cross-app-visible directory layer — any app can subscribe\n * to another app's directory in real-time via SHARED_CONNECTIONS.\n *\n * Five collections covering all standard communication/social patterns:\n * - conversations: channels, DMs, email threads, support chats\n * - conversation_state: per-user metadata (read cursor, stars, labels, folders)\n * - communities: groups, forums, boards, projects\n * - memberships: user membership in communities\n * - posts: feed items, tweets, Q&A questions, announcements\n *\n * Domain-specific data (tickets, CRM, procurement) stays in app DOs.\n * Message content stays in conv:{id} DOs (CONVERSATION_SCHEMAS).\n */\n\nimport type { CollectionSchema } from './registry'\n\n// Re-export data interfaces from types (single source of truth)\nexport type {\n DirectoryConversationData,\n ConversationStateData,\n DirectoryCommunityData,\n DirectoryMembershipData,\n DirectoryPostData,\n} from '../../shared/types'\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nconst conversationsSchema: CollectionSchema = {\n name: 'conversations',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Description', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'dm', 'group', 'email', 'outbound-email', 'support', 'announcement'] } },\n { name: 'Visibility', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'restricted'] } },\n { name: 'CreatedBy', storage: 'text', interpretation: 'plain' },\n { name: 'ParticipantHash', storage: 'text', interpretation: 'plain' },\n { name: 'ParticipantIds', storage: 'text', interpretation: 'json' },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['active', 'archived', 'resolved', 'closed'] } },\n { name: 'AssigneeId', storage: 'text', interpretation: 'plain' },\n { name: 'LinkedRef', storage: 'text', interpretation: 'json' },\n { name: 'LastMessageAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'LastMessagePreview', storage: 'text', interpretation: 'plain' },\n { name: 'LastMessageAuthor', storage: 'text', interpretation: 'plain' },\n { name: 'MessageCount', storage: 'number', interpretation: 'plain' },\n ],\n collaboratorsField: 'ParticipantIds',\n visibilityField: 'Visibility',\n permissions: {\n viewer: { read: 'shared', create: false, update: false, delete: false },\n member: { read: 'shared', create: true, update: true, delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'CreatedBy',\n}\n\nconst conversationStateSchema: CollectionSchema = {\n name: 'conversation_state',\n columns: [\n { name: 'ConversationId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'LastReadAt', storage: 'text', interpretation: { kind: 'datetime' } },\n { name: 'LastReadMessageCount', storage: 'number', interpretation: 'plain' },\n { name: 'Starred', storage: 'number', interpretation: 'boolean' },\n { name: 'Archived', storage: 'number', interpretation: 'boolean' },\n { name: 'Trashed', storage: 'number', interpretation: 'boolean' },\n { name: 'Labels', storage: 'text', interpretation: 'json' },\n { name: 'Folder', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['ConversationId', 'UserId'],\n ownerField: 'UserId',\n permissions: {\n viewer: { read: false, create: false, update: false, delete: false },\n member: { read: 'own', create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n}\n\nconst communitiesSchema: CollectionSchema = {\n name: 'communities',\n columns: [\n { name: 'Name', storage: 'text', interpretation: 'plain' },\n { name: 'Description', storage: 'text', interpretation: 'plain' },\n { name: 'CreatedBy', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['community', 'forum', 'board', 'project', 'server'] } },\n { name: 'Visibility', storage: 'text', interpretation: { kind: 'select', options: ['public', 'private', 'restricted'] } },\n { name: 'MemberCount', storage: 'number', interpretation: 'plain' },\n { name: 'Rules', storage: 'text', interpretation: 'json' },\n { name: 'IconUrl', storage: 'text', interpretation: 'plain' },\n { name: 'CoverUrl', storage: 'text', interpretation: 'plain' },\n ],\n uniqueOn: ['Name'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: false },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'CreatedBy',\n}\n\nconst membershipsSchema: CollectionSchema = {\n name: 'memberships',\n columns: [\n { name: 'CommunityId', storage: 'text', interpretation: 'plain' },\n { name: 'UserId', storage: 'text', interpretation: 'plain' },\n { name: 'UserName', storage: 'text', interpretation: 'plain' },\n { name: 'Role', storage: 'text', interpretation: { kind: 'select', options: ['member', 'moderator', 'admin'] } },\n { name: 'JoinedAt', storage: 'text', interpretation: { kind: 'datetime' } },\n ],\n uniqueOn: ['CommunityId', 'UserId'],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'UserId',\n}\n\nconst postsSchema: CollectionSchema = {\n name: 'posts',\n columns: [\n { name: 'Title', storage: 'text', interpretation: 'plain' },\n { name: 'Content', storage: 'text', interpretation: 'plain' },\n { name: 'AuthorId', storage: 'text', interpretation: 'plain' },\n { name: 'Type', storage: 'text', interpretation: { kind: 'select', options: ['post', 'question', 'answer', 'tweet', 'announcement', 'link'] } },\n { name: 'CommunityId', storage: 'text', interpretation: 'plain' },\n { name: 'ParentId', storage: 'text', interpretation: 'plain' },\n { name: 'ConversationId', storage: 'text', interpretation: 'plain' },\n { name: 'Status', storage: 'text', interpretation: { kind: 'select', options: ['draft', 'published', 'pinned', 'archived', 'accepted'] } },\n { name: 'Tags', storage: 'text', interpretation: 'json' },\n { name: 'LinkUrl', storage: 'text', interpretation: 'plain' },\n ],\n permissions: {\n viewer: { read: true, create: false, update: false, delete: false },\n member: { read: true, create: true, update: 'own', delete: 'own' },\n admin: { read: true, create: true, update: true, delete: true },\n },\n ownerField: 'AuthorId',\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\n/** All directory schemas for the `dir:{appName}` global DO type. */\nexport const DIRECTORY_SCHEMAS: CollectionSchema[] = [\n conversationsSchema,\n conversationStateSchema,\n communitiesSchema,\n membershipsSchema,\n postsSchema,\n]\n","/**\n * Shared Durable Object Schemas\n *\n * Central registry of global DO types with fixed schemas.\n * Apps connect to shared DOs via SHARED_CONNECTIONS in constants.ts.\n * The ScopeRegistry resolves collection names to the correct scope automatically.\n *\n * Scope tiers:\n * - App DO (app:{appHandle}) — private to each app, app defines tables\n * - Dir DO (dir:{appHandle}) — cross-app directory (conversations, communities, posts)\n * - Conv DO (conv:{id}) — single conversation (messages, reactions, members)\n *\n * There is no platform-wide singleton scope: every global room is keyed by\n * a conversation or an app, so no one DO holds data shared by all apps.\n */\n\nimport type { CollectionSchema } from './registry'\nimport { CONVERSATION_SCHEMAS, VOTING_SCHEMAS } from './conversation'\nimport { DIRECTORY_SCHEMAS } from './directory'\n\n// ============================================================================\n// Registry types\n// ============================================================================\n\nexport interface SharedConnection {\n type: string\n instanceId?: string\n}\n\nexport interface GlobalDOType {\n name: string\n schemas: CollectionSchema[]\n description: string\n}\n\n// ============================================================================\n// Global DO type registry\n// ============================================================================\n\nexport const GLOBAL_DO_TYPES: GlobalDOType[] = [\n {\n name: 'conv',\n schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],\n description: 'Conversations (messages, reactions, members, voting)',\n },\n {\n name: 'dir',\n schemas: DIRECTORY_SCHEMAS,\n description: 'App directory (conversations, communities, posts) — cross-app accessible',\n },\n]\n\n/** All valid global DO type names. */\nexport const GLOBAL_DO_TYPE_NAMES = GLOBAL_DO_TYPES.map(t => t.name)\n\n/** Look up a global DO type by name, returns null if not found. */\nexport function getGlobalDOType(name: string): GlobalDOType | null {\n return GLOBAL_DO_TYPES.find(t => t.name === name) ?? null\n}\n\n/** Get the fixed schemas for a global DO type. Returns empty array if unknown type. */\nexport function getGlobalDOSchemas(typeName: string): CollectionSchema[] {\n return getGlobalDOType(typeName)?.schemas ?? []\n}\n\n/** All collection names reserved by global DO types. Apps must not reuse these. */\nexport const RESERVED_COLLECTION_NAMES = new Set(\n GLOBAL_DO_TYPES.flatMap(t => t.schemas.map(s => s.name)),\n)\n","/**\n * JWT verification for DeepSpace workers.\n *\n * jose-based ES256 verification (jose runs on the Cloudflare Workers\n * edge runtime). Imported public keys are cached per-PEM to avoid\n * re-importing on every request, and `azp` is matched against an\n * optional list of authorized-party patterns supporting `*` wildcards.\n */\n\nimport { importSPKI, jwtVerify } from 'jose'\nimport type { JwtVerifierConfig, VerifyOutcome, JwtClaims } from './types'\nimport { decodeJwtPayload, normalizeArray } from './utils'\n\nconst DEFAULT_CLOCK_SKEW_MS = 5_000\n\n/**\n * Check if azp matches any pattern in authorizedParties.\n * Supports wildcards like \"https://*.app.space\"\n */\nfunction matchesAuthorizedParty(\n azp: string | null | undefined,\n patterns: string[],\n): boolean {\n // Tokens without azp are valid (e.g., server-issued tokens)\n if (!azp) return true\n\n for (const pattern of patterns) {\n if (pattern === azp) return true\n\n if (pattern.includes('*')) {\n const escaped: string = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const regexPattern: string = escaped.replace(/\\*/g, '[^/]*')\n const regex: RegExp = new RegExp(`^${regexPattern}$`)\n if (regex.test(azp)) return true\n\n // \"*.example.com\" also allows \"example.com\"\n if (pattern.includes('*.')) {\n const rootPattern: string = pattern.replace('*.', '')\n if (rootPattern === azp) return true\n }\n }\n }\n\n return false\n}\n\n// Cache imported keys to avoid re-importing on every request\nconst keyCache = new Map<string, CryptoKey>()\n\nasync function getPublicKey(pem: string): Promise<CryptoKey> {\n const cached = keyCache.get(pem)\n if (cached) return cached\n // .dev.vars stores PEM with literal \\n — replace with actual newlines\n const normalized = pem.replace(/\\\\n/g, '\\n')\n const key = await importSPKI(normalized, 'ES256')\n keyCache.set(pem, key)\n return key\n}\n\n/**\n * Verify a DeepSpace JWT token.\n *\n * @param config - Verification configuration (public key, issuer, audience)\n * @param token - The JWT string to verify\n * @returns VerifyOutcome with either the verified result or error details\n */\nexport async function verifyJwt(\n config: JwtVerifierConfig,\n token: string | null | undefined,\n): Promise<VerifyOutcome> {\n if (!token) {\n return { result: null }\n }\n\n try {\n const publicKey = await getPublicKey(config.publicKey)\n\n const audience = normalizeArray(config.audience)\n const clockTolerance = Math.floor(\n (config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1000,\n )\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: config.issuer,\n audience: audience && audience.length === 1 ? audience[0] : audience,\n clockTolerance,\n })\n\n const claims = payload as JwtClaims\n\n if (!claims.sub) {\n throw new Error('JWT verification succeeded but subject claim is missing')\n }\n\n // Check authorized parties if configured\n const authorizedParties = normalizeArray(config.authorizedParties)\n if (authorizedParties) {\n const azp = claims.azp\n if (!matchesAuthorizedParty(azp, authorizedParties)) {\n throw new Error(\n `Authorized party '${azp}' does not match any allowed pattern`,\n )\n }\n }\n\n return {\n result: {\n userId: claims.sub,\n claims,\n },\n }\n } catch (error) {\n return {\n result: null,\n error,\n debug: decodeJwtPayload(token),\n }\n }\n}\n","import type { TokenDebugInfo } from './types'\n\nconst BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/\n\nexport function bufferToHex(bytes: ArrayBuffer | Uint8Array): string {\n const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)\n let hex = ''\n for (const byte of view) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function normalizeArray<T>(value?: T | T[] | null): T[] | undefined {\n if (value == null) return undefined\n if (Array.isArray(value)) {\n return value.length ? value : undefined\n }\n return [value]\n}\n\nexport function decodeJwtPayload(token: string | null | undefined): TokenDebugInfo | undefined {\n if (!token) return undefined\n const parts = token.split('.')\n if (parts.length < 2) return undefined\n const payloadSegment = parts[1]\n if (!payloadSegment) return undefined\n if (!BASE64_URL_REGEX.test(payloadSegment)) return undefined\n try {\n const normalized = payloadSegment\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payloadSegment.length / 4) * 4, '=')\n const bytes = base64ToUint8Array(normalized)\n const json = new TextDecoder().decode(bytes)\n const payload = JSON.parse(json) as Partial<TokenDebugInfo>\n return {\n iss: payload.iss ?? null,\n aud: payload.aud ?? null,\n azp: (payload as Record<string, unknown>)?.azp as string | null ?? null,\n exp: payload.exp ?? null,\n iat: payload.iat ?? null,\n }\n } catch {\n return undefined\n }\n}\n\nfunction base64ToUint8Array(value: string): Uint8Array {\n if (typeof atob === 'function') {\n const binary = atob(value)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) {\n out[i] = binary.charCodeAt(i)\n }\n return out\n }\n if (typeof Buffer !== 'undefined') {\n return Uint8Array.from(Buffer.from(value, 'base64'))\n }\n throw new Error('No base64 decoder available in current environment')\n}\n","/**\n * HMAC primitives used by the per-app identity tokens (APP_IDENTITY_TOKEN)\n * minted at deploy time and verified on every /internal/* call into the\n * platform worker. Auth-provider-agnostic.\n */\n\nimport { bufferToHex } from './utils'\n\nlet nodeCryptoPromise: Promise<typeof import('node:crypto') | null> | null = null\n\nasync function loadNodeCrypto(): Promise<typeof import('node:crypto') | null> {\n if (!nodeCryptoPromise) {\n nodeCryptoPromise = import('node:crypto')\n .then((module) => module)\n .catch(() => null)\n }\n return nodeCryptoPromise\n}\n\nfunction hasSubtleCrypto(): boolean {\n return typeof globalThis.crypto?.subtle?.importKey === 'function'\n}\n\nexport async function computeHmacHex(secret: string, payload: string): Promise<string> {\n if (hasSubtleCrypto()) {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return bufferToHex(signature)\n }\n\n const nodeCrypto = await loadNodeCrypto()\n if (!nodeCrypto) {\n throw new Error('Crypto implementation unavailable for HMAC computation')\n }\n return nodeCrypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nexport async function timingSafeEqualHex(a: string, b: string): Promise<boolean> {\n if (a.length !== b.length) {\n return false\n }\n\n const nodeCrypto = await loadNodeCrypto()\n if (nodeCrypto?.timingSafeEqual) {\n const aBuf = Buffer.from(a, 'hex')\n const bBuf = Buffer.from(b, 'hex')\n try {\n return nodeCrypto.timingSafeEqual(aBuf, bBuf)\n } catch {\n return false\n }\n }\n\n let diff = 0\n for (let i = 0; i < a.length; i += 1) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\n/**\n * Constant-time equality for arbitrary strings (hash-then-compare: SHA-256\n * normalizes lengths so neither length nor content short-circuits). The one\n * implementation every worker's bearer/secret checks share.\n */\nexport async function timingSafeEqualStrings(a: string, b: string): Promise<boolean> {\n const encoder = new TextEncoder()\n const [aHash, bHash] = await Promise.all([\n crypto.subtle.digest('SHA-256', encoder.encode(a)),\n crypto.subtle.digest('SHA-256', encoder.encode(b)),\n ])\n const aBytes = new Uint8Array(aHash)\n const bBytes = new Uint8Array(bHash)\n let diff = 0\n for (let i = 0; i < aBytes.length; i++) diff |= aBytes[i] ^ bBytes[i]\n return diff === 0\n}\n","/**\n * Test-account identification, shared by every platform worker.\n *\n * Test accounts (created via `deepspace test accounts`) are real Better Auth\n * users demarcated three ways, and a caller may present as any of them\n * depending on where the check runs:\n * - their email always ends with @deepspace.test (enforced at creation)\n * - their JWTs carry `isTestAccount: true` (minted by the auth-worker,\n * the source of truth)\n * - their billing profile's subscription tier is 'test' (mirrored by the\n * api-worker on every authenticated request)\n *\n * Gate anything a test account must not do with these helpers rather than\n * hand-rolled string comparisons, so the policy stays in one place. Prefer\n * checking the claim when a verified JWT is at hand, and the tier as the\n * fallback for persisted state that may outlive a claim (e.g. rows written\n * before a guard existed).\n */\n\nimport type { JwtClaims } from './types'\n\nexport const TEST_ACCOUNT_EMAIL_SUFFIX = '@deepspace.test'\n\n/** True when the email belongs to a test account (case-insensitive). */\nexport function isTestAccountEmail(email: string | null | undefined): boolean {\n return !!email && email.trim().toLowerCase().endsWith(TEST_ACCOUNT_EMAIL_SUFFIX)\n}\n\n/** True when verified JWT claims identify the caller as a test account. */\nexport function isTestAccountClaims(\n claims: JwtClaims | Record<string, unknown> | null | undefined,\n): boolean {\n return claims?.isTestAccount === true\n}\n\n/** True when a billing subscription tier marks a test account. */\nexport function isTestAccountTier(tier: string | null | undefined): boolean {\n return tier === 'test'\n}\n","/**\n * Better Auth configuration factory for DeepSpace\n *\n * Provides pre-configured Better Auth instances for Cloudflare Workers + D1.\n */\n\nimport { betterAuth, type BetterAuthOptions } from 'better-auth'\nimport { organization, twoFactor } from 'better-auth/plugins'\nimport { SignJWT, importPKCS8 } from 'jose'\n\nexport interface DeepSpaceAuthConfig {\n /** D1 database binding */\n database: D1Database\n /** Base URL for the auth worker (e.g. \"https://auth.deep.space\") */\n baseURL: string\n /** Secret for session signing */\n secret: string\n /** Google OAuth credentials (optional) */\n google?: { clientId: string; clientSecret: string }\n /** GitHub OAuth credentials (optional) */\n github?: { clientId: string; clientSecret: string }\n /** Microsoft (Entra ID) OAuth credentials (optional) */\n microsoft?: { clientId: string; clientSecret: string }\n /**\n * Apple (Sign in with Apple) credentials (optional). Pass the raw\n * Sign-in-with-Apple key material; the ES256 client-secret JWT is minted\n * on demand, so there is no six-month token to rotate by hand.\n */\n apple?: { clientId: string; teamId: string; keyId: string; privateKey: string }\n /** Enable email/password authentication */\n emailAndPassword?: boolean\n /** Trusted origins for CORS */\n trustedOrigins?: string[]\n /**\n * Called after a new user row is created (any flow: social OAuth callback,\n * email/password, server-side `auth.api.signUpEmail`). When the signup came\n * in over HTTP, `ctx.request` exposes the inbound request; some better-auth\n * entry points carry standalone `ctx.headers` instead of (or as well as) a\n * request, so both are forwarded. For server-side creations (`auth.api.*`\n * calls) an endpoint context usually still exists but may carry NEITHER —\n * guard every field.\n *\n * Better Auth awaits `user.create.after` hooks inline before the response\n * is sent, so keep this cheap (e.g. a single D1 write) and hand slow work\n * to `ctx.waitUntil` at the call site. Exceptions are caught and logged so\n * a failing observer can never break signup itself.\n */\n onUserCreated?: (\n user: { id: string; email: string; name: string },\n ctx: { request?: Request; headers?: Headers } | null,\n ) => Promise<void>\n}\n\n/**\n * Create a Better Auth instance configured for DeepSpace.\n *\n * This is called per-request in the auth worker since D1 bindings\n * are request-scoped in Cloudflare Workers.\n */\n/**\n * A static `{ clientId, clientSecret }` provider config, or an async resolver\n * that returns one (Apple mints its client secret on demand). The union keeps\n * the static google/github/microsoft assignments type-checked while allowing\n * Apple's function form, which better-auth's own types don't model.\n */\ntype SocialProviderConfig =\n | { clientId: string; clientSecret: string }\n | (() => Promise<{ clientId: string; clientSecret: string }>)\n\nexport function createDeepSpaceAuth(config: DeepSpaceAuthConfig) {\n const socialProviders: Record<string, SocialProviderConfig> = {}\n\n if (config.google) {\n socialProviders.google = config.google\n }\n if (config.github) {\n socialProviders.github = config.github\n }\n if (config.microsoft) {\n socialProviders.microsoft = config.microsoft\n }\n if (config.apple) {\n const apple = config.apple\n // better-auth resolves every social provider's config in one Promise.all\n // when it builds its request context, so an exception here rejects the\n // whole context and 500s *all* auth flows — not just Apple. Mint the fresh\n // client-secret JWT (we sign on demand rather than store a rotating token)\n // inside try/catch so a bad Apple key degrades to \"Apple unavailable\"\n // instead of taking Google/GitHub/Microsoft/password down with it.\n socialProviders.apple = async () => {\n try {\n return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) }\n } catch (err) {\n console.error('[deepspace] failed to mint Apple client secret; Apple sign-in disabled', err)\n return { clientId: apple.clientId, clientSecret: '' }\n }\n }\n }\n\n return betterAuth({\n database: config.database,\n baseURL: config.baseURL,\n secret: config.secret,\n // Login is a one-time human act; everything after it is agents minting\n // short-lived JWTs from this session. 30 days idle (vs the 7-day\n // default) with daily sliding renewal means any agent active within a\n // month keeps the session alive indefinitely — the human re-logs-in\n // only after a full month of zero activity. Short-lived access stays\n // short: CLI JWTs expire in minutes (auth-worker /api/auth/token).\n session: {\n expiresIn: 60 * 60 * 24 * 30,\n updateAge: 60 * 60 * 24,\n },\n emailAndPassword: {\n enabled: config.emailAndPassword ?? true,\n },\n socialProviders: socialProviders as Parameters<typeof betterAuth>[0]['socialProviders'],\n trustedOrigins: config.trustedOrigins ?? [\n 'https://deep.space',\n 'https://*.deep.space',\n 'https://*.app.space',\n 'http://localhost:*',\n // Apple posts its OAuth callback from this origin (form_post).\n 'https://appleid.apple.com',\n ],\n // Apple returns its callback as a cross-site form_post, which a\n // SameSite=Lax cookie won't ride along with — so the OAuth handshake\n // cookie must be SameSite=None. Scoped to just that cookie; the session\n // cookie stays Lax. Gated on Apple being configured: deployments without\n // Apple keep Lax entirely. Note that when Apple IS configured, this also\n // relaxes the handshake cookie for Google/GitHub/Microsoft on the same\n // instance — which is safe, since the state is encrypted and verified\n // independently of SameSite (plus a separate per-flow CSRF cookie).\n ...(config.apple\n ? {\n advanced: {\n cookies: {\n state: { attributes: { sameSite: 'none' as const, secure: true } },\n oauth_state: { attributes: { sameSite: 'none' as const, secure: true } },\n },\n },\n }\n : {}),\n // Observer hook for new-user creation (see DeepSpaceAuthConfig.onUserCreated).\n // Two-layer wiring guard — be honest about what each layer catches:\n // 1. buildDatabaseHooks's return type is pinned to\n // BetterAuthOptions['databaseHooks'], so a better-auth upgrade that\n // renames/reshapes the option fails tsc.\n // 2. A LOCAL typo of this key would still compile (betterAuth's generic\n // options parameter defeats excess-property checking even for plain\n // keys) — that case is caught by the runtime assertions in\n // __tests__/betterAuth-hooks.test.ts, which read the wired hook back\n // off auth.options. Don't rename the key without running them.\n databaseHooks: buildDatabaseHooks(config.onUserCreated),\n plugins: [organization(), twoFactor()],\n })\n}\n\n/**\n * Wire onUserCreated into better-auth's user.create.after hook. The wrapper\n * try/catches so a throwing observer can't fail the signup — better-auth\n * awaits `after` hooks inline before responding. Returns undefined when no\n * observer is configured (better-auth treats that as \"no hooks\").\n */\nfunction buildDatabaseHooks(\n onUserCreated: DeepSpaceAuthConfig['onUserCreated'],\n): BetterAuthOptions['databaseHooks'] {\n if (!onUserCreated) return undefined\n return {\n user: {\n create: {\n after: async (user, ctx) => {\n try {\n await onUserCreated(\n { id: user.id, email: user.email, name: user.name },\n // Forward BOTH request and standalone headers — some better-auth\n // entry points populate ctx.headers without a request.\n ctx ? { request: ctx.request, headers: ctx.headers ?? undefined } : null,\n )\n } catch (err) {\n console.error('[deepspace] onUserCreated hook failed (signup unaffected)', err)\n }\n },\n },\n },\n }\n}\n\n/**\n * Mint the \"Sign in with Apple\" client-secret JWT (ES256) from the\n * downloaded .p8 key. Apple caps the lifetime at six months; we use ~180\n * days and re-mint on demand, so nothing has to be rotated manually.\n */\n// Isolate-level cache of the minted Apple client secret. Apple permits\n// reusing a client secret for up to six months, but better-auth resolves the\n// provider config on every /api/auth/* context build — so without this we'd\n// importPKCS8 + ES256-sign on every auth request, including non-Apple ones.\nlet cachedAppleSecret: { keyId: string; token: string; expiresAt: number } | null = null\n\nasync function generateAppleClientSecret(apple: {\n clientId: string\n teamId: string\n keyId: string\n privateKey: string\n}): Promise<string> {\n const now = Math.floor(Date.now() / 1000)\n // Reuse until an hour before expiry; re-mint if the signing key rotates.\n if (\n cachedAppleSecret &&\n cachedAppleSecret.keyId === apple.keyId &&\n cachedAppleSecret.expiresAt - now > 3600\n ) {\n return cachedAppleSecret.token\n }\n // PEM secrets in this stack are stored with escaped newlines (mirrors the\n // JWT_PRIVATE_KEY handling in the auth worker); normalize before importPKCS8\n // so an escaped-newline key doesn't throw. No-op for real-newline keys.\n const pem = apple.privateKey.replace(/\\\\n/g, '\\n')\n const key = await importPKCS8(pem, 'ES256')\n const expiresAt = now + 180 * 24 * 60 * 60\n const token = await new SignJWT({})\n .setProtectedHeader({ alg: 'ES256', kid: apple.keyId })\n .setIssuer(apple.teamId)\n .setSubject(apple.clientId)\n .setAudience('https://appleid.apple.com')\n .setIssuedAt(now)\n .setExpirationTime(expiresAt)\n .sign(key)\n cachedAppleSecret = { keyId: apple.keyId, token, expiresAt }\n return token\n}\n\nexport type DeepSpaceAuth = ReturnType<typeof createDeepSpaceAuth>\n","/**\n * Subscription primitives shared between client (useSubscription hook) and\n * server (requireSubscription helper). Pulled into a shared module so the\n * entitlement gate has a single source of truth — if it ever diverges between\n * client and server, gated features silently disagree about who's allowed in,\n * which is a security bug, not a UX bug.\n */\n\nexport type SubscriptionStatus =\n | 'none'\n | 'trialing'\n | 'active'\n | 'past_due'\n | 'canceled'\n | 'incomplete'\n | 'incomplete_expired'\n | 'unpaid'\n | 'paused'\n\n/** Per-interval price advertised by a plan. */\nexport interface PlanPrice {\n interval: 'month' | 'year'\n priceCents: number\n currency?: string\n}\n\n/**\n * Plan shape returned by `/api/subscriptions/me` and consumed by\n * `<PricingTable plans={sub.plans} onSelect={...}>`. Fields beyond slug/rank\n * are optional so a free tier (no prices, no trial) doesn't need to fabricate\n * them.\n */\nexport interface PlanInfo {\n slug: string\n rank: number\n name: string\n trialDays?: number | null\n prices: PlanPrice[]\n}\n\n// Statuses that grant access to paid features. Other statuses (past_due,\n// unpaid, incomplete, paused, canceled) keep the user's recorded planSlug so\n// the UI can surface \"your Pro plan is past-due\" — but they MUST NOT unlock.\n//\n// Typed as `ReadonlySet<string>` (not `Set<SubscriptionStatus>`) so callers\n// passing a server-supplied string don't need a runtime cast; unknown statuses\n// fall through as \"not entitled\", which is the safe default.\nexport const ENTITLED_STATUSES: ReadonlySet<string> = new Set([\n 'active',\n 'trialing',\n])\n\n/**\n * True iff the subscription currently grants access to paid features.\n *\n * - Free tier always counts as entitled — no payment required.\n * - Otherwise the status must be in `ENTITLED_STATUSES`.\n *\n * `status` is typed as `string` deliberately: server responses can carry\n * statuses we haven't enumerated yet (e.g. if Stripe adds a new one), and\n * those should fall through as not-entitled rather than crashing the check.\n */\nexport function isEntitled(tier: string, status: string): boolean {\n if (tier === 'free') return true\n return ENTITLED_STATUSES.has(status)\n}\n","/**\n * Server-side subscription helpers. Called from the developer's worker\n * (Hono Context) — they proxy to the api-worker's `/api/subscriptions/me`\n * using the worker's signed app-identity headers, so the same trust\n * model the SDK hook uses applies here.\n */\n\nimport type { Context } from 'hono'\nimport { apiWorkerFetch } from './utils/proxies'\nimport type { ApiWorkerEnv } from './utils/proxies'\nimport { appendAppIdentity } from './utils/app-identity'\nimport { isEntitled, type PlanInfo, type SubscriptionStatus } from '../shared/subscription'\n\n// Re-export shared types so consumers can import them from this barrel.\nexport type { PlanInfo, SubscriptionStatus }\n\nexport interface SubscriptionRead {\n tier: string\n status: SubscriptionStatus\n currentPeriodEnd: number | null\n cancelAtPeriodEnd: boolean\n trialEndsAt: number | null\n plans: PlanInfo[]\n}\n\ninterface StarterAppEnv extends ApiWorkerEnv {\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n}\n\nfunction appIdentityHeaders(\n c: Context<{ Bindings: StarterAppEnv }>,\n extra?: Record<string, string>,\n): Headers {\n const h = new Headers(extra ?? {})\n appendAppIdentity(h, c.env)\n // The api-worker's auth middleware only honors `Authorization: Bearer <jwt>`.\n // If the inbound request doesn't carry a Bearer, `/api/subscriptions/me`\n // will 401 — calling code should ensure the browser used `getAuthToken()`\n // or otherwise put a JWT on the request before reaching here.\n const authz = c.req.header('authorization')\n if (authz) h.set('authorization', authz)\n return h\n}\n\nexport async function getSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n): Promise<SubscriptionRead> {\n const res = await apiWorkerFetch(c.env, '/api/subscriptions/me', {\n method: 'GET',\n headers: appIdentityHeaders(c),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n // 401/403 means the caller isn't authenticated or isn't allowed —\n // distinct from 5xx/network blips so route handlers can map it to a\n // 401 response instead of a 500. See `requireSubscription` docs example.\n if (res.status === 401 || res.status === 403) {\n throw new SubscriptionAuthError(\n body.error ?? `subscription auth failed (${res.status})`,\n res.status,\n )\n }\n throw new Error(body.error ?? `subscription read failed (${res.status})`)\n }\n return (await res.json()) as SubscriptionRead\n}\n\n/**\n * Enforce a tier gate inside a route. Throws `SubscriptionRequiredError` if\n * the caller's subscription doesn't meet the requested tier OR isn't currently\n * entitled (status ∈ {active, trialing}). Callers do\n * `await requireSubscription(c, { atLeast: 'pro' })` and let the thrown\n * response propagate.\n *\n * Why the status gate: a row with `planSlug='pro'` and `status='past_due'`\n * means \"this user used to be on Pro but their payment is overdue\" — paid\n * features should not unlock. Same for canceled/unpaid/incomplete.\n */\nexport async function requireSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: { tier?: string; atLeast?: string },\n): Promise<SubscriptionRead> {\n const sub = await getSubscription(c)\n // Free-tier gates pass unconditionally; everything else needs entitlement.\n if (!isEntitled(sub.tier, sub.status)) {\n throw new SubscriptionRequiredError(opts.tier ?? opts.atLeast ?? 'paid', sub.tier)\n }\n if (opts.tier && sub.tier !== opts.tier) {\n throw new SubscriptionRequiredError(opts.tier, sub.tier)\n }\n if (opts.atLeast) {\n const target = sub.plans.find((p) => p.slug === opts.atLeast)?.rank\n const current = sub.plans.find((p) => p.slug === sub.tier)?.rank\n if (target == null || current == null || current < target) {\n throw new SubscriptionRequiredError(opts.atLeast, sub.tier)\n }\n }\n return sub\n}\n\nexport class SubscriptionRequiredError extends Error {\n readonly required: string\n readonly current: string\n constructor(required: string, current: string) {\n super(`subscription tier required: ${required} (have: ${current})`)\n this.required = required\n this.current = current\n this.name = 'SubscriptionRequiredError'\n }\n}\n\n/**\n * Thrown by `getSubscription` / `requireSubscription` when the upstream\n * `/api/subscriptions/me` rejects the caller with 401 or 403 — typically\n * because the inbound request didn't carry a Bearer token, or carried one\n * the api-worker can't verify. Distinct from `SubscriptionRequiredError`\n * (which is about tier/entitlement, not identity) so route handlers can\n * map identity failures to 401 and tier failures to 402.\n */\nexport class SubscriptionAuthError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'SubscriptionAuthError'\n this.status = status\n }\n}\n\n/**\n * Cancel one customer's subscription, or every subscription on a given plan.\n *\n * Forwards the inbound `Authorization` header — the platform verifies the\n * actor is the app owner before calling Stripe. As with `refundInvoice`,\n * gate this in your own admin route too; the platform check is the second\n * layer, not the first.\n *\n * Defaults to `atPeriodEnd: true` so customers aren't cut off mid-cycle.\n * Pass `atPeriodEnd: false` for an immediate cancel (refund handled\n * separately if you want one).\n */\nexport interface CancelSubscriptionOpts {\n /** Cancel one specific customer's subscription. Mutually exclusive with `planSlug`. */\n userId?: string\n /** Cancel every active subscription on this plan. Mutually exclusive with `userId`. */\n planSlug?: string\n /** Default true. Pass false for an immediate cancel. */\n atPeriodEnd?: boolean\n /** Optional free-form audit reason. */\n reason?: string\n}\n\nexport interface CancelSubscriptionResult {\n success: boolean\n canceled: number\n failures: Array<{ stripeSubscriptionId: string; error: string }>\n atPeriodEnd: boolean\n /**\n * True when the matching subscription set was larger than the server-side\n * batch limit (currently 50). Loop the call until this returns false to\n * cancel every remaining row — the `cancel_at_period_end` flag is\n * idempotent, so re-flagging an already-flagged subscription is a no-op.\n */\n hasMore: boolean\n}\n\nexport async function cancelSubscription(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: CancelSubscriptionOpts,\n): Promise<CancelSubscriptionResult> {\n const authz = c.req.header('authorization')\n if (!authz) {\n throw new CancelSubscriptionError('missing authorization header (forward the caller JWT)', 401)\n }\n const res = await apiWorkerFetch(c.env, '/api/subscriptions/admin-cancel', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n authorization: authz,\n },\n body: JSON.stringify({\n appId: c.env.DEEPSPACE_APP_ID,\n userId: opts.userId,\n planSlug: opts.planSlug,\n atPeriodEnd: opts.atPeriodEnd,\n reason: opts.reason,\n }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new CancelSubscriptionError(body.error ?? `cancel failed (${res.status})`, res.status)\n }\n return (await res.json()) as CancelSubscriptionResult\n}\n\nexport class CancelSubscriptionError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'CancelSubscriptionError'\n this.status = status\n }\n}\n","// Server-side refund helper. Forwards the inbound user's JWT to the platform,\n// which verifies the caller is the app owner before any Stripe call. Wrap\n// your route with admin/role gating before calling refundInvoice — only the\n// app owner's JWT will pass the platform's `not_app_owner` check, but you\n// still want to reject non-admin paths in your own app's auth.\n\nimport type { Context } from 'hono'\nimport { apiWorkerFetch } from './utils/proxies'\nimport type { ApiWorkerEnv } from './utils/proxies'\nimport { appendAppIdentity } from './utils/app-identity'\n\ninterface StarterAppEnv extends ApiWorkerEnv {\n /** Absent until the app's first deploy injects it — see appendAppIdentity. */\n APP_IDENTITY_TOKEN?: string\n /** Immutable app id — the identity the platform verifies (HMAC input). */\n DEEPSPACE_APP_ID: string\n}\n\nexport interface RefundResult {\n success: boolean\n stripeRefundId: string\n amountRefunded: number\n status: 'pending' | 'succeeded' | 'failed' | 'canceled' | 'requires_action' | null\n}\n\nexport interface RefundOpts {\n invoiceId: string\n amount?: number\n reason?: 'requested_by_customer' | 'duplicate' | 'fraudulent'\n requestNonce?: string\n}\n\nexport async function refundInvoice(\n c: Context<{ Bindings: StarterAppEnv }>,\n opts: RefundOpts,\n): Promise<RefundResult> {\n // The platform requires a verified actor — forward the inbound user's JWT.\n // No JWT → the helper can't authenticate; let the platform return 401 so\n // the caller gets a clear error rather than a silent no-op.\n const authz = c.req.header('authorization')\n const nonce = opts.requestNonce ?? crypto.randomUUID()\n const headers = new Headers({ 'Content-Type': 'application/json' })\n appendAppIdentity(headers, c.env)\n if (authz) headers.set('authorization', authz)\n const res = await apiWorkerFetch(c.env, '/api/refunds/create', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n invoiceId: opts.invoiceId,\n amount: opts.amount,\n reason: opts.reason,\n requestNonce: nonce,\n }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new RefundError(body.error ?? `refund failed (${res.status})`, res.status)\n }\n return (await res.json()) as RefundResult\n}\n\nexport class RefundError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = 'RefundError'\n this.status = status\n }\n}\n"],"mappings":"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","names":["CORS_HEADERS","fail","request","upload","url","executeTool","tool","records","listItems","tool","bytes","request","cleaned","n","Y","cursor","row","docKeyBytes","docKey","parsed","cursor","row","Y","CORS_HEADERS","request","tool","result","status","CORS_HEADERS","request","cursor","request","isOwner","Y","Y","request","request","request","canRead","updated"]}