@ewhauser/world-celld 0.8.0 → 0.8.1
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 +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -37
- package/dist/index.js.map +1 -1
- package/dist/queue-consumer.d.ts +1 -1
- package/dist/queue-consumer.js +1 -1
- package/dist/{queue-delivery-BdeNUbRN.d.ts → queue-delivery-wvPp6_Hh.d.ts} +2 -1
- package/dist/{queue-delivery-BdeNUbRN.d.ts.map → queue-delivery-wvPp6_Hh.d.ts.map} +1 -1
- package/dist/{queue-protocol-DKYt8qZJ.js → queue-protocol-C1zbXwa_.js} +5 -2
- package/dist/queue-protocol-C1zbXwa_.js.map +1 -0
- package/dist/{run-list-C987PkJ2.js → run-list-DGsdv6Jf.js} +5 -2
- package/dist/{run-list-C987PkJ2.js.map → run-list-DGsdv6Jf.js.map} +1 -1
- package/dist/testing.js +405 -180
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +33 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +408 -183
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/queue-protocol-DKYt8qZJ.js.map +0 -1
package/dist/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { S as strictIntegerSetting, _ as QUEUE_CLAIM_STALE_MS, a as isInlineRunBody, b as isRecord$1, c as queueOrphanName, d as validateNativeQueueEnvelope, f as validateNativeQueueSendOptions, g as MAX_RUN_INDEX_PUBLICATION_LIFETIME_MS, h as LIFECYCLE_COMPACTION_RETRY_MS, i as QUEUE_RESERVATION_GRACE_MS, l as queuePayloadObjectKey, m as HOOK_CLAIM_LEASE_MS, n as QUEUE_PAYLOAD_REGISTRY_PREFIX, r as QUEUE_PUBLICATION_LEASE_MS, s as queueClaimName, t as NATIVE_QUEUE_MAX_MESSAGE_BYTES, u as queuePayloadRegistryKey, v as isNonNegativeSafeInteger, x as queueDelayDeadline, y as isPositiveSafeInteger } from "./queue-protocol-C1zbXwa_.js";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
3
3
|
import { CreateEventSchema, EventSchema, HookSchema, SPEC_VERSION_CURRENT, StepSchema, WaitSchema, WorkflowRunSchema, applyAttributeChanges, eventIdToSlot, getMaxEventsPerRun, isChildEntityCreationEvent, isTerminalStepStatus, isTerminalWorkflowRunStatus, slotToEventId, validateAttributeChanges } from "@workflow/world";
|
|
4
4
|
//#region src/worker/auth.ts
|
|
@@ -56,6 +56,11 @@ function cellQueueClaim(cell) {
|
|
|
56
56
|
}),
|
|
57
57
|
hold: (params) => cell.holdInflight(params),
|
|
58
58
|
release: (messageId) => cell.releaseInflight(messageId),
|
|
59
|
+
abandonReservation: (messageId) => cell.abandonQueueMessageReservation(messageId),
|
|
60
|
+
confirmPublication: (messageId, expiresAt) => cell.confirmQueueMessagePublication({
|
|
61
|
+
messageId,
|
|
62
|
+
expiresAt
|
|
63
|
+
}),
|
|
59
64
|
complete: (messageId) => cell.completeQueueMessage(messageId)
|
|
60
65
|
};
|
|
61
66
|
}
|
|
@@ -80,6 +85,15 @@ function runQueueClaim(run, claimName) {
|
|
|
80
85
|
claimName,
|
|
81
86
|
messageId
|
|
82
87
|
}),
|
|
88
|
+
abandonReservation: (messageId) => run.abandonRunQueueMessageReservation({
|
|
89
|
+
claimName,
|
|
90
|
+
messageId
|
|
91
|
+
}),
|
|
92
|
+
confirmPublication: (messageId, expiresAt) => run.confirmRunQueueMessagePublication({
|
|
93
|
+
claimName,
|
|
94
|
+
messageId,
|
|
95
|
+
expiresAt
|
|
96
|
+
}),
|
|
83
97
|
complete: (messageId) => run.completeRunQueueMessage({
|
|
84
98
|
claimName,
|
|
85
99
|
messageId
|
|
@@ -96,7 +110,7 @@ function deliveryQueueClaim(envelope, runStub) {
|
|
|
96
110
|
//#region src/worker/queue-delivery.ts
|
|
97
111
|
/** Internal delivery contract. No Request/Response objects cross the RPC boundary. */
|
|
98
112
|
const MAX_QUEUE_DELIVERY_RESPONSE_BYTES = 65536;
|
|
99
|
-
const QUEUE_ORPHAN_GRACE_MS
|
|
113
|
+
const QUEUE_ORPHAN_GRACE_MS = 432e6;
|
|
100
114
|
const PERMANENT_QUEUE_STATUSES = /* @__PURE__ */ new Set([
|
|
101
115
|
404,
|
|
102
116
|
409,
|
|
@@ -210,13 +224,13 @@ async function deliverQueueMessage(env, secret, input, attempt) {
|
|
|
210
224
|
messageId: envelope.messageId,
|
|
211
225
|
runId: envelope.runId,
|
|
212
226
|
key: envelope.payloadKey,
|
|
213
|
-
expiresAt: notBefore + QUEUE_ORPHAN_GRACE_MS
|
|
227
|
+
expiresAt: notBefore + QUEUE_ORPHAN_GRACE_MS
|
|
214
228
|
});
|
|
215
229
|
if (claim) await claim.hold({
|
|
216
230
|
messageId: envelope.messageId,
|
|
217
231
|
retryAt: notBefore,
|
|
218
232
|
expiresAt: notBefore + QUEUE_CLAIM_STALE_MS,
|
|
219
|
-
reservationExpiresAt: notBefore + QUEUE_ORPHAN_GRACE_MS
|
|
233
|
+
reservationExpiresAt: notBefore + QUEUE_ORPHAN_GRACE_MS
|
|
220
234
|
});
|
|
221
235
|
return {
|
|
222
236
|
kind: "suspend",
|
|
@@ -427,8 +441,8 @@ function readStringProp(value, key) {
|
|
|
427
441
|
}
|
|
428
442
|
}
|
|
429
443
|
/**
|
|
430
|
-
* Map a failure eventData.error (which may be a string, an Error-shaped
|
|
431
|
-
* object, or anything else) to the structured error stored on
|
|
444
|
+
* Map a run failure eventData.error (which may be a string, an Error-shaped
|
|
445
|
+
* object, or anything else) to the structured error stored on runs.
|
|
432
446
|
* Matches upstream: string errors keep their text, errorCode is preserved.
|
|
433
447
|
*/
|
|
434
448
|
function toStructuredError(error, opts) {
|
|
@@ -904,14 +918,17 @@ async function applyEvent(store, ctx) {
|
|
|
904
918
|
}
|
|
905
919
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
906
920
|
if (step.retryAfter && step.retryAfter.getTime() > now.getTime()) return failure("TOO_EARLY", `Cannot start step "${data.correlationId}": retryAfter timestamp has not been reached yet`, { retryAfterSeconds: Math.ceil((step.retryAfter.getTime() - now.getTime()) / 1e3) });
|
|
907
|
-
const updated = StepSchema.parse(
|
|
908
|
-
...
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
921
|
+
const updated = StepSchema.parse({
|
|
922
|
+
...compact({
|
|
923
|
+
...step,
|
|
924
|
+
status: "running",
|
|
925
|
+
startedAt: step.startedAt ?? now,
|
|
926
|
+
attempt: step.attempt + 1,
|
|
927
|
+
retryAfter: void 0,
|
|
928
|
+
updatedAt: now
|
|
929
|
+
}),
|
|
930
|
+
error: step.error
|
|
931
|
+
});
|
|
915
932
|
const event = buildEvent({ ...data });
|
|
916
933
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
917
934
|
await putEvent(event);
|
|
@@ -926,13 +943,16 @@ async function applyEvent(store, ctx) {
|
|
|
926
943
|
case "step_completed": {
|
|
927
944
|
const step = validatedStep;
|
|
928
945
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
929
|
-
const updated = StepSchema.parse(
|
|
930
|
-
...
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
946
|
+
const updated = StepSchema.parse({
|
|
947
|
+
...compact({
|
|
948
|
+
...step,
|
|
949
|
+
status: "completed",
|
|
950
|
+
output: data.eventData.result,
|
|
951
|
+
completedAt: now,
|
|
952
|
+
updatedAt: now
|
|
953
|
+
}),
|
|
954
|
+
error: step.error
|
|
955
|
+
});
|
|
936
956
|
const event = buildEvent({ ...data });
|
|
937
957
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
938
958
|
await putEvent(event);
|
|
@@ -946,13 +966,13 @@ async function applyEvent(store, ctx) {
|
|
|
946
966
|
case "step_failed": {
|
|
947
967
|
const step = validatedStep;
|
|
948
968
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
949
|
-
const updated = StepSchema.parse(
|
|
969
|
+
const updated = StepSchema.parse({
|
|
950
970
|
...step,
|
|
951
971
|
status: "failed",
|
|
952
|
-
error:
|
|
972
|
+
error: data.eventData.error,
|
|
953
973
|
completedAt: now,
|
|
954
974
|
updatedAt: now
|
|
955
|
-
})
|
|
975
|
+
});
|
|
956
976
|
const event = buildEvent({ ...data });
|
|
957
977
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
958
978
|
await putEvent(event);
|
|
@@ -966,13 +986,13 @@ async function applyEvent(store, ctx) {
|
|
|
966
986
|
case "step_retrying": {
|
|
967
987
|
const step = validatedStep;
|
|
968
988
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
969
|
-
const updated = StepSchema.parse(
|
|
989
|
+
const updated = StepSchema.parse({
|
|
970
990
|
...step,
|
|
971
991
|
status: "pending",
|
|
972
|
-
error:
|
|
992
|
+
error: data.eventData.error,
|
|
973
993
|
retryAfter: data.eventData.retryAfter ? new Date(data.eventData.retryAfter) : void 0,
|
|
974
994
|
updatedAt: now
|
|
975
|
-
})
|
|
995
|
+
});
|
|
976
996
|
const event = buildEvent({ ...data });
|
|
977
997
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
978
998
|
await putEvent(event);
|
|
@@ -1008,7 +1028,7 @@ async function applyEvent(store, ctx) {
|
|
|
1008
1028
|
}
|
|
1009
1029
|
if (await store.get(markerKey) !== void 0) {
|
|
1010
1030
|
const existingHook = await store.get(hookKey);
|
|
1011
|
-
if (existingHook && !holder) return {
|
|
1031
|
+
if (existingHook && !holder && existingHook.token === token) return {
|
|
1012
1032
|
ok: true,
|
|
1013
1033
|
hook: existingHook,
|
|
1014
1034
|
hookToIndex: existingHook,
|
|
@@ -1977,7 +1997,8 @@ const BINDINGS = {
|
|
|
1977
1997
|
"scheduleCleanup",
|
|
1978
1998
|
"cleanupNow",
|
|
1979
1999
|
"rearmCleanup",
|
|
1980
|
-
"resolveHookTokenClaim"
|
|
2000
|
+
"resolveHookTokenClaim",
|
|
2001
|
+
"resolveExpiredHookClaim"
|
|
1981
2002
|
])
|
|
1982
2003
|
},
|
|
1983
2004
|
streams: {
|
|
@@ -2004,7 +2025,6 @@ const BINDINGS = {
|
|
|
2004
2025
|
/** Request body cap: oversize payloads get a clear 413 instead of an OOM. */
|
|
2005
2026
|
const MAX_BODY_BYTES = 33554432;
|
|
2006
2027
|
const MAX_STREAM_WRITE_BODY_BYTES = 8391680;
|
|
2007
|
-
const QUEUE_ORPHAN_GRACE_MS = 432e6;
|
|
2008
2028
|
function errorResponse(status, name, message) {
|
|
2009
2029
|
return Response.json({ error: {
|
|
2010
2030
|
name,
|
|
@@ -2157,6 +2177,7 @@ function createRouter(env) {
|
|
|
2157
2177
|
} catch (error) {
|
|
2158
2178
|
return errorResponse(400, "BadRequest", error.message);
|
|
2159
2179
|
}
|
|
2180
|
+
let enqueueClaim;
|
|
2160
2181
|
try {
|
|
2161
2182
|
const namespace = env.WORKFLOW_DB;
|
|
2162
2183
|
const payloadStore = env.WORKFLOW_QUEUE_PAYLOADS;
|
|
@@ -2173,26 +2194,26 @@ function createRouter(env) {
|
|
|
2173
2194
|
});
|
|
2174
2195
|
if (new TextEncoder().encode(sized).byteLength > 128e3) return errorResponse(413, "PayloadTooLarge", `native queue envelope exceeds ${NATIVE_QUEUE_MAX_MESSAGE_BYTES} bytes`);
|
|
2175
2196
|
const now = Date.now();
|
|
2176
|
-
const reservationExpiresAt = Math.max(now, envelope.notBefore ?? now) +
|
|
2197
|
+
const reservationExpiresAt = Math.max(now, envelope.notBefore ?? now) + QUEUE_RESERVATION_GRACE_MS;
|
|
2177
2198
|
const run = envelope.runId ? namespace.get(namespace.idFromName(envelope.runId)) : void 0;
|
|
2178
|
-
let claim;
|
|
2179
2199
|
let admission;
|
|
2180
2200
|
if (envelope.idempotencyKey) {
|
|
2181
2201
|
const claimName = queueClaimName(envelope.queueName, envelope.idempotencyKey);
|
|
2182
2202
|
let scope = run && envelope.runId ? runClaimScopes.get(envelope.runId) : "cell";
|
|
2183
2203
|
let reservation;
|
|
2184
2204
|
if (run && envelope.runId && scope !== "cell") {
|
|
2205
|
+
enqueueClaim = runQueueClaim(run, claimName);
|
|
2185
2206
|
const reserved = await run.reserveRunQueueMessage({
|
|
2186
2207
|
claimName,
|
|
2187
2208
|
messageId: envelope.messageId,
|
|
2188
|
-
expiresAt:
|
|
2209
|
+
expiresAt: now + QUEUE_PUBLICATION_LEASE_MS,
|
|
2210
|
+
publicationPending: true
|
|
2189
2211
|
});
|
|
2190
2212
|
if (!reserved.ok) return errorResponse(410, "RunExpiredError", reserved.message);
|
|
2191
2213
|
scope = reserved.scope;
|
|
2192
2214
|
rememberRunClaimScope(envelope.runId, reserved.scope);
|
|
2193
2215
|
if (reserved.scope === "run") {
|
|
2194
2216
|
reservation = reserved;
|
|
2195
|
-
claim = runQueueClaim(run, claimName);
|
|
2196
2217
|
brokerEnvelope = {
|
|
2197
2218
|
...brokerEnvelope,
|
|
2198
2219
|
claimScope: "run"
|
|
@@ -2204,27 +2225,34 @@ function createRouter(env) {
|
|
|
2204
2225
|
}
|
|
2205
2226
|
if (!reservation) {
|
|
2206
2227
|
const cell = namespace.get(namespace.idFromName(claimName));
|
|
2228
|
+
enqueueClaim = cellQueueClaim(cell);
|
|
2207
2229
|
reservation = await cell.reserveQueueMessage({
|
|
2208
2230
|
messageId: envelope.messageId,
|
|
2209
|
-
expiresAt:
|
|
2231
|
+
expiresAt: now + QUEUE_PUBLICATION_LEASE_MS,
|
|
2232
|
+
publicationPending: true
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
if (!reservation.admitted) {
|
|
2236
|
+
if (reservation.publicationPending) return errorResponse(503, "QueuePublicationPending", "the reserved Queue message has not been confirmed by the broker; retry publication");
|
|
2237
|
+
return new Response(rpcStringify({ messageId: reservation.messageId }), {
|
|
2238
|
+
status: 200,
|
|
2239
|
+
headers: { "content-type": "application/json" }
|
|
2210
2240
|
});
|
|
2211
|
-
claim = cellQueueClaim(cell);
|
|
2212
2241
|
}
|
|
2213
|
-
if (!reservation.admitted) return new Response(rpcStringify({ messageId: reservation.messageId }), {
|
|
2214
|
-
status: 200,
|
|
2215
|
-
headers: { "content-type": "application/json" }
|
|
2216
|
-
});
|
|
2217
2242
|
} else if (inlineRunBody && run) admission = run.getQueueAdmission();
|
|
2218
2243
|
const encoded = JSON.stringify(brokerEnvelope);
|
|
2219
2244
|
if (admission) {
|
|
2220
2245
|
const admitted = await admission;
|
|
2221
2246
|
if (!admitted.ok) {
|
|
2222
|
-
await
|
|
2247
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
2223
2248
|
return errorResponse(410, "RunExpiredError", admitted.message);
|
|
2224
2249
|
}
|
|
2225
2250
|
}
|
|
2226
2251
|
if (run && envelope.runId && payloadKey) {
|
|
2227
|
-
if (!payloadStore)
|
|
2252
|
+
if (!payloadStore) {
|
|
2253
|
+
await enqueueClaim?.abandonReservation(envelope.messageId);
|
|
2254
|
+
return errorResponse(500, "WorldMisconfigured", "missing binding: WORKFLOW_QUEUE_PAYLOADS");
|
|
2255
|
+
}
|
|
2228
2256
|
const orphanExpiresAt = reservationExpiresAt;
|
|
2229
2257
|
const registered = await run.registerQueuePayload({
|
|
2230
2258
|
messageId: envelope.messageId,
|
|
@@ -2232,7 +2260,7 @@ function createRouter(env) {
|
|
|
2232
2260
|
orphanExpiresAt
|
|
2233
2261
|
});
|
|
2234
2262
|
if (!registered.ok) {
|
|
2235
|
-
await
|
|
2263
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
2236
2264
|
return errorResponse(410, "RunExpiredError", registered.message);
|
|
2237
2265
|
}
|
|
2238
2266
|
const orphan = namespace.get(namespace.idFromName(queueOrphanName(envelope.messageId)));
|
|
@@ -2245,30 +2273,18 @@ function createRouter(env) {
|
|
|
2245
2273
|
});
|
|
2246
2274
|
} catch (error) {
|
|
2247
2275
|
await run.unregisterQueuePayload(envelope.messageId);
|
|
2248
|
-
await claim?.complete(envelope.messageId);
|
|
2249
|
-
throw error;
|
|
2250
|
-
}
|
|
2251
|
-
try {
|
|
2252
|
-
await payloadStore.write(payloadKey, envelope.body, {
|
|
2253
|
-
runId: envelope.runId,
|
|
2254
|
-
messageId: envelope.messageId
|
|
2255
|
-
});
|
|
2256
|
-
} catch (error) {
|
|
2257
|
-
await claim?.complete(envelope.messageId);
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
2260
|
-
let finalized;
|
|
2261
|
-
try {
|
|
2262
|
-
finalized = await run.finalizeQueuePayload(envelope.messageId);
|
|
2263
|
-
} catch (error) {
|
|
2264
|
-
await claim?.complete(envelope.messageId);
|
|
2265
2276
|
throw error;
|
|
2266
2277
|
}
|
|
2278
|
+
await payloadStore.write(payloadKey, envelope.body, {
|
|
2279
|
+
runId: envelope.runId,
|
|
2280
|
+
messageId: envelope.messageId
|
|
2281
|
+
});
|
|
2282
|
+
const finalized = await run.finalizeQueuePayload(envelope.messageId);
|
|
2267
2283
|
if (!finalized.ok) {
|
|
2268
2284
|
await payloadStore.delete(payloadKey);
|
|
2269
2285
|
await run.unregisterQueuePayload(envelope.messageId);
|
|
2270
2286
|
await orphan.cancelQueuePayloadOrphan(envelope.messageId);
|
|
2271
|
-
await
|
|
2287
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
2272
2288
|
return errorResponse(410, "RunExpiredError", finalized.message);
|
|
2273
2289
|
}
|
|
2274
2290
|
}
|
|
@@ -2276,11 +2292,17 @@ function createRouter(env) {
|
|
|
2276
2292
|
contentType: "text",
|
|
2277
2293
|
...options
|
|
2278
2294
|
});
|
|
2295
|
+
await enqueueClaim?.confirmPublication(envelope.messageId, reservationExpiresAt);
|
|
2279
2296
|
return new Response(rpcStringify({ messageId: envelope.messageId }), {
|
|
2280
2297
|
status: 200,
|
|
2281
2298
|
headers: { "content-type": "application/json" }
|
|
2282
2299
|
});
|
|
2283
2300
|
} catch (error) {
|
|
2301
|
+
try {
|
|
2302
|
+
await enqueueClaim?.abandonReservation(envelope.messageId);
|
|
2303
|
+
} catch (cleanupError) {
|
|
2304
|
+
console.error("world-celld could not abandon a failed Queue reservation", cleanupError);
|
|
2305
|
+
}
|
|
2284
2306
|
const err = error;
|
|
2285
2307
|
return errorResponse(500, err.name ?? "Error", err.message ?? String(error));
|
|
2286
2308
|
}
|
|
@@ -2651,6 +2673,16 @@ async function runIsActive(env, runId) {
|
|
|
2651
2673
|
if (!namespace) throw new Error("world-celld hook index missing WORKFLOW_DB binding");
|
|
2652
2674
|
return await namespace.get(namespace.idFromName(runId)).getLifecycleStatus() === "active";
|
|
2653
2675
|
}
|
|
2676
|
+
/** Resolve a stale shard reservation in its authoritative event writer. */
|
|
2677
|
+
async function resolveExpiredHookClaim(env, owner, claimId, token) {
|
|
2678
|
+
const namespace = env.WORKFLOW_DB;
|
|
2679
|
+
if (!namespace) throw new Error("world-celld hook index missing WORKFLOW_DB binding");
|
|
2680
|
+
return await namespace.get(namespace.idFromName(owner.runId)).resolveExpiredHookClaim({
|
|
2681
|
+
hookId: owner.hookId,
|
|
2682
|
+
claimId,
|
|
2683
|
+
token
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2654
2686
|
//#endregion
|
|
2655
2687
|
//#region src/worker/durable-objects/HookIdDO.ts
|
|
2656
2688
|
const MAX_RELEASE_BATCH$1 = 64;
|
|
@@ -2679,52 +2711,69 @@ var HookIdDO = class extends DurableObject {
|
|
|
2679
2711
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
2680
2712
|
}
|
|
2681
2713
|
async reserve(hookId, owner, proposedClaimId) {
|
|
2682
|
-
|
|
2683
|
-
const
|
|
2684
|
-
const
|
|
2685
|
-
const
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
existing =
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2714
|
+
for (;;) {
|
|
2715
|
+
const observed = await this.ctx.storage.get(claimKey$1(hookId));
|
|
2716
|
+
const expired = observed && observed.expiresAt <= this.now() ? observed : void 0;
|
|
2717
|
+
const committed = expired ? await resolveExpiredHookClaim(this.env, expired.owner, expired.claimId) : void 0;
|
|
2718
|
+
const result = await this.ctx.storage.transaction(async (txn) => {
|
|
2719
|
+
const record = recordKey$1(hookId);
|
|
2720
|
+
const claim = claimKey$1(hookId);
|
|
2721
|
+
const values = await txn.get([record, claim]);
|
|
2722
|
+
const indexed = values.get(record);
|
|
2723
|
+
if (indexed !== void 0) return {
|
|
2724
|
+
claimed: false,
|
|
2725
|
+
holder: ownerFromSerializedHook(indexed)
|
|
2726
|
+
};
|
|
2727
|
+
let existing = values.get(claim);
|
|
2728
|
+
const now = this.now();
|
|
2729
|
+
if (existing && existing.expiresAt <= now) {
|
|
2730
|
+
if (existing.claimId !== expired?.claimId || existing.expiresAt !== expired.expiresAt) return null;
|
|
2731
|
+
if (committed) {
|
|
2732
|
+
await this.renewClaim(txn, hookId, existing, now);
|
|
2733
|
+
return sameOwner(existing.owner, owner) ? {
|
|
2734
|
+
claimed: true,
|
|
2735
|
+
claimId: existing.claimId
|
|
2736
|
+
} : {
|
|
2737
|
+
claimed: false,
|
|
2738
|
+
holder: existing.owner
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
await txn.delete([claim, existing.deadlineKey]);
|
|
2742
|
+
existing = void 0;
|
|
2743
|
+
}
|
|
2744
|
+
if (existing !== void 0 && !sameOwner(existing.owner, owner)) return {
|
|
2745
|
+
claimed: false,
|
|
2746
|
+
holder: existing.owner
|
|
2747
|
+
};
|
|
2748
|
+
if (existing === void 0 || existing.claimId !== proposedClaimId) {
|
|
2749
|
+
if (existing) await txn.delete(existing.deadlineKey);
|
|
2750
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2751
|
+
const deadlineKey = claimDeadlineKey$1(hookId, expiresAt);
|
|
2752
|
+
await txn.put(claim, {
|
|
2753
|
+
owner,
|
|
2754
|
+
claimId: proposedClaimId,
|
|
2755
|
+
reservedAt: now,
|
|
2756
|
+
expiresAt,
|
|
2757
|
+
deadlineKey
|
|
2758
|
+
});
|
|
2759
|
+
await txn.put(deadlineKey, {
|
|
2760
|
+
hookId,
|
|
2761
|
+
claimId: proposedClaimId,
|
|
2762
|
+
expiresAt
|
|
2763
|
+
});
|
|
2764
|
+
await this.armAtMost(txn, expiresAt);
|
|
2765
|
+
return {
|
|
2766
|
+
claimed: true,
|
|
2767
|
+
claimId: proposedClaimId
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2718
2770
|
return {
|
|
2719
2771
|
claimed: true,
|
|
2720
|
-
claimId:
|
|
2772
|
+
claimId: existing.claimId
|
|
2721
2773
|
};
|
|
2722
|
-
}
|
|
2723
|
-
return
|
|
2724
|
-
|
|
2725
|
-
claimId: existing.claimId
|
|
2726
|
-
};
|
|
2727
|
-
});
|
|
2774
|
+
});
|
|
2775
|
+
if (result) return result;
|
|
2776
|
+
}
|
|
2728
2777
|
}
|
|
2729
2778
|
async publish(hookId, serializedHook, owner, claimId) {
|
|
2730
2779
|
if (!await runIsActive(this.env, owner.runId)) {
|
|
@@ -2741,10 +2790,7 @@ var HookIdDO = class extends DurableObject {
|
|
|
2741
2790
|
const indexed = values.get(record);
|
|
2742
2791
|
if (indexed !== void 0 && !sameOwner(ownerFromSerializedHook(indexed), owner)) throw new Error(`Hook id ${hookId} is owned by another hook`);
|
|
2743
2792
|
const existingClaim = values.get(claim);
|
|
2744
|
-
if (existingClaim && existingClaim.expiresAt <= this.now()) {
|
|
2745
|
-
await txn.delete([claim, existingClaim.deadlineKey]);
|
|
2746
|
-
return { stored: false };
|
|
2747
|
-
}
|
|
2793
|
+
if (existingClaim && existingClaim.expiresAt <= this.now()) throw new Error(`Hook id ${hookId} reservation expired`);
|
|
2748
2794
|
if (existingClaim !== void 0 && (!sameOwner(existingClaim.owner, owner) || existingClaim.claimId !== claimId)) throw new Error(`Hook id ${hookId} is reserved by another hook`);
|
|
2749
2795
|
if (indexed === void 0 && existingClaim === void 0) throw new Error(`Hook id ${hookId} has no active reservation`);
|
|
2750
2796
|
await txn.put(record, serializedHook);
|
|
@@ -2794,21 +2840,51 @@ var HookIdDO = class extends DurableObject {
|
|
|
2794
2840
|
const current = await storage.getAlarm();
|
|
2795
2841
|
if (current === null || at < current) await storage.setAlarm(at);
|
|
2796
2842
|
}
|
|
2843
|
+
async renewClaim(txn, hookId, claim, now) {
|
|
2844
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2845
|
+
const deadlineKey = claimDeadlineKey$1(hookId, expiresAt);
|
|
2846
|
+
await txn.delete(claim.deadlineKey);
|
|
2847
|
+
await txn.put(claimKey$1(hookId), {
|
|
2848
|
+
...claim,
|
|
2849
|
+
expiresAt,
|
|
2850
|
+
deadlineKey
|
|
2851
|
+
});
|
|
2852
|
+
await txn.put(deadlineKey, {
|
|
2853
|
+
hookId,
|
|
2854
|
+
claimId: claim.claimId,
|
|
2855
|
+
expiresAt
|
|
2856
|
+
});
|
|
2857
|
+
await this.armAtMost(txn, expiresAt);
|
|
2858
|
+
}
|
|
2797
2859
|
async compactClaims() {
|
|
2798
2860
|
const now = this.now();
|
|
2861
|
+
const due = await this.ctx.storage.list({
|
|
2862
|
+
prefix: CLAIM_DEADLINE_PREFIX$1,
|
|
2863
|
+
end: `${CLAIM_DEADLINE_PREFIX$1}${pad$2(now + 1)}`,
|
|
2864
|
+
limit: 129
|
|
2865
|
+
});
|
|
2866
|
+
const page = Array.from(due).slice(0, 128);
|
|
2867
|
+
const observed = await this.ctx.storage.get(page.map(([, item]) => claimKey$1(item.hookId)));
|
|
2868
|
+
const committed = /* @__PURE__ */ new Map();
|
|
2869
|
+
await Promise.all(page.map(async ([, item]) => {
|
|
2870
|
+
const claim = observed.get(claimKey$1(item.hookId));
|
|
2871
|
+
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
2872
|
+
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId);
|
|
2873
|
+
committed.set(claimKey$1(item.hookId), hook !== null);
|
|
2874
|
+
}));
|
|
2799
2875
|
await this.ctx.storage.transaction(async (txn) => {
|
|
2800
|
-
const due = await txn.list({
|
|
2801
|
-
prefix: CLAIM_DEADLINE_PREFIX$1,
|
|
2802
|
-
end: `${CLAIM_DEADLINE_PREFIX$1}${pad$2(now + 1)}`,
|
|
2803
|
-
limit: 129
|
|
2804
|
-
});
|
|
2805
|
-
const page = Array.from(due).slice(0, 128);
|
|
2806
2876
|
const claims = await txn.get(page.map(([, item]) => claimKey$1(item.hookId)));
|
|
2807
|
-
const deletes =
|
|
2877
|
+
const deletes = [];
|
|
2878
|
+
for (const [deadline, item] of page) {
|
|
2808
2879
|
const key = claimKey$1(item.hookId);
|
|
2809
2880
|
const claim = claims.get(key);
|
|
2810
|
-
|
|
2811
|
-
|
|
2881
|
+
const snapshot = observed.get(key);
|
|
2882
|
+
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
2883
|
+
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
2884
|
+
if (committed.get(key)) await this.renewClaim(txn, item.hookId, claim, now);
|
|
2885
|
+
else deletes.push(deadline, key);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2812
2888
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
2813
2889
|
if (due.size > 128) {
|
|
2814
2890
|
await txn.setAlarm(now + 1);
|
|
@@ -2851,51 +2927,68 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2851
2927
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
2852
2928
|
}
|
|
2853
2929
|
async reserve(token, owner, proposedClaimId) {
|
|
2854
|
-
|
|
2855
|
-
const
|
|
2856
|
-
const
|
|
2857
|
-
const
|
|
2858
|
-
const
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
existing =
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
deadlineKey
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2930
|
+
for (;;) {
|
|
2931
|
+
const observed = await this.ctx.storage.get(claimKey(token));
|
|
2932
|
+
const expired = observed && observed.expiresAt <= this.now() ? observed : void 0;
|
|
2933
|
+
const committed = expired ? await resolveExpiredHookClaim(this.env, expired.owner, expired.claimId, token) : void 0;
|
|
2934
|
+
const result = await this.ctx.storage.transaction(async (txn) => {
|
|
2935
|
+
const record = recordKey(token);
|
|
2936
|
+
const claim = claimKey(token);
|
|
2937
|
+
const values = await txn.get([record, claim]);
|
|
2938
|
+
const indexed = values.get(record);
|
|
2939
|
+
if (indexed !== void 0) return {
|
|
2940
|
+
claimed: false,
|
|
2941
|
+
holder: ownerFromSerializedHook(indexed)
|
|
2942
|
+
};
|
|
2943
|
+
let existing = values.get(claim);
|
|
2944
|
+
const now = this.now();
|
|
2945
|
+
if (existing && existing.expiresAt <= now) {
|
|
2946
|
+
if (existing.claimId !== expired?.claimId || existing.expiresAt !== expired.expiresAt) return null;
|
|
2947
|
+
if (committed) {
|
|
2948
|
+
await this.renewClaim(txn, token, existing, now);
|
|
2949
|
+
return sameOwner(existing.owner, owner) ? {
|
|
2950
|
+
claimed: true,
|
|
2951
|
+
claimId: existing.claimId
|
|
2952
|
+
} : {
|
|
2953
|
+
claimed: false,
|
|
2954
|
+
holder: existing.owner
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
await txn.delete([claim, existing.deadlineKey]);
|
|
2958
|
+
existing = void 0;
|
|
2959
|
+
}
|
|
2960
|
+
if (existing !== void 0 && !sameOwner(existing.owner, owner)) return {
|
|
2961
|
+
claimed: false,
|
|
2962
|
+
holder: existing.owner
|
|
2963
|
+
};
|
|
2964
|
+
if (existing === void 0) {
|
|
2965
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2966
|
+
const deadlineKey = claimDeadlineKey(token, expiresAt);
|
|
2967
|
+
await txn.put(claim, {
|
|
2968
|
+
owner,
|
|
2969
|
+
claimId: proposedClaimId,
|
|
2970
|
+
reservedAt: now,
|
|
2971
|
+
expiresAt,
|
|
2972
|
+
deadlineKey
|
|
2973
|
+
});
|
|
2974
|
+
await txn.put(deadlineKey, {
|
|
2975
|
+
token,
|
|
2976
|
+
claimId: proposedClaimId,
|
|
2977
|
+
expiresAt
|
|
2978
|
+
});
|
|
2979
|
+
await this.armAtMost(txn, expiresAt);
|
|
2980
|
+
return {
|
|
2981
|
+
claimed: true,
|
|
2982
|
+
claimId: proposedClaimId
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2889
2985
|
return {
|
|
2890
2986
|
claimed: true,
|
|
2891
|
-
claimId:
|
|
2987
|
+
claimId: existing.claimId
|
|
2892
2988
|
};
|
|
2893
|
-
}
|
|
2894
|
-
return
|
|
2895
|
-
|
|
2896
|
-
claimId: existing.claimId
|
|
2897
|
-
};
|
|
2898
|
-
});
|
|
2989
|
+
});
|
|
2990
|
+
if (result) return result;
|
|
2991
|
+
}
|
|
2899
2992
|
}
|
|
2900
2993
|
async finalize(token, serializedHook, owner, claimId) {
|
|
2901
2994
|
if (!await runIsActive(this.env, owner.runId)) {
|
|
@@ -2912,10 +3005,7 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2912
3005
|
const indexed = values.get(record);
|
|
2913
3006
|
if (indexed !== void 0 && !sameOwner(ownerFromSerializedHook(indexed), owner)) throw new Error(`Hook token ${token} is owned by another hook`);
|
|
2914
3007
|
const existingClaim = values.get(claim);
|
|
2915
|
-
if (existingClaim && existingClaim.expiresAt <= this.now()) {
|
|
2916
|
-
await txn.delete([claim, existingClaim.deadlineKey]);
|
|
2917
|
-
return { stored: false };
|
|
2918
|
-
}
|
|
3008
|
+
if (existingClaim && existingClaim.expiresAt <= this.now()) throw new Error(`Hook token ${token} reservation expired`);
|
|
2919
3009
|
if (existingClaim !== void 0 && (!sameOwner(existingClaim.owner, owner) || existingClaim.claimId !== claimId)) throw new Error(`Hook token ${token} is reserved by another hook`);
|
|
2920
3010
|
if (indexed === void 0 && existingClaim === void 0) throw new Error(`Hook token ${token} has no active reservation`);
|
|
2921
3011
|
await txn.put(record, serializedHook);
|
|
@@ -2965,21 +3055,51 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2965
3055
|
const current = await storage.getAlarm();
|
|
2966
3056
|
if (current === null || at < current) await storage.setAlarm(at);
|
|
2967
3057
|
}
|
|
3058
|
+
async renewClaim(txn, token, claim, now) {
|
|
3059
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
3060
|
+
const deadlineKey = claimDeadlineKey(token, expiresAt);
|
|
3061
|
+
await txn.delete(claim.deadlineKey);
|
|
3062
|
+
await txn.put(claimKey(token), {
|
|
3063
|
+
...claim,
|
|
3064
|
+
expiresAt,
|
|
3065
|
+
deadlineKey
|
|
3066
|
+
});
|
|
3067
|
+
await txn.put(deadlineKey, {
|
|
3068
|
+
token,
|
|
3069
|
+
claimId: claim.claimId,
|
|
3070
|
+
expiresAt
|
|
3071
|
+
});
|
|
3072
|
+
await this.armAtMost(txn, expiresAt);
|
|
3073
|
+
}
|
|
2968
3074
|
async compactClaims() {
|
|
2969
3075
|
const now = this.now();
|
|
3076
|
+
const due = await this.ctx.storage.list({
|
|
3077
|
+
prefix: CLAIM_DEADLINE_PREFIX,
|
|
3078
|
+
end: `${CLAIM_DEADLINE_PREFIX}${pad$1(now + 1)}`,
|
|
3079
|
+
limit: 129
|
|
3080
|
+
});
|
|
3081
|
+
const page = Array.from(due).slice(0, 128);
|
|
3082
|
+
const observed = await this.ctx.storage.get(page.map(([, item]) => claimKey(item.token)));
|
|
3083
|
+
const committed = /* @__PURE__ */ new Map();
|
|
3084
|
+
await Promise.all(page.map(async ([, item]) => {
|
|
3085
|
+
const claim = observed.get(claimKey(item.token));
|
|
3086
|
+
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
3087
|
+
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId, item.token);
|
|
3088
|
+
committed.set(claimKey(item.token), hook !== null);
|
|
3089
|
+
}));
|
|
2970
3090
|
await this.ctx.storage.transaction(async (txn) => {
|
|
2971
|
-
const due = await txn.list({
|
|
2972
|
-
prefix: CLAIM_DEADLINE_PREFIX,
|
|
2973
|
-
end: `${CLAIM_DEADLINE_PREFIX}${pad$1(now + 1)}`,
|
|
2974
|
-
limit: 129
|
|
2975
|
-
});
|
|
2976
|
-
const page = Array.from(due).slice(0, 128);
|
|
2977
3091
|
const claims = await txn.get(page.map(([, item]) => claimKey(item.token)));
|
|
2978
|
-
const deletes =
|
|
3092
|
+
const deletes = [];
|
|
3093
|
+
for (const [deadline, item] of page) {
|
|
2979
3094
|
const key = claimKey(item.token);
|
|
2980
3095
|
const claim = claims.get(key);
|
|
2981
|
-
|
|
2982
|
-
|
|
3096
|
+
const snapshot = observed.get(key);
|
|
3097
|
+
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
3098
|
+
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
3099
|
+
if (committed.get(key)) await this.renewClaim(txn, item.token, claim, now);
|
|
3100
|
+
else deletes.push(deadline, key);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
2983
3103
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
2984
3104
|
if (due.size > 128) {
|
|
2985
3105
|
await txn.setAlarm(now + 1);
|
|
@@ -3456,6 +3576,15 @@ var StreamDO = class extends DurableObject {
|
|
|
3456
3576
|
size: validateSegmentSize(entry[1], start)
|
|
3457
3577
|
});
|
|
3458
3578
|
}
|
|
3579
|
+
let covered = startIndex;
|
|
3580
|
+
for (let index = 0; index < planned.length; index++) {
|
|
3581
|
+
const { start, size } = planned[index];
|
|
3582
|
+
if (start > covered) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3583
|
+
if (index > 0 && start !== covered) throw new Error(`Overlapping persisted stream segment at index ${start}`);
|
|
3584
|
+
if (start + size.count <= covered) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3585
|
+
covered = start + size.count;
|
|
3586
|
+
}
|
|
3587
|
+
if (covered < endIndex) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3459
3588
|
const selected = [];
|
|
3460
3589
|
let plannedBytes = 0;
|
|
3461
3590
|
for (const segment of planned) {
|
|
@@ -4011,6 +4140,28 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4011
4140
|
async getRun() {
|
|
4012
4141
|
return this.readKey("run");
|
|
4013
4142
|
}
|
|
4143
|
+
/** Fence both supported event-claim forms before an expired shard claim is released. */
|
|
4144
|
+
async resolveExpiredHookClaim(request) {
|
|
4145
|
+
if (typeof request?.hookId !== "string" || request.hookId.length === 0 || typeof request.claimId !== "string" || request.claimId.length === 0 || request.token !== void 0 && typeof request.token !== "string") throw new TypeError("world-celld expired hook claim resolution is invalid");
|
|
4146
|
+
return await this.ctx.storage.transaction(async (txn) => {
|
|
4147
|
+
const { tombstone } = await this.retentionState(txn);
|
|
4148
|
+
if (tombstone) return null;
|
|
4149
|
+
const run = await txn.get("run");
|
|
4150
|
+
if (run && isTerminalWorkflowRunStatus(run.status)) return null;
|
|
4151
|
+
const hook = await txn.get(`${HOOK_KEY_PREFIX}${request.hookId}`);
|
|
4152
|
+
if (hook && (request.token === void 0 || hook.token === request.token)) return hook;
|
|
4153
|
+
const cancellation = {
|
|
4154
|
+
hookId: request.hookId,
|
|
4155
|
+
token: request.token,
|
|
4156
|
+
canceledAt: this.now()
|
|
4157
|
+
};
|
|
4158
|
+
await txn.put({
|
|
4159
|
+
[hookClaimCancellationKey(`${request.claimId}:${request.claimId}`)]: cancellation,
|
|
4160
|
+
[hookClaimCancellationKey(`-:${request.claimId}`)]: cancellation
|
|
4161
|
+
});
|
|
4162
|
+
return null;
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
4014
4165
|
/** Single authoritative lifecycle answer used after derivative compaction. */
|
|
4015
4166
|
async getLifecycleStatus() {
|
|
4016
4167
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4642,7 +4793,7 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4642
4793
|
* the run expiry check.
|
|
4643
4794
|
*/
|
|
4644
4795
|
async reserveRunQueueMessage(params) {
|
|
4645
|
-
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld run queue reservation is invalid");
|
|
4796
|
+
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt) || params.publicationPending !== void 0 && typeof params.publicationPending !== "boolean") throw new TypeError("world-celld run queue reservation is invalid");
|
|
4646
4797
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4647
4798
|
const key = runQueueClaimKey(params.claimName);
|
|
4648
4799
|
const values = await txn.get([
|
|
@@ -4662,17 +4813,19 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4662
4813
|
};
|
|
4663
4814
|
const state = values.get(key) ?? {};
|
|
4664
4815
|
const existing = state.reservation;
|
|
4665
|
-
if (existing && existing.expiresAt > this.now() && existing.messageId !== params.messageId) return {
|
|
4816
|
+
if (existing && existing.expiresAt > this.now() && (existing.messageId !== params.messageId || params.publicationPending)) return {
|
|
4666
4817
|
ok: true,
|
|
4667
4818
|
scope: "run",
|
|
4668
4819
|
admitted: false,
|
|
4669
|
-
messageId: existing.messageId
|
|
4820
|
+
messageId: existing.messageId,
|
|
4821
|
+
...existing.publicationPending ? { publicationPending: true } : {}
|
|
4670
4822
|
};
|
|
4671
4823
|
await txn.put(key, {
|
|
4672
4824
|
...state,
|
|
4673
4825
|
reservation: {
|
|
4674
4826
|
messageId: params.messageId,
|
|
4675
|
-
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt)
|
|
4827
|
+
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt),
|
|
4828
|
+
...params.publicationPending ? { publicationPending: true } : {}
|
|
4676
4829
|
}
|
|
4677
4830
|
});
|
|
4678
4831
|
return {
|
|
@@ -4683,6 +4836,21 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4683
4836
|
};
|
|
4684
4837
|
});
|
|
4685
4838
|
}
|
|
4839
|
+
async confirmRunQueueMessagePublication(params) {
|
|
4840
|
+
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld run queue publication confirmation is invalid");
|
|
4841
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4842
|
+
const key = runQueueClaimKey(params.claimName);
|
|
4843
|
+
const state = await txn.get(key);
|
|
4844
|
+
if (state?.reservation?.messageId !== params.messageId) return;
|
|
4845
|
+
await txn.put(key, {
|
|
4846
|
+
...state,
|
|
4847
|
+
reservation: {
|
|
4848
|
+
messageId: params.messageId,
|
|
4849
|
+
expiresAt: Math.max(state.reservation.expiresAt, params.expiresAt)
|
|
4850
|
+
}
|
|
4851
|
+
});
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4686
4854
|
/** Delivery-side run expiry check and in-flight claim in one transaction. */
|
|
4687
4855
|
async claimRunQueueMessage(params) {
|
|
4688
4856
|
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0 || !isPositiveSafeInteger(params.staleMs)) throw new TypeError("world-celld run queue claim is invalid");
|
|
@@ -4694,9 +4862,19 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4694
4862
|
key
|
|
4695
4863
|
]);
|
|
4696
4864
|
if (this.tombstoneFrom(values)) return { expired: true };
|
|
4697
|
-
|
|
4698
|
-
const existing = state.claim;
|
|
4865
|
+
let state = values.get(key) ?? {};
|
|
4699
4866
|
const now = this.now();
|
|
4867
|
+
if (state.reservation?.publicationPending && state.reservation.messageId === params.messageId) {
|
|
4868
|
+
state = {
|
|
4869
|
+
...state,
|
|
4870
|
+
reservation: {
|
|
4871
|
+
messageId: params.messageId,
|
|
4872
|
+
expiresAt: Math.max(state.reservation.expiresAt, now + QUEUE_RESERVATION_GRACE_MS)
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
await txn.put(key, state);
|
|
4876
|
+
}
|
|
4877
|
+
const existing = state.claim;
|
|
4700
4878
|
if (existing && existing.expiresAt > now) {
|
|
4701
4879
|
if (existing.messageId !== params.messageId) return {
|
|
4702
4880
|
expired: false,
|
|
@@ -4768,6 +4946,18 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4768
4946
|
else await txn.delete(key);
|
|
4769
4947
|
});
|
|
4770
4948
|
}
|
|
4949
|
+
/** Failed enqueue cleanup must preserve delivery already accepted by the broker. */
|
|
4950
|
+
async abandonRunQueueMessageReservation(params) {
|
|
4951
|
+
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0) throw new TypeError("world-celld run queue reservation abandonment is invalid");
|
|
4952
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4953
|
+
const key = runQueueClaimKey(params.claimName);
|
|
4954
|
+
const state = await txn.get(key);
|
|
4955
|
+
if (state?.reservation?.messageId !== params.messageId) return;
|
|
4956
|
+
const { reservation: _abandoned, ...rest } = state;
|
|
4957
|
+
if (rest.claim) await txn.put(key, rest);
|
|
4958
|
+
else await txn.delete(key);
|
|
4959
|
+
});
|
|
4960
|
+
}
|
|
4771
4961
|
async registerQueuePayload(registration) {
|
|
4772
4962
|
if (!registration || typeof registration.messageId !== "string" || registration.messageId.length === 0 || typeof registration.key !== "string" || registration.key.length === 0 || !isNonNegativeSafeInteger(registration.orphanExpiresAt)) throw new TypeError("world-celld queue payload registration is invalid");
|
|
4773
4963
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4824,22 +5014,24 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4824
5014
|
});
|
|
4825
5015
|
}
|
|
4826
5016
|
async reserveQueueMessage(params) {
|
|
4827
|
-
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld queue message reservation is invalid");
|
|
5017
|
+
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt) || params.publicationPending !== void 0 && typeof params.publicationPending !== "boolean") throw new TypeError("world-celld queue message reservation is invalid");
|
|
4828
5018
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4829
5019
|
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
4830
5020
|
const existing = values.get(QUEUE_RESERVATION_KEY);
|
|
4831
5021
|
const claim = values.get("claim");
|
|
4832
5022
|
const now = this.now();
|
|
4833
|
-
if (existing && existing.expiresAt > now && existing.messageId !== params.messageId) {
|
|
5023
|
+
if (existing && existing.expiresAt > now && (existing.messageId !== params.messageId || params.publicationPending)) {
|
|
4834
5024
|
await txn.setAlarm(Math.min(existing.expiresAt, claim?.expiresAt ?? existing.expiresAt));
|
|
4835
5025
|
return {
|
|
4836
5026
|
admitted: false,
|
|
4837
|
-
messageId: existing.messageId
|
|
5027
|
+
messageId: existing.messageId,
|
|
5028
|
+
...existing.publicationPending ? { publicationPending: true } : {}
|
|
4838
5029
|
};
|
|
4839
5030
|
}
|
|
4840
5031
|
const reservation = {
|
|
4841
5032
|
messageId: params.messageId,
|
|
4842
|
-
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt)
|
|
5033
|
+
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt),
|
|
5034
|
+
...params.publicationPending ? { publicationPending: true } : {}
|
|
4843
5035
|
};
|
|
4844
5036
|
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
4845
5037
|
await txn.setAlarm(Math.min(reservation.expiresAt, claim?.expiresAt ?? reservation.expiresAt));
|
|
@@ -4849,6 +5041,20 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4849
5041
|
};
|
|
4850
5042
|
});
|
|
4851
5043
|
}
|
|
5044
|
+
async confirmQueueMessagePublication(params) {
|
|
5045
|
+
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld queue publication confirmation is invalid");
|
|
5046
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
5047
|
+
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
5048
|
+
const existing = values.get(QUEUE_RESERVATION_KEY);
|
|
5049
|
+
if (existing?.messageId !== params.messageId) return;
|
|
5050
|
+
const reservation = {
|
|
5051
|
+
messageId: params.messageId,
|
|
5052
|
+
expiresAt: Math.max(existing.expiresAt, params.expiresAt)
|
|
5053
|
+
};
|
|
5054
|
+
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
5055
|
+
await txn.setAlarm(Math.min(reservation.expiresAt, values.get("claim")?.expiresAt ?? reservation.expiresAt));
|
|
5056
|
+
});
|
|
5057
|
+
}
|
|
4852
5058
|
async completeQueueMessage(messageId) {
|
|
4853
5059
|
if (typeof messageId !== "string" || messageId.length === 0) throw new TypeError("world-celld queue messageId must be a non-empty string");
|
|
4854
5060
|
await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4868,13 +5074,32 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4868
5074
|
else await txn.deleteAlarm();
|
|
4869
5075
|
});
|
|
4870
5076
|
}
|
|
5077
|
+
/** Abandon the enqueue reservation without releasing an active delivery claim. */
|
|
5078
|
+
async abandonQueueMessageReservation(messageId) {
|
|
5079
|
+
if (typeof messageId !== "string" || messageId.length === 0) throw new TypeError("world-celld queue reservation messageId must be a non-empty string");
|
|
5080
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
5081
|
+
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
5082
|
+
if (values.get(QUEUE_RESERVATION_KEY)?.messageId !== messageId) return;
|
|
5083
|
+
await txn.delete(QUEUE_RESERVATION_KEY);
|
|
5084
|
+
const claim = values.get("claim");
|
|
5085
|
+
if (claim) await txn.setAlarm(claim.expiresAt);
|
|
5086
|
+
else await txn.deleteAlarm();
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
4871
5089
|
async claimInflight(params) {
|
|
4872
5090
|
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isPositiveSafeInteger(params.staleMs)) throw new TypeError("world-celld queue claim is invalid");
|
|
4873
5091
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4874
5092
|
const values = await txn.get(["claim", QUEUE_RESERVATION_KEY]);
|
|
4875
5093
|
const existing = values.get("claim");
|
|
4876
|
-
|
|
5094
|
+
let reservation = values.get(QUEUE_RESERVATION_KEY);
|
|
4877
5095
|
const now = this.now();
|
|
5096
|
+
if (reservation?.publicationPending && reservation.messageId === params.messageId) {
|
|
5097
|
+
reservation = {
|
|
5098
|
+
messageId: params.messageId,
|
|
5099
|
+
expiresAt: Math.max(reservation.expiresAt, now + QUEUE_RESERVATION_GRACE_MS)
|
|
5100
|
+
};
|
|
5101
|
+
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
5102
|
+
}
|
|
4878
5103
|
if (existing && existing.expiresAt > now) {
|
|
4879
5104
|
if (existing.messageId !== params.messageId) return { claimed: false };
|
|
4880
5105
|
if (existing.retryAt === void 0 || existing.retryAt > now) return {
|