@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/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as validateStreamReadRequest, B as MAX_STREAM_READ_BYTES, C as HOOK_CLAIM_LEASE_MS, I as MAX_STREAM_BATCH_BYTES, J as encodeStreamReadResult, K as decodeStreamWriteBatch, L as MAX_STREAM_CHUNK_BYTES, M as isPositiveSafeInteger, N as isRecord$1, O as MAX_RUN_INDEX_PUBLICATION_LIFETIME_MS, Q as normalizeStreamError, U as STREAM_BATCH_CONTENT_TYPE, W as STREAM_CHUNK_LIMIT_HEADER, X as encodeStreamWriteResult, _ as rpcStringify, a as QUEUE_PUBLICATION_LEASE_MS, b as createWorkflowIndex, d as queuePayloadObjectKey, et as validateStreamWriteChunks, f as queuePayloadRegistryKey, g as rpcParse, h as compact, i as QUEUE_PAYLOAD_REGISTRY_PREFIX, j as isNonNegativeSafeInteger, l as queueClaimName, m as validateNativeQueueSendOptions, n as listRunsPage, o as QUEUE_RESERVATION_GRACE_MS, p as validateNativeQueueEnvelope, r as NATIVE_QUEUE_MAX_MESSAGE_BYTES, s as isInlineRunBody, t as MAX_RUN_LIST_LIMIT, u as queueOrphanName, v as parse, w as LIFECYCLE_COMPACTION_RETRY_MS, x as runIndexMetadata, z as MAX_STREAM_LONG_POLL_MS } from "./run-list-DGsdv6Jf.js";
|
|
2
2
|
import { CreateEventSchema, EventSchema, HookSchema, SPEC_VERSION_CURRENT, StepSchema, WaitSchema, WorkflowRunSchema, applyAttributeChanges, eventIdToSlot, getMaxEventsPerRun, isChildEntityCreationEvent, isTerminalStepStatus, isTerminalWorkflowRunStatus, slotToEventId, validateAttributeChanges } from "@workflow/world";
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
//#region src/testing/fake-cell.ts
|
|
@@ -546,8 +546,8 @@ function readStringProp(value, key) {
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
/**
|
|
549
|
-
* Map a failure eventData.error (which may be a string, an Error-shaped
|
|
550
|
-
* object, or anything else) to the structured error stored on
|
|
549
|
+
* Map a run failure eventData.error (which may be a string, an Error-shaped
|
|
550
|
+
* object, or anything else) to the structured error stored on runs.
|
|
551
551
|
* Matches upstream: string errors keep their text, errorCode is preserved.
|
|
552
552
|
*/
|
|
553
553
|
function toStructuredError(error, opts) {
|
|
@@ -1023,14 +1023,17 @@ async function applyEvent(store, ctx) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
1025
1025
|
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) });
|
|
1026
|
-
const updated = StepSchema.parse(
|
|
1027
|
-
...
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1026
|
+
const updated = StepSchema.parse({
|
|
1027
|
+
...compact({
|
|
1028
|
+
...step,
|
|
1029
|
+
status: "running",
|
|
1030
|
+
startedAt: step.startedAt ?? now,
|
|
1031
|
+
attempt: step.attempt + 1,
|
|
1032
|
+
retryAfter: void 0,
|
|
1033
|
+
updatedAt: now
|
|
1034
|
+
}),
|
|
1035
|
+
error: step.error
|
|
1036
|
+
});
|
|
1034
1037
|
const event = buildEvent({ ...data });
|
|
1035
1038
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
1036
1039
|
await putEvent(event);
|
|
@@ -1045,13 +1048,16 @@ async function applyEvent(store, ctx) {
|
|
|
1045
1048
|
case "step_completed": {
|
|
1046
1049
|
const step = validatedStep;
|
|
1047
1050
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
1048
|
-
const updated = StepSchema.parse(
|
|
1049
|
-
...
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1051
|
+
const updated = StepSchema.parse({
|
|
1052
|
+
...compact({
|
|
1053
|
+
...step,
|
|
1054
|
+
status: "completed",
|
|
1055
|
+
output: data.eventData.result,
|
|
1056
|
+
completedAt: now,
|
|
1057
|
+
updatedAt: now
|
|
1058
|
+
}),
|
|
1059
|
+
error: step.error
|
|
1060
|
+
});
|
|
1055
1061
|
const event = buildEvent({ ...data });
|
|
1056
1062
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
1057
1063
|
await putEvent(event);
|
|
@@ -1065,13 +1071,13 @@ async function applyEvent(store, ctx) {
|
|
|
1065
1071
|
case "step_failed": {
|
|
1066
1072
|
const step = validatedStep;
|
|
1067
1073
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
1068
|
-
const updated = StepSchema.parse(
|
|
1074
|
+
const updated = StepSchema.parse({
|
|
1069
1075
|
...step,
|
|
1070
1076
|
status: "failed",
|
|
1071
|
-
error:
|
|
1077
|
+
error: data.eventData.error,
|
|
1072
1078
|
completedAt: now,
|
|
1073
1079
|
updatedAt: now
|
|
1074
|
-
})
|
|
1080
|
+
});
|
|
1075
1081
|
const event = buildEvent({ ...data });
|
|
1076
1082
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
1077
1083
|
await putEvent(event);
|
|
@@ -1085,13 +1091,13 @@ async function applyEvent(store, ctx) {
|
|
|
1085
1091
|
case "step_retrying": {
|
|
1086
1092
|
const step = validatedStep;
|
|
1087
1093
|
if (!step) return failure("STEP_NOT_FOUND", `Step "${data.correlationId}" not found`);
|
|
1088
|
-
const updated = StepSchema.parse(
|
|
1094
|
+
const updated = StepSchema.parse({
|
|
1089
1095
|
...step,
|
|
1090
1096
|
status: "pending",
|
|
1091
|
-
error:
|
|
1097
|
+
error: data.eventData.error,
|
|
1092
1098
|
retryAfter: data.eventData.retryAfter ? new Date(data.eventData.retryAfter) : void 0,
|
|
1093
1099
|
updatedAt: now
|
|
1094
|
-
})
|
|
1100
|
+
});
|
|
1095
1101
|
const event = buildEvent({ ...data });
|
|
1096
1102
|
await store.put(`${STEP_KEY_PREFIX}${data.correlationId}`, updated);
|
|
1097
1103
|
await putEvent(event);
|
|
@@ -1127,7 +1133,7 @@ async function applyEvent(store, ctx) {
|
|
|
1127
1133
|
}
|
|
1128
1134
|
if (await store.get(markerKey) !== void 0) {
|
|
1129
1135
|
const existingHook = await store.get(hookKey);
|
|
1130
|
-
if (existingHook && !holder) return {
|
|
1136
|
+
if (existingHook && !holder && existingHook.token === token) return {
|
|
1131
1137
|
ok: true,
|
|
1132
1138
|
hook: existingHook,
|
|
1133
1139
|
hookToIndex: existingHook,
|
|
@@ -1363,6 +1369,11 @@ function cellQueueClaim(cell) {
|
|
|
1363
1369
|
}),
|
|
1364
1370
|
hold: (params) => cell.holdInflight(params),
|
|
1365
1371
|
release: (messageId) => cell.releaseInflight(messageId),
|
|
1372
|
+
abandonReservation: (messageId) => cell.abandonQueueMessageReservation(messageId),
|
|
1373
|
+
confirmPublication: (messageId, expiresAt) => cell.confirmQueueMessagePublication({
|
|
1374
|
+
messageId,
|
|
1375
|
+
expiresAt
|
|
1376
|
+
}),
|
|
1366
1377
|
complete: (messageId) => cell.completeQueueMessage(messageId)
|
|
1367
1378
|
};
|
|
1368
1379
|
}
|
|
@@ -1387,6 +1398,15 @@ function runQueueClaim(run, claimName) {
|
|
|
1387
1398
|
claimName,
|
|
1388
1399
|
messageId
|
|
1389
1400
|
}),
|
|
1401
|
+
abandonReservation: (messageId) => run.abandonRunQueueMessageReservation({
|
|
1402
|
+
claimName,
|
|
1403
|
+
messageId
|
|
1404
|
+
}),
|
|
1405
|
+
confirmPublication: (messageId, expiresAt) => run.confirmRunQueueMessagePublication({
|
|
1406
|
+
claimName,
|
|
1407
|
+
messageId,
|
|
1408
|
+
expiresAt
|
|
1409
|
+
}),
|
|
1390
1410
|
complete: (messageId) => run.completeRunQueueMessage({
|
|
1391
1411
|
claimName,
|
|
1392
1412
|
messageId
|
|
@@ -1708,7 +1728,8 @@ const BINDINGS = {
|
|
|
1708
1728
|
"scheduleCleanup",
|
|
1709
1729
|
"cleanupNow",
|
|
1710
1730
|
"rearmCleanup",
|
|
1711
|
-
"resolveHookTokenClaim"
|
|
1731
|
+
"resolveHookTokenClaim",
|
|
1732
|
+
"resolveExpiredHookClaim"
|
|
1712
1733
|
])
|
|
1713
1734
|
},
|
|
1714
1735
|
streams: {
|
|
@@ -1735,7 +1756,6 @@ const BINDINGS = {
|
|
|
1735
1756
|
/** Request body cap: oversize payloads get a clear 413 instead of an OOM. */
|
|
1736
1757
|
const MAX_BODY_BYTES = 33554432;
|
|
1737
1758
|
const MAX_STREAM_WRITE_BODY_BYTES = MAX_STREAM_BATCH_BYTES + 2048 + 1024;
|
|
1738
|
-
const QUEUE_ORPHAN_GRACE_MS = 432e6;
|
|
1739
1759
|
function errorResponse(status, name, message) {
|
|
1740
1760
|
return Response.json({ error: {
|
|
1741
1761
|
name,
|
|
@@ -1888,6 +1908,7 @@ function createRouter(env) {
|
|
|
1888
1908
|
} catch (error) {
|
|
1889
1909
|
return errorResponse(400, "BadRequest", error.message);
|
|
1890
1910
|
}
|
|
1911
|
+
let enqueueClaim;
|
|
1891
1912
|
try {
|
|
1892
1913
|
const namespace = env.WORKFLOW_DB;
|
|
1893
1914
|
const payloadStore = env.WORKFLOW_QUEUE_PAYLOADS;
|
|
@@ -1904,26 +1925,26 @@ function createRouter(env) {
|
|
|
1904
1925
|
});
|
|
1905
1926
|
if (new TextEncoder().encode(sized).byteLength > 128e3) return errorResponse(413, "PayloadTooLarge", `native queue envelope exceeds ${NATIVE_QUEUE_MAX_MESSAGE_BYTES} bytes`);
|
|
1906
1927
|
const now = Date.now();
|
|
1907
|
-
const reservationExpiresAt = Math.max(now, envelope.notBefore ?? now) +
|
|
1928
|
+
const reservationExpiresAt = Math.max(now, envelope.notBefore ?? now) + QUEUE_RESERVATION_GRACE_MS;
|
|
1908
1929
|
const run = envelope.runId ? namespace.get(namespace.idFromName(envelope.runId)) : void 0;
|
|
1909
|
-
let claim;
|
|
1910
1930
|
let admission;
|
|
1911
1931
|
if (envelope.idempotencyKey) {
|
|
1912
1932
|
const claimName = queueClaimName(envelope.queueName, envelope.idempotencyKey);
|
|
1913
1933
|
let scope = run && envelope.runId ? runClaimScopes.get(envelope.runId) : "cell";
|
|
1914
1934
|
let reservation;
|
|
1915
1935
|
if (run && envelope.runId && scope !== "cell") {
|
|
1936
|
+
enqueueClaim = runQueueClaim(run, claimName);
|
|
1916
1937
|
const reserved = await run.reserveRunQueueMessage({
|
|
1917
1938
|
claimName,
|
|
1918
1939
|
messageId: envelope.messageId,
|
|
1919
|
-
expiresAt:
|
|
1940
|
+
expiresAt: now + QUEUE_PUBLICATION_LEASE_MS,
|
|
1941
|
+
publicationPending: true
|
|
1920
1942
|
});
|
|
1921
1943
|
if (!reserved.ok) return errorResponse(410, "RunExpiredError", reserved.message);
|
|
1922
1944
|
scope = reserved.scope;
|
|
1923
1945
|
rememberRunClaimScope(envelope.runId, reserved.scope);
|
|
1924
1946
|
if (reserved.scope === "run") {
|
|
1925
1947
|
reservation = reserved;
|
|
1926
|
-
claim = runQueueClaim(run, claimName);
|
|
1927
1948
|
brokerEnvelope = {
|
|
1928
1949
|
...brokerEnvelope,
|
|
1929
1950
|
claimScope: "run"
|
|
@@ -1935,27 +1956,34 @@ function createRouter(env) {
|
|
|
1935
1956
|
}
|
|
1936
1957
|
if (!reservation) {
|
|
1937
1958
|
const cell = namespace.get(namespace.idFromName(claimName));
|
|
1959
|
+
enqueueClaim = cellQueueClaim(cell);
|
|
1938
1960
|
reservation = await cell.reserveQueueMessage({
|
|
1939
1961
|
messageId: envelope.messageId,
|
|
1940
|
-
expiresAt:
|
|
1962
|
+
expiresAt: now + QUEUE_PUBLICATION_LEASE_MS,
|
|
1963
|
+
publicationPending: true
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
if (!reservation.admitted) {
|
|
1967
|
+
if (reservation.publicationPending) return errorResponse(503, "QueuePublicationPending", "the reserved Queue message has not been confirmed by the broker; retry publication");
|
|
1968
|
+
return new Response(rpcStringify({ messageId: reservation.messageId }), {
|
|
1969
|
+
status: 200,
|
|
1970
|
+
headers: { "content-type": "application/json" }
|
|
1941
1971
|
});
|
|
1942
|
-
claim = cellQueueClaim(cell);
|
|
1943
1972
|
}
|
|
1944
|
-
if (!reservation.admitted) return new Response(rpcStringify({ messageId: reservation.messageId }), {
|
|
1945
|
-
status: 200,
|
|
1946
|
-
headers: { "content-type": "application/json" }
|
|
1947
|
-
});
|
|
1948
1973
|
} else if (inlineRunBody && run) admission = run.getQueueAdmission();
|
|
1949
1974
|
const encoded = JSON.stringify(brokerEnvelope);
|
|
1950
1975
|
if (admission) {
|
|
1951
1976
|
const admitted = await admission;
|
|
1952
1977
|
if (!admitted.ok) {
|
|
1953
|
-
await
|
|
1978
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
1954
1979
|
return errorResponse(410, "RunExpiredError", admitted.message);
|
|
1955
1980
|
}
|
|
1956
1981
|
}
|
|
1957
1982
|
if (run && envelope.runId && payloadKey) {
|
|
1958
|
-
if (!payloadStore)
|
|
1983
|
+
if (!payloadStore) {
|
|
1984
|
+
await enqueueClaim?.abandonReservation(envelope.messageId);
|
|
1985
|
+
return errorResponse(500, "WorldMisconfigured", "missing binding: WORKFLOW_QUEUE_PAYLOADS");
|
|
1986
|
+
}
|
|
1959
1987
|
const orphanExpiresAt = reservationExpiresAt;
|
|
1960
1988
|
const registered = await run.registerQueuePayload({
|
|
1961
1989
|
messageId: envelope.messageId,
|
|
@@ -1963,7 +1991,7 @@ function createRouter(env) {
|
|
|
1963
1991
|
orphanExpiresAt
|
|
1964
1992
|
});
|
|
1965
1993
|
if (!registered.ok) {
|
|
1966
|
-
await
|
|
1994
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
1967
1995
|
return errorResponse(410, "RunExpiredError", registered.message);
|
|
1968
1996
|
}
|
|
1969
1997
|
const orphan = namespace.get(namespace.idFromName(queueOrphanName(envelope.messageId)));
|
|
@@ -1976,30 +2004,18 @@ function createRouter(env) {
|
|
|
1976
2004
|
});
|
|
1977
2005
|
} catch (error) {
|
|
1978
2006
|
await run.unregisterQueuePayload(envelope.messageId);
|
|
1979
|
-
await claim?.complete(envelope.messageId);
|
|
1980
|
-
throw error;
|
|
1981
|
-
}
|
|
1982
|
-
try {
|
|
1983
|
-
await payloadStore.write(payloadKey, envelope.body, {
|
|
1984
|
-
runId: envelope.runId,
|
|
1985
|
-
messageId: envelope.messageId
|
|
1986
|
-
});
|
|
1987
|
-
} catch (error) {
|
|
1988
|
-
await claim?.complete(envelope.messageId);
|
|
1989
|
-
throw error;
|
|
1990
|
-
}
|
|
1991
|
-
let finalized;
|
|
1992
|
-
try {
|
|
1993
|
-
finalized = await run.finalizeQueuePayload(envelope.messageId);
|
|
1994
|
-
} catch (error) {
|
|
1995
|
-
await claim?.complete(envelope.messageId);
|
|
1996
2007
|
throw error;
|
|
1997
2008
|
}
|
|
2009
|
+
await payloadStore.write(payloadKey, envelope.body, {
|
|
2010
|
+
runId: envelope.runId,
|
|
2011
|
+
messageId: envelope.messageId
|
|
2012
|
+
});
|
|
2013
|
+
const finalized = await run.finalizeQueuePayload(envelope.messageId);
|
|
1998
2014
|
if (!finalized.ok) {
|
|
1999
2015
|
await payloadStore.delete(payloadKey);
|
|
2000
2016
|
await run.unregisterQueuePayload(envelope.messageId);
|
|
2001
2017
|
await orphan.cancelQueuePayloadOrphan(envelope.messageId);
|
|
2002
|
-
await
|
|
2018
|
+
await enqueueClaim?.complete(envelope.messageId);
|
|
2003
2019
|
return errorResponse(410, "RunExpiredError", finalized.message);
|
|
2004
2020
|
}
|
|
2005
2021
|
}
|
|
@@ -2007,11 +2023,17 @@ function createRouter(env) {
|
|
|
2007
2023
|
contentType: "text",
|
|
2008
2024
|
...options
|
|
2009
2025
|
});
|
|
2026
|
+
await enqueueClaim?.confirmPublication(envelope.messageId, reservationExpiresAt);
|
|
2010
2027
|
return new Response(rpcStringify({ messageId: envelope.messageId }), {
|
|
2011
2028
|
status: 200,
|
|
2012
2029
|
headers: { "content-type": "application/json" }
|
|
2013
2030
|
});
|
|
2014
2031
|
} catch (error) {
|
|
2032
|
+
try {
|
|
2033
|
+
await enqueueClaim?.abandonReservation(envelope.messageId);
|
|
2034
|
+
} catch (cleanupError) {
|
|
2035
|
+
console.error("world-celld could not abandon a failed Queue reservation", cleanupError);
|
|
2036
|
+
}
|
|
2015
2037
|
const err = error;
|
|
2016
2038
|
return errorResponse(500, err.name ?? "Error", err.message ?? String(error));
|
|
2017
2039
|
}
|
|
@@ -2244,6 +2266,16 @@ async function runIsActive(env, runId) {
|
|
|
2244
2266
|
if (!namespace) throw new Error("world-celld hook index missing WORKFLOW_DB binding");
|
|
2245
2267
|
return await namespace.get(namespace.idFromName(runId)).getLifecycleStatus() === "active";
|
|
2246
2268
|
}
|
|
2269
|
+
/** Resolve a stale shard reservation in its authoritative event writer. */
|
|
2270
|
+
async function resolveExpiredHookClaim(env, owner, claimId, token) {
|
|
2271
|
+
const namespace = env.WORKFLOW_DB;
|
|
2272
|
+
if (!namespace) throw new Error("world-celld hook index missing WORKFLOW_DB binding");
|
|
2273
|
+
return await namespace.get(namespace.idFromName(owner.runId)).resolveExpiredHookClaim({
|
|
2274
|
+
hookId: owner.hookId,
|
|
2275
|
+
claimId,
|
|
2276
|
+
token
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2247
2279
|
//#endregion
|
|
2248
2280
|
//#region src/worker/durable-objects/HookIdDO.ts
|
|
2249
2281
|
const MAX_RELEASE_BATCH$1 = 64;
|
|
@@ -2272,52 +2304,69 @@ var HookIdDO = class extends DurableObject {
|
|
|
2272
2304
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
2273
2305
|
}
|
|
2274
2306
|
async reserve(hookId, owner, proposedClaimId) {
|
|
2275
|
-
|
|
2276
|
-
const
|
|
2277
|
-
const
|
|
2278
|
-
const
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
existing =
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2307
|
+
for (;;) {
|
|
2308
|
+
const observed = await this.ctx.storage.get(claimKey$1(hookId));
|
|
2309
|
+
const expired = observed && observed.expiresAt <= this.now() ? observed : void 0;
|
|
2310
|
+
const committed = expired ? await resolveExpiredHookClaim(this.env, expired.owner, expired.claimId) : void 0;
|
|
2311
|
+
const result = await this.ctx.storage.transaction(async (txn) => {
|
|
2312
|
+
const record = recordKey$1(hookId);
|
|
2313
|
+
const claim = claimKey$1(hookId);
|
|
2314
|
+
const values = await txn.get([record, claim]);
|
|
2315
|
+
const indexed = values.get(record);
|
|
2316
|
+
if (indexed !== void 0) return {
|
|
2317
|
+
claimed: false,
|
|
2318
|
+
holder: ownerFromSerializedHook(indexed)
|
|
2319
|
+
};
|
|
2320
|
+
let existing = values.get(claim);
|
|
2321
|
+
const now = this.now();
|
|
2322
|
+
if (existing && existing.expiresAt <= now) {
|
|
2323
|
+
if (existing.claimId !== expired?.claimId || existing.expiresAt !== expired.expiresAt) return null;
|
|
2324
|
+
if (committed) {
|
|
2325
|
+
await this.renewClaim(txn, hookId, existing, now);
|
|
2326
|
+
return sameOwner(existing.owner, owner) ? {
|
|
2327
|
+
claimed: true,
|
|
2328
|
+
claimId: existing.claimId
|
|
2329
|
+
} : {
|
|
2330
|
+
claimed: false,
|
|
2331
|
+
holder: existing.owner
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
await txn.delete([claim, existing.deadlineKey]);
|
|
2335
|
+
existing = void 0;
|
|
2336
|
+
}
|
|
2337
|
+
if (existing !== void 0 && !sameOwner(existing.owner, owner)) return {
|
|
2338
|
+
claimed: false,
|
|
2339
|
+
holder: existing.owner
|
|
2340
|
+
};
|
|
2341
|
+
if (existing === void 0 || existing.claimId !== proposedClaimId) {
|
|
2342
|
+
if (existing) await txn.delete(existing.deadlineKey);
|
|
2343
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2344
|
+
const deadlineKey = claimDeadlineKey$1(hookId, expiresAt);
|
|
2345
|
+
await txn.put(claim, {
|
|
2346
|
+
owner,
|
|
2347
|
+
claimId: proposedClaimId,
|
|
2348
|
+
reservedAt: now,
|
|
2349
|
+
expiresAt,
|
|
2350
|
+
deadlineKey
|
|
2351
|
+
});
|
|
2352
|
+
await txn.put(deadlineKey, {
|
|
2353
|
+
hookId,
|
|
2354
|
+
claimId: proposedClaimId,
|
|
2355
|
+
expiresAt
|
|
2356
|
+
});
|
|
2357
|
+
await this.armAtMost(txn, expiresAt);
|
|
2358
|
+
return {
|
|
2359
|
+
claimed: true,
|
|
2360
|
+
claimId: proposedClaimId
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2311
2363
|
return {
|
|
2312
2364
|
claimed: true,
|
|
2313
|
-
claimId:
|
|
2365
|
+
claimId: existing.claimId
|
|
2314
2366
|
};
|
|
2315
|
-
}
|
|
2316
|
-
return
|
|
2317
|
-
|
|
2318
|
-
claimId: existing.claimId
|
|
2319
|
-
};
|
|
2320
|
-
});
|
|
2367
|
+
});
|
|
2368
|
+
if (result) return result;
|
|
2369
|
+
}
|
|
2321
2370
|
}
|
|
2322
2371
|
async publish(hookId, serializedHook, owner, claimId) {
|
|
2323
2372
|
if (!await runIsActive(this.env, owner.runId)) {
|
|
@@ -2334,10 +2383,7 @@ var HookIdDO = class extends DurableObject {
|
|
|
2334
2383
|
const indexed = values.get(record);
|
|
2335
2384
|
if (indexed !== void 0 && !sameOwner(ownerFromSerializedHook(indexed), owner)) throw new Error(`Hook id ${hookId} is owned by another hook`);
|
|
2336
2385
|
const existingClaim = values.get(claim);
|
|
2337
|
-
if (existingClaim && existingClaim.expiresAt <= this.now()) {
|
|
2338
|
-
await txn.delete([claim, existingClaim.deadlineKey]);
|
|
2339
|
-
return { stored: false };
|
|
2340
|
-
}
|
|
2386
|
+
if (existingClaim && existingClaim.expiresAt <= this.now()) throw new Error(`Hook id ${hookId} reservation expired`);
|
|
2341
2387
|
if (existingClaim !== void 0 && (!sameOwner(existingClaim.owner, owner) || existingClaim.claimId !== claimId)) throw new Error(`Hook id ${hookId} is reserved by another hook`);
|
|
2342
2388
|
if (indexed === void 0 && existingClaim === void 0) throw new Error(`Hook id ${hookId} has no active reservation`);
|
|
2343
2389
|
await txn.put(record, serializedHook);
|
|
@@ -2387,21 +2433,51 @@ var HookIdDO = class extends DurableObject {
|
|
|
2387
2433
|
const current = await storage.getAlarm();
|
|
2388
2434
|
if (current === null || at < current) await storage.setAlarm(at);
|
|
2389
2435
|
}
|
|
2436
|
+
async renewClaim(txn, hookId, claim, now) {
|
|
2437
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2438
|
+
const deadlineKey = claimDeadlineKey$1(hookId, expiresAt);
|
|
2439
|
+
await txn.delete(claim.deadlineKey);
|
|
2440
|
+
await txn.put(claimKey$1(hookId), {
|
|
2441
|
+
...claim,
|
|
2442
|
+
expiresAt,
|
|
2443
|
+
deadlineKey
|
|
2444
|
+
});
|
|
2445
|
+
await txn.put(deadlineKey, {
|
|
2446
|
+
hookId,
|
|
2447
|
+
claimId: claim.claimId,
|
|
2448
|
+
expiresAt
|
|
2449
|
+
});
|
|
2450
|
+
await this.armAtMost(txn, expiresAt);
|
|
2451
|
+
}
|
|
2390
2452
|
async compactClaims() {
|
|
2391
2453
|
const now = this.now();
|
|
2454
|
+
const due = await this.ctx.storage.list({
|
|
2455
|
+
prefix: CLAIM_DEADLINE_PREFIX$1,
|
|
2456
|
+
end: `${CLAIM_DEADLINE_PREFIX$1}${pad$2(now + 1)}`,
|
|
2457
|
+
limit: 129
|
|
2458
|
+
});
|
|
2459
|
+
const page = Array.from(due).slice(0, 128);
|
|
2460
|
+
const observed = await this.ctx.storage.get(page.map(([, item]) => claimKey$1(item.hookId)));
|
|
2461
|
+
const committed = /* @__PURE__ */ new Map();
|
|
2462
|
+
await Promise.all(page.map(async ([, item]) => {
|
|
2463
|
+
const claim = observed.get(claimKey$1(item.hookId));
|
|
2464
|
+
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
2465
|
+
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId);
|
|
2466
|
+
committed.set(claimKey$1(item.hookId), hook !== null);
|
|
2467
|
+
}));
|
|
2392
2468
|
await this.ctx.storage.transaction(async (txn) => {
|
|
2393
|
-
const due = await txn.list({
|
|
2394
|
-
prefix: CLAIM_DEADLINE_PREFIX$1,
|
|
2395
|
-
end: `${CLAIM_DEADLINE_PREFIX$1}${pad$2(now + 1)}`,
|
|
2396
|
-
limit: 129
|
|
2397
|
-
});
|
|
2398
|
-
const page = Array.from(due).slice(0, 128);
|
|
2399
2469
|
const claims = await txn.get(page.map(([, item]) => claimKey$1(item.hookId)));
|
|
2400
|
-
const deletes =
|
|
2470
|
+
const deletes = [];
|
|
2471
|
+
for (const [deadline, item] of page) {
|
|
2401
2472
|
const key = claimKey$1(item.hookId);
|
|
2402
2473
|
const claim = claims.get(key);
|
|
2403
|
-
|
|
2404
|
-
|
|
2474
|
+
const snapshot = observed.get(key);
|
|
2475
|
+
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
2476
|
+
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
2477
|
+
if (committed.get(key)) await this.renewClaim(txn, item.hookId, claim, now);
|
|
2478
|
+
else deletes.push(deadline, key);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2405
2481
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
2406
2482
|
if (due.size > 128) {
|
|
2407
2483
|
await txn.setAlarm(now + 1);
|
|
@@ -2444,51 +2520,68 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2444
2520
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
2445
2521
|
}
|
|
2446
2522
|
async reserve(token, owner, proposedClaimId) {
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
const
|
|
2450
|
-
const
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
existing =
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
deadlineKey
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2523
|
+
for (;;) {
|
|
2524
|
+
const observed = await this.ctx.storage.get(claimKey(token));
|
|
2525
|
+
const expired = observed && observed.expiresAt <= this.now() ? observed : void 0;
|
|
2526
|
+
const committed = expired ? await resolveExpiredHookClaim(this.env, expired.owner, expired.claimId, token) : void 0;
|
|
2527
|
+
const result = await this.ctx.storage.transaction(async (txn) => {
|
|
2528
|
+
const record = recordKey(token);
|
|
2529
|
+
const claim = claimKey(token);
|
|
2530
|
+
const values = await txn.get([record, claim]);
|
|
2531
|
+
const indexed = values.get(record);
|
|
2532
|
+
if (indexed !== void 0) return {
|
|
2533
|
+
claimed: false,
|
|
2534
|
+
holder: ownerFromSerializedHook(indexed)
|
|
2535
|
+
};
|
|
2536
|
+
let existing = values.get(claim);
|
|
2537
|
+
const now = this.now();
|
|
2538
|
+
if (existing && existing.expiresAt <= now) {
|
|
2539
|
+
if (existing.claimId !== expired?.claimId || existing.expiresAt !== expired.expiresAt) return null;
|
|
2540
|
+
if (committed) {
|
|
2541
|
+
await this.renewClaim(txn, token, existing, now);
|
|
2542
|
+
return sameOwner(existing.owner, owner) ? {
|
|
2543
|
+
claimed: true,
|
|
2544
|
+
claimId: existing.claimId
|
|
2545
|
+
} : {
|
|
2546
|
+
claimed: false,
|
|
2547
|
+
holder: existing.owner
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
await txn.delete([claim, existing.deadlineKey]);
|
|
2551
|
+
existing = void 0;
|
|
2552
|
+
}
|
|
2553
|
+
if (existing !== void 0 && !sameOwner(existing.owner, owner)) return {
|
|
2554
|
+
claimed: false,
|
|
2555
|
+
holder: existing.owner
|
|
2556
|
+
};
|
|
2557
|
+
if (existing === void 0) {
|
|
2558
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2559
|
+
const deadlineKey = claimDeadlineKey(token, expiresAt);
|
|
2560
|
+
await txn.put(claim, {
|
|
2561
|
+
owner,
|
|
2562
|
+
claimId: proposedClaimId,
|
|
2563
|
+
reservedAt: now,
|
|
2564
|
+
expiresAt,
|
|
2565
|
+
deadlineKey
|
|
2566
|
+
});
|
|
2567
|
+
await txn.put(deadlineKey, {
|
|
2568
|
+
token,
|
|
2569
|
+
claimId: proposedClaimId,
|
|
2570
|
+
expiresAt
|
|
2571
|
+
});
|
|
2572
|
+
await this.armAtMost(txn, expiresAt);
|
|
2573
|
+
return {
|
|
2574
|
+
claimed: true,
|
|
2575
|
+
claimId: proposedClaimId
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2482
2578
|
return {
|
|
2483
2579
|
claimed: true,
|
|
2484
|
-
claimId:
|
|
2580
|
+
claimId: existing.claimId
|
|
2485
2581
|
};
|
|
2486
|
-
}
|
|
2487
|
-
return
|
|
2488
|
-
|
|
2489
|
-
claimId: existing.claimId
|
|
2490
|
-
};
|
|
2491
|
-
});
|
|
2582
|
+
});
|
|
2583
|
+
if (result) return result;
|
|
2584
|
+
}
|
|
2492
2585
|
}
|
|
2493
2586
|
async finalize(token, serializedHook, owner, claimId) {
|
|
2494
2587
|
if (!await runIsActive(this.env, owner.runId)) {
|
|
@@ -2505,10 +2598,7 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2505
2598
|
const indexed = values.get(record);
|
|
2506
2599
|
if (indexed !== void 0 && !sameOwner(ownerFromSerializedHook(indexed), owner)) throw new Error(`Hook token ${token} is owned by another hook`);
|
|
2507
2600
|
const existingClaim = values.get(claim);
|
|
2508
|
-
if (existingClaim && existingClaim.expiresAt <= this.now()) {
|
|
2509
|
-
await txn.delete([claim, existingClaim.deadlineKey]);
|
|
2510
|
-
return { stored: false };
|
|
2511
|
-
}
|
|
2601
|
+
if (existingClaim && existingClaim.expiresAt <= this.now()) throw new Error(`Hook token ${token} reservation expired`);
|
|
2512
2602
|
if (existingClaim !== void 0 && (!sameOwner(existingClaim.owner, owner) || existingClaim.claimId !== claimId)) throw new Error(`Hook token ${token} is reserved by another hook`);
|
|
2513
2603
|
if (indexed === void 0 && existingClaim === void 0) throw new Error(`Hook token ${token} has no active reservation`);
|
|
2514
2604
|
await txn.put(record, serializedHook);
|
|
@@ -2558,21 +2648,51 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2558
2648
|
const current = await storage.getAlarm();
|
|
2559
2649
|
if (current === null || at < current) await storage.setAlarm(at);
|
|
2560
2650
|
}
|
|
2651
|
+
async renewClaim(txn, token, claim, now) {
|
|
2652
|
+
const expiresAt = now + HOOK_CLAIM_LEASE_MS;
|
|
2653
|
+
const deadlineKey = claimDeadlineKey(token, expiresAt);
|
|
2654
|
+
await txn.delete(claim.deadlineKey);
|
|
2655
|
+
await txn.put(claimKey(token), {
|
|
2656
|
+
...claim,
|
|
2657
|
+
expiresAt,
|
|
2658
|
+
deadlineKey
|
|
2659
|
+
});
|
|
2660
|
+
await txn.put(deadlineKey, {
|
|
2661
|
+
token,
|
|
2662
|
+
claimId: claim.claimId,
|
|
2663
|
+
expiresAt
|
|
2664
|
+
});
|
|
2665
|
+
await this.armAtMost(txn, expiresAt);
|
|
2666
|
+
}
|
|
2561
2667
|
async compactClaims() {
|
|
2562
2668
|
const now = this.now();
|
|
2669
|
+
const due = await this.ctx.storage.list({
|
|
2670
|
+
prefix: CLAIM_DEADLINE_PREFIX,
|
|
2671
|
+
end: `${CLAIM_DEADLINE_PREFIX}${pad$1(now + 1)}`,
|
|
2672
|
+
limit: 129
|
|
2673
|
+
});
|
|
2674
|
+
const page = Array.from(due).slice(0, 128);
|
|
2675
|
+
const observed = await this.ctx.storage.get(page.map(([, item]) => claimKey(item.token)));
|
|
2676
|
+
const committed = /* @__PURE__ */ new Map();
|
|
2677
|
+
await Promise.all(page.map(async ([, item]) => {
|
|
2678
|
+
const claim = observed.get(claimKey(item.token));
|
|
2679
|
+
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
2680
|
+
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId, item.token);
|
|
2681
|
+
committed.set(claimKey(item.token), hook !== null);
|
|
2682
|
+
}));
|
|
2563
2683
|
await this.ctx.storage.transaction(async (txn) => {
|
|
2564
|
-
const due = await txn.list({
|
|
2565
|
-
prefix: CLAIM_DEADLINE_PREFIX,
|
|
2566
|
-
end: `${CLAIM_DEADLINE_PREFIX}${pad$1(now + 1)}`,
|
|
2567
|
-
limit: 129
|
|
2568
|
-
});
|
|
2569
|
-
const page = Array.from(due).slice(0, 128);
|
|
2570
2684
|
const claims = await txn.get(page.map(([, item]) => claimKey(item.token)));
|
|
2571
|
-
const deletes =
|
|
2685
|
+
const deletes = [];
|
|
2686
|
+
for (const [deadline, item] of page) {
|
|
2572
2687
|
const key = claimKey(item.token);
|
|
2573
2688
|
const claim = claims.get(key);
|
|
2574
|
-
|
|
2575
|
-
|
|
2689
|
+
const snapshot = observed.get(key);
|
|
2690
|
+
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
2691
|
+
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
2692
|
+
if (committed.get(key)) await this.renewClaim(txn, item.token, claim, now);
|
|
2693
|
+
else deletes.push(deadline, key);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2576
2696
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
2577
2697
|
if (due.size > 128) {
|
|
2578
2698
|
await txn.setAlarm(now + 1);
|
|
@@ -3049,6 +3169,15 @@ var StreamDO = class extends DurableObject {
|
|
|
3049
3169
|
size: validateSegmentSize(entry[1], start)
|
|
3050
3170
|
});
|
|
3051
3171
|
}
|
|
3172
|
+
let covered = startIndex;
|
|
3173
|
+
for (let index = 0; index < planned.length; index++) {
|
|
3174
|
+
const { start, size } = planned[index];
|
|
3175
|
+
if (start > covered) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3176
|
+
if (index > 0 && start !== covered) throw new Error(`Overlapping persisted stream segment at index ${start}`);
|
|
3177
|
+
if (start + size.count <= covered) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3178
|
+
covered = start + size.count;
|
|
3179
|
+
}
|
|
3180
|
+
if (covered < endIndex) throw new Error(`Missing persisted stream segment at index ${covered}`);
|
|
3052
3181
|
const selected = [];
|
|
3053
3182
|
let plannedBytes = 0;
|
|
3054
3183
|
for (const segment of planned) {
|
|
@@ -3659,6 +3788,28 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
3659
3788
|
async getRun() {
|
|
3660
3789
|
return this.readKey("run");
|
|
3661
3790
|
}
|
|
3791
|
+
/** Fence both supported event-claim forms before an expired shard claim is released. */
|
|
3792
|
+
async resolveExpiredHookClaim(request) {
|
|
3793
|
+
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");
|
|
3794
|
+
return await this.ctx.storage.transaction(async (txn) => {
|
|
3795
|
+
const { tombstone } = await this.retentionState(txn);
|
|
3796
|
+
if (tombstone) return null;
|
|
3797
|
+
const run = await txn.get("run");
|
|
3798
|
+
if (run && isTerminalWorkflowRunStatus(run.status)) return null;
|
|
3799
|
+
const hook = await txn.get(`${HOOK_KEY_PREFIX}${request.hookId}`);
|
|
3800
|
+
if (hook && (request.token === void 0 || hook.token === request.token)) return hook;
|
|
3801
|
+
const cancellation = {
|
|
3802
|
+
hookId: request.hookId,
|
|
3803
|
+
token: request.token,
|
|
3804
|
+
canceledAt: this.now()
|
|
3805
|
+
};
|
|
3806
|
+
await txn.put({
|
|
3807
|
+
[hookClaimCancellationKey(`${request.claimId}:${request.claimId}`)]: cancellation,
|
|
3808
|
+
[hookClaimCancellationKey(`-:${request.claimId}`)]: cancellation
|
|
3809
|
+
});
|
|
3810
|
+
return null;
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3662
3813
|
/** Single authoritative lifecycle answer used after derivative compaction. */
|
|
3663
3814
|
async getLifecycleStatus() {
|
|
3664
3815
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4290,7 +4441,7 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4290
4441
|
* the run expiry check.
|
|
4291
4442
|
*/
|
|
4292
4443
|
async reserveRunQueueMessage(params) {
|
|
4293
|
-
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");
|
|
4444
|
+
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");
|
|
4294
4445
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4295
4446
|
const key = runQueueClaimKey(params.claimName);
|
|
4296
4447
|
const values = await txn.get([
|
|
@@ -4310,17 +4461,19 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4310
4461
|
};
|
|
4311
4462
|
const state = values.get(key) ?? {};
|
|
4312
4463
|
const existing = state.reservation;
|
|
4313
|
-
if (existing && existing.expiresAt > this.now() && existing.messageId !== params.messageId) return {
|
|
4464
|
+
if (existing && existing.expiresAt > this.now() && (existing.messageId !== params.messageId || params.publicationPending)) return {
|
|
4314
4465
|
ok: true,
|
|
4315
4466
|
scope: "run",
|
|
4316
4467
|
admitted: false,
|
|
4317
|
-
messageId: existing.messageId
|
|
4468
|
+
messageId: existing.messageId,
|
|
4469
|
+
...existing.publicationPending ? { publicationPending: true } : {}
|
|
4318
4470
|
};
|
|
4319
4471
|
await txn.put(key, {
|
|
4320
4472
|
...state,
|
|
4321
4473
|
reservation: {
|
|
4322
4474
|
messageId: params.messageId,
|
|
4323
|
-
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt)
|
|
4475
|
+
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt),
|
|
4476
|
+
...params.publicationPending ? { publicationPending: true } : {}
|
|
4324
4477
|
}
|
|
4325
4478
|
});
|
|
4326
4479
|
return {
|
|
@@ -4331,6 +4484,21 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4331
4484
|
};
|
|
4332
4485
|
});
|
|
4333
4486
|
}
|
|
4487
|
+
async confirmRunQueueMessagePublication(params) {
|
|
4488
|
+
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");
|
|
4489
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4490
|
+
const key = runQueueClaimKey(params.claimName);
|
|
4491
|
+
const state = await txn.get(key);
|
|
4492
|
+
if (state?.reservation?.messageId !== params.messageId) return;
|
|
4493
|
+
await txn.put(key, {
|
|
4494
|
+
...state,
|
|
4495
|
+
reservation: {
|
|
4496
|
+
messageId: params.messageId,
|
|
4497
|
+
expiresAt: Math.max(state.reservation.expiresAt, params.expiresAt)
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
});
|
|
4501
|
+
}
|
|
4334
4502
|
/** Delivery-side run expiry check and in-flight claim in one transaction. */
|
|
4335
4503
|
async claimRunQueueMessage(params) {
|
|
4336
4504
|
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");
|
|
@@ -4342,9 +4510,19 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4342
4510
|
key
|
|
4343
4511
|
]);
|
|
4344
4512
|
if (this.tombstoneFrom(values)) return { expired: true };
|
|
4345
|
-
|
|
4346
|
-
const existing = state.claim;
|
|
4513
|
+
let state = values.get(key) ?? {};
|
|
4347
4514
|
const now = this.now();
|
|
4515
|
+
if (state.reservation?.publicationPending && state.reservation.messageId === params.messageId) {
|
|
4516
|
+
state = {
|
|
4517
|
+
...state,
|
|
4518
|
+
reservation: {
|
|
4519
|
+
messageId: params.messageId,
|
|
4520
|
+
expiresAt: Math.max(state.reservation.expiresAt, now + QUEUE_RESERVATION_GRACE_MS)
|
|
4521
|
+
}
|
|
4522
|
+
};
|
|
4523
|
+
await txn.put(key, state);
|
|
4524
|
+
}
|
|
4525
|
+
const existing = state.claim;
|
|
4348
4526
|
if (existing && existing.expiresAt > now) {
|
|
4349
4527
|
if (existing.messageId !== params.messageId) return {
|
|
4350
4528
|
expired: false,
|
|
@@ -4416,6 +4594,18 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4416
4594
|
else await txn.delete(key);
|
|
4417
4595
|
});
|
|
4418
4596
|
}
|
|
4597
|
+
/** Failed enqueue cleanup must preserve delivery already accepted by the broker. */
|
|
4598
|
+
async abandonRunQueueMessageReservation(params) {
|
|
4599
|
+
if (!validRunQueueClaimName(params?.claimName) || typeof params.messageId !== "string" || params.messageId.length === 0) throw new TypeError("world-celld run queue reservation abandonment is invalid");
|
|
4600
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4601
|
+
const key = runQueueClaimKey(params.claimName);
|
|
4602
|
+
const state = await txn.get(key);
|
|
4603
|
+
if (state?.reservation?.messageId !== params.messageId) return;
|
|
4604
|
+
const { reservation: _abandoned, ...rest } = state;
|
|
4605
|
+
if (rest.claim) await txn.put(key, rest);
|
|
4606
|
+
else await txn.delete(key);
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4419
4609
|
async registerQueuePayload(registration) {
|
|
4420
4610
|
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");
|
|
4421
4611
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4472,22 +4662,24 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4472
4662
|
});
|
|
4473
4663
|
}
|
|
4474
4664
|
async reserveQueueMessage(params) {
|
|
4475
|
-
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld queue message reservation is invalid");
|
|
4665
|
+
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");
|
|
4476
4666
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4477
4667
|
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
4478
4668
|
const existing = values.get(QUEUE_RESERVATION_KEY);
|
|
4479
4669
|
const claim = values.get("claim");
|
|
4480
4670
|
const now = this.now();
|
|
4481
|
-
if (existing && existing.expiresAt > now && existing.messageId !== params.messageId) {
|
|
4671
|
+
if (existing && existing.expiresAt > now && (existing.messageId !== params.messageId || params.publicationPending)) {
|
|
4482
4672
|
await txn.setAlarm(Math.min(existing.expiresAt, claim?.expiresAt ?? existing.expiresAt));
|
|
4483
4673
|
return {
|
|
4484
4674
|
admitted: false,
|
|
4485
|
-
messageId: existing.messageId
|
|
4675
|
+
messageId: existing.messageId,
|
|
4676
|
+
...existing.publicationPending ? { publicationPending: true } : {}
|
|
4486
4677
|
};
|
|
4487
4678
|
}
|
|
4488
4679
|
const reservation = {
|
|
4489
4680
|
messageId: params.messageId,
|
|
4490
|
-
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt)
|
|
4681
|
+
expiresAt: Math.max(existing?.expiresAt ?? 0, params.expiresAt),
|
|
4682
|
+
...params.publicationPending ? { publicationPending: true } : {}
|
|
4491
4683
|
};
|
|
4492
4684
|
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
4493
4685
|
await txn.setAlarm(Math.min(reservation.expiresAt, claim?.expiresAt ?? reservation.expiresAt));
|
|
@@ -4497,6 +4689,20 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4497
4689
|
};
|
|
4498
4690
|
});
|
|
4499
4691
|
}
|
|
4692
|
+
async confirmQueueMessagePublication(params) {
|
|
4693
|
+
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isNonNegativeSafeInteger(params.expiresAt)) throw new TypeError("world-celld queue publication confirmation is invalid");
|
|
4694
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4695
|
+
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
4696
|
+
const existing = values.get(QUEUE_RESERVATION_KEY);
|
|
4697
|
+
if (existing?.messageId !== params.messageId) return;
|
|
4698
|
+
const reservation = {
|
|
4699
|
+
messageId: params.messageId,
|
|
4700
|
+
expiresAt: Math.max(existing.expiresAt, params.expiresAt)
|
|
4701
|
+
};
|
|
4702
|
+
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
4703
|
+
await txn.setAlarm(Math.min(reservation.expiresAt, values.get("claim")?.expiresAt ?? reservation.expiresAt));
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4500
4706
|
async completeQueueMessage(messageId) {
|
|
4501
4707
|
if (typeof messageId !== "string" || messageId.length === 0) throw new TypeError("world-celld queue messageId must be a non-empty string");
|
|
4502
4708
|
await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -4516,13 +4722,32 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4516
4722
|
else await txn.deleteAlarm();
|
|
4517
4723
|
});
|
|
4518
4724
|
}
|
|
4725
|
+
/** Abandon the enqueue reservation without releasing an active delivery claim. */
|
|
4726
|
+
async abandonQueueMessageReservation(messageId) {
|
|
4727
|
+
if (typeof messageId !== "string" || messageId.length === 0) throw new TypeError("world-celld queue reservation messageId must be a non-empty string");
|
|
4728
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
4729
|
+
const values = await txn.get([QUEUE_RESERVATION_KEY, "claim"]);
|
|
4730
|
+
if (values.get(QUEUE_RESERVATION_KEY)?.messageId !== messageId) return;
|
|
4731
|
+
await txn.delete(QUEUE_RESERVATION_KEY);
|
|
4732
|
+
const claim = values.get("claim");
|
|
4733
|
+
if (claim) await txn.setAlarm(claim.expiresAt);
|
|
4734
|
+
else await txn.deleteAlarm();
|
|
4735
|
+
});
|
|
4736
|
+
}
|
|
4519
4737
|
async claimInflight(params) {
|
|
4520
4738
|
if (typeof params?.messageId !== "string" || params.messageId.length === 0 || !isPositiveSafeInteger(params.staleMs)) throw new TypeError("world-celld queue claim is invalid");
|
|
4521
4739
|
return await this.ctx.storage.transaction(async (txn) => {
|
|
4522
4740
|
const values = await txn.get(["claim", QUEUE_RESERVATION_KEY]);
|
|
4523
4741
|
const existing = values.get("claim");
|
|
4524
|
-
|
|
4742
|
+
let reservation = values.get(QUEUE_RESERVATION_KEY);
|
|
4525
4743
|
const now = this.now();
|
|
4744
|
+
if (reservation?.publicationPending && reservation.messageId === params.messageId) {
|
|
4745
|
+
reservation = {
|
|
4746
|
+
messageId: params.messageId,
|
|
4747
|
+
expiresAt: Math.max(reservation.expiresAt, now + QUEUE_RESERVATION_GRACE_MS)
|
|
4748
|
+
};
|
|
4749
|
+
await txn.put(QUEUE_RESERVATION_KEY, reservation);
|
|
4750
|
+
}
|
|
4526
4751
|
if (existing && existing.expiresAt > now) {
|
|
4527
4752
|
if (existing.messageId !== params.messageId) return { claimed: false };
|
|
4528
4753
|
if (existing.retryAt === void 0 || existing.retryAt > now) return {
|