@ewhauser/world-celld 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +17 -5
- package/dist/index.d.ts +6 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -38
- 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-8iiZ8o_1.d.ts +42 -0
- package/dist/queue-protocol-8iiZ8o_1.d.ts.map +1 -0
- 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-C4B4SBYW.js} +6 -2
- package/dist/{run-list-C987PkJ2.js.map → run-list-C4B4SBYW.js.map} +1 -1
- package/dist/testing.d.ts +17 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +707 -183
- 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 +4 -4
- package/dist/queue-protocol-DKYt8qZJ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.9.0 (2026-10-06)
|
|
6
|
+
|
|
7
|
+
## What's Changed
|
|
8
|
+
* feat: support World spec v8 and deliver demo queues by @ewhauser in https://github.com/ewhauser/world-celld/pull/90
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.8.1...v0.9.0
|
|
12
|
+
|
|
13
|
+
## 0.8.1 (2026-10-02)
|
|
14
|
+
|
|
15
|
+
## What's Changed
|
|
16
|
+
* fix: harden workflow fault recovery by @ewhauser in https://github.com/ewhauser/world-celld/pull/87
|
|
17
|
+
* chore(deps): bump hono from 4.13.2 to 4.13.7 by @dependabot[bot] in https://github.com/ewhauser/world-celld/pull/88
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.8.0...v0.8.1
|
|
21
|
+
|
|
5
22
|
## 0.8.0 (2026-09-27)
|
|
6
23
|
|
|
7
24
|
## What's Changed
|
package/README.md
CHANGED
|
@@ -14,11 +14,18 @@ self-hosted alternative to platform-specific Workflow backends.
|
|
|
14
14
|
|
|
15
15
|
## World protocol compatibility
|
|
16
16
|
|
|
17
|
-
This adapter supports World spec
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
This adapter supports World spec v8 with the Workflow 5 beta packages. This is
|
|
18
|
+
a breaking upgrade for applications using a v7 Workflow runtime: that runtime
|
|
19
|
+
rejects a World advertising v8. This adapter also rejects event writes to runs
|
|
20
|
+
stamped v7. Keep the v7 adapter and matching Workflow packages in place until
|
|
21
|
+
those runs finish, then upgrade the application and adapter together.
|
|
22
|
+
|
|
23
|
+
Event slots are allocated in the same transaction that stores each event, so
|
|
24
|
+
reads see a dense log and failed writes leave no gaps. No background sealing or
|
|
25
|
+
`noop` events are needed.
|
|
26
|
+
|
|
27
|
+
World v8 adds forced hook claims. This adapter does not advertise the optional
|
|
28
|
+
`hookForceClaim` capability, so `experimental_force` is unavailable.
|
|
22
29
|
|
|
23
30
|
Run listing accepts either a single status or an array of statuses (OR matching).
|
|
24
31
|
An empty status array matches no runs.
|
|
@@ -316,6 +323,11 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
|
|
|
316
323
|
|
|
317
324
|
- Delivery is at least once. Workflow steps and other external side effects
|
|
318
325
|
must be idempotent.
|
|
326
|
+
- A concurrent enqueue with the same idempotency key returns a retryable `503`
|
|
327
|
+
while broker publication is pending. Publication confirmation starts the
|
|
328
|
+
normal deduplication window. If a producer stops before publication completes,
|
|
329
|
+
its pending reservation can be replaced after five minutes. Broker delivery
|
|
330
|
+
also confirms publication when the producer's confirmation was interrupted.
|
|
319
331
|
- Native Queue operations are administered with celld's `queue info`, `peek`,
|
|
320
332
|
`pause`, `resume`, `purge`, and `redrive` commands.
|
|
321
333
|
- Run cells expose retention status, scheduling, immediate cleanup, and alarm
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as NativeQueueSendOptions, r as NativeQueueSendResult, t as NativeQueueEnvelope } from "./queue-protocol-8iiZ8o_1.js";
|
|
1
2
|
import { CreateEventParams, CreateEventRequest, Event, GetChunksOptions, Hook, RunCreatedEventRequest, Step, StreamChunksResponse, StreamInfoResponse, Streamer, TerminalWorkflowRunStatus, Wait, WorkflowRun, WorkflowRunStatus, World } from "@workflow/world";
|
|
2
3
|
//#region src/retention.d.ts
|
|
3
4
|
type CleanupPhase = 'retained' | 'index' | 'streams' | 'queues' | 'payload' | 'tombstoned';
|
|
@@ -175,6 +176,11 @@ interface WorkflowRunDOStub {
|
|
|
175
176
|
}): Promise<{
|
|
176
177
|
committed: boolean;
|
|
177
178
|
}>;
|
|
179
|
+
resolveExpiredHookClaim(request: {
|
|
180
|
+
hookId: string;
|
|
181
|
+
claimId: string;
|
|
182
|
+
token?: string;
|
|
183
|
+
}): Promise<Hook | null>;
|
|
178
184
|
getLifecycleStatus(): Promise<RunLifecycleStatus>;
|
|
179
185
|
getRun(): Promise<RunReadOutcome<WorkflowRun | null>>;
|
|
180
186
|
getStep(stepId: string): Promise<RunReadOutcome<Step | null>>;
|
|
@@ -307,46 +313,6 @@ interface StreamDOId {
|
|
|
307
313
|
toString(): string;
|
|
308
314
|
}
|
|
309
315
|
//#endregion
|
|
310
|
-
//#region src/queue-protocol.d.ts
|
|
311
|
-
/**
|
|
312
|
-
* Small broker message carried by celld's native Queue.
|
|
313
|
-
*
|
|
314
|
-
* Run-bearing payload bytes live in object storage so the broker's 128 kB limit does not
|
|
315
|
-
* narrow the Workflow World contract and run retention can delete those bytes
|
|
316
|
-
* without waiting for the Queue's fixed four-day retention window.
|
|
317
|
-
*/
|
|
318
|
-
interface NativeQueueEnvelope {
|
|
319
|
-
version: 1;
|
|
320
|
-
messageId: string;
|
|
321
|
-
queueName: string;
|
|
322
|
-
targetBaseUrl: string;
|
|
323
|
-
runId?: string;
|
|
324
|
-
idempotencyKey?: string;
|
|
325
|
-
payloadKey?: string;
|
|
326
|
-
/**
|
|
327
|
-
* Inline body. Health checks always travel inline; a run-bearing body may
|
|
328
|
-
* travel inline when it is small and carries no user data, in which case
|
|
329
|
-
* delivery checks the run's expiry instead of reading a payload object.
|
|
330
|
-
*/
|
|
331
|
-
body?: string;
|
|
332
|
-
/** Absolute workflow redelivery deadline; long waits are chained by the consumer. */
|
|
333
|
-
notBefore?: number;
|
|
334
|
-
/**
|
|
335
|
-
* Where the idempotency claim lives. Absent: the claim cell named by queue
|
|
336
|
-
* and key. `run`: an entry in the run's own cell (requires `runId`).
|
|
337
|
-
*/
|
|
338
|
-
claimScope?: 'run';
|
|
339
|
-
/** Failed deliveries carried across suspension re-publishes. */
|
|
340
|
-
deliveryFailures?: number;
|
|
341
|
-
suspensionCount?: number;
|
|
342
|
-
}
|
|
343
|
-
interface NativeQueueSendOptions {
|
|
344
|
-
delaySeconds?: number;
|
|
345
|
-
}
|
|
346
|
-
interface NativeQueueSendResult {
|
|
347
|
-
messageId: string;
|
|
348
|
-
}
|
|
349
|
-
//#endregion
|
|
350
316
|
//#region src/queue.d.ts
|
|
351
317
|
interface CelldQueueProducer {
|
|
352
318
|
send(envelope: NativeQueueEnvelope, options?: NativeQueueSendOptions): Promise<NativeQueueSendResult>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/stream-protocol.ts","../src/streamer.ts","../src/queue
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/stream-protocol.ts","../src/streamer.ts","../src/queue.ts","../src/run-list.ts","../src/indexes.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as normalizeStreamError, A as boundedIntegerOption, B as MAX_STREAM_LONG_POLL_MS, D as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, E as MAX_QUEUE_DELIVERY_TIMEOUT_MS, F as queueDelayDeadline, G as STREAM_CHUNK_LIMIT_HEADER, H as MAX_STREAM_READ_CHUNKS, I as strictIntegerSetting, J as decodeStreamWriteResult, K as decodeStreamReadResult, L as MAX_STREAM_BATCH_BYTES, Q as isChunkLimitRejection, R as MAX_STREAM_CHUNK_BYTES, T as MAX_FLEET_RPC_TIMEOUT_MS, U as MAX_STREAM_WRITE_CHUNKS, V as MAX_STREAM_READ_BYTES, W as STREAM_BATCH_CONTENT_TYPE, X as encodeStreamWriteBatch, _ as rpcStringify, b as createWorkflowIndex, c as nativeQueueDelaySeconds, g as rpcParse, h as compact, j as checkedQueueTimestampAdd, l as queueClaimName, n as listRunsPage, v as parse, x as runIndexMetadata, y as stringify, z as MAX_STREAM_ERROR_BYTES } from "./run-list-C4B4SBYW.js";
|
|
2
2
|
import { CreateEventSchema, EventSchema, HookSchema, MessageId, QueuePayloadSchema, SPEC_VERSION_CURRENT, StepSchema, WaitSchema, WorkflowRunSchema, parseQueueName } from "@workflow/world";
|
|
3
3
|
import { EntityConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, TooEarlyError, WorkflowRunNotFoundError, WorkflowWorldError } from "@workflow/errors";
|
|
4
4
|
import { monotonicFactory } from "ulid";
|
|
@@ -106,7 +106,13 @@ async function callFleetRoute(transport, path, args, opts) {
|
|
|
106
106
|
if (attempt < attempts) continue;
|
|
107
107
|
throw lastError;
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
let text;
|
|
110
|
+
try {
|
|
111
|
+
text = await response.text();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
lastError = new FleetTransportError(`world-celld: interrupted error response from ${url}`, error);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
110
116
|
let wire;
|
|
111
117
|
try {
|
|
112
118
|
wire = JSON.parse(text).error;
|
|
@@ -240,7 +246,9 @@ async function writeStreamChunks(transport, name, runId, chunks) {
|
|
|
240
246
|
if (!response.ok) throw await errorFromResponse(response);
|
|
241
247
|
recordStreamChunkLimit(transport, response);
|
|
242
248
|
try {
|
|
243
|
-
|
|
249
|
+
const result = decodeStreamWriteResult(await readBoundedResponse(response, MAX_WRITE_RESPONSE_BYTES));
|
|
250
|
+
if (result.count !== chunks.length) throw new Error("Stream write acknowledgement does not match the requested chunk count");
|
|
251
|
+
return result;
|
|
244
252
|
} catch (error) {
|
|
245
253
|
if (error instanceof FleetTransportError) throw error;
|
|
246
254
|
throw new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
|
|
@@ -256,6 +264,7 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
256
264
|
url.searchParams.set("waitMs", String(request.waitMs));
|
|
257
265
|
const doFetch = transport.fetchImpl ?? fetch;
|
|
258
266
|
let lastError;
|
|
267
|
+
let maxChunks = request.maxChunks;
|
|
259
268
|
for (let attempt = 1; attempt <= READ_ATTEMPTS; attempt++) {
|
|
260
269
|
if (signal?.aborted) throw aborted(signal);
|
|
261
270
|
if (attempt > 1) await delayMs(100 * attempt + Math.random() * 200, signal);
|
|
@@ -278,7 +287,9 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
278
287
|
if (response.ok) {
|
|
279
288
|
recordStreamChunkLimit(transport, response);
|
|
280
289
|
try {
|
|
281
|
-
|
|
290
|
+
const result = decodeStreamReadResult(await readBoundedResponse(response, MAX_READ_RESPONSE_BYTES));
|
|
291
|
+
if (result.startIndex !== request.startIndex || result.chunks.length > maxChunks || result.chunks.reduce((bytes, chunk) => bytes + chunk.byteLength, 0) > request.maxBytes || result.chunks.length > 0 && result.startIndex + result.chunks.length - 1 > result.tailIndex || maxChunks > 0 && result.chunks.length === 0 && result.startIndex <= result.tailIndex && result.state !== "expired") throw new Error("Stream read response does not match the requested range and bounds");
|
|
292
|
+
return result;
|
|
282
293
|
} catch (error) {
|
|
283
294
|
if (signal?.aborted) throw aborted(signal);
|
|
284
295
|
lastError = error instanceof FleetTransportError ? error : new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
|
|
@@ -299,6 +310,7 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
299
310
|
}
|
|
300
311
|
if (request.maxChunks > 32 && isChunkLimitRejection(error) && attempt < READ_ATTEMPTS) {
|
|
301
312
|
resetStreamChunkLimit(transport);
|
|
313
|
+
maxChunks = 32;
|
|
302
314
|
url.searchParams.set("maxChunks", String(32));
|
|
303
315
|
lastError = error;
|
|
304
316
|
continue;
|
|
@@ -323,14 +335,16 @@ const RUNS = {
|
|
|
323
335
|
"scheduleCleanup",
|
|
324
336
|
"cleanupNow",
|
|
325
337
|
"rearmCleanup",
|
|
326
|
-
"resolveHookTokenClaim"
|
|
338
|
+
"resolveHookTokenClaim",
|
|
339
|
+
"resolveExpiredHookClaim"
|
|
327
340
|
],
|
|
328
341
|
mutating: /* @__PURE__ */ new Set([
|
|
329
342
|
"applyEvent",
|
|
330
343
|
"scheduleCleanup",
|
|
331
344
|
"cleanupNow",
|
|
332
345
|
"rearmCleanup",
|
|
333
|
-
"resolveHookTokenClaim"
|
|
346
|
+
"resolveHookTokenClaim",
|
|
347
|
+
"resolveExpiredHookClaim"
|
|
334
348
|
])
|
|
335
349
|
};
|
|
336
350
|
const STREAMS = {
|
|
@@ -581,7 +595,7 @@ function createTestPump(config) {
|
|
|
581
595
|
const inflightMessages = /* @__PURE__ */ new Map();
|
|
582
596
|
let running = false;
|
|
583
597
|
function release(envelope) {
|
|
584
|
-
if (envelope.idempotencyKey) inflightMessages.delete(envelope.idempotencyKey);
|
|
598
|
+
if (envelope.idempotencyKey) inflightMessages.delete(queueClaimName(envelope.queueName, envelope.idempotencyKey));
|
|
585
599
|
}
|
|
586
600
|
function enqueue(pathname, envelope) {
|
|
587
601
|
queues[pathname].push(envelope);
|
|
@@ -594,6 +608,24 @@ function createTestPump(config) {
|
|
|
594
608
|
wakers[pathname].push(() => resolve(queues[pathname].shift() ?? null));
|
|
595
609
|
});
|
|
596
610
|
}
|
|
611
|
+
function retry(envelope, pathname, reason) {
|
|
612
|
+
if (envelope.attempt >= maxAttempts) {
|
|
613
|
+
release(envelope);
|
|
614
|
+
console.error(`[world-celld test pump] dropping ${envelope.messageId} after ${envelope.attempt} attempts: ${reason}`);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const next = {
|
|
618
|
+
...envelope,
|
|
619
|
+
attempt: envelope.attempt + 1
|
|
620
|
+
};
|
|
621
|
+
const backoff = Math.min(MAX_TEST_PUMP_BACKOFF_MS, baseBackoffMs * 2 ** Math.min(next.attempt - 1, 30));
|
|
622
|
+
if (checkedQueueTimestampAdd(Date.now(), backoff, 900001) === null) {
|
|
623
|
+
release(envelope);
|
|
624
|
+
console.error(`[world-celld test pump] dropping ${envelope.messageId}: retry deadline exceeds the queue timestamp horizon`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
delayFor(backoff).then(() => enqueue(pathname, next));
|
|
628
|
+
}
|
|
597
629
|
async function dispatch(envelope, pathname) {
|
|
598
630
|
const url = `${resolveBaseUrl(config)}/.well-known/workflow/v1/${pathname}`;
|
|
599
631
|
const response = await fetch(url, {
|
|
@@ -607,11 +639,12 @@ function createTestPump(config) {
|
|
|
607
639
|
body: stringify(envelope.message),
|
|
608
640
|
signal: AbortSignal.timeout(httpTimeoutMs)
|
|
609
641
|
});
|
|
610
|
-
if (response.ok) {
|
|
642
|
+
if (response.ok || PERMANENT_ERROR_STATUSES.has(response.status)) {
|
|
611
643
|
try {
|
|
612
644
|
await response.body?.cancel();
|
|
613
645
|
} catch {}
|
|
614
646
|
release(envelope);
|
|
647
|
+
if (!response.ok) debug(`[world-celld test pump] dropping ${envelope.messageId}: permanent HTTP ${response.status}`);
|
|
615
648
|
return;
|
|
616
649
|
}
|
|
617
650
|
const text = await response.text();
|
|
@@ -630,27 +663,7 @@ function createTestPump(config) {
|
|
|
630
663
|
return;
|
|
631
664
|
}
|
|
632
665
|
}
|
|
633
|
-
|
|
634
|
-
release(envelope);
|
|
635
|
-
debug(`[world-celld test pump] dropping ${envelope.messageId}: permanent HTTP ${response.status}: ${text}`);
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
if (envelope.attempt < maxAttempts) {
|
|
639
|
-
const next = {
|
|
640
|
-
...envelope,
|
|
641
|
-
attempt: envelope.attempt + 1
|
|
642
|
-
};
|
|
643
|
-
const backoff = Math.min(MAX_TEST_PUMP_BACKOFF_MS, baseBackoffMs * 2 ** Math.min(next.attempt - 1, 30));
|
|
644
|
-
if (checkedQueueTimestampAdd(Date.now(), backoff, 900001) === null) {
|
|
645
|
-
release(envelope);
|
|
646
|
-
console.error(`[world-celld test pump] dropping ${envelope.messageId}: retry deadline exceeds the queue timestamp horizon`);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
delayFor(backoff).then(() => enqueue(pathname, next));
|
|
650
|
-
} else {
|
|
651
|
-
release(envelope);
|
|
652
|
-
console.error(`[world-celld test pump] dropping ${envelope.messageId} after ${envelope.attempt} attempts: HTTP ${response.status}: ${text}`);
|
|
653
|
-
}
|
|
666
|
+
retry(envelope, pathname, `HTTP ${response.status}: ${text}`);
|
|
654
667
|
}
|
|
655
668
|
async function loop(pathname) {
|
|
656
669
|
while (true) {
|
|
@@ -660,19 +673,18 @@ function createTestPump(config) {
|
|
|
660
673
|
try {
|
|
661
674
|
await dispatch(envelope, pathname);
|
|
662
675
|
} catch (err) {
|
|
663
|
-
|
|
664
|
-
console.error(`[world-celld test pump] dispatch error on ${pathname}:`, err);
|
|
676
|
+
retry(envelope, pathname, err instanceof Error ? err.message : String(err));
|
|
665
677
|
}
|
|
666
678
|
}
|
|
667
679
|
}
|
|
668
680
|
return {
|
|
669
681
|
/** Returns the inflight messageId when the key is already claimed. */
|
|
670
|
-
inflight(idempotencyKey) {
|
|
671
|
-
return idempotencyKey ? inflightMessages.get(idempotencyKey) : void 0;
|
|
682
|
+
inflight(queueName, idempotencyKey) {
|
|
683
|
+
return idempotencyKey ? inflightMessages.get(queueClaimName(queueName, idempotencyKey)) : void 0;
|
|
672
684
|
},
|
|
673
685
|
push(pathname, envelope, delaySeconds) {
|
|
674
686
|
if (queueDelayDeadline(Date.now(), delaySeconds ?? 0, 0, 900001) === null) throw new Error("world-celld queue delaySeconds lacks required delivery headroom");
|
|
675
|
-
if (envelope.idempotencyKey) inflightMessages.set(envelope.idempotencyKey, MessageId.parse(envelope.messageId));
|
|
687
|
+
if (envelope.idempotencyKey) inflightMessages.set(queueClaimName(envelope.queueName, envelope.idempotencyKey), MessageId.parse(envelope.messageId));
|
|
676
688
|
if (delaySeconds && delaySeconds > 0) delayFor(delaySeconds * 1e3).then(() => enqueue(pathname, envelope));
|
|
677
689
|
else enqueue(pathname, envelope);
|
|
678
690
|
},
|
|
@@ -700,7 +712,7 @@ function createQueue(config) {
|
|
|
700
712
|
if (queueDelayDeadline(Date.now(), opts?.delaySeconds ?? 0, 0, 900001) === null) throw new Error("world-celld queue delaySeconds lacks required delivery headroom");
|
|
701
713
|
const runId = "runId" in parsedMessage.data && typeof parsedMessage.data.runId === "string" ? parsedMessage.data.runId : void 0;
|
|
702
714
|
if (isTestMode()) {
|
|
703
|
-
const existing = testPump.inflight(opts?.idempotencyKey);
|
|
715
|
+
const existing = testPump.inflight(queueName, opts?.idempotencyKey);
|
|
704
716
|
if (existing) return { messageId: existing };
|
|
705
717
|
const messageId = MessageId.parse(`msg_${generateMessageId()}`);
|
|
706
718
|
testPump.push(QUEUE_PATHNAME, {
|
|
@@ -757,7 +769,7 @@ function createQueue(config) {
|
|
|
757
769
|
queueName: reqQueueName,
|
|
758
770
|
messageId: reqMessageId
|
|
759
771
|
});
|
|
760
|
-
if (result && typeof result.timeoutSeconds === "number") {
|
|
772
|
+
if (!("invoke" in parsedBody.data && parsedBody.data.invoke) && result !== null && typeof result === "object" && "timeoutSeconds" in result && typeof result.timeoutSeconds === "number") {
|
|
761
773
|
if (queueDelayDeadline(Date.now(), result.timeoutSeconds, 1, 900001) === null) throw new Error("Queue handler timeoutSeconds is out of range");
|
|
762
774
|
return Response.json({ timeoutSeconds: result.timeoutSeconds }, {
|
|
763
775
|
status: 503,
|
|
@@ -836,7 +848,9 @@ function parseSuccessEntity(outcome, key, schema) {
|
|
|
836
848
|
const value = outcome[key];
|
|
837
849
|
if (value === void 0) return void 0;
|
|
838
850
|
if (!isRecord(value)) malformedApplyEventOutcome(`${key} is invalid`);
|
|
839
|
-
const
|
|
851
|
+
const entity = compact(value);
|
|
852
|
+
if (key === "step") entity.error = value.error;
|
|
853
|
+
const result = schema.safeParse(entity);
|
|
840
854
|
if (!result.success) malformedApplyEventOutcome(`${key} is invalid`);
|
|
841
855
|
return result.data;
|
|
842
856
|
}
|
|
@@ -991,7 +1005,10 @@ function throwOutcomeError(outcome, runId, data) {
|
|
|
991
1005
|
}
|
|
992
1006
|
}
|
|
993
1007
|
const parseRun = (run) => WorkflowRunSchema.parse(compact(run));
|
|
994
|
-
const parseStep = (step) => StepSchema.parse(
|
|
1008
|
+
const parseStep = (step) => StepSchema.parse({
|
|
1009
|
+
...compact(step),
|
|
1010
|
+
error: step.error
|
|
1011
|
+
});
|
|
995
1012
|
const parseHook = (hook) => HookSchema.parse(compact(hook));
|
|
996
1013
|
const parseEvent = (event) => EventSchema.parse(compact(event));
|
|
997
1014
|
function unwrapRead(outcome) {
|