@ewhauser/world-celld 0.1.1 → 0.2.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 +17 -0
- package/README.md +60 -22
- package/dist/index.d.ts +112 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -39
- package/dist/index.js.map +1 -1
- package/dist/{util-BvWlkqgV.js → retention-bLY1vcOu.js} +44 -2
- package/dist/retention-bLY1vcOu.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +557 -78
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +120 -57
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +585 -75
- package/dist/worker.js.map +1 -1
- package/package.json +6 -6
- package/dist/util-BvWlkqgV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as globalRunIndexKey, d as workflowRunIndexKey, f as compact, g as stringify, h as parse, m as rpcStringify, o as correlationIndexKey, p as rpcParse } from "./retention-bLY1vcOu.js";
|
|
2
2
|
import { EventSchema, HookSchema, MessageId, SPEC_VERSION_CURRENT, StepSchema, WorkflowRunSchema, parseQueueName } from "@workflow/world";
|
|
3
3
|
import { EntityConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, TooEarlyError, WorkflowRunNotFoundError, WorkflowWorldError } from "@workflow/errors";
|
|
4
4
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
5
5
|
import { monotonicFactory } from "ulid";
|
|
6
6
|
//#region src/config.ts
|
|
7
7
|
function resolveConfig(config) {
|
|
8
|
+
const retentionRaw = config?.runRetentionMs ?? process.env.CELLD_RUN_RETENTION_MS ?? 0;
|
|
9
|
+
const runRetentionMs = typeof retentionRaw === "number" ? retentionRaw : Number.parseInt(retentionRaw, 10);
|
|
10
|
+
if (!Number.isSafeInteger(runRetentionMs) || runRetentionMs < 0) throw new Error("world-celld: runRetentionMs must be a non-negative safe integer");
|
|
8
11
|
return {
|
|
9
12
|
fleetUrl: config?.fleetUrl ?? process.env.CELLD_FLEET_URL,
|
|
10
13
|
secret: config?.secret ?? process.env.CELLD_WORLD_SECRET,
|
|
@@ -12,6 +15,7 @@ function resolveConfig(config) {
|
|
|
12
15
|
deploymentId: config?.deploymentId ?? process.env.CELLD_DEPLOYMENT_ID ?? "celld-default",
|
|
13
16
|
baseUrl: config?.baseUrl,
|
|
14
17
|
queueShards: config?.queueShards ?? 1,
|
|
18
|
+
runRetentionMs,
|
|
15
19
|
readPollMs: config?.readPollMs ?? 250,
|
|
16
20
|
rpcTimeoutMs: config?.rpcTimeoutMs ?? 3e4
|
|
17
21
|
};
|
|
@@ -113,9 +117,18 @@ const RUNS = {
|
|
|
113
117
|
"getEvent",
|
|
114
118
|
"listEvents",
|
|
115
119
|
"listSteps",
|
|
116
|
-
"listHooks"
|
|
120
|
+
"listHooks",
|
|
121
|
+
"getCleanupStatus",
|
|
122
|
+
"scheduleCleanup",
|
|
123
|
+
"cleanupNow",
|
|
124
|
+
"rearmCleanup"
|
|
117
125
|
],
|
|
118
|
-
mutating: /* @__PURE__ */ new Set([
|
|
126
|
+
mutating: /* @__PURE__ */ new Set([
|
|
127
|
+
"applyEvent",
|
|
128
|
+
"scheduleCleanup",
|
|
129
|
+
"cleanupNow",
|
|
130
|
+
"rearmCleanup"
|
|
131
|
+
])
|
|
119
132
|
};
|
|
120
133
|
const STREAMS = {
|
|
121
134
|
methods: [
|
|
@@ -124,12 +137,18 @@ const STREAMS = {
|
|
|
124
137
|
"getChunks",
|
|
125
138
|
"getInfo",
|
|
126
139
|
"registerStream",
|
|
127
|
-
"listStreams"
|
|
140
|
+
"listStreams",
|
|
141
|
+
"expireRegistry",
|
|
142
|
+
"finalizeRegistry",
|
|
143
|
+
"expireStream"
|
|
128
144
|
],
|
|
129
145
|
mutating: /* @__PURE__ */ new Set([
|
|
130
146
|
"writeChunk",
|
|
131
147
|
"closeStream",
|
|
132
|
-
"registerStream"
|
|
148
|
+
"registerStream",
|
|
149
|
+
"expireRegistry",
|
|
150
|
+
"finalizeRegistry",
|
|
151
|
+
"expireStream"
|
|
133
152
|
])
|
|
134
153
|
};
|
|
135
154
|
const QUEUE = {
|
|
@@ -139,13 +158,15 @@ const QUEUE = {
|
|
|
139
158
|
"listDeadLetters",
|
|
140
159
|
"redriveDeadLetter",
|
|
141
160
|
"purgeDeadLetters",
|
|
142
|
-
"rearmAlarm"
|
|
161
|
+
"rearmAlarm",
|
|
162
|
+
"expireRun"
|
|
143
163
|
],
|
|
144
164
|
mutating: /* @__PURE__ */ new Set([
|
|
145
165
|
"enqueue",
|
|
146
166
|
"redriveDeadLetter",
|
|
147
167
|
"purgeDeadLetters",
|
|
148
|
-
"rearmAlarm"
|
|
168
|
+
"rearmAlarm",
|
|
169
|
+
"expireRun"
|
|
149
170
|
])
|
|
150
171
|
};
|
|
151
172
|
function makeStub(transport, binding, name, spec) {
|
|
@@ -171,6 +192,12 @@ function makeIndexNamespace(transport) {
|
|
|
171
192
|
get: (key) => call("get", [key], true),
|
|
172
193
|
put: (key, value) => call("put", [key, value], false),
|
|
173
194
|
delete: (key) => call("delete", [key], false),
|
|
195
|
+
putOwned: (runId, key, value) => call("putOwned", [
|
|
196
|
+
runId,
|
|
197
|
+
key,
|
|
198
|
+
value
|
|
199
|
+
], false),
|
|
200
|
+
expireRun: (request) => call("expireRun", [request], false),
|
|
174
201
|
list: (options) => call("list", [options], true),
|
|
175
202
|
reserveHookToken: (token, owner) => call("reserveHookToken", [token, owner], false),
|
|
176
203
|
finalizeHookIndexes: (token, hookId, serializedHook, owner) => call("finalizeHookIndexes", [
|
|
@@ -430,6 +457,7 @@ function createQueue(config) {
|
|
|
430
457
|
return {
|
|
431
458
|
async queue(queueName, message, opts) {
|
|
432
459
|
const { kind } = parseQueueName(queueName);
|
|
460
|
+
const runId = "runId" in message ? message.runId : "workflowRunId" in message ? message.workflowRunId : void 0;
|
|
433
461
|
if (isTestMode()) {
|
|
434
462
|
const existing = testPump.inflight(opts?.idempotencyKey);
|
|
435
463
|
if (existing) return { messageId: existing };
|
|
@@ -450,6 +478,7 @@ function createQueue(config) {
|
|
|
450
478
|
queueName,
|
|
451
479
|
pathname: QUEUE_PATHNAMES[kind],
|
|
452
480
|
body: stringify(message),
|
|
481
|
+
runId,
|
|
453
482
|
idempotencyKey: opts?.idempotencyKey,
|
|
454
483
|
delaySeconds: opts?.delaySeconds,
|
|
455
484
|
config: {
|
|
@@ -457,7 +486,10 @@ function createQueue(config) {
|
|
|
457
486
|
queueShards
|
|
458
487
|
}
|
|
459
488
|
});
|
|
460
|
-
if (!outcome.ok)
|
|
489
|
+
if (!outcome.ok) {
|
|
490
|
+
if (outcome.code === "RUN_EXPIRED") throw new RunExpiredError(outcome.message);
|
|
491
|
+
throw new WorkflowWorldError(outcome.message, { status: 409 });
|
|
492
|
+
}
|
|
461
493
|
return { messageId: MessageId.parse(outcome.messageId) };
|
|
462
494
|
},
|
|
463
495
|
createQueueHandler(queueNamePrefix, handler) {
|
|
@@ -515,15 +547,6 @@ function createQueue(config) {
|
|
|
515
547
|
}
|
|
516
548
|
//#endregion
|
|
517
549
|
//#region src/storage.ts
|
|
518
|
-
function sortableTimestamp(date) {
|
|
519
|
-
return date.getTime().toString().padStart(13, "0");
|
|
520
|
-
}
|
|
521
|
-
function workflowRunIndexKey(run) {
|
|
522
|
-
return `run:${run.workflowName}:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
523
|
-
}
|
|
524
|
-
function globalRunIndexKey(run) {
|
|
525
|
-
return `runall:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
526
|
-
}
|
|
527
550
|
function hookOwner(hook) {
|
|
528
551
|
return {
|
|
529
552
|
runId: hook.runId,
|
|
@@ -577,15 +600,23 @@ const parseRun = (run) => WorkflowRunSchema.parse(compact(run));
|
|
|
577
600
|
const parseStep = (step) => StepSchema.parse(compact(step));
|
|
578
601
|
const parseHook = (hook) => HookSchema.parse(compact(hook));
|
|
579
602
|
const parseEvent = (event) => EventSchema.parse(compact(event));
|
|
603
|
+
function unwrapRead(outcome) {
|
|
604
|
+
if (!outcome.ok) throw new RunExpiredError(outcome.message);
|
|
605
|
+
return outcome.value;
|
|
606
|
+
}
|
|
580
607
|
function createStorage(config) {
|
|
581
608
|
const { env } = config;
|
|
609
|
+
const cleanup = {
|
|
610
|
+
retentionMs: config.runRetentionMs ?? 0,
|
|
611
|
+
queueShards: config.queueShards ?? 1
|
|
612
|
+
};
|
|
582
613
|
const ulid = monotonicFactory();
|
|
583
614
|
const getRunDO = (runId) => {
|
|
584
615
|
const id = env.WORKFLOW_DB.idFromName(runId);
|
|
585
616
|
return env.WORKFLOW_DB.get(id);
|
|
586
617
|
};
|
|
587
618
|
const runsGet = async (runId, params) => {
|
|
588
|
-
const run = await getRunDO(runId).getRun();
|
|
619
|
+
const run = unwrapRead(await getRunDO(runId).getRun());
|
|
589
620
|
if (!run) throw new WorkflowRunNotFoundError(runId);
|
|
590
621
|
return filterData(parseRun(run), params?.resolveData, ["input", "output"]);
|
|
591
622
|
};
|
|
@@ -624,7 +655,7 @@ function createStorage(config) {
|
|
|
624
655
|
if (matches.length > limit) break;
|
|
625
656
|
}
|
|
626
657
|
} catch (error) {
|
|
627
|
-
if (!WorkflowRunNotFoundError.is(error)) throw error;
|
|
658
|
+
if (!WorkflowRunNotFoundError.is(error) && !RunExpiredError.is(error)) throw error;
|
|
628
659
|
}
|
|
629
660
|
}
|
|
630
661
|
exhausted = kvList.list_complete;
|
|
@@ -661,7 +692,8 @@ function createStorage(config) {
|
|
|
661
692
|
outcome = await stub.applyEvent({
|
|
662
693
|
runId: effectiveRunId,
|
|
663
694
|
data,
|
|
664
|
-
tokenHolder
|
|
695
|
+
tokenHolder,
|
|
696
|
+
cleanup
|
|
665
697
|
});
|
|
666
698
|
} catch (error) {
|
|
667
699
|
if (hookReservation && data.eventType === "hook_created") await env.WORKFLOW_INDEX.releaseHookToken(data.eventData.token, hookReservation);
|
|
@@ -677,8 +709,8 @@ function createStorage(config) {
|
|
|
677
709
|
createdAt: outcome.run.createdAt.toISOString(),
|
|
678
710
|
status: outcome.run.status
|
|
679
711
|
});
|
|
680
|
-
await env.WORKFLOW_INDEX.
|
|
681
|
-
await env.WORKFLOW_INDEX.
|
|
712
|
+
await env.WORKFLOW_INDEX.putOwned(effectiveRunId, workflowRunIndexKey(outcome.run), meta);
|
|
713
|
+
await env.WORKFLOW_INDEX.putOwned(effectiveRunId, globalRunIndexKey(outcome.run), meta);
|
|
682
714
|
}
|
|
683
715
|
if (outcome.hookToIndex) {
|
|
684
716
|
const serialized = stringify(outcome.hookToIndex);
|
|
@@ -689,8 +721,8 @@ function createStorage(config) {
|
|
|
689
721
|
hookId: released.hookId
|
|
690
722
|
});
|
|
691
723
|
if (outcome.event?.correlationId) {
|
|
692
|
-
const correlationKey =
|
|
693
|
-
await env.WORKFLOW_INDEX.
|
|
724
|
+
const correlationKey = correlationIndexKey(outcome.event.correlationId, outcome.event.createdAt, outcome.event.eventId, effectiveRunId);
|
|
725
|
+
await env.WORKFLOW_INDEX.putOwned(effectiveRunId, correlationKey, JSON.stringify({
|
|
694
726
|
runId: effectiveRunId,
|
|
695
727
|
eventId: outcome.event.eventId
|
|
696
728
|
}));
|
|
@@ -704,18 +736,18 @@ function createStorage(config) {
|
|
|
704
736
|
};
|
|
705
737
|
},
|
|
706
738
|
async get(runId, eventId, _params) {
|
|
707
|
-
const event = await getRunDO(runId).getEvent(eventId);
|
|
739
|
+
const event = unwrapRead(await getRunDO(runId).getEvent(eventId));
|
|
708
740
|
if (!event) throw new WorkflowWorldError(`Event not found: ${eventId}`, { status: 404 });
|
|
709
741
|
return parseEvent(event);
|
|
710
742
|
},
|
|
711
743
|
async list(params) {
|
|
712
744
|
const { runId } = params;
|
|
713
745
|
const limit = params?.pagination?.limit ?? 100;
|
|
714
|
-
const result = await getRunDO(runId).listEvents({
|
|
746
|
+
const result = unwrapRead(await getRunDO(runId).listEvents({
|
|
715
747
|
limit,
|
|
716
748
|
cursor: params?.pagination?.cursor || void 0,
|
|
717
749
|
sortOrder: params.pagination?.sortOrder || "asc"
|
|
718
|
-
});
|
|
750
|
+
}));
|
|
719
751
|
return {
|
|
720
752
|
data: result.data.map(parseEvent),
|
|
721
753
|
cursor: result.cursor,
|
|
@@ -736,7 +768,8 @@ function createStorage(config) {
|
|
|
736
768
|
const raw = await env.WORKFLOW_INDEX.get(name);
|
|
737
769
|
if (!raw) return null;
|
|
738
770
|
const { runId, eventId } = JSON.parse(raw);
|
|
739
|
-
|
|
771
|
+
const outcome = await getRunDO(runId).getEvent(eventId);
|
|
772
|
+
return outcome.ok ? outcome.value : null;
|
|
740
773
|
}))).filter((event) => event !== null).map(parseEvent),
|
|
741
774
|
cursor: listed.list_complete ? null : listed.cursor ?? null,
|
|
742
775
|
hasMore: !listed.list_complete
|
|
@@ -746,18 +779,18 @@ function createStorage(config) {
|
|
|
746
779
|
steps: {
|
|
747
780
|
async get(runId, stepId, params) {
|
|
748
781
|
if (!runId) throw new WorkflowWorldError("runId is required for Cloudflare step lookup", { status: 400 });
|
|
749
|
-
const step = await getRunDO(runId).getStep(stepId);
|
|
782
|
+
const step = unwrapRead(await getRunDO(runId).getStep(stepId));
|
|
750
783
|
if (!step) throw new WorkflowWorldError(`Step not found: ${stepId}`, { status: 404 });
|
|
751
784
|
return filterData(parseStep(step), params?.resolveData, ["input", "output"]);
|
|
752
785
|
},
|
|
753
786
|
async list(params) {
|
|
754
787
|
const { runId } = params;
|
|
755
788
|
const limit = params?.pagination?.limit ?? 20;
|
|
756
|
-
const result = await getRunDO(runId).listSteps({
|
|
789
|
+
const result = unwrapRead(await getRunDO(runId).listSteps({
|
|
757
790
|
limit,
|
|
758
791
|
cursor: params?.pagination?.cursor || void 0,
|
|
759
792
|
sortOrder: params?.pagination?.sortOrder ?? "asc"
|
|
760
|
-
});
|
|
793
|
+
}));
|
|
761
794
|
return {
|
|
762
795
|
data: result.data.map((s) => filterData(parseStep(s), params?.resolveData, ["input", "output"])),
|
|
763
796
|
cursor: result.cursor,
|
|
@@ -780,11 +813,11 @@ function createStorage(config) {
|
|
|
780
813
|
if (!params.runId) throw new WorkflowWorldError("runId is required for listing hooks", { status: 400 });
|
|
781
814
|
const runId = params.runId;
|
|
782
815
|
const limit = params?.pagination?.limit ?? 100;
|
|
783
|
-
const result = await getRunDO(runId).listHooks({
|
|
816
|
+
const result = unwrapRead(await getRunDO(runId).listHooks({
|
|
784
817
|
limit,
|
|
785
818
|
cursor: params?.pagination?.cursor || void 0,
|
|
786
819
|
sortOrder: params?.pagination?.sortOrder ?? "asc"
|
|
787
|
-
});
|
|
820
|
+
}));
|
|
788
821
|
return {
|
|
789
822
|
data: result.data.map((h) => filterHookData(parseHook(h), params?.resolveData ?? "all")),
|
|
790
823
|
cursor: result.cursor,
|
|
@@ -822,17 +855,19 @@ function createStreamer(config) {
|
|
|
822
855
|
async function registerStreamForRun(name, runId) {
|
|
823
856
|
const cacheKey = `${runId}\0${name}`;
|
|
824
857
|
if (registeredStreams.has(cacheKey)) return;
|
|
825
|
-
await getRunRegistryDO(runId).registerStream(name);
|
|
858
|
+
await getRunRegistryDO(runId).registerStream(runId, name);
|
|
826
859
|
registeredStreams.add(cacheKey);
|
|
827
860
|
}
|
|
828
861
|
return {
|
|
829
862
|
async writeToStream(name, runId, chunk) {
|
|
830
|
-
|
|
831
|
-
await
|
|
863
|
+
const resolvedRunId = await runId;
|
|
864
|
+
await registerStreamForRun(name, resolvedRunId);
|
|
865
|
+
await getStreamDO(name).writeChunk(resolvedRunId, toBytes(chunk));
|
|
832
866
|
},
|
|
833
867
|
async closeStream(name, runId) {
|
|
834
|
-
|
|
835
|
-
await
|
|
868
|
+
const resolvedRunId = await runId;
|
|
869
|
+
await registerStreamForRun(name, resolvedRunId);
|
|
870
|
+
await getStreamDO(name).closeStream(resolvedRunId);
|
|
836
871
|
},
|
|
837
872
|
async readFromStream(name, startIndex = 0) {
|
|
838
873
|
const stub = getStreamDO(name);
|
|
@@ -924,7 +959,9 @@ function createCelldWorld(config) {
|
|
|
924
959
|
WORKFLOW_DB: env.WORKFLOW_DB,
|
|
925
960
|
WORKFLOW_INDEX: env.WORKFLOW_INDEX
|
|
926
961
|
},
|
|
927
|
-
deploymentId: resolved.deploymentId
|
|
962
|
+
deploymentId: resolved.deploymentId,
|
|
963
|
+
runRetentionMs: resolved.runRetentionMs,
|
|
964
|
+
queueShards: resolved.queueShards
|
|
928
965
|
});
|
|
929
966
|
const queue = createQueue({
|
|
930
967
|
env: { WORKFLOW_QUEUE: env.WORKFLOW_QUEUE },
|
|
@@ -936,10 +973,27 @@ function createCelldWorld(config) {
|
|
|
936
973
|
env: { WORKFLOW_STREAMS: env.WORKFLOW_STREAMS },
|
|
937
974
|
readPollMs: resolved.readPollMs
|
|
938
975
|
});
|
|
976
|
+
const runStub = (runId) => env.WORKFLOW_DB.get(env.WORKFLOW_DB.idFromName(runId));
|
|
977
|
+
const retention = {
|
|
978
|
+
getStatus: (runId) => runStub(runId).getCleanupStatus(),
|
|
979
|
+
schedule: (runId) => {
|
|
980
|
+
if (resolved.runRetentionMs === 0) throw new Error("world-celld: runRetentionMs must be greater than zero to schedule cleanup");
|
|
981
|
+
return runStub(runId).scheduleCleanup({
|
|
982
|
+
retentionMs: resolved.runRetentionMs,
|
|
983
|
+
queueShards: resolved.queueShards
|
|
984
|
+
});
|
|
985
|
+
},
|
|
986
|
+
cleanupNow: (runId) => runStub(runId).cleanupNow({
|
|
987
|
+
retentionMs: Math.max(1, resolved.runRetentionMs),
|
|
988
|
+
queueShards: resolved.queueShards
|
|
989
|
+
}),
|
|
990
|
+
rearm: (runId) => runStub(runId).rearmCleanup()
|
|
991
|
+
};
|
|
939
992
|
return {
|
|
940
993
|
...storage,
|
|
941
994
|
...queue,
|
|
942
995
|
...streamer,
|
|
996
|
+
retention,
|
|
943
997
|
specVersion: SPEC_VERSION_CURRENT
|
|
944
998
|
};
|
|
945
999
|
}
|