@ewhauser/world-celld 0.8.1 → 0.9.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 +17 -0
- package/README.md +40 -15
- package/celld-worker/README.md +9 -0
- package/celld-worker/wrangler.jsonc +3 -0
- package/dist/index.d.ts +6 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -8
- package/dist/index.js.map +1 -1
- package/dist/queue-delivery-wvPp6_Hh.d.ts.map +1 -1
- package/dist/queue-protocol-8iiZ8o_1.d.ts +42 -0
- package/dist/queue-protocol-8iiZ8o_1.d.ts.map +1 -0
- package/dist/{run-list-DGsdv6Jf.js → run-list-CHRaDRvU.js} +39 -2
- package/dist/run-list-CHRaDRvU.js.map +1 -0
- package/dist/testing.d.ts +17 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +433 -12
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +14 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +268 -96
- package/dist/worker.js.map +1 -1
- package/package.json +5 -4
- package/dist/run-list-DGsdv6Jf.js.map +0 -1
package/dist/worker.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ interface WorkerEnv {
|
|
|
52
52
|
WORKFLOW_QUEUE_PAYLOADS?: QueuePayloadStore;
|
|
53
53
|
WORLD_SECRET?: string;
|
|
54
54
|
WORKFLOW_CALLBACK_SECRET?: string;
|
|
55
|
+
WORKFLOW_DEPLOYMENT_URLS?: string;
|
|
55
56
|
WORKFLOW_RETENTION_MS?: string | number;
|
|
56
57
|
WORKFLOW_RETENTION_BATCH_SIZE?: string | number;
|
|
57
58
|
}
|
|
@@ -304,6 +305,10 @@ interface RunLifecycleStub {
|
|
|
304
305
|
claimId: string;
|
|
305
306
|
token?: string;
|
|
306
307
|
}): Promise<Hook | null>;
|
|
308
|
+
getCommittedHook(request: {
|
|
309
|
+
hookId: string;
|
|
310
|
+
token?: string;
|
|
311
|
+
}): Promise<Hook | null>;
|
|
307
312
|
}
|
|
308
313
|
interface HookIndexEnv {
|
|
309
314
|
WORKFLOW_DB?: CellNamespaceLike<RunLifecycleStub>;
|
|
@@ -491,6 +496,15 @@ declare class WorkflowRunDO extends DurableObject {
|
|
|
491
496
|
claimId: string;
|
|
492
497
|
token?: string;
|
|
493
498
|
}): Promise<Hook | null>;
|
|
499
|
+
/**
|
|
500
|
+
* Read-only twin of resolveExpiredHookClaim for index repair: the committed
|
|
501
|
+
* hook of a live run, or null. It fences nothing, so a shard may ask while
|
|
502
|
+
* the hook's creation is still in flight.
|
|
503
|
+
*/
|
|
504
|
+
getCommittedHook(request: {
|
|
505
|
+
hookId: string;
|
|
506
|
+
token?: string;
|
|
507
|
+
}): Promise<Hook | null>;
|
|
494
508
|
/** Single authoritative lifecycle answer used after derivative compaction. */
|
|
495
509
|
getLifecycleStatus(): Promise<RunLifecycleStatus>;
|
|
496
510
|
getStep(stepId: string): Promise<RunReadOutcome<Step | null>>;
|
package/dist/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","names":[],"sources":["../src/worker/queue-payload-store.ts","../src/worker/router.ts","../src/worker/do-base.ts","../src/retention.ts","../src/apply-event.ts","../src/stream-protocol.ts","../src/indexes.ts","../src/config.ts","../src/worker/durable-objects/hook-index-shared.ts","../src/worker/durable-objects/HookIdDO.ts","../src/worker/durable-objects/HookTokenDO.ts","../src/worker/durable-objects/RunCatalogDO.ts","../src/worker/durable-objects/StreamDO.ts","../src/worker/durable-objects/WorkflowRunDO.ts","../src/worker/worker.ts"],"mappings":";;;;;;;;;;UAOiB;EACf,MAAM,aAAa,eAAe;IAAY;IAAe;MAAsB;EACnF,KAAK,cAAc;EACnB,OAAO,0BAA0B;;UAGlB;EACf,IACE,aACA,eACA;IAAY,iBAAiB;MAC5B;EACH,IAAI,cAAc;IAAU,QAAQ;;EACpC,OAAO,0BAA0B;;;;;UC6ClB;EACf,WAAW;IAAiB;;EAC5B,IAAI;IAAM;;;UAGF;EACR,KAAK,cAAc;IAAY;IAAsB;MAA0B;;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","names":[],"sources":["../src/worker/queue-payload-store.ts","../src/worker/router.ts","../src/worker/do-base.ts","../src/retention.ts","../src/apply-event.ts","../src/stream-protocol.ts","../src/indexes.ts","../src/config.ts","../src/worker/durable-objects/hook-index-shared.ts","../src/worker/durable-objects/HookIdDO.ts","../src/worker/durable-objects/HookTokenDO.ts","../src/worker/durable-objects/RunCatalogDO.ts","../src/worker/durable-objects/StreamDO.ts","../src/worker/durable-objects/WorkflowRunDO.ts","../src/worker/worker.ts"],"mappings":"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"}
|
package/dist/worker.js
CHANGED
|
@@ -107,6 +107,142 @@ function deliveryQueueClaim(envelope, runStub) {
|
|
|
107
107
|
return envelope.claimScope === "run" && envelope.runId ? runQueueClaim(runStub(envelope.runId), claimName) : cellQueueClaim(runStub(claimName));
|
|
108
108
|
}
|
|
109
109
|
//#endregion
|
|
110
|
+
//#region src/deployment-routing.ts
|
|
111
|
+
function validateUrl(value, deploymentId) {
|
|
112
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`world-celld: deployment URL for ${deploymentId} must be a non-empty string`);
|
|
113
|
+
let url;
|
|
114
|
+
try {
|
|
115
|
+
url = new URL(value);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new Error(`world-celld: deployment URL for ${deploymentId} must be absolute`);
|
|
118
|
+
}
|
|
119
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error(`world-celld: deployment URL for ${deploymentId} is invalid`);
|
|
120
|
+
return url.href.replace(/\/$/, "");
|
|
121
|
+
}
|
|
122
|
+
function validateDeploymentUrls(value) {
|
|
123
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("world-celld: deployment URLs must be an object");
|
|
124
|
+
const urls = Object.create(null);
|
|
125
|
+
for (const [id, url] of Object.entries(value)) {
|
|
126
|
+
if (!id || id.trim() !== id) throw new Error("world-celld: deployment URL IDs must be non-empty and unpadded");
|
|
127
|
+
urls[id] = validateUrl(url, id);
|
|
128
|
+
}
|
|
129
|
+
return urls;
|
|
130
|
+
}
|
|
131
|
+
function configuredDeploymentUrl(urls, deploymentId) {
|
|
132
|
+
if (!Object.hasOwn(urls, deploymentId)) throw new Error(`world-celld: no callback URL configured for deployment ${deploymentId}`);
|
|
133
|
+
return urls[deploymentId];
|
|
134
|
+
}
|
|
135
|
+
function parseDeploymentUrls(value) {
|
|
136
|
+
if (value === void 0 || value === "") return void 0;
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = JSON.parse(value);
|
|
140
|
+
} catch {
|
|
141
|
+
throw new Error("world-celld: WORKFLOW_DEPLOYMENT_URLS must be a JSON object");
|
|
142
|
+
}
|
|
143
|
+
return validateDeploymentUrls(parsed);
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/vendor/shared/serialization.ts
|
|
147
|
+
/** Standard Date fields used across all worlds */
|
|
148
|
+
const DATE_FIELDS = /* @__PURE__ */ new Set([
|
|
149
|
+
"createdAt",
|
|
150
|
+
"updatedAt",
|
|
151
|
+
"startedAt",
|
|
152
|
+
"completedAt",
|
|
153
|
+
"retryAfter"
|
|
154
|
+
]);
|
|
155
|
+
/**
|
|
156
|
+
* JSON reviver that converts ISO date strings to Date objects.
|
|
157
|
+
* Use with JSON.parse(json, dateReviver).
|
|
158
|
+
*/
|
|
159
|
+
function dateReviver(key, value) {
|
|
160
|
+
if (DATE_FIELDS.has(key) && typeof value === "string") {
|
|
161
|
+
const date = new Date(value);
|
|
162
|
+
return Number.isNaN(date.getTime()) ? value : date;
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Base64 helpers. Modified vs upstream: implemented without Buffer so this
|
|
168
|
+
* module is safe inside the celld worker bundle (workerd has no Buffer
|
|
169
|
+
* global). Uses the native Uint8Array base64 methods where the runtime has
|
|
170
|
+
* them (Node >= 25, current V8) and falls back to atob/btoa elsewhere.
|
|
171
|
+
*/
|
|
172
|
+
const nativeToBase64 = Uint8Array.prototype.toBase64;
|
|
173
|
+
const nativeFromBase64 = Uint8Array.fromBase64;
|
|
174
|
+
/** Small enough to stay far below engine argument-count limits and fast to spread. */
|
|
175
|
+
const FALLBACK_ENCODE_CHUNK = 4096;
|
|
176
|
+
/** @internal Exported for tests; use b64encode. */
|
|
177
|
+
function b64encodeFallback(bytes) {
|
|
178
|
+
let bin = "";
|
|
179
|
+
for (let i = 0; i < bytes.length; i += FALLBACK_ENCODE_CHUNK) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + FALLBACK_ENCODE_CHUNK));
|
|
180
|
+
return btoa(bin);
|
|
181
|
+
}
|
|
182
|
+
/** @internal Exported for tests; use b64decode. */
|
|
183
|
+
function b64decodeFallback(text) {
|
|
184
|
+
const bin = atob(text);
|
|
185
|
+
const out = new Uint8Array(bin.length);
|
|
186
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
function b64encode(bytes) {
|
|
190
|
+
return nativeToBase64 ? nativeToBase64.call(bytes) : b64encodeFallback(bytes);
|
|
191
|
+
}
|
|
192
|
+
function b64decode(text) {
|
|
193
|
+
return nativeFromBase64 ? nativeFromBase64(text) : b64decodeFallback(text);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* JSON replacer that encodes Uint8Array as a tagged object with base64 data.
|
|
197
|
+
*/
|
|
198
|
+
function uint8ArrayReplacer(_key, value) {
|
|
199
|
+
if (value instanceof Uint8Array) return {
|
|
200
|
+
__type: "Uint8Array",
|
|
201
|
+
data: b64encode(value)
|
|
202
|
+
};
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* JSON reviver that decodes tagged Uint8Array objects and ISO date strings.
|
|
207
|
+
* Supports both:
|
|
208
|
+
* - New format: { __type: 'Uint8Array', data: '<base64>' }
|
|
209
|
+
* - Legacy format: { __uint8array: true, data: [1, 2, 3] }
|
|
210
|
+
*/
|
|
211
|
+
function uint8ArrayReviver(key, value) {
|
|
212
|
+
if (value && typeof value === "object") {
|
|
213
|
+
const obj = value;
|
|
214
|
+
if (obj.__type === "Uint8Array") {
|
|
215
|
+
if (typeof obj.data !== "string") throw new SyntaxError("Malformed Uint8Array tag");
|
|
216
|
+
try {
|
|
217
|
+
return b64decode(obj.data);
|
|
218
|
+
} catch {
|
|
219
|
+
throw new SyntaxError("Malformed Uint8Array tag");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (obj.__uint8array === true) {
|
|
223
|
+
if (!Array.isArray(obj.data) || !obj.data.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) throw new SyntaxError("Malformed legacy Uint8Array tag");
|
|
224
|
+
return new Uint8Array(obj.data);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return dateReviver(key, value);
|
|
228
|
+
}
|
|
229
|
+
/** Stringify with Uint8Array support. */
|
|
230
|
+
function stringify(value) {
|
|
231
|
+
return JSON.stringify(value, uint8ArrayReplacer);
|
|
232
|
+
}
|
|
233
|
+
/** Parse with Uint8Array and Date support. */
|
|
234
|
+
function parse(text) {
|
|
235
|
+
return JSON.parse(text, uint8ArrayReviver);
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/vendor/shared/util.ts
|
|
239
|
+
function compact(obj) {
|
|
240
|
+
const value = {};
|
|
241
|
+
for (const key in obj) if (obj[key] !== null) value[key] = obj[key];
|
|
242
|
+
else value[key] = void 0;
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
110
246
|
//#region src/worker/queue-delivery.ts
|
|
111
247
|
/** Internal delivery contract. No Request/Response objects cross the RPC boundary. */
|
|
112
248
|
const MAX_QUEUE_DELIVERY_RESPONSE_BYTES = 65536;
|
|
@@ -193,6 +329,17 @@ async function deliverQueueMessage(env, secret, input, attempt) {
|
|
|
193
329
|
return { kind: "complete" };
|
|
194
330
|
}
|
|
195
331
|
}
|
|
332
|
+
const deploymentUrls = parseDeploymentUrls(env.WORKFLOW_DEPLOYMENT_URLS);
|
|
333
|
+
let targetBaseUrl = envelope.targetBaseUrl;
|
|
334
|
+
if (deploymentUrls && envelope.runId) {
|
|
335
|
+
const stored = await runStub(envelope.runId).getRun();
|
|
336
|
+
if (!stored.ok) throw new Error("world-celld: cannot route an expired run");
|
|
337
|
+
const initialId = parse(body).runInput?.deploymentId;
|
|
338
|
+
const pinnedId = stored.value?.deploymentId ?? initialId;
|
|
339
|
+
if (typeof pinnedId !== "string" || pinnedId.length === 0) throw new Error(`world-celld: no pinned deployment for run ${envelope.runId}`);
|
|
340
|
+
if (stored.value && initialId !== void 0 && initialId !== pinnedId) throw new Error(`world-celld: queue run deployment hints disagree for ${envelope.runId}`);
|
|
341
|
+
targetBaseUrl = configuredDeploymentUrl(deploymentUrls, pinnedId);
|
|
342
|
+
}
|
|
196
343
|
const headers = {
|
|
197
344
|
"content-type": "application/json",
|
|
198
345
|
"x-vqs-queue-name": envelope.queueName,
|
|
@@ -200,13 +347,13 @@ async function deliverQueueMessage(env, secret, input, attempt) {
|
|
|
200
347
|
"x-vqs-message-attempt": String(attempt)
|
|
201
348
|
};
|
|
202
349
|
if (env.WORKFLOW_CALLBACK_SECRET) headers["x-workflow-callback-secret"] = env.WORKFLOW_CALLBACK_SECRET;
|
|
203
|
-
const callback = await fetch(`${
|
|
350
|
+
const callback = await fetch(`${targetBaseUrl.replace(/\/$/, "")}/.well-known/workflow/v1/flow`, {
|
|
204
351
|
method: "POST",
|
|
205
352
|
headers,
|
|
206
353
|
body,
|
|
207
354
|
signal: AbortSignal.timeout(3e5)
|
|
208
355
|
});
|
|
209
|
-
if (callback.ok || PERMANENT_QUEUE_STATUSES.has(callback.status)) {
|
|
356
|
+
if (callback.ok || !deploymentUrls && PERMANENT_QUEUE_STATUSES.has(callback.status) || !envelope.runId && PERMANENT_QUEUE_STATUSES.has(callback.status)) {
|
|
210
357
|
await callback.body?.cancel().catch(() => void 0);
|
|
211
358
|
if (envelope.payloadKey) {
|
|
212
359
|
await payloadStore.delete(envelope.payloadKey);
|
|
@@ -247,92 +394,6 @@ async function deliverQueueMessage(env, secret, input, attempt) {
|
|
|
247
394
|
}
|
|
248
395
|
}
|
|
249
396
|
//#endregion
|
|
250
|
-
//#region src/vendor/shared/serialization.ts
|
|
251
|
-
/** Standard Date fields used across all worlds */
|
|
252
|
-
const DATE_FIELDS = /* @__PURE__ */ new Set([
|
|
253
|
-
"createdAt",
|
|
254
|
-
"updatedAt",
|
|
255
|
-
"startedAt",
|
|
256
|
-
"completedAt",
|
|
257
|
-
"retryAfter"
|
|
258
|
-
]);
|
|
259
|
-
/**
|
|
260
|
-
* JSON reviver that converts ISO date strings to Date objects.
|
|
261
|
-
* Use with JSON.parse(json, dateReviver).
|
|
262
|
-
*/
|
|
263
|
-
function dateReviver(key, value) {
|
|
264
|
-
if (DATE_FIELDS.has(key) && typeof value === "string") {
|
|
265
|
-
const date = new Date(value);
|
|
266
|
-
return Number.isNaN(date.getTime()) ? value : date;
|
|
267
|
-
}
|
|
268
|
-
return value;
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Base64 helpers. Modified vs upstream: implemented without Buffer so this
|
|
272
|
-
* module is safe inside the celld worker bundle (workerd has no Buffer
|
|
273
|
-
* global). Uses the native Uint8Array base64 methods where the runtime has
|
|
274
|
-
* them (Node >= 25, current V8) and falls back to atob/btoa elsewhere.
|
|
275
|
-
*/
|
|
276
|
-
const nativeToBase64 = Uint8Array.prototype.toBase64;
|
|
277
|
-
const nativeFromBase64 = Uint8Array.fromBase64;
|
|
278
|
-
/** Small enough to stay far below engine argument-count limits and fast to spread. */
|
|
279
|
-
const FALLBACK_ENCODE_CHUNK = 4096;
|
|
280
|
-
/** @internal Exported for tests; use b64encode. */
|
|
281
|
-
function b64encodeFallback(bytes) {
|
|
282
|
-
let bin = "";
|
|
283
|
-
for (let i = 0; i < bytes.length; i += FALLBACK_ENCODE_CHUNK) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + FALLBACK_ENCODE_CHUNK));
|
|
284
|
-
return btoa(bin);
|
|
285
|
-
}
|
|
286
|
-
/** @internal Exported for tests; use b64decode. */
|
|
287
|
-
function b64decodeFallback(text) {
|
|
288
|
-
const bin = atob(text);
|
|
289
|
-
const out = new Uint8Array(bin.length);
|
|
290
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
291
|
-
return out;
|
|
292
|
-
}
|
|
293
|
-
function b64encode(bytes) {
|
|
294
|
-
return nativeToBase64 ? nativeToBase64.call(bytes) : b64encodeFallback(bytes);
|
|
295
|
-
}
|
|
296
|
-
function b64decode(text) {
|
|
297
|
-
return nativeFromBase64 ? nativeFromBase64(text) : b64decodeFallback(text);
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* JSON reviver that decodes tagged Uint8Array objects and ISO date strings.
|
|
301
|
-
* Supports both:
|
|
302
|
-
* - New format: { __type: 'Uint8Array', data: '<base64>' }
|
|
303
|
-
* - Legacy format: { __uint8array: true, data: [1, 2, 3] }
|
|
304
|
-
*/
|
|
305
|
-
function uint8ArrayReviver(key, value) {
|
|
306
|
-
if (value && typeof value === "object") {
|
|
307
|
-
const obj = value;
|
|
308
|
-
if (obj.__type === "Uint8Array") {
|
|
309
|
-
if (typeof obj.data !== "string") throw new SyntaxError("Malformed Uint8Array tag");
|
|
310
|
-
try {
|
|
311
|
-
return b64decode(obj.data);
|
|
312
|
-
} catch {
|
|
313
|
-
throw new SyntaxError("Malformed Uint8Array tag");
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (obj.__uint8array === true) {
|
|
317
|
-
if (!Array.isArray(obj.data) || !obj.data.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) throw new SyntaxError("Malformed legacy Uint8Array tag");
|
|
318
|
-
return new Uint8Array(obj.data);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return dateReviver(key, value);
|
|
322
|
-
}
|
|
323
|
-
/** Parse with Uint8Array and Date support. */
|
|
324
|
-
function parse(text) {
|
|
325
|
-
return JSON.parse(text, uint8ArrayReviver);
|
|
326
|
-
}
|
|
327
|
-
//#endregion
|
|
328
|
-
//#region src/vendor/shared/util.ts
|
|
329
|
-
function compact(obj) {
|
|
330
|
-
const value = {};
|
|
331
|
-
for (const key in obj) if (obj[key] !== null) value[key] = obj[key];
|
|
332
|
-
else value[key] = void 0;
|
|
333
|
-
return value;
|
|
334
|
-
}
|
|
335
|
-
//#endregion
|
|
336
397
|
//#region src/apply-event.ts
|
|
337
398
|
/** Storage key for the run entity. */
|
|
338
399
|
const RUN_KEY = "run";
|
|
@@ -2683,6 +2744,70 @@ async function resolveExpiredHookClaim(env, owner, claimId, token) {
|
|
|
2683
2744
|
token
|
|
2684
2745
|
});
|
|
2685
2746
|
}
|
|
2747
|
+
/** Read-only owner check used by index repair; unlike the resolver it never fences. */
|
|
2748
|
+
async function getCommittedHook(env, owner, token) {
|
|
2749
|
+
const namespace = env.WORKFLOW_DB;
|
|
2750
|
+
if (!namespace) throw new Error("world-celld hook index missing WORKFLOW_DB binding");
|
|
2751
|
+
return await namespace.get(namespace.idFromName(owner.runId)).getCommittedHook({
|
|
2752
|
+
hookId: owner.hookId,
|
|
2753
|
+
token
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
function warnRepair(action, owner) {
|
|
2757
|
+
console.warn(`world-celld ${action} hook index record for run ${owner.runId} hook ${owner.hookId}`);
|
|
2758
|
+
}
|
|
2759
|
+
function warnRepublished(owner) {
|
|
2760
|
+
warnRepair("republished", owner);
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Publish the record for a claim whose hook committed in its run but whose
|
|
2764
|
+
* creating client stopped before finalizing. The write applies only while the
|
|
2765
|
+
* observed claim is unchanged, so a concurrent release or finalize wins.
|
|
2766
|
+
* Returns the record now stored for the key, if any.
|
|
2767
|
+
*/
|
|
2768
|
+
async function publishCommittedClaim(storage, env, keys, token) {
|
|
2769
|
+
const observed = await storage.get(keys.claim);
|
|
2770
|
+
if (!observed) return null;
|
|
2771
|
+
const hook = await getCommittedHook(env, observed.owner, token);
|
|
2772
|
+
if (!hook) return null;
|
|
2773
|
+
const serialized = stringify(hook);
|
|
2774
|
+
const published = await storage.transaction(async (txn) => {
|
|
2775
|
+
const values = await txn.get([keys.record, keys.claim]);
|
|
2776
|
+
const indexed = values.get(keys.record);
|
|
2777
|
+
if (indexed !== void 0) return {
|
|
2778
|
+
raw: indexed,
|
|
2779
|
+
written: false
|
|
2780
|
+
};
|
|
2781
|
+
const claim = values.get(keys.claim);
|
|
2782
|
+
if (claim?.claimId !== observed.claimId) return {
|
|
2783
|
+
raw: null,
|
|
2784
|
+
written: false
|
|
2785
|
+
};
|
|
2786
|
+
await txn.put(keys.record, serialized);
|
|
2787
|
+
await txn.delete([keys.claim, claim.deadlineKey]);
|
|
2788
|
+
return {
|
|
2789
|
+
raw: serialized,
|
|
2790
|
+
written: true
|
|
2791
|
+
};
|
|
2792
|
+
});
|
|
2793
|
+
if (!published.written) return published.raw;
|
|
2794
|
+
warnRepublished(observed.owner);
|
|
2795
|
+
return await confirmRepublished(storage, env, keys.record, observed.owner, token) ? serialized : null;
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Re-ask the owner after a repair write. A hook disposed between the owner's
|
|
2799
|
+
* answer and the write would otherwise keep a record whose index release was
|
|
2800
|
+
* already spent, so withdraw it.
|
|
2801
|
+
*/
|
|
2802
|
+
async function confirmRepublished(storage, env, record, owner, token) {
|
|
2803
|
+
if (await getCommittedHook(env, owner, token)) return true;
|
|
2804
|
+
await storage.transaction(async (txn) => {
|
|
2805
|
+
const indexed = await txn.get(record);
|
|
2806
|
+
if (indexed !== void 0 && sameOwner(ownerFromSerializedHook(indexed), owner)) await txn.delete(record);
|
|
2807
|
+
});
|
|
2808
|
+
warnRepair("withdrew", owner);
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2686
2811
|
//#endregion
|
|
2687
2812
|
//#region src/worker/durable-objects/HookIdDO.ts
|
|
2688
2813
|
const MAX_RELEASE_BATCH$1 = 64;
|
|
@@ -2705,7 +2830,10 @@ var HookIdDO = class extends DurableObject {
|
|
|
2705
2830
|
return typeof this.env.clock === "function" ? this.env.clock() : Date.now();
|
|
2706
2831
|
}
|
|
2707
2832
|
async get(hookId) {
|
|
2708
|
-
const raw = await this.ctx.storage.get(recordKey$1(hookId))
|
|
2833
|
+
const raw = await this.ctx.storage.get(recordKey$1(hookId)) ?? await publishCommittedClaim(this.ctx.storage, this.env, {
|
|
2834
|
+
record: recordKey$1(hookId),
|
|
2835
|
+
claim: claimKey$1(hookId)
|
|
2836
|
+
});
|
|
2709
2837
|
if (!raw) return null;
|
|
2710
2838
|
const owner = ownerFromSerializedHook(raw);
|
|
2711
2839
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
@@ -2870,8 +2998,9 @@ var HookIdDO = class extends DurableObject {
|
|
|
2870
2998
|
const claim = observed.get(claimKey$1(item.hookId));
|
|
2871
2999
|
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
2872
3000
|
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId);
|
|
2873
|
-
committed.set(claimKey$1(item.hookId), hook
|
|
3001
|
+
committed.set(claimKey$1(item.hookId), hook);
|
|
2874
3002
|
}));
|
|
3003
|
+
const published = [];
|
|
2875
3004
|
await this.ctx.storage.transaction(async (txn) => {
|
|
2876
3005
|
const claims = await txn.get(page.map(([, item]) => claimKey$1(item.hookId)));
|
|
2877
3006
|
const deletes = [];
|
|
@@ -2881,8 +3010,15 @@ var HookIdDO = class extends DurableObject {
|
|
|
2881
3010
|
const snapshot = observed.get(key);
|
|
2882
3011
|
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
2883
3012
|
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
2884
|
-
|
|
2885
|
-
|
|
3013
|
+
const hook = committed.get(key);
|
|
3014
|
+
if (hook) {
|
|
3015
|
+
await txn.put(recordKey$1(item.hookId), stringify(hook));
|
|
3016
|
+
published.push({
|
|
3017
|
+
hookId: item.hookId,
|
|
3018
|
+
owner: claim.owner
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
deletes.push(deadline, key);
|
|
2886
3022
|
}
|
|
2887
3023
|
}
|
|
2888
3024
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
@@ -2897,6 +3033,10 @@ var HookIdDO = class extends DurableObject {
|
|
|
2897
3033
|
if (nextAt === void 0) await txn.deleteAlarm();
|
|
2898
3034
|
else await txn.setAlarm(Math.max(now + 1, nextAt));
|
|
2899
3035
|
});
|
|
3036
|
+
for (const { hookId, owner } of published) {
|
|
3037
|
+
warnRepublished(owner);
|
|
3038
|
+
await confirmRepublished(this.ctx.storage, this.env, recordKey$1(hookId), owner);
|
|
3039
|
+
}
|
|
2900
3040
|
}
|
|
2901
3041
|
};
|
|
2902
3042
|
//#endregion
|
|
@@ -2921,7 +3061,10 @@ var HookTokenDO = class extends DurableObject {
|
|
|
2921
3061
|
return typeof this.env.clock === "function" ? this.env.clock() : Date.now();
|
|
2922
3062
|
}
|
|
2923
3063
|
async get(token) {
|
|
2924
|
-
const raw = await this.ctx.storage.get(recordKey(token))
|
|
3064
|
+
const raw = await this.ctx.storage.get(recordKey(token)) ?? await publishCommittedClaim(this.ctx.storage, this.env, {
|
|
3065
|
+
record: recordKey(token),
|
|
3066
|
+
claim: claimKey(token)
|
|
3067
|
+
}, token);
|
|
2925
3068
|
if (!raw) return null;
|
|
2926
3069
|
const owner = ownerFromSerializedHook(raw);
|
|
2927
3070
|
return await runIsActive(this.env, owner.runId) ? raw : null;
|
|
@@ -3085,8 +3228,9 @@ var HookTokenDO = class extends DurableObject {
|
|
|
3085
3228
|
const claim = observed.get(claimKey(item.token));
|
|
3086
3229
|
if (!claim || claim.claimId !== item.claimId || claim.expiresAt > now) return;
|
|
3087
3230
|
const hook = await resolveExpiredHookClaim(this.env, claim.owner, claim.claimId, item.token);
|
|
3088
|
-
committed.set(claimKey(item.token), hook
|
|
3231
|
+
committed.set(claimKey(item.token), hook);
|
|
3089
3232
|
}));
|
|
3233
|
+
const published = [];
|
|
3090
3234
|
await this.ctx.storage.transaction(async (txn) => {
|
|
3091
3235
|
const claims = await txn.get(page.map(([, item]) => claimKey(item.token)));
|
|
3092
3236
|
const deletes = [];
|
|
@@ -3096,8 +3240,15 @@ var HookTokenDO = class extends DurableObject {
|
|
|
3096
3240
|
const snapshot = observed.get(key);
|
|
3097
3241
|
if (claim?.claimId !== item.claimId || claim.expiresAt > now) deletes.push(deadline);
|
|
3098
3242
|
else if (claim.claimId === snapshot?.claimId && claim.expiresAt === snapshot.expiresAt) {
|
|
3099
|
-
|
|
3100
|
-
|
|
3243
|
+
const hook = committed.get(key);
|
|
3244
|
+
if (hook) {
|
|
3245
|
+
await txn.put(recordKey(item.token), stringify(hook));
|
|
3246
|
+
published.push({
|
|
3247
|
+
token: item.token,
|
|
3248
|
+
owner: claim.owner
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
deletes.push(deadline, key);
|
|
3101
3252
|
}
|
|
3102
3253
|
}
|
|
3103
3254
|
for (let offset = 0; offset < deletes.length; offset += 128) await txn.delete(deletes.slice(offset, offset + 128));
|
|
@@ -3112,6 +3263,10 @@ var HookTokenDO = class extends DurableObject {
|
|
|
3112
3263
|
if (nextAt === void 0) await txn.deleteAlarm();
|
|
3113
3264
|
else await txn.setAlarm(Math.max(now + 1, nextAt));
|
|
3114
3265
|
});
|
|
3266
|
+
for (const { token, owner } of published) {
|
|
3267
|
+
warnRepublished(owner);
|
|
3268
|
+
await confirmRepublished(this.ctx.storage, this.env, recordKey(token), owner, token);
|
|
3269
|
+
}
|
|
3115
3270
|
}
|
|
3116
3271
|
};
|
|
3117
3272
|
//#endregion
|
|
@@ -4162,6 +4317,23 @@ var WorkflowRunDO = class extends DurableObject {
|
|
|
4162
4317
|
return null;
|
|
4163
4318
|
});
|
|
4164
4319
|
}
|
|
4320
|
+
/**
|
|
4321
|
+
* Read-only twin of resolveExpiredHookClaim for index repair: the committed
|
|
4322
|
+
* hook of a live run, or null. It fences nothing, so a shard may ask while
|
|
4323
|
+
* the hook's creation is still in flight.
|
|
4324
|
+
*/
|
|
4325
|
+
async getCommittedHook(request) {
|
|
4326
|
+
if (typeof request?.hookId !== "string" || request.hookId.length === 0 || request.token !== void 0 && typeof request.token !== "string") throw new TypeError("world-celld committed hook lookup is invalid");
|
|
4327
|
+
return await this.ctx.storage.transaction(async (txn) => {
|
|
4328
|
+
const { tombstone } = await this.retentionState(txn);
|
|
4329
|
+
if (tombstone) return null;
|
|
4330
|
+
const run = await txn.get("run");
|
|
4331
|
+
if (!run || isTerminalWorkflowRunStatus(run.status)) return null;
|
|
4332
|
+
const hook = await txn.get(`${HOOK_KEY_PREFIX}${request.hookId}`);
|
|
4333
|
+
if (!hook || request.token !== void 0 && hook.token !== request.token) return null;
|
|
4334
|
+
return hook;
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4165
4337
|
/** Single authoritative lifecycle answer used after derivative compaction. */
|
|
4166
4338
|
async getLifecycleStatus() {
|
|
4167
4339
|
return await this.ctx.storage.transaction(async (txn) => {
|