import * as Y from 'yjs'; import * as react_jsx_runtime from 'react/jsx-runtime'; import React$1, { ReactNode } from 'react'; import * as zod_v4_core from 'zod/v4/core'; import * as zod from 'zod'; import * as better_auth_plugins_two_factor from 'better-auth/plugins/two-factor'; import * as node_modules_better_auth_dist_client_query_mjs from 'node_modules/better-auth/dist/client/query.mjs'; import * as better_auth_client_plugins from 'better-auth/client/plugins'; import * as better_auth_react from 'better-auth/react'; import * as node_modules_better_auth_dist_plugins_organization_organization_mjs from 'node_modules/better-auth/dist/plugins/organization/organization.mjs'; import * as better_auth from 'better-auth'; import * as node_modules_better_auth_dist_plugins_access_types_mjs from 'node_modules/better-auth/dist/plugins/access/types.mjs'; /** * Shared type definitions for the DeepSpace SDK * * Types used by both client-side SDK and server-side workers. */ type ColumnInterpretation = { kind: 'plain'; } | { kind: 'currency'; symbol: string; decimals: number; } | { kind: 'date'; format?: string; } | { kind: 'datetime'; format?: string; } | { kind: 'boolean'; trueLabel?: string; falseLabel?: string; } | { kind: 'percent'; decimals?: number; } | { kind: 'select'; options: string[]; } | { kind: 'multiselect'; options: string[]; } | { kind: 'url'; } | { kind: 'email'; } | { kind: 'json'; } | { kind: 'reference'; targetTable: string; displayColumn: string; }; interface ColumnDefinition { /** Stable ID override (survives renames). Falls back to `col_{name}`. */ id?: string; name: string; storage: 'number' | 'text'; interpretation: ColumnInterpretation | string; expression?: string; userBound?: boolean; immutable?: boolean; required?: boolean; default?: unknown; timestampTrigger?: { field: string; value?: unknown; }; } type PermissionLevel = boolean | 'own' | 'unclaimed-or-own' | 'collaborator' | 'team' | 'access' | 'published' | 'shared'; interface RolePermissions { read: PermissionLevel; /** Create has no existing record to evaluate, so it is an explicit grant. */ create: boolean; update: PermissionLevel; delete: PermissionLevel; /** If set, only these caller-supplied columns can be created or updated by this role. */ writableFields?: string[]; } interface CollectionSchema { name: string; /** Every collection is stored in typed SQL columns. */ columns: ColumnDefinition[]; /** Composite uniqueness constraint (for example `['userId', 'taskId']`). */ uniqueOn?: string[]; /** Ownership column; defaults to the row creator. */ ownerField?: string; /** Column containing a JSON array of collaborator user ids. */ collaboratorsField?: string; /** Column containing the team id used by team permission rules. */ teamField?: string; /** A string is public when equal to `public`; an object supplies an exact value. */ visibilityField?: string | { field: string; value: unknown; }; /** Permissions per role; `*` is the catch-all role. */ permissions: Record; /** Default role assigned on the `users` collection. */ defaultRole?: string; /** * `users` collection only: what the `user.list` roster shows a non-admin * whose `read` policy is row-scoped (`'own'`, `'team'`, …). Default * `'public-identity'`: every registered user's id/name/imageUrl/role/lastSeenAt * (`lastSeenAt` is what `usePresence` reads) — the * row policy keeps guarding full-row reads on the records/query path. * `'read-policy'`: the roster contains only the rows the caller's read * policy grants (still projected to public identity) — for apps that scope * users to a tenant/team and must not show names across it. */ roster?: 'public-identity' | 'read-policy'; } interface Query { collection: string; where?: Record; orderBy?: string; orderDir?: 'asc' | 'desc'; limit?: number; } interface Subscription { id: string; query: Query; } /** Key for Yjs doc: collection:recordId:fieldName */ type YjsDocKey = string; interface YjsSubscription { collection: string; recordId: string; fieldName: string; } /** * Environment detection and URL configuration * * Determines environment and provides URLs for DeepSpace services. * * Environment detection priority: * 1. Build-time __DEEPSPACE_ENV__ define (set via esbuild/Vite) * 2. Runtime window.__DEEPSPACE_ENV__ * 3. Server-side process.env.DEEPSPACE_ENV * 4. Hostname detection (fallback) */ type Environment = 'dev' | 'staging' | 'prod'; interface EnvironmentConfig { name: Environment; /** Platform API worker URL */ apiUrl: string; /** Platform worker URL (RecordRoom, schema registry) */ platformWorkerUrl: string; /** Auth worker URL (Better Auth) */ authUrl: string; /** Auth sign-in page URL */ authSignInUrl: string; /** Auth sign-up page URL */ authSignUpUrl: string; /** Main DeepSpace app URL */ mainAppUrl: string; /** Builder dashboard URL */ dashboardUrl: string; } declare function detectEnvironment(): Environment; declare function getEnvironmentConfig(): EnvironmentConfig; declare function getApiUrl(): string; declare function getPlatformWorkerUrl(): string; declare function getAuthUrl(): string; declare function isLocalDev(): boolean; declare function isProduction(): boolean; /** Reset cached environment (useful for testing) */ declare function resetEnvironmentCache(): void; declare const ENV: { readonly current: Environment; readonly config: EnvironmentConfig; readonly apiUrl: string; readonly platformWorkerUrl: string; readonly authUrl: string; readonly isLocal: boolean; readonly isProd: boolean; }; /** * Standard DeepSpace role constants. * * Every DeepSpace app uses the same three roles. * Apps can import these instead of defining them locally. */ declare const ROLES: { readonly VIEWER: "viewer"; readonly MEMBER: "member"; readonly ADMIN: "admin"; }; type Role = (typeof ROLES)[keyof typeof ROLES]; /** * Whether a role may write app content: member and admin can, viewer is * read-only. The one chokepoint for every client-side write gate — an app * that defines custom write-capable roles extends this predicate once. The * server always re-checks writes against the schema's permission table; this * only controls what the UI offers. */ declare function isWriterRole(role: unknown): role is Exclude; declare const ROLE_CONFIG: Record; /** * Canonical DeepSpace model catalog and agent policy. * * Provider catalogs are discovery inputs, not a safe runtime allowlist: a * model is promoted here only after its tool loop, transport, pricing, and * streaming contract have been verified end to end through the DeepSpace * proxy. Every client picker and server agent profile consumes this module. */ type DeepSpaceAIProvider = 'anthropic' | 'openai' | 'cerebras'; type DeepSpaceAgentProfileId = 'application' | 'documentation'; type DeepSpaceAgentSupport = 'multi-step' | 'single-step' | 'none'; interface DeepSpaceAIModel { id: string; label: string; provider: DeepSpaceAIProvider; providerLabel: string; family: string; /** Whether the model is eligible for DeepSpace's server-owned agent loop. */ agentSupport: DeepSpaceAgentSupport; /** Profiles in which the model has passed the complete runtime contract. */ agentProfiles: readonly DeepSpaceAgentProfileId[]; /** Provider transport currently used by the DeepSpace proxy adapter. */ transport: 'messages' | 'chat-completions'; recommendation: 'frontier' | 'balanced' | 'fast' | 'available' | 'limited'; note?: string; } interface DeepSpaceAgentProfile { id: DeepSpaceAgentProfileId; defaultModel: string; maxSteps: number; maxToolCalls?: number; allowedTools: 'application-defined' | readonly string[]; } /** * Versioned provenance for the provider catalogs used during the latest * promotion review. Keeping this in the shipped artifact makes selection * policy auditable without allowing a mutable upstream list to change a * deployed app underneath a commit. */ declare const DEEPSPACE_MODEL_CATALOG_PROVENANCE: { readonly version: "2026-08-04"; readonly verifiedAt: "2026-08-04"; readonly sources: { readonly anthropic: "https://platform.claude.com/docs/en/api/beta/models/list"; readonly openai: "https://developers.openai.com/api/docs/models/all"; readonly cerebras: "https://inference-docs.cerebras.ai/models/overview"; }; }; declare const DEEPSPACE_AI_MODELS: readonly [{ readonly id: "claude-fable-5"; readonly label: "Claude Fable 5"; readonly family: "Claude 5"; readonly recommendation: "frontier"; } & { readonly provider: "anthropic"; readonly providerLabel: "Anthropic"; readonly transport: "messages"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "claude-opus-5"; readonly label: "Claude Opus 5"; readonly family: "Claude 5"; readonly recommendation: "frontier"; } & { readonly provider: "anthropic"; readonly providerLabel: "Anthropic"; readonly transport: "messages"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "claude-sonnet-5"; readonly label: "Claude Sonnet 5"; readonly family: "Claude 5"; readonly recommendation: "balanced"; } & { readonly provider: "anthropic"; readonly providerLabel: "Anthropic"; readonly transport: "messages"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "claude-haiku-4-5"; readonly label: "Claude Haiku 4.5"; readonly family: "Claude 4.5"; readonly recommendation: "fast"; } & { readonly provider: "anthropic"; readonly providerLabel: "Anthropic"; readonly transport: "messages"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "gpt-5.6-sol"; readonly label: "GPT-5.6 Sol"; readonly family: "GPT-5.6"; readonly recommendation: "frontier"; } & { readonly provider: "openai"; readonly providerLabel: "OpenAI"; readonly transport: "chat-completions"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "gpt-5.6-terra"; readonly label: "GPT-5.6 Terra"; readonly family: "GPT-5.6"; readonly recommendation: "balanced"; } & { readonly provider: "openai"; readonly providerLabel: "OpenAI"; readonly transport: "chat-completions"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "gpt-5.6-luna"; readonly label: "GPT-5.6 Luna"; readonly family: "GPT-5.6"; readonly recommendation: "fast"; } & { readonly provider: "openai"; readonly providerLabel: "OpenAI"; readonly transport: "chat-completions"; } & { agentSupport: "multi-step"; agentProfiles: readonly ["application", "documentation"]; }, { readonly id: "gpt-oss-120b"; readonly label: "GPT-OSS 120B"; readonly provider: "cerebras"; readonly providerLabel: "Cerebras"; readonly family: "GPT-OSS"; readonly agentSupport: "single-step"; readonly agentProfiles: readonly []; readonly transport: "chat-completions"; readonly recommendation: "limited"; readonly note: "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation."; }]; declare const DEEPSPACE_AI_DEFAULTS: { readonly agent: "claude-sonnet-5"; readonly directGeneration: "claude-sonnet-5"; readonly summarization: "claude-haiku-4-5"; }; declare const DEEPSPACE_AGENT_PROFILES: { readonly application: { readonly id: "application"; readonly defaultModel: "claude-sonnet-5"; readonly maxSteps: 21; readonly maxToolCalls: 20; readonly allowedTools: "application-defined"; }; readonly documentation: { readonly id: "documentation"; readonly defaultModel: "claude-sonnet-5"; readonly maxSteps: 21; readonly maxToolCalls: 20; readonly allowedTools: readonly ["documentation_search", "documentation_read"]; }; }; type DeepSpaceAIModelId = (typeof DEEPSPACE_AI_MODELS)[number]['id']; declare function getDeepSpaceAIModel(modelId: unknown): DeepSpaceAIModel | null; declare function listDeepSpaceAgentModels(profileId?: DeepSpaceAgentProfileId): readonly DeepSpaceAIModel[]; interface ResolvedDeepSpaceAgentModel { modelId: string; provider: DeepSpaceAIProvider; model: DeepSpaceAIModel; profile: DeepSpaceAgentProfile; } declare function resolveDeepSpaceAgentModel(modelId: unknown, profileId?: DeepSpaceAgentProfileId): ResolvedDeepSpaceAgentModel | null; /** * Yjs Sync Protocol Implementation * * This implements the y-protocols sync and awareness protocols without * depending on y-websocket internal paths. Makes it easier to bundle * and template. * * Protocol spec: https://github.com/yjs/y-protocols */ declare const MSG_SYNC = 0; declare const MSG_AWARENESS = 1; declare const MSG_SYNC_STEP1 = 0; declare const MSG_SYNC_STEP2 = 1; declare const MSG_SYNC_UPDATE = 2; declare function createEncoder(): { data: number[]; }; declare function toUint8Array(encoder: { data: number[]; }): Uint8Array; declare function writeVarUint(encoder: { data: number[]; }, num: number): void; declare function writeVarUint8Array(encoder: { data: number[]; }, arr: Uint8Array): void; declare function createDecoder(data: Uint8Array): { data: Uint8Array; pos: number; }; declare function readVarUint(decoder: { data: Uint8Array; pos: number; }): number; declare function readVarUint8Array(decoder: { data: Uint8Array; pos: number; }): Uint8Array; declare function hasContent(decoder: { data: Uint8Array; pos: number; }): boolean; /** * Encode sync step 1: our state vector */ declare function encodeSyncStep1(doc: Y.Doc): Uint8Array; /** * Encode sync step 2: diff based on received state vector */ declare function encodeSyncStep2(doc: Y.Doc, stateVector: Uint8Array): Uint8Array; /** * Encode an incremental update */ declare function encodeUpdate(update: Uint8Array): Uint8Array; interface SyncResult { type: 'step1' | 'step2' | 'update'; response?: Uint8Array; stateVector?: Uint8Array; update?: Uint8Array; } /** * Handle an incoming sync message * Returns response to send (if any) and whether this was an update */ declare function handleSyncMessage(doc: Y.Doc, data: Uint8Array): SyncResult; interface AwarenessState { [key: string]: unknown; } interface AwarenessStates { [clientId: number]: AwarenessState; } /** * Simple awareness implementation for presence/cursors */ declare class Awareness { doc: Y.Doc; clientID: number; states: Map; private meta; private updateListeners; private changeListeners; constructor(doc: Y.Doc); getStates(): Map; getLocalState(): AwarenessState | null; setLocalState(state: AwarenessState | null): void; setLocalStateField(field: string, value: unknown): void; on(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[]; }) => void): void; off(event: 'update' | 'change', handler: (changes: { added: number[]; updated: number[]; removed: number[]; }) => void): void; private emit; /** * Encode awareness update for specific clients */ encodeUpdate(clientIds?: number[]): Uint8Array; /** * Apply awareness update from another client */ applyUpdate(data: Uint8Array): { added: number[]; updated: number[]; removed: number[]; }; /** * Remove awareness states for disconnected clients */ removeStates(clientIds: number[]): void; /** Release all state and listeners owned by this awareness instance. */ destroy(): void; } /** * Encode a full awareness message */ declare function encodeAwarenessMessage(awareness: Awareness, clientIds?: number[]): Uint8Array; /** * Decode and apply awareness message */ declare function handleAwarenessMessage(awareness: Awareness, data: Uint8Array): { added: number[]; updated: number[]; removed: number[]; }; /** * Get message type from raw data */ declare function getMessageType(data: Uint8Array): number; /** * Wire protocol constants. * * The JSON WebSocket protocol uses dotted string identifiers (e.g. * `"records.query"`) as the `type` discriminator. All message types are * grouped under a single `MSG` object so imports stay tidy: * * import { MSG, dispatch, clientBuild } from 'deepspace' * * dispatch(raw, { * }) * * Each key's value is the on-wire string — grep-friendly, self- * documenting, and infinite per namespace. Adding a new message is one * line here plus one arm in the discriminated union in `./messages.ts`. * * Yjs binary protocol constants (`MSG_YJS_SYNC`, `MSG_YJS_AWARENESS`) are * intentionally kept numeric and separate from `MSG` — they ride a * binary WebSocket frame format and aren't part of the JSON dispatcher. */ declare const MSG: { readonly SUBSCRIBE: "core.subscribe"; readonly UNSUBSCRIBE: "core.unsubscribe"; readonly QUERY_RESULT: "core.query_result"; readonly RECORD_CHANGE: "core.record_change"; readonly PUT: "core.put"; readonly DELETE: "core.delete"; readonly ERROR: "core.error"; readonly USER_INFO: "user.info"; readonly USER_LIST: "user.list"; readonly SET_ROLE: "user.set_role"; readonly USER_UPDATE: "user.update"; readonly AUTH: "auth"; readonly YJS_JOIN: "yjs.join"; readonly YJS_LEAVE: "yjs.leave"; readonly ACK: "records.ack"; readonly LIST_SCHEMAS: "records.list_schemas"; readonly RESUBSCRIBE: "records.resubscribe"; readonly CANVAS_SHAPES: "canvas.shapes"; readonly CANVAS_ADD: "canvas.add"; readonly CANVAS_MOVE: "canvas.move"; readonly CANVAS_RESIZE: "canvas.resize"; readonly CANVAS_DELETE: "canvas.delete"; readonly CANVAS_UPDATE: "canvas.update"; readonly CANVAS_VIEWPORT: "canvas.viewport"; readonly CANVAS_UNDO: "canvas.undo"; readonly CANVAS_REDO: "canvas.redo"; readonly CRON_TASKS: "cron.tasks"; readonly CRON_HISTORY: "cron.history"; readonly CRON_TRIGGER: "cron.trigger"; readonly CRON_PAUSE: "cron.pause"; readonly CRON_RESUME: "cron.resume"; readonly CRON_STATUS: "cron.status"; readonly CRON_ACK: "cron.ack"; readonly JOB_ENQUEUE: "job.enqueue"; readonly JOB_CANCEL: "job.cancel"; readonly JOB_RETRY: "job.retry"; readonly JOB_UPDATE: "job.update"; readonly PRESENCE_SYNC: "presence.sync"; readonly PRESENCE_JOIN: "presence.join"; readonly PRESENCE_LEAVE: "presence.leave"; readonly PRESENCE_UPDATE: "presence.update"; }; /** * Type of any message type constant — the union of every string literal * stored in `MSG`. Useful when declaring functions that accept "any known * message type" without enumerating all 54 strings by hand. */ type MsgType = (typeof MSG)[keyof typeof MSG]; /** Outer envelope id for binary-framed yjs sync messages. Varuint-encoded. */ declare const MSG_YJS_SYNC = 22; /** Outer envelope id for binary-framed yjs awareness messages. Varuint-encoded. */ declare const MSG_YJS_AWARENESS = 23; /** Role assigned to unauthenticated WebSocket connections */ declare const ROLE_ANONYMOUS = "viewer"; /** Prefix `base-room` mints anonymous socket ids with. */ declare const ANONYMOUS_USER_ID_PREFIX = "anon-"; /** The one test for an anonymous socket identity — `ROLE_ANONYMOUS` cannot be * it, since authenticated users can legitimately hold the same role name. */ declare function isAnonymousUserId(userId: string): boolean; /** Default role for newly registered authenticated users */ declare const ROLE_DEFAULT = "member"; /** Admin role */ declare const ROLE_ADMIN = "admin"; /** * Reported when a record id resolves to nothing. * * This crosses the DO boundary as a response body and callers branch on it, * so it is a contract rather than prose. `getChat` in particular treats it as * "no such chat" and returns null — the same answer it gives for a chat owned * by someone else, which is what stops chat ids being enumerable. If a room * reworded this independently, a miss would start throwing while a cross-user * hit kept returning null, and that difference is an existence oracle. */ declare const RECORD_NOT_FOUND = "Record not found"; /** * Typed wire-protocol layer — discriminated unions, typed builders, and a * type-safe dispatcher for every `MSG.*` the SDK understands. * * Why this exists * --------------- * * The string `MSG.*` constants in `./constants.ts` are the authoritative * wire protocol, but using them directly is error-prone: a typo picks the * wrong message with the wrong payload shape and fails silently at * runtime. This module pairs every constant with its payload type, so * that: * * 1. Building a message with `clientBuild.canvasAdd(...)` is payload- * checked at the call site — the compiler refuses to ship a wrong * shape. * * 2. Parsing an inbound message via `dispatch(raw, handlers)` narrows the * payload type inside each handler automatically, replacing the * unsafe `switch (msg.type) { case MSG.X: (payload as any).foo }` * pattern. * * 3. Tightening `BaseRoom.sendTo` / `BaseRoom.broadcast` / * `HandlerContext.send` / `SubscriptionContext.send` to accept * `ServerMessage` turns the type layer into enforcement: any room * that ships a payload inconsistent with its declared arm fails to * compile. Without that, the discriminated union is documentation, * not contract. * * 4. Adding a new `MSG.*` is localized: one entry in the discriminated * union, one builder function, one handler key in every dispatcher * that cares. No grep-and-fix across the codebase. * * 5. Apps can extend the SDK protocol without forking: `dispatch` is * generic over any `M extends ProtocolMessage`, and builders are * plain objects so apps compose via spread (`{ ...clientBuild, * myMessage: ... }`). * * Direction split * --------------- * * Some message types carry different payloads depending on who's sending. * `MSG.RECORDS_RESUBSCRIBE`, for example, is `{}` when the client asks to * but `{ state, tick }` when the server broadcasts the start event. * `MSG.CANVAS_ADD` is a flat shape dict on the way in and a `{ shape }` * wrapper on the way out. Modelling these with one union would force * handlers to juggle a union payload — clunky and error-prone. Instead we * split by direction: * * - `ClientMessage` — what the client sends to the server * - `ServerMessage` — what the server sends to the client * - `ProtocolMessage = ClientMessage | ServerMessage` (for code that * really doesn't care — avoid when possible) * * Each side gets its own builder (`clientBuild` / `serverBuild`) and each * side's dispatcher is parameterised with the union it expects. * * Payload strictness * ------------------ * * Where payload shapes are stable + narrow (ids, flags), we type them * precisely. Where they're opaque or escape the protocol layer (record * data blobs, Yjs binary frames, canvas shapes), we use `unknown` and * defer narrowing to the caller. This is intentional: over-typing opaque * payloads would require the protocol layer to import application types * and defeat the "thin wire contract" goal. */ /** * The outer shape of every wire message. `T` is the string discriminator * (e.g. `"canvas.add"`) — keeping it as a generic literal type lets the * discriminated-union narrowing in `dispatch()` pick the right payload. * * Callers extending the protocol should pass a string-literal type for * `T`, not the widened `string`. `BaseMessage` collapses the * discriminated union and handler-map key inference falls back to a * single untyped `string` key, losing all narrowing. */ interface BaseMessage { type: T; payload: P; } /** Matches when a payload is intentionally empty — `{}` on the wire. */ type EmptyPayload = Record; /** * Every message the client can legitimately send. Extend in app code with * a string-literal union arm: * * type MyClientMessage = ClientMessage | BaseMessage<'myapp.foo', { x: number }> */ type ClientMessage = BaseMessage | BaseMessage | BaseMessage; requestId?: string; }> | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage; }> | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage>; /** * Every message the server can send. Room and handler `send` / `broadcast` * signatures are tightened to this union so outbound payloads are * compile-checked against the wire contract. As with `ClientMessage`, * extend via a string-literal union arm in app code when adding new * server-side broadcasts. */ type ServerMessage = BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage; }> | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage | BaseMessage; }>; /** * Any wire message, regardless of direction. Use this only when the code * path genuinely doesn't care — otherwise pick `ClientMessage` or * `ServerMessage` so the handler's payload type is narrower. */ type ProtocolMessage = ClientMessage | ServerMessage; /** Factories for every client → server message. */ declare const clientBuild: { subscribe: (subscriptionId: string, query: unknown) => { readonly type: "core.subscribe"; readonly payload: { readonly subscriptionId: string; readonly query: unknown; }; }; unsubscribe: (subscriptionId: string) => { readonly type: "core.unsubscribe"; readonly payload: { readonly subscriptionId: string; }; }; put: (collection: string, recordId: string, data: Record, requestId?: string) => { readonly type: "core.put"; readonly payload: { readonly collection: string; readonly recordId: string; readonly data: Record; readonly requestId: string | undefined; }; }; remove: (collection: string, recordId: string, requestId?: string) => { readonly type: "core.delete"; readonly payload: { readonly collection: string; readonly recordId: string; readonly requestId: string | undefined; }; }; userList: () => { readonly type: "user.list"; readonly payload: EmptyPayload; }; setRole: (userId: string, role: string) => { readonly type: "user.set_role"; readonly payload: { readonly userId: string; readonly role: string; }; }; userUpdate: () => { readonly type: "user.update"; readonly payload: EmptyPayload; }; listSchemas: () => { readonly type: "records.list_schemas"; readonly payload: EmptyPayload; }; yjsJoin: (collection: string, recordId: string, fieldName: string) => { readonly type: "yjs.join"; readonly payload: { readonly collection: string; readonly recordId: string; readonly fieldName: string; }; }; yjsLeave: (collection: string, recordId: string, fieldName: string) => { readonly type: "yjs.leave"; readonly payload: { readonly collection: string; readonly recordId: string; readonly fieldName: string; }; }; canvasAdd: (shape: Record) => { readonly type: "canvas.add"; readonly payload: Record; }; canvasMove: (shapeId: string, x: number, y: number) => { readonly type: "canvas.move"; readonly payload: { readonly shapeId: string; readonly x: number; readonly y: number; }; }; canvasResize: (shapeId: string, width: number, height: number, x?: number, y?: number) => { readonly type: "canvas.resize"; readonly payload: { readonly shapeId: string; readonly width: number; readonly height: number; readonly x: number | undefined; readonly y: number | undefined; }; }; canvasDelete: (shapeId: string) => { readonly type: "canvas.delete"; readonly payload: { readonly shapeId: string; }; }; canvasUpdate: (shapeId: string, props: Record) => { readonly type: "canvas.update"; readonly payload: { readonly shapeId: string; readonly props: Record; }; }; canvasViewport: (viewport: Record) => { readonly type: "canvas.viewport"; readonly payload: Record; }; canvasUndo: () => { readonly type: "canvas.undo"; readonly payload: EmptyPayload; }; canvasRedo: () => { readonly type: "canvas.redo"; readonly payload: EmptyPayload; }; cronTrigger: (taskName: string, requestId?: string) => { readonly type: "cron.trigger"; readonly payload: { readonly taskName: string; readonly requestId: string | undefined; }; }; cronPause: (taskName: string, requestId?: string) => { readonly type: "cron.pause"; readonly payload: { readonly taskName: string; readonly requestId: string | undefined; }; }; cronResume: (taskName: string, requestId?: string) => { readonly type: "cron.resume"; readonly payload: { readonly taskName: string; readonly requestId: string | undefined; }; }; jobEnqueue: (requestId: string, type: string, payload?: unknown, maxAttempts?: number) => { readonly type: "job.enqueue"; readonly payload: { readonly requestId: string; readonly type: string; readonly payload: unknown; readonly maxAttempts: number | undefined; }; }; jobCancel: (jobId: string) => { readonly type: "job.cancel"; readonly payload: { readonly jobId: string; }; }; jobRetry: (jobId: string) => { readonly type: "job.retry"; readonly payload: { readonly jobId: string; }; }; presenceUpdate: (state: Record) => { readonly type: "presence.update"; readonly payload: Record; }; }; /** Factories for every server → client message. Used by room DOs and * handler modules so server broadcasts are payload-checked too. Opaque * slots (`record`, `shape`, `peer`, etc.) take `unknown` so concrete * domain types — `RecordResult`, `CanvasShape`, `MediaPeer` — can be * passed without casts. */ declare const serverBuild: { queryResult: (subscriptionId: string, records: unknown[]) => { readonly type: "core.query_result"; readonly payload: { readonly subscriptionId: string; readonly records: unknown[]; }; }; recordChange: (collection: string, record: unknown, changeType: "create" | "update" | "delete") => { readonly type: "core.record_change"; readonly payload: { readonly collection: string; readonly record: unknown; readonly changeType: "update" | "create" | "delete"; }; }; error: (error: string, subscriptionId?: string) => { readonly type: "core.error"; readonly payload: { readonly error: string; readonly subscriptionId: string | undefined; }; }; ackSuccess: (requestId: string, recordId?: string) => { readonly type: "records.ack"; readonly payload: { readonly requestId: string; readonly success: true; readonly recordId: string | undefined; }; }; ackFailure: (requestId: string, error: string) => { readonly type: "records.ack"; readonly payload: { readonly requestId: string; readonly success: false; readonly error: string; }; }; resubscribe: () => { readonly type: "records.resubscribe"; readonly payload: EmptyPayload; }; schemas: (schemas: unknown) => { readonly type: "records.list_schemas"; readonly payload: { readonly schemas: unknown; }; }; userInfo: (user: unknown) => { readonly type: "user.info"; readonly payload: unknown; }; userList: (users: unknown[]) => { readonly type: "user.list"; readonly payload: { readonly users: unknown[]; }; }; yjsJoin: (collection: string, recordId: string, fieldName: string, canWrite: boolean) => { readonly type: "yjs.join"; readonly payload: { readonly collection: string; readonly recordId: string; readonly fieldName: string; readonly canWrite: boolean; }; }; canvasShapes: (shapes: unknown[], viewports: unknown[]) => { readonly type: "canvas.shapes"; readonly payload: { readonly shapes: unknown[]; readonly viewports: unknown[]; }; }; canvasAdd: (shape: unknown) => { readonly type: "canvas.add"; readonly payload: { readonly shape: unknown; }; }; canvasMove: (shapeId: string, x: number, y: number) => { readonly type: "canvas.move"; readonly payload: { readonly shapeId: string; readonly x: number; readonly y: number; }; }; canvasResize: (shapeId: string, width: number, height: number, x?: number, y?: number) => { readonly type: "canvas.resize"; readonly payload: { readonly shapeId: string; readonly width: number; readonly height: number; readonly x: number | undefined; readonly y: number | undefined; }; }; canvasDelete: (shapeId: string) => { readonly type: "canvas.delete"; readonly payload: { readonly shapeId: string; }; }; canvasUpdate: (shapeId: string, props: Record) => { readonly type: "canvas.update"; readonly payload: { readonly shapeId: string; readonly props: Record; }; }; canvasViewport: (viewport: unknown) => { readonly type: "canvas.viewport"; readonly payload: { readonly viewport: unknown; }; }; canvasViewportRemoved: (userId: string) => { readonly type: "canvas.viewport"; readonly payload: { readonly userId: string; readonly removed: true; }; }; cronTasks: (tasks: unknown) => { readonly type: "cron.tasks"; readonly payload: { readonly tasks: unknown; }; }; cronHistory: (history: unknown) => { readonly type: "cron.history"; readonly payload: { readonly history: unknown; }; }; cronStatus: (tasks: unknown, recentHistory: unknown) => { readonly type: "cron.status"; readonly payload: { readonly tasks: unknown; readonly recentHistory: unknown; }; }; cronAckSuccess: (requestId: string, taskName: string) => { readonly type: "cron.ack"; readonly payload: { readonly requestId: string; readonly taskName: string; readonly ok: true; }; }; cronAckFailure: (requestId: string, reason: "read_only" | "unknown_task" | "failed", error?: string, taskName?: string) => { readonly type: "cron.ack"; readonly payload: { readonly requestId: string; readonly taskName: string | undefined; readonly ok: false; readonly reason: "read_only" | "unknown_task" | "failed"; readonly error: string | undefined; }; }; jobSnapshot: (jobs: unknown[]) => { readonly type: "job.update"; readonly payload: { readonly kind: "snapshot"; readonly jobs: unknown[]; }; }; jobEnqueued: (job: unknown, requestId?: string) => { readonly type: "job.update"; readonly payload: { readonly kind: "enqueued"; readonly job: unknown; readonly requestId: string | undefined; }; }; jobProgress: (job: unknown) => { readonly type: "job.update"; readonly payload: { readonly kind: "progress"; readonly job: unknown; }; }; jobSucceeded: (job: unknown) => { readonly type: "job.update"; readonly payload: { readonly kind: "succeeded"; readonly job: unknown; }; }; jobFailed: (job: unknown) => { readonly type: "job.update"; readonly payload: { readonly kind: "failed"; readonly job: unknown; }; }; jobCanceled: (job: unknown) => { readonly type: "job.update"; readonly payload: { readonly kind: "canceled"; readonly job: unknown; }; }; jobRetried: (job: unknown) => { readonly type: "job.update"; readonly payload: { readonly kind: "retried"; readonly job: unknown; }; }; presenceSync: (peers: unknown[]) => { readonly type: "presence.sync"; readonly payload: { readonly peers: unknown[]; }; }; presenceJoin: (peer: unknown) => { readonly type: "presence.join"; readonly payload: { readonly peer: unknown; }; }; presenceLeave: (userId: string) => { readonly type: "presence.leave"; readonly payload: { readonly userId: string; }; }; presenceUpdate: (userId: string, state: Record) => { readonly type: "presence.update"; readonly payload: { readonly userId: string; readonly state: Record; }; }; }; /** * Handler map keyed by a message's string `type`. Every key maps to the * payload type of the matching arm in `M`, so handlers get automatic * payload narrowing with no runtime checks. * * Narrowing requires each `M` arm to use a string-literal `T`; widening * `M` to `BaseMessage` collapses all keys to a single * untyped `string` and the map becomes a plain `Record void>`. */ type MessageHandlers = ProtocolMessage> = { [K in M as K['type']]?: (payload: K['payload']) => void; }; /** * Parse and route an incoming wire message. Accepts either the raw JSON * string (directly from `ws.onmessage`'s `event.data`) or an already- * parsed object. Returns `true` if a handler ran, `false` otherwise * (unrecognised type, parse failure, or malformed envelope). * * Defaults `M` to `ProtocolMessage` so callers who don't care about * direction can just do `dispatch(raw, handlers)`; narrow with * `dispatch` / `dispatch` to restrict which * arms the handler map is allowed to cover. */ declare function dispatch = ProtocolMessage>(raw: unknown, handlers: MessageHandlers): boolean; /** * Serialise a typed-built message for `ws.send`. A tiny wrapper around * `JSON.stringify` kept around so call sites read as * `ws.send(encode(clientBuild.canvasAdd(...)))` rather than paying * attention to stringify arguments. */ declare function encode>(message: M): string; /** * BaseRoom — Abstract base class for all DeepSpace Durable Objects. * * Provides: * - WebSocket upgrade with Cloudflare hibernation API * - Connection tracking (WebSocket -> UserAttachment) * - Auth: parse JWT-verified user info from internal request headers * - Presence: connected users list, awareness on connect/disconnect * - Message routing: JSON parse -> dispatch by `type` field, binary hook * - Raw SQLite access via this.sql * - Broadcast helpers: broadcast(), sendTo() * - HTTP fetch handler with WebSocket upgrade detection * - Internal control-plane endpoint: POST /internal/disconnect-sockets * (force every client to reconnect and resync after out-of-band writes) * * Subclasses implement lifecycle hooks: * onConnect, onMessage, onBinaryMessage, onDisconnect, onRequest, onAlarm */ interface UserAttachment { userId: string; userName: string; userEmail: string; userImageUrl?: string; /** Subclass-specific data serialized alongside user info */ [key: string]: unknown; } /** * Server-specific protocol types * * These depend on Cloudflare Workers / Yjs imports and can't live in shared/types. * All other protocol types (Query, payloads, etc.) live in shared/types/index.ts. */ /** Stored on WebSocket attachment (survives hibernation) */ interface ConnectionAttachment extends UserAttachment { role: string; subscriptions: Subscription[]; /** Yjs docs this connection is editing */ yjsSubscriptions: YjsSubscription[]; /** Client-side Yjs awareness clientId for each subscribed document. */ awarenessClientIds?: Partial>; } /** * Context passed to handlers for accessing shared resources. * * `send` is typed against `ServerMessage` so every outbound message is * compile-checked against the wire-protocol discriminated union. See * `shared/protocol/messages.ts` for why. */ interface HandlerContext { sql: SqlStorage; state: DurableObjectState; yjsDocs: Map; getWebSockets(): Iterable; send(ws: WebSocket, message: ServerMessage): void; sendBinary(ws: WebSocket, data: Uint8Array): void; } /** Asset-layer misses became real 404s, and the worker owns the fallback. The * config and the worker's fallback path have to move together. */ declare const WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found"; /** Room identity moved to internal headers and privileged room routes gained app-role checks. */ declare const SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries"; /** `ActionTools` gained `deleteWhere`, so the app's own tools factory — which * declares that interface as its return type — has to supply it. */ declare const ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where"; /** The browser's app id moved off a scaffold-time literal onto a build-time * define that `deepspace/build` reads from the wrangler config being built. */ declare const BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id"; /** The action route refuses a call without a bearer token as 401 (a cookie * session has none) instead of crashing on a non-null assertion, and the * file states the trust model actions run under. */ declare const ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard"; /** The files proxy identifies a header-less same-origin read (an ``, * `