@ewhauser/world-celld 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["delay"],"sources":["../src/config.ts","../src/remote/errors.ts","../src/remote/rpc-client.ts","../src/remote/namespaces.ts","../src/vendor/shared/debug.ts","../src/util.ts","../src/queue.ts","../src/storage.ts","../src/streamer.ts","../src/index.ts"],"sourcesContent":["import type { WorkflowRunDONamespace } from './storage.js';\nimport type { StreamDONamespace } from './streamer.js';\nimport type { QueueCellNamespace } from './queue.js';\n\n/** Storage-layer KV interface (satisfied by IndexDO over HTTP, or a mock). */\nexport interface IndexNamespace {\n get(key: string): Promise<string | null>;\n put(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: { prefix?: string; cursor?: string; limit?: number; reverse?: boolean }): Promise<{\n keys: Array<{ name: string }>;\n list_complete: boolean;\n cursor?: string;\n }>;\n reserveHookToken(\n token: string,\n owner: HookTokenOwner,\n ): Promise<{ claimed: boolean; holder?: HookTokenOwner }>;\n finalizeHookIndexes(\n token: string,\n hookId: string,\n serializedHook: string,\n owner: HookTokenOwner,\n ): Promise<void>;\n releaseHookToken(token: string, owner: HookTokenOwner): Promise<void>;\n deleteHookIndexes(token: string, hookId: string, owner: HookTokenOwner): Promise<void>;\n}\n\nexport interface HookTokenOwner {\n runId: string;\n hookId: string;\n}\n\nexport interface CelldWorldEnv {\n WORKFLOW_DB: WorkflowRunDONamespace;\n WORKFLOW_INDEX: IndexNamespace;\n WORKFLOW_QUEUE: QueueCellNamespace;\n WORKFLOW_STREAMS: StreamDONamespace;\n}\n\nexport interface CelldWorldConfig {\n /**\n * Base URL of any celld fleet node's public listener (e.g.\n * `http://fleet.internal:8080`). Requests are routed to the deployed\n * world-celld worker, which forwards them to the owning cells.\n * Default: process.env.CELLD_FLEET_URL\n */\n fleetUrl?: string;\n /**\n * Bearer secret shared with the worker (its WORLD_SECRET var).\n * Default: process.env.CELLD_WORLD_SECRET\n */\n secret?: string;\n /**\n * Pre-built namespaces (used by tests and the in-process harness). When\n * provided, fleetUrl/secret are not used.\n */\n env?: CelldWorldEnv;\n /** Default: process.env.CELLD_DEPLOYMENT_ID || 'celld-default' */\n deploymentId?: string;\n /**\n * Base URL the app's workflow endpoints are mounted on; QueueDO cells\n * deliver to `${baseUrl}/.well-known/workflow/v1/{flow|step}`.\n * Default: process.env.WORKFLOW_BASE_URL || `http://localhost:${PORT ?? 3000}`\n */\n baseUrl?: string;\n /** Number of queue cells to spread enqueues over. Default: 1 */\n queueShards?: number;\n /** Poll interval (ms) while waiting for new chunks on a live stream. Default: 250 */\n readPollMs?: number;\n /** Per-attempt fleet RPC deadline in milliseconds. Default: 30000 */\n rpcTimeoutMs?: number;\n}\n\nexport interface ResolvedCelldConfig {\n fleetUrl?: string;\n secret?: string;\n env?: CelldWorldEnv;\n deploymentId: string;\n baseUrl?: string;\n queueShards: number;\n readPollMs: number;\n rpcTimeoutMs: number;\n}\n\nexport function resolveConfig(config?: CelldWorldConfig): ResolvedCelldConfig {\n return {\n fleetUrl: config?.fleetUrl ?? process.env.CELLD_FLEET_URL,\n secret: config?.secret ?? process.env.CELLD_WORLD_SECRET,\n env: config?.env,\n deploymentId: config?.deploymentId ?? process.env.CELLD_DEPLOYMENT_ID ?? 'celld-default',\n baseUrl: config?.baseUrl,\n queueShards: config?.queueShards ?? 1,\n readPollMs: config?.readPollMs ?? 250,\n rpcTimeoutMs: config?.rpcTimeoutMs ?? 30_000,\n };\n}\n","import { WorkflowWorldError } from '@workflow/errors';\n\nexport interface WireError {\n name?: string;\n message?: string;\n status?: number;\n}\n\n/**\n * Reconstruct a typed error from a router error payload. `@workflow/errors`\n * matches with name-based `.is()` checks, so a WorkflowWorldError with the\n * original name (and status) restored behaves identically to the source\n * error for every consumer in the workflow runtime.\n */\nexport function reconstructError(wire: WireError | undefined, httpStatus: number): Error {\n const message = wire?.message ?? `world-celld rpc failed with HTTP ${httpStatus}`;\n const error = new WorkflowWorldError(message, {\n status: wire?.status ?? httpStatus,\n });\n if (wire?.name) {\n error.name = wire.name;\n }\n return error;\n}\n\n/** Transport-level failure (fleet unreachable, malformed response). */\nexport class FleetTransportError extends Error {\n constructor(\n message: string,\n readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'FleetTransportError';\n }\n}\n","import { rpcParse, rpcStringify } from '../codec.js';\nimport { FleetTransportError, reconstructError, type WireError } from './errors.js';\n\nexport interface RpcTransport {\n fleetUrl: string;\n secret: string;\n /** Per-attempt request deadline. Default: 30 seconds. */\n timeoutMs?: number;\n /** Injectable for tests. Default: globalThis.fetch */\n fetchImpl?: typeof fetch;\n}\n\n/** Statuses that mean \"the fleet couldn't take the request\" (safe to retry reads). */\nconst RETRYABLE_STATUSES = new Set([502, 503, 504]);\nconst READ_RETRIES = 2;\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction delayMs(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Invoke one DO method through the worker router.\n *\n * Mutating calls run exactly once from the client's perspective: a blind\n * retry of e.g. applyEvent would turn commit-then-network-fail into a\n * spurious ENTITY_CONFLICT. The workflow runtime already gets at-least-once\n * via queue redelivery. Read-only calls retry on transport failures.\n */\nexport async function callDO<T>(\n transport: RpcTransport,\n binding: string,\n name: string,\n method: string,\n args: unknown[],\n opts?: { idempotent?: boolean },\n): Promise<T> {\n const doFetch = transport.fetchImpl ?? fetch;\n const url = `${transport.fleetUrl.replace(/\\/$/, '')}/v1/rpc/${binding}/${encodeURIComponent(\n name,\n )}/${method}`;\n const attempts = opts?.idempotent ? 1 + READ_RETRIES : 1;\n\n let lastError: unknown;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n if (attempt > 1) {\n await delayMs(100 * attempt + Math.random() * 200);\n }\n let response: Response;\n try {\n response = await doFetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${transport.secret}`,\n },\n body: rpcStringify(args),\n signal: AbortSignal.timeout(Math.max(1, transport.timeoutMs ?? DEFAULT_TIMEOUT_MS)),\n });\n } catch (error) {\n lastError = new FleetTransportError(`world-celld: fleet unreachable at ${url}`, error);\n continue;\n }\n\n if (response.ok) {\n try {\n return rpcParse<T>(await response.text());\n } catch (error) {\n lastError = new FleetTransportError(\n `world-celld: malformed successful response from ${url}`,\n error,\n );\n if (attempt < attempts) continue;\n throw lastError;\n }\n }\n\n const text = await response.text();\n let wire: WireError | undefined;\n try {\n wire = (JSON.parse(text) as { error?: WireError }).error;\n } catch {\n wire = { message: text || `HTTP ${response.status}` };\n }\n\n if (RETRYABLE_STATUSES.has(response.status) && attempt < attempts) {\n lastError = reconstructError(wire, response.status);\n continue;\n }\n\n throw reconstructError(wire, response.status);\n }\n\n throw lastError instanceof Error\n ? lastError\n : new FleetTransportError('world-celld: rpc failed', lastError);\n}\n","/**\n * HTTP-backed implementations of the structural namespace interfaces the\n * vendored storage/streamer/queue layers consume. `get(id)` returns a stub\n * whose method calls become `POST /v1/rpc/{binding}/{name}/{method}` against\n * the fleet — the celld stand-in for Cloudflare's `env` bindings.\n */\nimport type { CelldWorldEnv, HookTokenOwner, IndexNamespace } from '../config.js';\nimport type { QueueCellStub } from '../queue.js';\nimport type { WorkflowRunDOStub } from '../storage.js';\nimport type { StreamDOStub } from '../streamer.js';\nimport { callDO, type RpcTransport } from './rpc-client.js';\n\ninterface MethodSpec {\n methods: readonly string[];\n mutating: ReadonlySet<string>;\n}\n\nconst RUNS: MethodSpec = {\n methods: ['applyEvent', 'getRun', 'getStep', 'getEvent', 'listEvents', 'listSteps', 'listHooks'],\n mutating: new Set(['applyEvent']),\n};\n\nconst STREAMS: MethodSpec = {\n methods: ['writeChunk', 'closeStream', 'getChunks', 'getInfo', 'registerStream', 'listStreams'],\n mutating: new Set(['writeChunk', 'closeStream', 'registerStream']),\n};\n\nconst QUEUE: MethodSpec = {\n methods: [\n 'enqueue',\n 'stats',\n 'listDeadLetters',\n 'redriveDeadLetter',\n 'purgeDeadLetters',\n 'rearmAlarm',\n ],\n mutating: new Set(['enqueue', 'redriveDeadLetter', 'purgeDeadLetters', 'rearmAlarm']),\n};\n\nfunction makeStub<T>(transport: RpcTransport, binding: string, name: string, spec: MethodSpec): T {\n const stub: Record<string, (...args: unknown[]) => Promise<unknown>> = {};\n for (const method of spec.methods) {\n stub[method] = (...args: unknown[]) =>\n callDO(transport, binding, name, method, args, {\n idempotent: !spec.mutating.has(method),\n });\n }\n return stub as T;\n}\n\nfunction makeNamespace<T>(transport: RpcTransport, binding: string, spec: MethodSpec) {\n return {\n idFromName(name: string) {\n return { toString: () => name };\n },\n get(id: { toString(): string }): T {\n return makeStub<T>(transport, binding, id.toString(), spec);\n },\n };\n}\n\n/** The IndexDO is a single well-known cell. */\nconst INDEX_CELL_NAME = 'index';\n\nfunction makeIndexNamespace(transport: RpcTransport): IndexNamespace {\n const call = <T>(method: string, args: unknown[], idempotent: boolean) =>\n callDO<T>(transport, 'index', INDEX_CELL_NAME, method, args, { idempotent });\n return {\n get: (key) => call<string | null>('get', [key], true),\n put: (key, value) => call<void>('put', [key, value], false),\n delete: (key) => call<void>('delete', [key], false),\n list: (options) => call('list', [options], true),\n reserveHookToken: (token: string, owner: HookTokenOwner) =>\n call('reserveHookToken', [token, owner], false),\n finalizeHookIndexes: (token, hookId, serializedHook, owner) =>\n call<void>('finalizeHookIndexes', [token, hookId, serializedHook, owner], false),\n releaseHookToken: (token, owner) => call<void>('releaseHookToken', [token, owner], false),\n deleteHookIndexes: (token, hookId, owner) =>\n call<void>('deleteHookIndexes', [token, hookId, owner], false),\n };\n}\n\nexport function createRemoteEnv(transport: RpcTransport): CelldWorldEnv {\n return {\n WORKFLOW_DB: makeNamespace<WorkflowRunDOStub>(transport, 'runs', RUNS),\n WORKFLOW_STREAMS: makeNamespace<StreamDOStub>(transport, 'streams', STREAMS),\n WORKFLOW_INDEX: makeIndexNamespace(transport),\n WORKFLOW_QUEUE: makeNamespace<QueueCellStub>(transport, 'queue', QUEUE),\n };\n}\n","/**\n * Creates a namespaced debug logger.\n * Logs to stderr to avoid corrupting CLI JSON output on stdout.\n *\n * Control via WORKFLOW_DEBUG environment variable:\n * WORKFLOW_DEBUG=1 // Enable all\n * WORKFLOW_DEBUG=mysql-world // Specific namespace\n * WORKFLOW_DEBUG=mysql-world,redis-world // Multiple namespaces\n *\n * Vendored from vinnymac/worlds packages/shared, modified: guarded for\n * non-Node runtimes (celld/workerd), where `process` may be absent or\n * stubbed — falls back to console.error.\n */\nexport function createDebugLogger(namespace: string) {\n return (...args: unknown[]) => {\n const debug = typeof process === 'undefined' ? undefined : process.env?.WORKFLOW_DEBUG;\n if (!debug) return;\n\n const enabled =\n debug === '1' ||\n debug === 'true' ||\n debug === '*' ||\n debug.split(',').some((ns: string) => ns.trim() === namespace);\n\n if (!enabled) return;\n\n const timestamp = new Date().toISOString();\n const prefix = `[${timestamp}] [${namespace}]`;\n const message = args\n .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)))\n .join(' ');\n\n if (typeof process.stderr?.write === 'function') {\n process.stderr.write(`${prefix} ${message}\\n`);\n } else {\n console.error(`${prefix} ${message}`);\n }\n };\n}\n","import { createDebugLogger } from './vendor/shared/index.js';\n\nexport { compact } from './vendor/shared/index.js';\nexport const debug = createDebugLogger('celld-world');\n","/**\n * Queue implementation for the celld world.\n *\n * Vendored from vinnymac/worlds packages/world-cloudflare/src/queue.ts\n * (Apache-2.0, see NOTICE), modified for celld:\n *\n * - The Cloudflare Queues producer is replaced by an `enqueue` RPC to a\n * QueueDO cell (`q:<shard>`), which owns the message lifecycle: scheduling\n * via durable alarms, delivery via outbound fetch, retries with capped\n * backoff, and a dead-letter table.\n * - The consumer-side claim-DO idempotency mechanism is gone: a queue cell is\n * a single serialized writer, so idempotencyKey dedup happens inside the\n * cell (at enqueue and across the delivery state machine).\n * - `createQueueHandler` has ONE dialect — the x-vqs wire format the test\n * pump has always used — extended so permanent errors surface as their\n * HTTP statuses. QueueDO deliveries and the in-process test pump hit the\n * exact same handler path.\n */\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { WorkflowWorldError } from '@workflow/errors';\nimport {\n MessageId,\n parseQueueName,\n type Queue,\n type QueueKind,\n type QueuePayload,\n type ValidQueueName,\n} from '@workflow/world';\nimport { parse, stringify } from './vendor/shared/index.js';\nimport { monotonicFactory } from 'ulid';\nimport { debug } from './util.js';\n\n/**\n * HTTP status codes that indicate permanent (non-retryable) failures.\n * Deliveries with these statuses are dropped immediately instead of retried.\n *\n * - 404: Resource not found (e.g., run was deleted)\n * - 409: Conflict (e.g., duplicate event that can't be replayed)\n * - 410: Gone (e.g., run was already terminal)\n * - 422: Unprocessable entity (e.g., invalid payload structure)\n */\nexport const PERMANENT_ERROR_STATUSES = new Set([404, 409, 410, 422]);\n\nfunction isPermanentError(err: unknown): err is WorkflowWorldError {\n if (err instanceof WorkflowWorldError && err.status !== undefined) {\n return PERMANENT_ERROR_STATUSES.has(err.status);\n }\n return false;\n}\n\n/** Caps at 60 seconds. */\nexport function computeBackoff(attempt: number): number {\n return Math.min(60, 2 ** attempt);\n}\n\ntype Pathname = 'flow' | 'step';\n\nconst QUEUE_PATHNAMES = {\n workflow: 'flow',\n step: 'step',\n} as const satisfies Record<QueueKind, Pathname>;\n\nfunction isTestMode(): boolean {\n // Explicit override: force the production QueueDO path even under a test\n // runner (used by the conformance suite to exercise live queue cells —\n // the world-testing server inherits VITEST from the vitest parent).\n if (process.env.CELLD_QUEUE_MODE === 'cells') return false;\n return process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';\n}\n\n/**\n * Enqueue request accepted by a QueueDO cell. The message payload travels\n * pre-encoded with the shared tagged-JSON codec (`body`), so the cell can\n * forward it byte-identical to the app's queue handler without re-encoding.\n */\nexport interface EnqueueRequest {\n messageId: string;\n queueName: ValidQueueName;\n pathname: Pathname;\n /** `stringify(message)` — the exact bytes POSTed to the app on delivery. */\n body: string;\n idempotencyKey?: string;\n delaySeconds?: number;\n /**\n * Delivery configuration. The cell pins this from its first enqueue (the\n * eve-ambient pattern); a later mismatch is rejected as CONFIG_MISMATCH.\n */\n config: QueueCellConfig;\n}\n\nexport interface QueueCellConfig {\n /** Base URL of the app; deliveries POST to `${baseUrl}/.well-known/workflow/v1/{flow|step}`. */\n targetBaseUrl: string;\n queueShards: number;\n}\n\nexport type EnqueueOutcome =\n | { ok: true; messageId: string; deduped: boolean }\n | { ok: false; code: 'CONFIG_MISMATCH'; message: string };\n\n/** RPC surface of QueueDO used by the queue producer. */\nexport interface QueueCellStub {\n enqueue(request: EnqueueRequest): Promise<EnqueueOutcome>;\n}\n\nexport interface QueueCellNamespace {\n idFromName(name: string): { toString(): string };\n get(id: { toString(): string }): QueueCellStub;\n}\n\nexport interface CelldQueueConfig {\n env: {\n WORKFLOW_QUEUE: QueueCellNamespace;\n };\n deploymentId: string;\n /**\n * Base URL the app's workflow endpoints are mounted on. QueueDO cells\n * deliver to `${baseUrl}/.well-known/workflow/v1/{flow|step}`; the test\n * pump uses the same value.\n * Default: process.env.WORKFLOW_BASE_URL || `http://localhost:${process.env.PORT ?? 3000}`\n */\n baseUrl?: string;\n /** Number of `q:<shard>` cells to spread enqueues over. Default: 1 */\n queueShards?: number;\n /** Per-job HTTP request timeout (ms) for the test pump. Default: 300_000 */\n httpTimeoutMs?: number;\n /** Maximum retry attempts in the test pump before dropping a job. Default: 5 */\n maxAttempts?: number;\n /** Base backoff delay (ms) for test pump retries. Default: 1000 */\n backoffDelayMs?: number;\n}\n\ninterface PumpEnvelope {\n messageId: string;\n queueName: ValidQueueName;\n attempt: number;\n message: QueuePayload;\n idempotencyKey?: string;\n}\n\nfunction resolveBaseUrl(config: CelldQueueConfig): string {\n if (config.baseUrl) return config.baseUrl;\n if (process.env.WORKFLOW_BASE_URL) return process.env.WORKFLOW_BASE_URL;\n const port = process.env.PORT ?? '3000';\n return `http://localhost:${port}`;\n}\n\n/**\n * FNV-1a hash for shard selection. All messages sharing an idempotencyKey\n * land on the same cell so dedup stays strictly consistent.\n */\nexport function shardFor(key: string, shards: number): number {\n if (shards <= 1) return 0;\n let hash = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n hash ^= key.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) % shards;\n}\n\n/**\n * In-process test pump. Holds two in-memory FIFOs and HTTP-dispatches\n * envelopes to the user's server at\n * `${baseUrl}/.well-known/workflow/v1/{flow|step}`.\n *\n * Mirrors world-local's idempotency semantics: messages are deduplicated on\n * `idempotencyKey` while a message with the same key is in flight, and the\n * key is released only when the message is fully handled (success or drop).\n *\n * Modified vs upstream: permanent-error statuses (404/409/410/422) from the\n * handler drop the message immediately instead of burning retry budget —\n * matching QueueDO's production semantics.\n */\nfunction createTestPump(config: CelldQueueConfig) {\n const httpTimeoutMs = config.httpTimeoutMs ?? 300_000;\n const maxAttempts = config.maxAttempts ?? 5;\n const baseBackoffMs = config.backoffDelayMs ?? 1000;\n\n const queues: Record<Pathname, PumpEnvelope[]> = { flow: [], step: [] };\n const wakers: Record<Pathname, Array<() => void>> = { flow: [], step: [] };\n /** Inflight messageIds by idempotencyKey (world-local queue.js semantics). */\n const inflightMessages = new Map<string, MessageId>();\n let running = false;\n\n function release(envelope: PumpEnvelope) {\n if (envelope.idempotencyKey) {\n inflightMessages.delete(envelope.idempotencyKey);\n }\n }\n\n function enqueue(pathname: Pathname, envelope: PumpEnvelope) {\n queues[pathname].push(envelope);\n wakers[pathname].shift()?.();\n }\n\n async function take(pathname: Pathname): Promise<PumpEnvelope | null> {\n const existing = queues[pathname].shift();\n if (existing) return existing;\n return new Promise((resolve) => {\n wakers[pathname].push(() => resolve(queues[pathname].shift() ?? null));\n });\n }\n\n async function dispatch(envelope: PumpEnvelope, pathname: Pathname): Promise<void> {\n const url = `${resolveBaseUrl(config)}/.well-known/workflow/v1/${pathname}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-vqs-queue-name': envelope.queueName,\n 'x-vqs-message-id': envelope.messageId,\n 'x-vqs-message-attempt': String(envelope.attempt),\n },\n body: stringify(envelope.message),\n signal: AbortSignal.timeout(httpTimeoutMs),\n });\n\n if (response.ok) {\n release(envelope);\n return;\n }\n\n const text = await response.text();\n\n if (response.status === 503) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = null;\n }\n const timeoutSeconds = (parsed as { timeoutSeconds?: number } | null)?.timeoutSeconds;\n if (typeof timeoutSeconds === 'number') {\n // Same message re-delivered later: the idempotency key stays claimed.\n void delay(timeoutSeconds * 1000).then(() => enqueue(pathname, envelope));\n return;\n }\n }\n\n if (PERMANENT_ERROR_STATUSES.has(response.status)) {\n release(envelope);\n debug(\n `[world-celld test pump] dropping ${envelope.messageId}: permanent HTTP ${response.status}: ${text}`,\n );\n return;\n }\n\n if (envelope.attempt < maxAttempts) {\n const next: PumpEnvelope = { ...envelope, attempt: envelope.attempt + 1 };\n const backoff = baseBackoffMs * 2 ** (next.attempt - 1);\n void delay(backoff).then(() => enqueue(pathname, next));\n } else {\n release(envelope);\n console.error(\n `[world-celld test pump] dropping ${envelope.messageId} after ${envelope.attempt} attempts: HTTP ${response.status}: ${text}`,\n );\n }\n }\n\n async function loop(pathname: Pathname) {\n while (true) {\n if (!running) return;\n const envelope = await take(pathname);\n if (!envelope) continue;\n try {\n await dispatch(envelope, pathname);\n } catch (err) {\n release(envelope);\n console.error(`[world-celld test pump] dispatch error on ${pathname}:`, err);\n }\n }\n }\n\n return {\n /** Returns the inflight messageId when the key is already claimed. */\n inflight(idempotencyKey: string | undefined): MessageId | undefined {\n return idempotencyKey ? inflightMessages.get(idempotencyKey) : undefined;\n },\n push(pathname: Pathname, envelope: PumpEnvelope, delaySeconds?: number) {\n if (envelope.idempotencyKey) {\n inflightMessages.set(envelope.idempotencyKey, MessageId.parse(envelope.messageId));\n }\n if (delaySeconds && delaySeconds > 0) {\n void delay(delaySeconds * 1000).then(() => enqueue(pathname, envelope));\n } else {\n enqueue(pathname, envelope);\n }\n },\n async start() {\n if (running) return;\n running = true;\n void loop('flow');\n void loop('step');\n },\n stop() {\n running = false;\n for (const list of Object.values(wakers)) for (const w of list) w();\n },\n };\n}\n\nexport function createQueue(config: CelldQueueConfig): Queue & { start(): Promise<void> } {\n const { env, deploymentId } = config;\n const queueShards = config.queueShards ?? 1;\n\n const generateMessageId = monotonicFactory();\n const testPump = createTestPump(config);\n\n const getQueueStub = (shard: number): QueueCellStub => {\n const id = env.WORKFLOW_QUEUE.idFromName(`q:${shard}`);\n return env.WORKFLOW_QUEUE.get(id);\n };\n\n return {\n async queue(queueName, message, opts) {\n const { kind } = parseQueueName(queueName);\n\n if (isTestMode()) {\n // Dedup on idempotencyKey while a message with the same key is in\n // flight — core re-enqueues every still-pending step on every replay\n // with idempotencyKey = stepId and relies on queue-level dedup.\n const existing = testPump.inflight(opts?.idempotencyKey);\n if (existing) {\n return { messageId: existing };\n }\n const messageId = MessageId.parse(`msg_${generateMessageId()}`);\n testPump.push(\n QUEUE_PATHNAMES[kind],\n {\n messageId,\n queueName,\n attempt: 1,\n message,\n idempotencyKey: opts?.idempotencyKey,\n },\n opts?.delaySeconds,\n );\n return { messageId };\n }\n\n // Production: enqueue into a QueueDO cell. The cell owns scheduling,\n // delivery, retries, dedup, and the dead-letter table.\n const messageId = MessageId.parse(`msg_${generateMessageId()}`);\n const shard = shardFor(opts?.idempotencyKey ?? messageId, queueShards);\n\n const outcome = await getQueueStub(shard).enqueue({\n messageId,\n queueName,\n pathname: QUEUE_PATHNAMES[kind],\n body: stringify(message),\n idempotencyKey: opts?.idempotencyKey,\n delaySeconds: opts?.delaySeconds,\n config: {\n targetBaseUrl: resolveBaseUrl(config),\n queueShards,\n },\n });\n\n if (!outcome.ok) {\n throw new WorkflowWorldError(outcome.message, { status: 409 });\n }\n\n return { messageId: MessageId.parse(outcome.messageId) };\n },\n\n createQueueHandler(queueNamePrefix, handler) {\n // ONE dialect for the pump, QueueDO deliveries, and\n // @workflow/world-testing's mounted routes: x-vqs headers + tagged-JSON\n // body. The response taxonomy drives the sender's retry state machine:\n // - 2xx -> ack\n // - 503 + {timeoutSeconds} -> redeliver after N seconds (key stays claimed)\n // - 404/409/410/422 -> permanent, drop without retrying\n // - other non-2xx -> retry with capped backoff\n return async (req: Request) => {\n const reqQueueName = req.headers.get('x-vqs-queue-name');\n const reqMessageId = req.headers.get('x-vqs-message-id') as MessageId | null;\n const attemptStr = req.headers.get('x-vqs-message-attempt');\n\n if (!reqQueueName || !reqMessageId || !attemptStr || !req.body) {\n return Response.json({ error: 'Missing required headers or body' }, { status: 400 });\n }\n if (!reqQueueName.startsWith(queueNamePrefix)) {\n return Response.json({ error: 'Unhandled queue' }, { status: 400 });\n }\n const attempt = Number.parseInt(attemptStr, 10);\n try {\n // Tagged-JSON codec: revives Uint8Array payloads (runInput.input).\n const body = parse<unknown>(await req.text());\n const result = await handler(body, {\n attempt,\n queueName: reqQueueName,\n messageId: reqMessageId,\n });\n if (result && typeof result.timeoutSeconds === 'number') {\n return Response.json(\n { timeoutSeconds: result.timeoutSeconds },\n { status: 503, headers: { 'Retry-After': String(result.timeoutSeconds) } },\n );\n }\n return Response.json({ ok: true });\n } catch (error) {\n // Permanent vs transient distinction: permanent errors surface as\n // their own status so the sender drops instead of retrying.\n if (isPermanentError(error)) {\n debug('[world-celld queue handler] permanent error:', {\n status: error.status,\n error: String(error),\n });\n return Response.json(\n { error: String(error), permanent: true },\n { status: error.status },\n );\n }\n\n const backoffSeconds = computeBackoff(attempt);\n debug('[world-celld queue handler] transient error, will retry:', {\n error: String(error),\n backoffSeconds,\n });\n return Response.json(\n { error: String(error), retryAfter: backoffSeconds },\n { status: 500, headers: { 'Retry-After': String(backoffSeconds) } },\n );\n }\n };\n },\n\n async getDeploymentId() {\n return deploymentId;\n },\n\n async start() {\n if (isTestMode()) {\n await testPump.start();\n }\n // Production: QueueDO cells are push-based, nothing to start.\n },\n };\n}\n","import {\n EntityConflictError,\n HookNotFoundError,\n RunExpiredError,\n RunNotSupportedError,\n TooEarlyError,\n WorkflowRunNotFoundError,\n WorkflowWorldError,\n} from '@workflow/errors';\nimport type {\n CreateEventParams,\n CreateEventRequest,\n Event,\n EventResult,\n GetEventParams,\n GetHookParams,\n GetStepParams,\n GetWorkflowRunParams,\n Hook,\n ListEventsParams,\n ListHooksParams,\n ListWorkflowRunStepsParams,\n ListWorkflowRunsParams,\n PaginatedResponse,\n ResolveData,\n RunCreatedEventRequest,\n Step,\n StepWithoutData,\n Storage,\n WorkflowRun,\n WorkflowRunWithoutData,\n} from '@workflow/world';\nimport {\n EventSchema,\n HookSchema,\n SPEC_VERSION_CURRENT,\n StepSchema,\n WorkflowRunSchema,\n} from '@workflow/world';\nimport { parse, stringify } from './vendor/shared/index.js';\nimport { monotonicFactory } from 'ulid';\nimport type { ApplyEventFailure, ApplyEventOutcome, ApplyEventRequest } from './apply-event.js';\nimport type { HookTokenOwner, IndexNamespace } from './config.js';\nimport { compact } from './util.js';\n\n/**\n * RPC surface of WorkflowRunDO used by the storage layer. Declared\n * structurally to avoid a circular type reference on the DO class.\n */\nexport interface WorkflowRunDOStub {\n applyEvent(request: ApplyEventRequest): Promise<ApplyEventOutcome>;\n getRun(): Promise<WorkflowRun | null>;\n getStep(stepId: string): Promise<Step | null>;\n getEvent(eventId: string): Promise<Event | null>;\n listEvents(params?: {\n limit?: number;\n cursor?: string;\n sortOrder?: 'asc' | 'desc';\n }): Promise<{ data: Event[]; cursor: string | null; hasMore: boolean }>;\n listSteps(params?: {\n limit?: number;\n cursor?: string;\n sortOrder?: 'asc' | 'desc';\n }): Promise<{ data: Step[]; cursor: string | null; hasMore: boolean }>;\n listHooks(params?: {\n limit?: number;\n cursor?: string;\n sortOrder?: 'asc' | 'desc';\n }): Promise<{ data: Hook[]; cursor: string | null; hasMore: boolean }>;\n}\n\nexport interface WorkflowRunDONamespace {\n idFromName(name: string): DurableObjectIdLike;\n get(id: DurableObjectIdLike): WorkflowRunDOStub;\n}\n\nexport interface DurableObjectIdLike {\n toString(): string;\n}\n\nexport interface CloudflareStorageConfig {\n env: {\n WORKFLOW_DB: WorkflowRunDONamespace;\n WORKFLOW_INDEX: IndexNamespace;\n };\n deploymentId: string;\n}\n\nfunction sortableTimestamp(date: Date): string {\n return date.getTime().toString().padStart(13, '0');\n}\n\nfunction workflowRunIndexKey(run: WorkflowRun): string {\n return `run:${run.workflowName}:${sortableTimestamp(run.createdAt)}:${run.runId}`;\n}\n\nfunction globalRunIndexKey(run: WorkflowRun): string {\n return `runall:${sortableTimestamp(run.createdAt)}:${run.runId}`;\n}\n\nfunction hookOwner(hook: Pick<Hook, 'runId' | 'hookId'>): HookTokenOwner {\n return { runId: hook.runId, hookId: hook.hookId };\n}\n\n/**\n * Filter data based on ResolveData parameter.\n * When resolveData is 'none', strips specified keys to reduce data transfer.\n */\nfunction filterData<T extends object>(\n data: T,\n resolveData: ResolveData | undefined,\n keysToStrip: (keyof T)[],\n): T {\n if (resolveData === 'none') {\n const newData = { ...data };\n for (const key of keysToStrip) {\n if (key in newData) {\n delete newData[key];\n }\n }\n return newData;\n }\n return data;\n}\n\n/**\n * Filter hook data based on resolveData parameter\n */\nfunction filterHookData(hook: Hook, resolveData: ResolveData): Hook {\n if (resolveData === 'none' && 'metadata' in hook) {\n const { metadata: _, ...rest } = hook;\n return { metadata: undefined, ...rest };\n }\n return hook;\n}\n\n/**\n * Convert a structured applyEvent failure back into the typed error the\n * runtime matches on (via error-name based `.is()` checks). Typed errors\n * cannot cross the DO RPC boundary intact, which is why the DO returns\n * outcome objects instead of throwing.\n */\nfunction throwOutcomeError(\n outcome: ApplyEventFailure,\n runId: string,\n data: CreateEventRequest | RunCreatedEventRequest,\n): never {\n switch (outcome.code) {\n case 'RUN_NOT_FOUND':\n throw new WorkflowRunNotFoundError(runId);\n case 'STEP_NOT_FOUND':\n throw new WorkflowWorldError(outcome.message, { status: 404 });\n case 'HOOK_NOT_FOUND':\n throw new HookNotFoundError(data.correlationId ?? runId);\n case 'ENTITY_CONFLICT':\n throw new EntityConflictError(outcome.message);\n case 'RUN_EXPIRED':\n throw new RunExpiredError(outcome.message);\n case 'TOO_EARLY':\n throw new TooEarlyError(outcome.message, { retryAfter: outcome.retryAfterSeconds });\n case 'RUN_NOT_SUPPORTED':\n throw new RunNotSupportedError(outcome.runSpecVersion ?? 0, SPEC_VERSION_CURRENT);\n case 'LEGACY_RUN_NOT_SUPPORTED':\n throw new WorkflowWorldError(outcome.message, { status: 422 });\n }\n}\n\nconst parseRun = (run: WorkflowRun): WorkflowRun => WorkflowRunSchema.parse(compact(run));\nconst parseStep = (step: Step): Step => StepSchema.parse(compact(step));\nconst parseHook = (hook: Hook): Hook => HookSchema.parse(compact(hook));\nconst parseEvent = (event: Event): Event => EventSchema.parse(compact(event));\n\nexport function createStorage(config: CloudflareStorageConfig): Storage {\n const { env } = config;\n const ulid = monotonicFactory();\n\n // Helper to get or create a DO for a run\n const getRunDO = (runId: string): WorkflowRunDOStub => {\n const id = env.WORKFLOW_DB.idFromName(runId);\n return env.WORKFLOW_DB.get(id);\n };\n\n const runsGet = async (\n runId: string,\n params?: GetWorkflowRunParams,\n ): Promise<WorkflowRun | WorkflowRunWithoutData> => {\n const stub = getRunDO(runId);\n const run = await stub.getRun();\n\n if (!run) {\n throw new WorkflowRunNotFoundError(runId);\n }\n\n return filterData(parseRun(run), params?.resolveData, ['input', 'output']);\n };\n\n return {\n runs: {\n get: runsGet,\n\n async list(\n params?: ListWorkflowRunsParams,\n ): Promise<PaginatedResponse<WorkflowRun | WorkflowRunWithoutData>> {\n const limit = params?.pagination?.limit ?? 20;\n const prefix = params?.workflowName ? `run:${params.workflowName}:` : 'runall:';\n const reverse = params?.pagination?.sortOrder === 'desc';\n const matches: Array<{\n key: string;\n run: WorkflowRun | WorkflowRunWithoutData;\n }> = [];\n let scanCursor = params?.pagination?.cursor;\n let exhausted = false;\n\n // Keep scanning index pages until the requested page is full. This\n // prevents status filters and stale derived entries from producing\n // short pages or cursors that skip matching runs.\n while (matches.length <= limit && !exhausted) {\n const kvList = await env.WORKFLOW_INDEX.list({\n prefix,\n limit: Math.min(1000, Math.max(50, limit)),\n cursor: scanCursor,\n reverse,\n });\n if (kvList.keys.length === 0) {\n exhausted = true;\n break;\n }\n\n for (const key of kvList.keys) {\n const meta = await env.WORKFLOW_INDEX.get(key.name);\n if (!meta) continue;\n const { runId } = JSON.parse(meta) as { runId: string };\n try {\n const run = await runsGet(runId, { resolveData: params?.resolveData });\n if (!params?.status || run.status === params.status) {\n matches.push({ key: key.name, run });\n if (matches.length > limit) break;\n }\n } catch (error) {\n if (!WorkflowRunNotFoundError.is(error)) throw error;\n }\n }\n\n exhausted = kvList.list_complete;\n scanCursor = kvList.cursor ?? kvList.keys.at(-1)?.name;\n }\n\n const hasMore = matches.length > limit;\n const page = matches.slice(0, limit);\n\n return {\n data: page.map(({ run }) => run),\n cursor: hasMore ? (page.at(-1)?.key ?? null) : null,\n hasMore,\n };\n },\n } as Storage['runs'],\n\n events: {\n async create(\n runId: string | null,\n data: RunCreatedEventRequest | CreateEventRequest,\n _params?: CreateEventParams,\n ): Promise<EventResult> {\n // For run_created events, generate a runId server-side if absent.\n let effectiveRunId: string;\n if (data.eventType === 'run_created' && (!runId || runId === '')) {\n effectiveRunId = `wrun_${ulid()}`;\n } else if (!runId) {\n throw new WorkflowWorldError('runId is required for non-run_created events', {\n status: 400,\n });\n } else {\n effectiveRunId = runId;\n }\n\n const stub = getRunDO(effectiveRunId);\n\n let tokenHolder: ApplyEventRequest['tokenHolder'];\n let hookReservation: HookTokenOwner | undefined;\n if (data.eventType === 'hook_created') {\n hookReservation = {\n runId: effectiveRunId,\n hookId: data.correlationId,\n };\n const reservation = await env.WORKFLOW_INDEX.reserveHookToken(\n data.eventData.token,\n hookReservation,\n );\n tokenHolder = reservation.claimed ? null : reservation.holder;\n }\n\n // Guards, event append, and entity mutation run in ONE DO storage\n // transaction (see apply-event.ts). The event is schema-validated\n // before anything is persisted.\n let outcome: ApplyEventOutcome;\n try {\n outcome = await stub.applyEvent({ runId: effectiveRunId, data, tokenHolder });\n } catch (error) {\n if (hookReservation && data.eventType === 'hook_created') {\n await env.WORKFLOW_INDEX.releaseHookToken(data.eventData.token, hookReservation);\n }\n throw error;\n }\n\n if (!outcome.ok) {\n if (hookReservation && data.eventType === 'hook_created') {\n await env.WORKFLOW_INDEX.releaseHookToken(data.eventData.token, hookReservation);\n }\n throwOutcomeError(outcome, effectiveRunId, data);\n }\n\n // Derived indexes are deliberately rewritten on idempotent replay so\n // a committed run or hook can repair an interrupted index update.\n if (outcome.run) {\n const meta = JSON.stringify({\n runId: effectiveRunId,\n createdAt: outcome.run.createdAt.toISOString(),\n status: outcome.run.status,\n });\n await env.WORKFLOW_INDEX.put(workflowRunIndexKey(outcome.run), meta);\n await env.WORKFLOW_INDEX.put(globalRunIndexKey(outcome.run), meta);\n }\n if (outcome.hookToIndex) {\n const serialized = stringify(outcome.hookToIndex);\n await env.WORKFLOW_INDEX.finalizeHookIndexes(\n outcome.hookToIndex.token,\n outcome.hookToIndex.hookId,\n serialized,\n hookOwner(outcome.hookToIndex),\n );\n } else if (hookReservation && data.eventType === 'hook_created') {\n await env.WORKFLOW_INDEX.releaseHookToken(data.eventData.token, hookReservation);\n }\n for (const released of outcome.releasedHooks) {\n await env.WORKFLOW_INDEX.deleteHookIndexes(released.token, released.hookId, {\n runId: effectiveRunId,\n hookId: released.hookId,\n });\n }\n if (outcome.event?.correlationId) {\n const correlationKey = `correlation:${encodeURIComponent(\n outcome.event.correlationId,\n )}:${sortableTimestamp(outcome.event.createdAt)}:${outcome.event.eventId}:${effectiveRunId}`;\n await env.WORKFLOW_INDEX.put(\n correlationKey,\n JSON.stringify({ runId: effectiveRunId, eventId: outcome.event.eventId }),\n );\n }\n\n return {\n event: outcome.event,\n run: outcome.run,\n step: outcome.step,\n hook: outcome.hook,\n events: outcome.events,\n };\n },\n\n async get(runId: string, eventId: string, _params?: GetEventParams): Promise<Event> {\n const stub = getRunDO(runId);\n const event = await stub.getEvent(eventId);\n\n if (!event) {\n throw new WorkflowWorldError(`Event not found: ${eventId}`, {\n status: 404,\n });\n }\n\n return parseEvent(event);\n },\n\n async list(params: ListEventsParams): Promise<PaginatedResponse<Event>> {\n const { runId } = params;\n const limit = params?.pagination?.limit ?? 100;\n\n const stub = getRunDO(runId);\n const result = await stub.listEvents({\n limit,\n cursor: params?.pagination?.cursor || undefined,\n sortOrder: params.pagination?.sortOrder || 'asc',\n });\n\n return {\n data: result.data.map(parseEvent),\n cursor: result.cursor,\n hasMore: result.hasMore,\n };\n },\n\n async listByCorrelationId(params) {\n const limit = params.pagination?.limit ?? 100;\n const prefix = `correlation:${encodeURIComponent(params.correlationId)}:`;\n const listed = await env.WORKFLOW_INDEX.list({\n prefix,\n limit,\n cursor: params.pagination?.cursor,\n reverse: params.pagination?.sortOrder === 'desc',\n });\n const events = await Promise.all(\n listed.keys.map(async ({ name }) => {\n const raw = await env.WORKFLOW_INDEX.get(name);\n if (!raw) return null;\n const { runId, eventId } = JSON.parse(raw) as { runId: string; eventId: string };\n return getRunDO(runId).getEvent(eventId);\n }),\n );\n return {\n data: events.filter((event): event is Event => event !== null).map(parseEvent),\n cursor: listed.list_complete ? null : (listed.cursor ?? null),\n hasMore: !listed.list_complete,\n };\n },\n },\n\n steps: {\n async get(runId: string | undefined, stepId: string, params?: GetStepParams) {\n if (!runId) {\n throw new WorkflowWorldError('runId is required for Cloudflare step lookup', {\n status: 400,\n });\n }\n const stub = getRunDO(runId);\n const step = await stub.getStep(stepId);\n\n if (!step) {\n throw new WorkflowWorldError(`Step not found: ${stepId}`, {\n status: 404,\n });\n }\n\n return filterData(parseStep(step), params?.resolveData, ['input', 'output']);\n },\n\n async list(\n params: ListWorkflowRunStepsParams,\n ): Promise<PaginatedResponse<Step | StepWithoutData>> {\n const { runId } = params;\n const limit = params?.pagination?.limit ?? 20;\n\n const stub = getRunDO(runId);\n const result = await stub.listSteps({\n limit,\n cursor: params?.pagination?.cursor || undefined,\n sortOrder: params?.pagination?.sortOrder ?? 'asc',\n });\n\n return {\n data: result.data.map((s) =>\n filterData(parseStep(s), params?.resolveData, ['input', 'output']),\n ),\n cursor: result.cursor,\n hasMore: result.hasMore,\n };\n },\n } as Storage['steps'],\n\n hooks: {\n async get(hookId: string, params?: GetHookParams) {\n const raw = await env.WORKFLOW_INDEX.get(`hookid:${hookId}`);\n\n if (!raw) {\n throw new HookNotFoundError(hookId);\n }\n\n const hook = parseHook(parse<Hook>(raw));\n return filterHookData(hook, params?.resolveData ?? 'all');\n },\n\n async getByToken(token: string, params?: GetHookParams) {\n const raw = await env.WORKFLOW_INDEX.get(`hook:${token}`);\n\n if (!raw) {\n throw new HookNotFoundError(token);\n }\n\n const hook = parseHook(parse<Hook>(raw));\n return filterHookData(hook, params?.resolveData ?? 'all');\n },\n\n async list(params: ListHooksParams): Promise<PaginatedResponse<Hook>> {\n if (!params.runId) {\n throw new WorkflowWorldError('runId is required for listing hooks', {\n status: 400,\n });\n }\n const runId = params.runId;\n const limit = params?.pagination?.limit ?? 100;\n\n const stub = getRunDO(runId);\n const result = await stub.listHooks({\n limit,\n cursor: params?.pagination?.cursor || undefined,\n sortOrder: params?.pagination?.sortOrder ?? 'asc',\n });\n\n return {\n data: result.data.map((h) => filterHookData(parseHook(h), params?.resolveData ?? 'all')),\n cursor: result.cursor,\n hasMore: result.hasMore,\n };\n },\n },\n };\n}\n","import type {\n GetChunksOptions,\n StreamChunk,\n StreamChunksResponse,\n StreamInfoResponse,\n Streamer,\n} from '@workflow/world';\n\nexport interface CelldStreamerConfig {\n env: {\n WORKFLOW_STREAMS: StreamDONamespace;\n };\n /**\n * Poll interval (ms) while waiting for new chunks on a live stream.\n * Modified vs upstream: configurable because each poll is a fleet HTTP\n * round-trip here, not an in-process DO call. Default: 100.\n */\n readPollMs?: number;\n}\n\n/**\n * RPC surface of StreamDO (see durable-objects/StreamDO.ts). The streamer\n * talks to the DO exclusively via these methods — there is no fetch()\n * protocol.\n */\nexport interface StreamDOStub {\n writeChunk(data: Uint8Array): Promise<number>;\n closeStream(): Promise<void>;\n getChunks(params: {\n startIndex: number;\n limit: number;\n }): Promise<{ chunks: Uint8Array[]; done: boolean; tailIndex: number }>;\n getInfo(): Promise<{ tailIndex: number; done: boolean }>;\n registerStream(name: string): Promise<void>;\n listStreams(): Promise<string[]>;\n}\n\nexport interface StreamDONamespace {\n idFromName(name: string): StreamDOId;\n get(id: StreamDOId): StreamDOStub;\n}\n\nexport interface StreamDOId {\n toString(): string;\n}\n\n/** Default poll interval while waiting for new chunks on a live stream. */\nconst READ_POLL_MS = 100;\n/** Chunks fetched per DO round-trip while reading. */\nconst READ_BATCH_SIZE = 32;\n/** Default / maximum page sizes for getStreamChunks. */\nconst DEFAULT_CHUNK_PAGE_SIZE = 32;\nconst MAX_CHUNK_PAGE_SIZE = 32;\n\nfunction toBytes(chunk: string | Uint8Array): Uint8Array {\n return typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk;\n}\n\nexport function createStreamer(config: CelldStreamerConfig): Streamer {\n const { env } = config;\n const readPollMs = config.readPollMs ?? READ_POLL_MS;\n\n const getStreamDO = (streamName: string): StreamDOStub => {\n const id = env.WORKFLOW_STREAMS.idFromName(`stream:${streamName}`);\n return env.WORKFLOW_STREAMS.get(id);\n };\n\n const getRunRegistryDO = (runId: string): StreamDOStub => {\n const id = env.WORKFLOW_STREAMS.idFromName(`run-streams:${runId}`);\n return env.WORKFLOW_STREAMS.get(id);\n };\n\n /** Per-isolate cache so each (runId, stream) pair registers only once. */\n const registeredStreams = new Set<string>();\n\n async function registerStreamForRun(name: string, runId: string): Promise<void> {\n const cacheKey = `${runId}\\0${name}`;\n if (registeredStreams.has(cacheKey)) return;\n await getRunRegistryDO(runId).registerStream(name);\n registeredStreams.add(cacheKey);\n }\n\n return {\n async writeToStream(name: string, runId: string | Promise<string>, chunk: string | Uint8Array) {\n const resolvedRunId = await runId;\n await registerStreamForRun(name, resolvedRunId);\n await getStreamDO(name).writeChunk(toBytes(chunk));\n },\n\n async closeStream(name: string, runId: string | Promise<string>) {\n const resolvedRunId = await runId;\n await registerStreamForRun(name, resolvedRunId);\n await getStreamDO(name).closeStream();\n },\n\n async readFromStream(name: string, startIndex = 0) {\n const stub = getStreamDO(name);\n\n // Negative startIndex counts back from the current end, clamped to 0.\n let nextIndex: number;\n if (startIndex < 0) {\n const info = await stub.getInfo();\n nextIndex = Math.max(0, info.tailIndex + 1 + startIndex);\n } else {\n nextIndex = startIndex;\n }\n\n let cancelled = false;\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n // Loop until we can enqueue data, close, or the reader cancels.\n // Errors from the DO propagate and error the stream — readers must\n // never see a silently-truncated stream.\n while (true) {\n if (cancelled) return;\n const { chunks, done } = await stub.getChunks({\n startIndex: nextIndex,\n limit: READ_BATCH_SIZE,\n });\n if (chunks.length > 0) {\n for (const chunk of chunks) {\n controller.enqueue(chunk);\n }\n nextIndex += chunks.length;\n return;\n }\n if (done) {\n controller.close();\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, readPollMs));\n }\n },\n\n cancel() {\n cancelled = true;\n },\n });\n },\n\n async listStreamsByRunId(runId: string): Promise<string[]> {\n return getRunRegistryDO(runId).listStreams();\n },\n\n async getStreamChunks(\n name: string,\n _runId: string,\n options?: GetChunksOptions,\n ): Promise<StreamChunksResponse> {\n const limit = Math.min(options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE, MAX_CHUNK_PAGE_SIZE);\n const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;\n if (Number.isNaN(startIndex) || startIndex < 0) {\n throw new Error(`Invalid stream cursor: ${options?.cursor}`);\n }\n\n const stub = getStreamDO(name);\n const result = await stub.getChunks({ startIndex, limit: limit + 1 });\n const hasMore = result.chunks.length > limit;\n const data: StreamChunk[] = result.chunks\n .slice(0, limit)\n .map((chunk, offset) => ({ index: startIndex + offset, data: chunk }));\n\n return {\n data,\n cursor: hasMore ? String(startIndex + limit) : null,\n hasMore,\n done: result.done,\n };\n },\n\n async getStreamInfo(name: string, _runId: string): Promise<StreamInfoResponse> {\n return getStreamDO(name).getInfo();\n },\n };\n}\n","/**\n * @ewhauser/world-celld — celld World implementation for the Vercel Workflow\n * DevKit. Modeled on vinnymac/worlds packages/world-cloudflare/src/index.ts\n * (Apache-2.0, see NOTICE).\n */\nimport { SPEC_VERSION_CURRENT, type World } from '@workflow/world';\nimport { type CelldWorldConfig, type CelldWorldEnv, resolveConfig } from './config.js';\nimport { createRemoteEnv } from './remote/namespaces.js';\nimport { createQueue } from './queue.js';\nimport { createStorage } from './storage.js';\nimport { createStreamer } from './streamer.js';\n\nexport type { CelldWorldConfig, CelldWorldEnv, IndexNamespace } from './config.js';\nexport type {\n EnqueueOutcome,\n EnqueueRequest,\n QueueCellConfig,\n QueueCellNamespace,\n QueueCellStub,\n} from './queue.js';\nexport type { WorkflowRunDONamespace, WorkflowRunDOStub } from './storage.js';\nexport type { StreamDONamespace, StreamDOStub } from './streamer.js';\n\nexport function createCelldWorld(config?: CelldWorldConfig): World {\n const resolved = resolveConfig(config);\n\n // Check for global test environment first (for @workflow/world-testing)\n let env = resolved.env;\n if (!env) {\n const globalEnv = (globalThis as { CELLD_ENV?: CelldWorldEnv }).CELLD_ENV;\n if (globalEnv) {\n env = globalEnv;\n }\n }\n\n if (!env) {\n if (resolved.fleetUrl) {\n if (!resolved.secret) {\n throw new Error(\n 'world-celld: config.secret (or CELLD_WORLD_SECRET) is required with fleetUrl',\n );\n }\n env = createRemoteEnv({\n fleetUrl: resolved.fleetUrl,\n secret: resolved.secret,\n timeoutMs: resolved.rpcTimeoutMs,\n });\n }\n }\n\n if (!env) {\n throw new Error(\n 'celld environment not configured. Provide config.fleetUrl + config.secret ' +\n '(or CELLD_FLEET_URL / CELLD_WORLD_SECRET), or pass config.env with ' +\n 'WORKFLOW_DB, WORKFLOW_INDEX, WORKFLOW_QUEUE, WORKFLOW_STREAMS',\n );\n }\n\n const storage = createStorage({\n env: {\n WORKFLOW_DB: env.WORKFLOW_DB,\n WORKFLOW_INDEX: env.WORKFLOW_INDEX,\n },\n deploymentId: resolved.deploymentId,\n });\n\n const queue = createQueue({\n env: {\n WORKFLOW_QUEUE: env.WORKFLOW_QUEUE,\n },\n deploymentId: resolved.deploymentId,\n baseUrl: resolved.baseUrl,\n queueShards: resolved.queueShards,\n });\n\n const streamer = createStreamer({\n env: {\n WORKFLOW_STREAMS: env.WORKFLOW_STREAMS,\n },\n readPollMs: resolved.readPollMs,\n });\n\n return {\n ...storage,\n ...queue,\n ...streamer,\n // Enables resilient start: runs are created at the current spec version,\n // so the queue message carries runInput and run_started can bootstrap the\n // run when run_created has not landed yet.\n specVersion: SPEC_VERSION_CURRENT,\n };\n}\n\n// Export createWorld as an alias for compatibility with @workflow/world\nexport { createCelldWorld as createWorld };\n"],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ //#region src/testing/fake-cell.d.ts
2
+ /**
3
+ * In-process fake celld fleet (the eve-ambient celld-harness pattern):
4
+ * instantiate the REAL cell classes against Map-backed fake DO state, with a
5
+ * virtual-clock alarm dispatcher. Only the runtime plumbing is faked — every
6
+ * storage/alarm/lifecycle behavior under test is the real class's code.
7
+ */
8
+ interface FakeListOptions {
9
+ prefix?: string;
10
+ start?: string;
11
+ startAfter?: string;
12
+ end?: string;
13
+ limit?: number;
14
+ reverse?: boolean;
15
+ }
16
+ interface FakeStorageMutation {
17
+ operation: 'put' | 'delete';
18
+ key: string;
19
+ value?: unknown;
20
+ }
21
+ /**
22
+ * Fake DurableObjectStorage over a Map. Transactions stage writes and commit
23
+ * on success (discard on throw), with reads seeing staged state — the
24
+ * semantics apply-event.ts relies on.
25
+ */
26
+ declare class FakeStorage {
27
+ private clock;
28
+ data: Map<string, unknown>;
29
+ alarmAt: number | null;
30
+ private mutationFailure?;
31
+ constructor(clock?: () => number);
32
+ get<T>(key: string): Promise<T | undefined>;
33
+ put<T>(key: string, value: T): Promise<void>;
34
+ delete(key: string): Promise<boolean>;
35
+ deleteAll(): Promise<void>;
36
+ list<T>(options?: FakeListOptions): Promise<Map<string, T>>;
37
+ transaction<T>(cb: (txn: FakeTransaction) => Promise<T>): Promise<T>;
38
+ getAlarm(): Promise<number | null>;
39
+ setAlarm(at: number | Date): Promise<void>;
40
+ deleteAlarm(): Promise<void>;
41
+ now(): number;
42
+ /** Inject one matching write failure, including writes inside transactions. */
43
+ failNextMutation(predicate: (mutation: FakeStorageMutation) => boolean, error?: Error): void;
44
+ /** @internal Shared with FakeTransaction so failure injection survives a transaction fix. */
45
+ maybeFailMutation(mutation: FakeStorageMutation): void;
46
+ }
47
+ declare class FakeTransaction {
48
+ private storage;
49
+ private staged;
50
+ private deleted;
51
+ private stagedAlarm;
52
+ constructor(storage: FakeStorage);
53
+ get<T>(key: string): Promise<T | undefined>;
54
+ put<T>(key: string, value: T): Promise<void>;
55
+ delete(key: string): Promise<boolean>;
56
+ list<T>(options?: FakeListOptions): Promise<Map<string, T>>;
57
+ getAlarm(): Promise<number | null>;
58
+ setAlarm(at: number | Date): Promise<void>;
59
+ deleteAlarm(): Promise<void>;
60
+ commit(): void;
61
+ }
62
+ type CellClass = new (ctx: unknown, env: unknown) => {
63
+ alarm?(info?: {
64
+ retryCount?: number;
65
+ }): Promise<void>;
66
+ };
67
+ interface CellSlot {
68
+ instance: InstanceType<CellClass>;
69
+ storage: FakeStorage;
70
+ pendingWaits: Promise<unknown>[];
71
+ alarmRetryCount: number;
72
+ alarmAbandoned: boolean;
73
+ }
74
+ /**
75
+ * A fake fleet: one namespace per binding, each lazily instantiating the real
76
+ * cell class with a fake ctx. `fireDueAlarms()` drives alarm dispatch
77
+ * deterministically (alarms never fire on their own), reproducing celld's
78
+ * retry ladder including the six-failure abandonment.
79
+ */
80
+ declare class FakeFleet {
81
+ private classes;
82
+ private cellEnv;
83
+ private cells;
84
+ now: number;
85
+ constructor(classes: Record<string, CellClass>, cellEnv?: Record<string, unknown>, startTime?: number);
86
+ namespace(bindingKey: string): {
87
+ idFromName(name: string): {
88
+ toString: () => string;
89
+ };
90
+ get: (id: {
91
+ toString(): string;
92
+ }) => {
93
+ alarm?(info?: {
94
+ retryCount?: number;
95
+ }): Promise<void>;
96
+ };
97
+ };
98
+ cell(bindingKey: string, name: string): CellSlot;
99
+ advance(ms: number): void;
100
+ /**
101
+ * Dispatch every alarm due at the virtual current time, awaiting waitUntil
102
+ * work. A throwing handler re-arms per celld's ladder (2s doubling) and
103
+ * abandons the alarm after the sixth counted failure — the cell keeps its
104
+ * state but has no timer.
105
+ */
106
+ fireDueAlarms(): Promise<void>;
107
+ private drainWaits;
108
+ /** Await all waitUntil work across the fleet (delivery fetches, etc.). */
109
+ settle(): Promise<void>;
110
+ }
111
+ //#endregion
112
+ //#region src/testing/http-harness.d.ts
113
+ interface Harness {
114
+ url: string;
115
+ fleet: FakeFleet;
116
+ close(): Promise<void>;
117
+ }
118
+ interface HarnessOptions {
119
+ secret?: string;
120
+ /** Additional cell classes by binding key (e.g. { queue: QueueDO }). */
121
+ extraClasses?: Record<string, new (ctx: unknown, env: unknown) => object>;
122
+ /** env passed to cell constructors (celld `vars`). */
123
+ cellEnv?: Record<string, unknown>;
124
+ }
125
+ declare function startHarness(options?: HarnessOptions): Promise<Harness>;
126
+ //#endregion
127
+ //#region src/testing/index.d.ts
128
+ interface DevFleet extends Harness {
129
+ stop(): Promise<void>;
130
+ }
131
+ declare function startDevFleet(options?: HarnessOptions): Promise<DevFleet>;
132
+ //#endregion
133
+ export { DevFleet, FakeFleet, FakeStorage, type Harness, type HarnessOptions, startDevFleet, startHarness };
134
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing/fake-cell.ts","../src/testing/http-harness.ts","../src/testing/index.ts"],"mappings":";;;;;;;UAOiB;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;;;;;;;cAmCW;UAQS;EAPpB,MAAI;EACJ;UACQ;EAKY,YAAA;EAEd,IAAI,GAAG,cAAc,QAAQ;EAI7B,IAAI,GAAG,aAAa,OAAO,IAAI;EAO/B,OAAO,cAAc;EAKrB,aAAa;EAIb,KAAK,GAAG,UAAS,kBAAuB,QAAQ,YAAY;EAK5D,YAAY,GAAG,KAAK,KAAK,oBAAoB,QAAQ,KAAK,QAAQ;EAOlE,YAAY;EAIZ,SAAS,aAAa,OAAO;EAI7B,eAAe;EAIrB;;EAKA,iBACE,YAAY,UAAU,iCACtB,QAAK;;EAMP,kBAAkB,UAAU;;cAQxB;UAKgB;UAJZ;UACA;UACA;EAEY,YAAA,SAAS;EAEvB,IAAI,GAAG,cAAc,QAAQ;EAM7B,IAAI,GAAG,aAAa,OAAO,IAAI;EAM/B,OAAO,cAAc;EAQrB,KAAK,GAAG,UAAS,kBAAuB,QAAQ,YAAY;EAQ5D,YAAY;EAIZ,SAAS,aAAa,OAAO;EAI7B,eAAe;EAIrB;;KAOG,iBACH,cACA;EAEA,OAAO;IAAS;MAAwB;;UAGhC;EACR,UAAU,aAAa;EACvB,SAAS;EACT,cAAc;EACd;EACA;;;;;;;;cAYW;UAKD;UACA;UALF;EACR;EAGU,YAAA,SAAS,eAAe,YACxB,UAAS,yBACjB;EAKF,UAAU;IAEW,WAAA;;;IAGP,MAAA;MAAE;;MArCF,OAAA;QAAE;UAAwB;;;EAyCxC,KAAK,oBAAoB,eAAe;EA8BxC,QAAQ;;;;;;;EAUF,iBAAiB;UAsBT;;EAQR,UAAU;;;;UC7RD;EACf;EACA,OAAO;EACP,SAAS;;UAGM;EACf;;EAEA,eAAe,oBAAoB,cAAc;;EAEjD,UAAU;;iBAGU,aAAa,UAAS,iBAAsB,QAAQ;;;UCdzD,iBAAiB;EAChC,QAAQ;;iBAMY,cAAc,UAAS,iBAAsB,QAAQ"}