@ewhauser/world-celld 0.2.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.3.0 (2026-08-16)
6
+
7
+ ## What's Changed
8
+ * fix: split Release Please release and PR phases by @ewhauser in https://github.com/ewhauser/world-celld/pull/16
9
+ * feat: target Workflow 5 beta by @ewhauser in https://github.com/ewhauser/world-celld/pull/17
10
+ * fix: enforce Workflow 5 storage contracts by @ewhauser in https://github.com/ewhauser/world-celld/pull/19
11
+
12
+
13
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.2.0...v0.3.0
14
+
5
15
  ## 0.2.0 (2026-08-16)
6
16
 
7
17
  ## What's Changed
package/README.md CHANGED
@@ -125,7 +125,7 @@ celld worker router
125
125
  |
126
126
  | HTTP callbacks
127
127
  v
128
- Workflow application /.well-known/workflow/v1/{flow|step}
128
+ Workflow application /.well-known/workflow/v1/flow
129
129
  ```
130
130
 
131
131
  The application-side package implements the Workflow `World` interface and
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateEventRequest, Event, Hook, RunCreatedEventRequest, Step, TerminalWorkflowRunStatus, ValidQueueName, WorkflowRun, World } from "@workflow/world";
1
+ import { CreateEventParams, CreateEventRequest, Event, Hook, RunCreatedEventRequest, Step, TerminalWorkflowRunStatus, ValidQueueName, Wait, WorkflowRun, World } from "@workflow/world";
2
2
  //#region src/retention.d.ts
3
3
  type CleanupPhase = 'retained' | 'index' | 'streams' | 'queues' | 'payload' | 'tombstoned';
4
4
  interface CleanupRecord {
@@ -69,6 +69,8 @@ interface ApplyEventRequest {
69
69
  /** The effective run id (already resolved / generated by the caller). */
70
70
  runId: string;
71
71
  data: CreateEventRequest | RunCreatedEventRequest;
72
+ /** Workflow 5 event-log position and preload options. */
73
+ params?: CreateEventParams;
72
74
  /**
73
75
  * Current holder of the hook token per the global KV token index
74
76
  * (hook_created only). `null` means the token is unclaimed.
@@ -80,7 +82,7 @@ interface ApplyEventRequest {
80
82
  /** Internal world-celld retention policy captured with the event. */
81
83
  cleanup?: ScheduleCleanupRequest;
82
84
  }
83
- type ApplyEventErrorCode = 'RUN_NOT_FOUND' | 'STEP_NOT_FOUND' | 'HOOK_NOT_FOUND' | 'ENTITY_CONFLICT' | 'RUN_EXPIRED' | 'TOO_EARLY' | 'RUN_NOT_SUPPORTED' | 'LEGACY_RUN_NOT_SUPPORTED';
85
+ type ApplyEventErrorCode = 'RUN_NOT_FOUND' | 'STEP_NOT_FOUND' | 'HOOK_NOT_FOUND' | 'WAIT_NOT_FOUND' | 'ENTITY_CONFLICT' | 'RUN_EXPIRED' | 'TOO_EARLY' | 'RUN_NOT_SUPPORTED';
84
86
  interface ApplyEventFailure {
85
87
  ok: false;
86
88
  code: ApplyEventErrorCode;
@@ -97,6 +99,9 @@ interface ApplyEventSuccess {
97
99
  run?: WorkflowRun;
98
100
  step?: Step;
99
101
  hook?: Hook;
102
+ wait?: Wait;
103
+ /** True when a lazy step_started atomically created its step. */
104
+ stepCreated?: true;
100
105
  /** Hooks whose tokens must be released from the global KV index. */
101
106
  releasedHooks: Array<{
102
107
  hookId: string;
@@ -111,6 +116,11 @@ interface ApplyEventSuccess {
111
116
  hookToIndex?: Hook;
112
117
  /** All events (ascending), preloaded for run_started responses. */
113
118
  events?: Event[];
119
+ /** Pagination metadata required whenever events are returned. */
120
+ cursor?: string | null;
121
+ hasMore?: boolean;
122
+ /** Server-owned event ceiling advertised on run lifecycle writes. */
123
+ maxEvents?: number;
114
124
  }
115
125
  type ApplyEventOutcome = ApplyEventSuccess | ApplyEventFailure;
116
126
  //#endregion
@@ -200,7 +210,7 @@ interface StreamDOId {
200
210
  }
201
211
  //#endregion
202
212
  //#region src/queue.d.ts
203
- type Pathname = 'flow' | 'step';
213
+ type Pathname = 'flow';
204
214
  /**
205
215
  * Enqueue request accepted by a QueueDO cell. The message payload travels
206
216
  * pre-encoded with the shared tagged-JSON codec (`body`), so the cell can
@@ -223,7 +233,7 @@ interface EnqueueRequest {
223
233
  config: QueueCellConfig;
224
234
  }
225
235
  interface QueueCellConfig {
226
- /** Base URL of the app; deliveries POST to `${baseUrl}/.well-known/workflow/v1/{flow|step}`. */
236
+ /** Base URL of the app; deliveries POST to `${baseUrl}/.well-known/workflow/v1/flow`. */
227
237
  targetBaseUrl: string;
228
238
  queueShards: number;
229
239
  }
@@ -312,7 +322,7 @@ interface CelldWorldConfig {
312
322
  deploymentId?: string;
313
323
  /**
314
324
  * Base URL the app's workflow endpoints are mounted on; QueueDO cells
315
- * deliver to `${baseUrl}/.well-known/workflow/v1/{flow|step}`.
325
+ * deliver to `${baseUrl}/.well-known/workflow/v1/flow`.
316
326
  * Default: process.env.WORKFLOW_BASE_URL || `http://localhost:${PORT ?? 3000}`
