@ewhauser/world-celld 0.3.2 → 0.4.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 +9 -0
- package/README.md +60 -20
- package/celld-worker/README.md +22 -0
- package/celld-worker/wrangler.jsonc +12 -0
- package/dist/index.d.ts +14 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/testing.js +133 -36
- package/dist/testing.js.map +1 -1
- package/dist/util-ya3V_FTA.js.map +1 -1
- package/dist/worker.d.ts +46 -5
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +259 -84
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -736,6 +736,120 @@ function createRouter(env) {
|
|
|
736
736
|
};
|
|
737
737
|
}
|
|
738
738
|
//#endregion
|
|
739
|
+
//#region src/retention.ts
|
|
740
|
+
const CLEANUP_RECORD_KEY = "retention:cleanup";
|
|
741
|
+
const TOMBSTONE_KEY = "retention:tombstone";
|
|
742
|
+
const HOOK_MARKER_PREFIX = "retention:hook:";
|
|
743
|
+
const TERMINAL_CLEANUP_KEY = "terminal:cleanup";
|
|
744
|
+
function sortableTimestamp(date) {
|
|
745
|
+
return date.getTime().toString().padStart(13, "0");
|
|
746
|
+
}
|
|
747
|
+
function workflowRunIndexKey(run) {
|
|
748
|
+
return `run:${run.workflowName}:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
749
|
+
}
|
|
750
|
+
function globalRunIndexKey(run) {
|
|
751
|
+
return `runall:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
752
|
+
}
|
|
753
|
+
function hookMarkerKey(reference) {
|
|
754
|
+
return `${HOOK_MARKER_PREFIX}${encodeURIComponent(reference.hookId)}:${encodeURIComponent(reference.token)}`;
|
|
755
|
+
}
|
|
756
|
+
function cleanupTombstone(record, now) {
|
|
757
|
+
const cleanup = {
|
|
758
|
+
...record,
|
|
759
|
+
phase: "tombstoned",
|
|
760
|
+
tombstonedAt: now
|
|
761
|
+
};
|
|
762
|
+
return {
|
|
763
|
+
version: 1,
|
|
764
|
+
runId: record.runId,
|
|
765
|
+
status: record.status ?? record.terminalStatus,
|
|
766
|
+
terminalStatus: record.terminalStatus,
|
|
767
|
+
completedAt: record.completedAt,
|
|
768
|
+
expiredAt: record.dueAt,
|
|
769
|
+
tombstonedAt: now,
|
|
770
|
+
cleanup
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function cleanupFromTombstone(tombstone) {
|
|
774
|
+
return tombstone.cleanup;
|
|
775
|
+
}
|
|
776
|
+
function expiredRead(tombstone) {
|
|
777
|
+
return {
|
|
778
|
+
ok: false,
|
|
779
|
+
code: "RUN_EXPIRED",
|
|
780
|
+
message: `Workflow run "${tombstone.runId}" expired at ${tombstone.expiredAt.toISOString()}`,
|
|
781
|
+
tombstone
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const MAX_RETENTION_SWEEP_BATCH_SIZE = 1e3;
|
|
785
|
+
const RETENTION_SWEEP_CONCURRENCY = 8;
|
|
786
|
+
function integerSetting(name, raw, fallback, minimum, maximum = Number.MAX_SAFE_INTEGER) {
|
|
787
|
+
const value = raw === void 0 || raw === "" ? fallback : Number(raw);
|
|
788
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) throw new Error(`world-celld: ${name} must be an integer between ${minimum} and ${maximum}`);
|
|
789
|
+
return value;
|
|
790
|
+
}
|
|
791
|
+
function runIdFromCatalogValue(value) {
|
|
792
|
+
const parsed = JSON.parse(value);
|
|
793
|
+
if (typeof parsed.runId !== "string" || parsed.runId.length === 0) throw new Error("world-celld: run catalog entry has no runId");
|
|
794
|
+
return parsed.runId;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Discover and fence one bounded page of workflows older than the configured
|
|
798
|
+
* maximum age. RunDO alarms finish the persisted, idempotent deletion phases.
|
|
799
|
+
*/
|
|
800
|
+
async function runRetentionSweep(scheduledTime, env) {
|
|
801
|
+
if (!Number.isSafeInteger(scheduledTime) || scheduledTime < 0) throw new Error("world-celld: scheduledTime must be a non-negative safe integer");
|
|
802
|
+
const retentionMs = integerSetting("WORKFLOW_RETENTION_MS", env.WORKFLOW_RETENTION_MS, 0, 0);
|
|
803
|
+
if (retentionMs === 0 || scheduledTime < retentionMs) return {
|
|
804
|
+
disabled: retentionMs === 0,
|
|
805
|
+
cutoff: retentionMs === 0 ? null : scheduledTime - retentionMs,
|
|
806
|
+
scanned: 0,
|
|
807
|
+
scheduled: 0,
|
|
808
|
+
expired: 0,
|
|
809
|
+
missing: 0,
|
|
810
|
+
notDue: 0
|
|
811
|
+
};
|
|
812
|
+
const batchSize = integerSetting("WORKFLOW_RETENTION_BATCH_SIZE", env.WORKFLOW_RETENTION_BATCH_SIZE, 128, 1, MAX_RETENTION_SWEEP_BATCH_SIZE);
|
|
813
|
+
const queueShards = integerSetting("WORKFLOW_RETENTION_QUEUE_SHARDS", env.WORKFLOW_RETENTION_QUEUE_SHARDS, 1, 1);
|
|
814
|
+
const cutoff = scheduledTime - retentionMs;
|
|
815
|
+
const page = await createWorkflowIndex({
|
|
816
|
+
runCatalog: env.WORKFLOW_RUN_CATALOG,
|
|
817
|
+
hookTokens: env.WORKFLOW_HOOK_TOKENS,
|
|
818
|
+
hookIds: env.WORKFLOW_HOOK_IDS
|
|
819
|
+
}).listRuns({
|
|
820
|
+
prefix: "runall:",
|
|
821
|
+
end: `runall:${sortableTimestamp(new Date(cutoff + 1))}:`,
|
|
822
|
+
limit: batchSize
|
|
823
|
+
});
|
|
824
|
+
const result = {
|
|
825
|
+
disabled: false,
|
|
826
|
+
cutoff,
|
|
827
|
+
scanned: page.keys.length,
|
|
828
|
+
scheduled: 0,
|
|
829
|
+
expired: 0,
|
|
830
|
+
missing: 0,
|
|
831
|
+
notDue: 0
|
|
832
|
+
};
|
|
833
|
+
const failures = [];
|
|
834
|
+
for (let offset = 0; offset < page.keys.length; offset += RETENTION_SWEEP_CONCURRENCY) {
|
|
835
|
+
const candidates = page.keys.slice(offset, offset + RETENTION_SWEEP_CONCURRENCY);
|
|
836
|
+
const settled = await Promise.allSettled(candidates.map(async (entry) => {
|
|
837
|
+
const runId = runIdFromCatalogValue(entry.value);
|
|
838
|
+
const outcome = await env.WORKFLOW_DB.get(env.WORKFLOW_DB.idFromName(runId)).enforceRetention({
|
|
839
|
+
retentionMs,
|
|
840
|
+
queueShards,
|
|
841
|
+
scheduledTime
|
|
842
|
+
});
|
|
843
|
+
if (outcome.state === "missing" || outcome.state === "not-due") await env.WORKFLOW_RUN_CATALOG.get(env.WORKFLOW_RUN_CATALOG.idFromName(runCatalogShardName(runId))).deleteStaleGlobalRun(runId, entry.name, entry.value);
|
|
844
|
+
return outcome;
|
|
845
|
+
}));
|
|
846
|
+
for (const outcome of settled) if (outcome.status === "rejected") failures.push(outcome.reason);
|
|
847
|
+
else result[outcome.value.state === "not-due" ? "notDue" : outcome.value.state]++;
|
|
848
|
+
}
|
|
849
|
+
if (failures.length > 0) throw new AggregateError(failures, `world-celld retention sweep failed for ${failures.length} run(s)`);
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
739
853
|
//#region src/vendor/shared/util.ts
|
|
740
854
|
function compact(obj) {
|
|
741
855
|
const value = {};
|
|
@@ -1781,8 +1895,11 @@ var RunCatalogDO = class extends DurableObject {
|
|
|
1781
1895
|
prefix: options.prefix ?? "",
|
|
1782
1896
|
...options.reverse ? {
|
|
1783
1897
|
reverse: true,
|
|
1784
|
-
end: options.cursor
|
|
1785
|
-
} : {
|
|
1898
|
+
end: options.cursor ?? options.end
|
|
1899
|
+
} : {
|
|
1900
|
+
startAfter: options.cursor,
|
|
1901
|
+
end: options.end
|
|
1902
|
+
},
|
|
1786
1903
|
limit: limit + 1
|
|
1787
1904
|
});
|
|
1788
1905
|
const page = Array.from(entries).slice(0, limit);
|
|
@@ -1796,6 +1913,15 @@ var RunCatalogDO = class extends DurableObject {
|
|
|
1796
1913
|
cursor: listComplete ? void 0 : page.at(-1)?.[0]
|
|
1797
1914
|
};
|
|
1798
1915
|
}
|
|
1916
|
+
/** Compare-and-delete one rejected retention discovery candidate. */
|
|
1917
|
+
async deleteStaleGlobalRun(runId, key, expectedValue) {
|
|
1918
|
+
if (!key.startsWith("runall:") || !key.endsWith(`:${runId}`)) throw new Error("Stale run catalog deletion requires the exact global run key");
|
|
1919
|
+
if (JSON.parse(expectedValue).runId !== runId) throw new Error("Stale run catalog deletion requires matching run metadata");
|
|
1920
|
+
return await this.ctx.storage.transaction(async (txn) => {
|
|
1921
|
+
if (await txn.get(key) !== expectedValue) return { deleted: false };
|
|
1922
|
+
return { deleted: await txn.delete(key) };
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1799
1925
|
async expireRun(runId, keys, expiredAt) {
|
|
1800
1926
|
if (keys.length !== 2) throw new Error("Run catalog expiry requires exactly two keys");
|
|
1801
1927
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -3054,51 +3180,6 @@ async function finalizeEventPage(store, outcome, params) {
|
|
|
3054
3180
|
};
|
|
3055
3181
|
}
|
|
3056
3182
|
//#endregion
|
|
3057
|
-
//#region src/retention.ts
|
|
3058
|
-
const CLEANUP_RECORD_KEY = "retention:cleanup";
|
|
3059
|
-
const TOMBSTONE_KEY = "retention:tombstone";
|
|
3060
|
-
const HOOK_MARKER_PREFIX = "retention:hook:";
|
|
3061
|
-
const TERMINAL_CLEANUP_KEY = "terminal:cleanup";
|
|
3062
|
-
function sortableTimestamp(date) {
|
|
3063
|
-
return date.getTime().toString().padStart(13, "0");
|
|
3064
|
-
}
|
|
3065
|
-
function workflowRunIndexKey(run) {
|
|
3066
|
-
return `run:${run.workflowName}:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
3067
|
-
}
|
|
3068
|
-
function globalRunIndexKey(run) {
|
|
3069
|
-
return `runall:${sortableTimestamp(run.createdAt)}:${run.runId}`;
|
|
3070
|
-
}
|
|
3071
|
-
function hookMarkerKey(reference) {
|
|
3072
|
-
return `${HOOK_MARKER_PREFIX}${encodeURIComponent(reference.hookId)}:${encodeURIComponent(reference.token)}`;
|
|
3073
|
-
}
|
|
3074
|
-
function cleanupTombstone(record, now) {
|
|
3075
|
-
const cleanup = {
|
|
3076
|
-
...record,
|
|
3077
|
-
phase: "tombstoned",
|
|
3078
|
-
tombstonedAt: now
|
|
3079
|
-
};
|
|
3080
|
-
return {
|
|
3081
|
-
version: 1,
|
|
3082
|
-
runId: record.runId,
|
|
3083
|
-
terminalStatus: record.terminalStatus,
|
|
3084
|
-
completedAt: record.completedAt,
|
|
3085
|
-
expiredAt: record.dueAt,
|
|
3086
|
-
tombstonedAt: now,
|
|
3087
|
-
cleanup
|
|
3088
|
-
};
|
|
3089
|
-
}
|
|
3090
|
-
function cleanupFromTombstone(tombstone) {
|
|
3091
|
-
return tombstone.cleanup;
|
|
3092
|
-
}
|
|
3093
|
-
function expiredRead(tombstone) {
|
|
3094
|
-
return {
|
|
3095
|
-
ok: false,
|
|
3096
|
-
code: "RUN_EXPIRED",
|
|
3097
|
-
message: `Workflow run "${tombstone.runId}" expired at ${tombstone.expiredAt.toISOString()}`,
|
|
3098
|
-
tombstone
|
|
3099
|
-
};
|
|
3100
|
-
}
|
|
3101
|
-
//#endregion
|
|
3102
3183
|
//#region src/worker/durable-objects/WorkflowRunDO.ts
|
|
3103
3184
|
const STORAGE_BATCH_SIZE = 128;
|
|
3104
3185
|
const PAYLOAD_DELETE_BATCH = STORAGE_BATCH_SIZE;
|
|
@@ -3111,6 +3192,28 @@ const STREAM_BYTES_PER_CLEANUP_PAGE = 16777216;
|
|
|
3111
3192
|
const QUEUE_REFERENCES_PER_CLEANUP_PAGE = 64;
|
|
3112
3193
|
const CLEANUP_RETRY_MAX_MS = 36e5;
|
|
3113
3194
|
const CLEANUP_PROGRESS_KEY = "retention:progress";
|
|
3195
|
+
const RUN_QUEUE_SHARDS_KEY = "retention:queue-shards";
|
|
3196
|
+
function cleanupRecord(run, dueAt, queueShards, reason) {
|
|
3197
|
+
const terminal = isTerminalWorkflowRunStatus(run.status) ? run : void 0;
|
|
3198
|
+
return {
|
|
3199
|
+
version: 1,
|
|
3200
|
+
runId: run.runId,
|
|
3201
|
+
workflowName: run.workflowName,
|
|
3202
|
+
createdAt: run.createdAt,
|
|
3203
|
+
status: run.status,
|
|
3204
|
+
completedAt: terminal?.completedAt,
|
|
3205
|
+
terminalStatus: terminal?.status,
|
|
3206
|
+
reason,
|
|
3207
|
+
dueAt,
|
|
3208
|
+
queueShards,
|
|
3209
|
+
phase: "retained",
|
|
3210
|
+
generation: 0,
|
|
3211
|
+
attempts: 0,
|
|
3212
|
+
deletedPayloadKeys: 0,
|
|
3213
|
+
deletedStreams: 0,
|
|
3214
|
+
deletedQueueMessages: 0
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3114
3217
|
/**
|
|
3115
3218
|
* Adapt DO storage (or a transaction) to the {@link EventStore} interface
|
|
3116
3219
|
* shared with the test mocks. No casts: the methods line up structurally,
|
|
@@ -3206,6 +3309,7 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3206
3309
|
now
|
|
3207
3310
|
});
|
|
3208
3311
|
if (!outcome.ok) return outcome;
|
|
3312
|
+
if (outcome.runCreated && request.cleanup) await txn.put(RUN_QUEUE_SHARDS_KEY, request.cleanup.queueShards);
|
|
3209
3313
|
await txn.put("event_sequence", eventSequence);
|
|
3210
3314
|
await putStorageEntries(txn, (outcome.hookToIndex ? [{
|
|
3211
3315
|
hookId: outcome.hookToIndex.hookId,
|
|
@@ -3219,22 +3323,7 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3219
3323
|
...terminalRun,
|
|
3220
3324
|
expiredAt: dueAt
|
|
3221
3325
|
});
|
|
3222
|
-
const cleanup =
|
|
3223
|
-
version: 1,
|
|
3224
|
-
runId: run.runId,
|
|
3225
|
-
workflowName: run.workflowName,
|
|
3226
|
-
createdAt: run.createdAt,
|
|
3227
|
-
completedAt: run.completedAt,
|
|
3228
|
-
terminalStatus: run.status,
|
|
3229
|
-
dueAt,
|
|
3230
|
-
queueShards: request.cleanup?.queueShards ?? 1,
|
|
3231
|
-
phase: "retained",
|
|
3232
|
-
generation: 0,
|
|
3233
|
-
attempts: 0,
|
|
3234
|
-
deletedPayloadKeys: 0,
|
|
3235
|
-
deletedStreams: 0,
|
|
3236
|
-
deletedQueueMessages: 0
|
|
3237
|
-
};
|
|
3326
|
+
const cleanup = cleanupRecord(run, dueAt, request.cleanup?.queueShards ?? 1, "terminal-retention");
|
|
3238
3327
|
await txn.put("run", run);
|
|
3239
3328
|
await txn.put(CLEANUP_RECORD_KEY, cleanup);
|
|
3240
3329
|
await txn.setAlarm(dueAt);
|
|
@@ -3344,28 +3433,95 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3344
3433
|
...terminalRun,
|
|
3345
3434
|
expiredAt: dueAt
|
|
3346
3435
|
});
|
|
3347
|
-
const cleanup =
|
|
3348
|
-
version: 1,
|
|
3349
|
-
runId: run.runId,
|
|
3350
|
-
workflowName: run.workflowName,
|
|
3351
|
-
createdAt: run.createdAt,
|
|
3352
|
-
completedAt: run.completedAt,
|
|
3353
|
-
terminalStatus: run.status,
|
|
3354
|
-
dueAt,
|
|
3355
|
-
queueShards: request.queueShards,
|
|
3356
|
-
phase: "retained",
|
|
3357
|
-
generation: 0,
|
|
3358
|
-
attempts: 0,
|
|
3359
|
-
deletedPayloadKeys: 0,
|
|
3360
|
-
deletedStreams: 0,
|
|
3361
|
-
deletedQueueMessages: 0
|
|
3362
|
-
};
|
|
3436
|
+
const cleanup = cleanupRecord(run, dueAt, request.queueShards, "manual");
|
|
3363
3437
|
await txn.put("run", run);
|
|
3364
3438
|
await txn.put(CLEANUP_RECORD_KEY, cleanup);
|
|
3365
3439
|
await txn.setAlarm(values.get("terminal:cleanup") || dueAt.getTime() <= this.now() ? this.now() + 1 : dueAt);
|
|
3366
3440
|
return cleanup;
|
|
3367
3441
|
});
|
|
3368
3442
|
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Apply the fleet-wide maximum-age policy to one authoritative run.
|
|
3445
|
+
*
|
|
3446
|
+
* The catalog is only a discovery index: eligibility is rechecked from the
|
|
3447
|
+
* RunDO's own createdAt. Once eligible, the cleanup deadline immediately
|
|
3448
|
+
* becomes an authoritative write/read fence, including for active runs.
|
|
3449
|
+
*/
|
|
3450
|
+
async enforceRetention(request) {
|
|
3451
|
+
if (!Number.isSafeInteger(request.retentionMs) || request.retentionMs <= 0) throw new Error("world-celld retentionMs must be a positive safe integer");
|
|
3452
|
+
if (!Number.isSafeInteger(request.queueShards) || request.queueShards <= 0) throw new Error("world-celld retention queueShards must be a positive safe integer");
|
|
3453
|
+
if (!Number.isSafeInteger(request.scheduledTime) || request.scheduledTime < 0) throw new Error("world-celld retention scheduledTime must be a non-negative safe integer");
|
|
3454
|
+
const result = await this.ctx.storage.transaction(async (txn) => {
|
|
3455
|
+
const values = await txn.get([
|
|
3456
|
+
CLEANUP_RECORD_KEY,
|
|
3457
|
+
TOMBSTONE_KEY,
|
|
3458
|
+
RUN_QUEUE_SHARDS_KEY,
|
|
3459
|
+
"run"
|
|
3460
|
+
]);
|
|
3461
|
+
const tombstone = values.get(TOMBSTONE_KEY);
|
|
3462
|
+
if (tombstone) return {
|
|
3463
|
+
state: "expired",
|
|
3464
|
+
cleanup: cleanupFromTombstone(tombstone)
|
|
3465
|
+
};
|
|
3466
|
+
const run = values.get("run");
|
|
3467
|
+
if (!run) return {
|
|
3468
|
+
state: "missing",
|
|
3469
|
+
cleanup: null
|
|
3470
|
+
};
|
|
3471
|
+
const dueAtMs = run.createdAt.getTime() + request.retentionMs;
|
|
3472
|
+
if (!Number.isSafeInteger(dueAtMs) || dueAtMs > 864e13) throw new Error(`world-celld retention deadline is out of range for ${run.runId}`);
|
|
3473
|
+
if (dueAtMs > request.scheduledTime) return {
|
|
3474
|
+
state: "not-due",
|
|
3475
|
+
cleanup: null
|
|
3476
|
+
};
|
|
3477
|
+
const dueAt = new Date(dueAtMs);
|
|
3478
|
+
const existing = values.get(CLEANUP_RECORD_KEY);
|
|
3479
|
+
const storedQueueShards = values.get(RUN_QUEUE_SHARDS_KEY);
|
|
3480
|
+
const queueShards = Number.isSafeInteger(storedQueueShards) && storedQueueShards > 0 ? storedQueueShards : request.queueShards;
|
|
3481
|
+
let cleanup = existing;
|
|
3482
|
+
if (!cleanup) {
|
|
3483
|
+
cleanup = cleanupRecord(run, dueAt, queueShards, "maximum-age");
|
|
3484
|
+
await txn.put("run", WorkflowRunSchema.parse({
|
|
3485
|
+
...run,
|
|
3486
|
+
expiredAt: dueAt
|
|
3487
|
+
}));
|
|
3488
|
+
await txn.put(CLEANUP_RECORD_KEY, cleanup);
|
|
3489
|
+
} else if (cleanup.phase === "retained" && dueAtMs < cleanup.dueAt.getTime()) {
|
|
3490
|
+
const terminal = isTerminalWorkflowRunStatus(run.status) ? run : void 0;
|
|
3491
|
+
cleanup = {
|
|
3492
|
+
...cleanup,
|
|
3493
|
+
status: run.status,
|
|
3494
|
+
completedAt: terminal?.completedAt,
|
|
3495
|
+
terminalStatus: terminal?.status,
|
|
3496
|
+
reason: "maximum-age",
|
|
3497
|
+
dueAt,
|
|
3498
|
+
generation: cleanup.generation + 1
|
|
3499
|
+
};
|
|
3500
|
+
await txn.put("run", WorkflowRunSchema.parse({
|
|
3501
|
+
...run,
|
|
3502
|
+
expiredAt: dueAt
|
|
3503
|
+
}));
|
|
3504
|
+
await txn.put(CLEANUP_RECORD_KEY, cleanup);
|
|
3505
|
+
}
|
|
3506
|
+
await txn.setAlarm(this.now() + 1);
|
|
3507
|
+
return {
|
|
3508
|
+
state: cleanup.dueAt.getTime() <= this.now() ? "expired" : "scheduled",
|
|
3509
|
+
cleanup
|
|
3510
|
+
};
|
|
3511
|
+
});
|
|
3512
|
+
if (result.state === "scheduled" || result.state === "expired") {
|
|
3513
|
+
for (let page = 0; page < 2; page++) {
|
|
3514
|
+
const cleanup = await this.ctx.storage.get(CLEANUP_RECORD_KEY);
|
|
3515
|
+
if (cleanup?.phase !== "retained" && cleanup?.phase !== "index") break;
|
|
3516
|
+
await this.executeScheduledCleanupPage();
|
|
3517
|
+
}
|
|
3518
|
+
return {
|
|
3519
|
+
...result,
|
|
3520
|
+
cleanup: await this.getCleanupStatus() ?? result.cleanup
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
return result;
|
|
3524
|
+
}
|
|
3369
3525
|
async cleanupNow(request) {
|
|
3370
3526
|
if (!await this.ctx.storage.get("retention:cleanup")) await this.scheduleCleanup({
|
|
3371
3527
|
...request,
|
|
@@ -3429,7 +3585,17 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3429
3585
|
});
|
|
3430
3586
|
}
|
|
3431
3587
|
async executeScheduledCleanupPage() {
|
|
3432
|
-
const
|
|
3588
|
+
const values = await this.ctx.storage.get([CLEANUP_RECORD_KEY, TERMINAL_CLEANUP_KEY]);
|
|
3589
|
+
const cleanup = values.get(CLEANUP_RECORD_KEY);
|
|
3590
|
+
const terminalCleanup = values.get(TERMINAL_CLEANUP_KEY);
|
|
3591
|
+
if (cleanup && cleanup.phase !== "tombstoned" && cleanup.dueAt.getTime() <= this.now()) {
|
|
3592
|
+
try {
|
|
3593
|
+
await this.executeCleanup(cleanup);
|
|
3594
|
+
} catch (error) {
|
|
3595
|
+
await this.recordCleanupFailure(error, cleanup);
|
|
3596
|
+
}
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3433
3599
|
if (terminalCleanup) {
|
|
3434
3600
|
try {
|
|
3435
3601
|
await this.executeTerminalCleanup(terminalCleanup);
|
|
@@ -3438,7 +3604,6 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3438
3604
|
}
|
|
3439
3605
|
return;
|
|
3440
3606
|
}
|
|
3441
|
-
const cleanup = await this.ctx.storage.get(CLEANUP_RECORD_KEY);
|
|
3442
3607
|
if (!cleanup || cleanup.phase === "tombstoned") {
|
|
3443
3608
|
await this.ctx.storage.deleteAlarm();
|
|
3444
3609
|
return;
|
|
@@ -3794,9 +3959,19 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3794
3959
|
* default fetch router. This module (and everything it imports) must stay
|
|
3795
3960
|
* free of Node built-ins — it runs inside celld's workerd runtime.
|
|
3796
3961
|
*/
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3962
|
+
async function scheduled(controller, env) {
|
|
3963
|
+
const result = await runRetentionSweep(controller.scheduledTime, env);
|
|
3964
|
+
if (result.scanned > 0) console.info("world-celld retention sweep", {
|
|
3965
|
+
cron: controller.cron,
|
|
3966
|
+
...result
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3969
|
+
var worker_default = {
|
|
3970
|
+
async fetch(request, env) {
|
|
3971
|
+
return createRouter(env)(request);
|
|
3972
|
+
},
|
|
3973
|
+
scheduled
|
|
3974
|
+
};
|
|
3800
3975
|
//#endregion
|
|
3801
3976
|
export { HookIdDO, HookTokenDO, QueueDO, RunCatalogDO, StreamDO, WorkflowRunDO, createRouter, worker_default as default };
|
|
3802
3977
|
|