317
327
  */
318
328
  baseUrl?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/streamer.ts","../src/queue.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/streamer.ts","../src/queue.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -294,10 +294,7 @@ function isPermanentError(err) {
294
294
  function computeBackoff(attempt) {
295
295
  return Math.min(60, 2 ** attempt);
296
296
  }
297
- const QUEUE_PATHNAMES = {
298
- workflow: "flow",
299
- step: "step"
300
- };
297
+ const QUEUE_PATHNAME = "flow";
301
298
  function isTestMode() {
302
299
  if (process.env.CELLD_QUEUE_MODE === "cells") return false;
303
300
  return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
@@ -321,9 +318,8 @@ function shardFor(key, shards) {
321
318
  return (hash >>> 0) % shards;
322
319
  }
323
320
  /**
324
- * In-process test pump. Holds two in-memory FIFOs and HTTP-dispatches
325
- * envelopes to the user's server at
326
- * `${baseUrl}/.well-known/workflow/v1/{flow|step}`.
321
+ * In-process test pump. Holds an in-memory FIFO and HTTP-dispatches envelopes
322
+ * to the user's server at `${baseUrl}/.well-known/workflow/v1/flow`.
327
323
  *
328
324
  * Mirrors world-local's idempotency semantics: messages are deduplicated on
329
325
  * `idempotencyKey` while a message with the same key is in flight, and the
@@ -337,14 +333,8 @@ function createTestPump(config) {
337
333
  const httpTimeoutMs = config.httpTimeoutMs ?? 3e5;
338
334
  const maxAttempts = config.maxAttempts ?? 5;
339
335
  const baseBackoffMs = config.backoffDelayMs ?? 1e3;
340
- const queues = {
341
- flow: [],
342
- step: []
343
- };
344
- const wakers = {
345
- flow: [],
346
- step: []
347
- };
336
+ const queues = { flow: [] };
337
+ const wakers = { flow: [] };
348
338
  /** Inflight messageIds by idempotencyKey (world-local queue.js semantics). */
349
339
  const inflightMessages = /* @__PURE__ */ new Map();
350
340
  let running = false;
@@ -437,7 +427,6 @@ function createTestPump(config) {
437
427
  if (running) return;
438
428
  running = true;
439
429
  loop("flow");
440
- loop("step");
441
430
  },
442
431
  stop() {
443
432
  running = false;
@@ -456,13 +445,13 @@ function createQueue(config) {
456
445
  };
457
446
  return {
458
447
  async queue(queueName, message, opts) {
459
- const { kind } = parseQueueName(queueName);
460
- const runId = "runId" in message ? message.runId : "workflowRunId" in message ? message.workflowRunId : void 0;
448
+ parseQueueName(queueName);
449
+ const runId = "runId" in message && typeof message.runId === "string" ? message.runId : void 0;
461
450
  if (isTestMode()) {
462
451
  const existing = testPump.inflight(opts?.idempotencyKey);
463
452
  if (existing) return { messageId: existing };
464
453
  const messageId = MessageId.parse(`msg_${generateMessageId()}`);
465
- testPump.push(QUEUE_PATHNAMES[kind], {
454
+ testPump.push(QUEUE_PATHNAME, {
466
455
  messageId,
467
456
  queueName,
468
457
  attempt: 1,
@@ -476,7 +465,7 @@ function createQueue(config) {
476
465
  const outcome = await getQueueStub(shard).enqueue({
477
466
  messageId,
478
467
  queueName,
479
- pathname: QUEUE_PATHNAMES[kind],
468
+ pathname: QUEUE_PATHNAME,
480
469
  body: stringify(message),
481
470
  runId,
482
471
  idempotencyKey: opts?.idempotencyKey,
@@ -589,11 +578,11 @@ function throwOutcomeError(outcome, runId, data) {
589
578
  case "RUN_NOT_FOUND": throw new WorkflowRunNotFoundError(runId);
590
579
  case "STEP_NOT_FOUND": throw new WorkflowWorldError(outcome.message, { status: 404 });
591
580
  case "HOOK_NOT_FOUND": throw new HookNotFoundError(data.correlationId ?? runId);
581
+ case "WAIT_NOT_FOUND": throw new WorkflowWorldError(outcome.message, { status: 404 });
592
582
  case "ENTITY_CONFLICT": throw new EntityConflictError(outcome.message);
593
583
  case "RUN_EXPIRED": throw new RunExpiredError(outcome.message);
594
584
  case "TOO_EARLY": throw new TooEarlyError(outcome.message, { retryAfter: outcome.retryAfterSeconds });
595
585
  case "RUN_NOT_SUPPORTED": throw new RunNotSupportedError(outcome.runSpecVersion ?? 0, SPEC_VERSION_CURRENT);
596
- case "LEGACY_RUN_NOT_SUPPORTED": throw new WorkflowWorldError(outcome.message, { status: 422 });
597
586
  }
598
587
  }
599
588
  const parseRun = (run) => WorkflowRunSchema.parse(compact(run));
@@ -671,7 +660,7 @@ function createStorage(config) {
671
660
  }
672
661
  },
673
662
  events: {
674
- async create(runId, data, _params) {
663
+ async create(runId, data, params) {
675
664
  let effectiveRunId;
676
665
  if (data.eventType === "run_created" && (!runId || runId === "")) effectiveRunId = `wrun_${ulid()}`;
677
666
  else if (!runId) throw new WorkflowWorldError("runId is required for non-run_created events", { status: 400 });
@@ -692,6 +681,7 @@ function createStorage(config) {
692
681
  outcome = await stub.applyEvent({
693
682
  runId: effectiveRunId,
694
683
  data,
684
+ params,
695
685
  tokenHolder,
696
686
  cleanup
697
687
  });
@@ -727,12 +717,20 @@ function createStorage(config) {
727
717
  eventId: outcome.event.eventId
728
718
  }));
729
719
  }
720
+ const eventPage = outcome.events === void 0 ? {} : {
721
+ events: outcome.events,
722
+ cursor: outcome.cursor ?? null,
723
+ hasMore: outcome.hasMore ?? false
724
+ };
730
725
  return {
731
726
  event: outcome.event,
732
727
  run: outcome.run,
733
728
  step: outcome.step,
734
729
  hook: outcome.hook,
735
- events: outcome.events
730
+ wait: outcome.wait,
731
+ stepCreated: outcome.stepCreated,
732
+ maxEvents: outcome.maxEvents,
733
+ ...eventPage
736
734
  };
737
735
  },
738
736
  async get(runId, eventId, _params) {
@@ -756,23 +754,30 @@ function createStorage(config) {
756
754
  },
757
755
  async listByCorrelationId(params) {
758
756
  const limit = params.pagination?.limit ?? 100;
759
- const prefix = `correlation:${encodeURIComponent(params.correlationId)}:`;
760
- const listed = await env.WORKFLOW_INDEX.list({
761
- prefix,
762
- limit,
763
- cursor: params.pagination?.cursor,
764
- reverse: params.pagination?.sortOrder === "desc"
765
- });
757
+ const stub = getRunDO(params.runId);
758
+ const matches = [];
759
+ let scanCursor = params.pagination?.cursor;
760
+ let exhausted = false;
761
+ while (matches.length <= limit && !exhausted) {
762
+ const page = unwrapRead(await stub.listEvents({
763
+ limit: Math.min(1e3, Math.max(50, limit + 1)),
764
+ cursor: scanCursor,
765
+ sortOrder: params.pagination?.sortOrder ?? "asc"
766
+ }));
767
+ for (const event of page.data) if (event.correlationId === params.correlationId) {
768
+ matches.push(event);
769
+ if (matches.length > limit) break;
770
+ }
771
+ exhausted = !page.hasMore;
772
+ scanCursor = page.cursor ?? page.data.at(-1)?.eventId;
773
+ if (page.data.length === 0) exhausted = true;
774
+ }
775
+ const hasMore = matches.length > limit;
776
+ const data = matches.slice(0, limit);
766
777
  return {
767
- data: (await Promise.all(listed.keys.map(async ({ name }) => {
768
- const raw = await env.WORKFLOW_INDEX.get(name);
769
- if (!raw) return null;
770
- const { runId, eventId } = JSON.parse(raw);
771
- const outcome = await getRunDO(runId).getEvent(eventId);
772
- return outcome.ok ? outcome.value : null;
773
- }))).filter((event) => event !== null).map(parseEvent),
774
- cursor: listed.list_complete ? null : listed.cursor ?? null,
775
- hasMore: !listed.list_complete
778
+ data: data.map(parseEvent),
779
+ cursor: hasMore ? data.at(-1)?.eventId ?? null : null,
780
+ hasMore
776
781
  };
777
782
  }
778
783
  },
@@ -858,75 +863,87 @@ function createStreamer(config) {
858
863
  await getRunRegistryDO(runId).registerStream(runId, name);
859
864
  registeredStreams.add(cacheKey);
860
865
  }
861
- return {
862
- async writeToStream(name, runId, chunk) {
863
- const resolvedRunId = await runId;
864
- await registerStreamForRun(name, resolvedRunId);
865
- await getStreamDO(name).writeChunk(resolvedRunId, toBytes(chunk));
866
- },
867
- async closeStream(name, runId) {
868
- const resolvedRunId = await runId;
869
- await registerStreamForRun(name, resolvedRunId);
870
- await getStreamDO(name).closeStream(resolvedRunId);
871
- },
872
- async readFromStream(name, startIndex = 0) {
873
- const stub = getStreamDO(name);
874
- let nextIndex;
875
- if (startIndex < 0) {
876
- const info = await stub.getInfo();
877
- nextIndex = Math.max(0, info.tailIndex + 1 + startIndex);
878
- } else nextIndex = startIndex;
879
- let cancelled = false;
880
- return new ReadableStream({
881
- async pull(controller) {
882
- while (true) {
883
- if (cancelled) return;
884
- const { chunks, done } = await stub.getChunks({
885
- startIndex: nextIndex,
886
- limit: READ_BATCH_SIZE
887
- });
888
- if (chunks.length > 0) {
889
- for (const chunk of chunks) controller.enqueue(chunk);
890
- nextIndex += chunks.length;
891
- return;
892
- }
893
- if (done) {
894
- controller.close();
895
- return;
896
- }
897
- await new Promise((resolve) => setTimeout(resolve, readPollMs));
866
+ const writeToStream = async (name, runId, chunk) => {
867
+ const resolvedRunId = await runId;
868
+ await registerStreamForRun(name, resolvedRunId);
869
+ await getStreamDO(name).writeChunk(resolvedRunId, toBytes(chunk));
870
+ };
871
+ const closeStream = async (name, runId) => {
872
+ const resolvedRunId = await runId;
873
+ await registerStreamForRun(name, resolvedRunId);
874
+ await getStreamDO(name).closeStream(resolvedRunId);
875
+ };
876
+ const readFromStream = async (name, startIndex = 0) => {
877
+ const stub = getStreamDO(name);
878
+ let nextIndex;
879
+ if (startIndex < 0) {
880
+ const info = await stub.getInfo();
881
+ nextIndex = Math.max(0, info.tailIndex + 1 + startIndex);
882
+ } else nextIndex = startIndex;
883
+ let cancelled = false;
884
+ return new ReadableStream({
885
+ async pull(controller) {
886
+ while (true) {
887
+ if (cancelled) return;
888
+ const { chunks, done } = await stub.getChunks({
889
+ startIndex: nextIndex,
890
+ limit: READ_BATCH_SIZE
891
+ });
892
+ if (chunks.length > 0) {
893
+ for (const chunk of chunks) controller.enqueue(chunk);
894
+ nextIndex += chunks.length;
895
+ return;
898
896
  }
899
- },
900
- cancel() {
901
- cancelled = true;
897
+ if (done) {
898
+ controller.close();
899
+ return;
900
+ }
901
+ await new Promise((resolve) => setTimeout(resolve, readPollMs));
902
902
  }
903
- });
904
- },
905
- async listStreamsByRunId(runId) {
906
- return getRunRegistryDO(runId).listStreams();
907
- },
908
- async getStreamChunks(name, _runId, options) {
909
- const limit = Math.min(options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE, MAX_CHUNK_PAGE_SIZE);
910
- const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
911
- if (Number.isNaN(startIndex) || startIndex < 0) throw new Error(`Invalid stream cursor: ${options?.cursor}`);
912
- const result = await getStreamDO(name).getChunks({
913
- startIndex,
914
- limit: limit + 1
915
- });
916
- const hasMore = result.chunks.length > limit;
917
- return {
918
- data: result.chunks.slice(0, limit).map((chunk, offset) => ({
919
- index: startIndex + offset,
920
- data: chunk
921
- })),
922
- cursor: hasMore ? String(startIndex + limit) : null,
923
- hasMore,
924
- done: result.done
925
- };
903
+ },
904
+ cancel() {
905
+ cancelled = true;
906
+ }
907
+ });
908
+ };
909
+ const listStreamsByRunId = (runId) => getRunRegistryDO(runId).listStreams();
910
+ const getStreamChunks = async (name, _runId, options) => {
911
+ const limit = Math.min(options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE, MAX_CHUNK_PAGE_SIZE);
912
+ const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
913
+ if (Number.isNaN(startIndex) || startIndex < 0) throw new Error(`Invalid stream cursor: ${options?.cursor}`);
914
+ const result = await getStreamDO(name).getChunks({
915
+ startIndex,
916
+ limit: limit + 1
917
+ });
918
+ const hasMore = result.chunks.length > limit;
919
+ return {
920
+ data: result.chunks.slice(0, limit).map((chunk, offset) => ({
921
+ index: startIndex + offset,
922
+ data: chunk
923
+ })),
924
+ cursor: hasMore ? String(startIndex + limit) : null,
925
+ hasMore,
926
+ done: result.done
927
+ };
928
+ };
929
+ const getStreamInfo = (name, _runId) => getStreamDO(name).getInfo();
930
+ return {
931
+ streams: {
932
+ write: (runId, name, chunk) => writeToStream(name, runId, chunk),
933
+ close: (runId, name) => closeStream(name, runId),
934
+ get: (runId, name, startIndex) => {
935
+ return readFromStream(name, startIndex);
936
+ },
937
+ list: listStreamsByRunId,
938
+ getChunks: (runId, name, options) => getStreamChunks(name, runId, options),
939
+ getInfo: (runId, name) => getStreamInfo(name, runId)
926
940
  },
927
- async getStreamInfo(name, _runId) {
928
- return getStreamDO(name).getInfo();
929
- }
941
+ writeToStream,
942
+ closeStream,
943
+ readFromStream,
944
+ listStreamsByRunId,
945
+ getStreamChunks,
946
+ getStreamInfo
930
947
  };
931
948
  }
932
949
  //#endregion