@ewhauser/world-celld 0.3.0 → 0.3.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 +15 -0
- package/README.md +46 -18
- package/celld-worker/README.md +5 -3
- package/celld-worker/worker.ts +10 -1
- package/celld-worker/wrangler.jsonc +15 -4
- package/dist/index.d.ts +150 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +432 -122
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +47 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1622 -479
- package/dist/testing.js.map +1 -1
- package/dist/util-DmO0XkFw.js +545 -0
- package/dist/util-DmO0XkFw.js.map +1 -0
- package/dist/worker.d.ts +210 -106
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1705 -445
- package/dist/worker.js.map +1 -1
- package/package.json +4 -2
- package/dist/retention-bLY1vcOu.js +0 -164
- package/dist/retention-bLY1vcOu.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { EventSchema, HookSchema, MessageId, SPEC_VERSION_CURRENT, StepSchema, WorkflowRunSchema, parseQueueName } from "@workflow/world";
|
|
1
|
+
import { S as normalizeStreamError, a as stringify, b as encodeStreamWriteBatch, c as MAX_STREAM_BATCH_BYTES, d as MAX_STREAM_LONG_POLL_MS, f as MAX_STREAM_READ_BYTES, g as decodeStreamReadResult, h as STREAM_BATCH_CONTENT_TYPE, i as parse, l as MAX_STREAM_CHUNK_BYTES, m as MAX_STREAM_WRITE_CHUNKS, n as rpcParse, o as createWorkflowIndex, p as MAX_STREAM_READ_CHUNKS, r as rpcStringify, t as compact, u as MAX_STREAM_ERROR_BYTES, v as decodeStreamWriteResult } from "./util-DmO0XkFw.js";
|
|
2
|
+
import { EventSchema, HookSchema, MessageId, SPEC_VERSION_CURRENT, StepSchema, WorkflowRunSchema, isTerminalWorkflowRunStatus, parseQueueName } from "@workflow/world";
|
|
3
3
|
import { EntityConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, TooEarlyError, WorkflowRunNotFoundError, WorkflowWorldError } from "@workflow/errors";
|
|
4
4
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
5
5
|
import { monotonicFactory } from "ulid";
|
|
@@ -8,6 +8,13 @@ function resolveConfig(config) {
|
|
|
8
8
|
const retentionRaw = config?.runRetentionMs ?? process.env.CELLD_RUN_RETENTION_MS ?? 0;
|
|
9
9
|
const runRetentionMs = typeof retentionRaw === "number" ? retentionRaw : Number.parseInt(retentionRaw, 10);
|
|
10
10
|
if (!Number.isSafeInteger(runRetentionMs) || runRetentionMs < 0) throw new Error("world-celld: runRetentionMs must be a non-negative safe integer");
|
|
11
|
+
const rpcTimeoutMs = config?.rpcTimeoutMs ?? 3e4;
|
|
12
|
+
if (!Number.isSafeInteger(rpcTimeoutMs) || rpcTimeoutMs < 1) throw new Error("world-celld: rpcTimeoutMs must be a positive safe integer");
|
|
13
|
+
const streamLongPollMs = config?.streamLongPollMs ?? Math.min(2e4, Math.max(1, rpcTimeoutMs - 1e3));
|
|
14
|
+
if (!Number.isSafeInteger(streamLongPollMs) || streamLongPollMs < 1 || streamLongPollMs > 2e4) throw new Error(`world-celld: streamLongPollMs must be between 1 and ${MAX_STREAM_LONG_POLL_MS}`);
|
|
15
|
+
if (streamLongPollMs >= rpcTimeoutMs) throw new Error("world-celld: streamLongPollMs must be less than rpcTimeoutMs");
|
|
16
|
+
const streamFlushIntervalMs = config?.streamFlushIntervalMs ?? 0;
|
|
17
|
+
if (!Number.isSafeInteger(streamFlushIntervalMs) || streamFlushIntervalMs < 0) throw new Error("world-celld: streamFlushIntervalMs must be a non-negative safe integer");
|
|
11
18
|
return {
|
|
12
19
|
fleetUrl: config?.fleetUrl ?? process.env.CELLD_FLEET_URL,
|
|
13
20
|
secret: config?.secret ?? process.env.CELLD_WORLD_SECRET,
|
|
@@ -16,8 +23,9 @@ function resolveConfig(config) {
|
|
|
16
23
|
baseUrl: config?.baseUrl,
|
|
17
24
|
queueShards: config?.queueShards ?? 1,
|
|
18
25
|
runRetentionMs,
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
streamLongPollMs,
|
|
27
|
+
streamFlushIntervalMs,
|
|
28
|
+
rpcTimeoutMs
|
|
21
29
|
};
|
|
22
30
|
}
|
|
23
31
|
//#endregion
|
|
@@ -46,13 +54,13 @@ var FleetTransportError = class extends Error {
|
|
|
46
54
|
//#endregion
|
|
47
55
|
//#region src/remote/rpc-client.ts
|
|
48
56
|
/** Statuses that mean "the fleet couldn't take the request" (safe to retry reads). */
|
|
49
|
-
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
57
|
+
const RETRYABLE_STATUSES$1 = /* @__PURE__ */ new Set([
|
|
50
58
|
502,
|
|
51
59
|
503,
|
|
52
60
|
504
|
|
53
61
|
]);
|
|
54
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
55
|
-
function delayMs(ms) {
|
|
62
|
+
const DEFAULT_TIMEOUT_MS$1 = 3e4;
|
|
63
|
+
function delayMs$1(ms) {
|
|
56
64
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
@@ -64,12 +72,16 @@ function delayMs(ms) {
|
|
|
64
72
|
* via queue redelivery. Read-only calls retry on transport failures.
|
|
65
73
|
*/
|
|
66
74
|
async function callDO(transport, binding, name, method, args, opts) {
|
|
75
|
+
return await callFleetRoute(transport, `/v1/rpc/${binding}/${encodeURIComponent(name)}/${method}`, args, opts);
|
|
76
|
+
}
|
|
77
|
+
/** Invoke a fixed authenticated worker route using the generic RPC codec. */
|
|
78
|
+
async function callFleetRoute(transport, path, args, opts) {
|
|
67
79
|
const doFetch = transport.fetchImpl ?? fetch;
|
|
68
|
-
const url = `${transport.fleetUrl.replace(/\/$/, "")}
|
|
80
|
+
const url = `${transport.fleetUrl.replace(/\/$/, "")}${path}`;
|
|
69
81
|
const attempts = opts?.idempotent ? 3 : 1;
|
|
70
82
|
let lastError;
|
|
71
83
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
72
|
-
if (attempt > 1) await delayMs(100 * attempt + Math.random() * 200);
|
|
84
|
+
if (attempt > 1) await delayMs$1(100 * attempt + Math.random() * 200);
|
|
73
85
|
let response;
|
|
74
86
|
try {
|
|
75
87
|
response = await doFetch(url, {
|
|
@@ -79,7 +91,7 @@ async function callDO(transport, binding, name, method, args, opts) {
|
|
|
79
91
|
authorization: `Bearer ${transport.secret}`
|
|
80
92
|
},
|
|
81
93
|
body: rpcStringify(args),
|
|
82
|
-
signal: AbortSignal.timeout(Math.max(1, transport.timeoutMs ?? DEFAULT_TIMEOUT_MS))
|
|
94
|
+
signal: AbortSignal.timeout(Math.max(1, transport.timeoutMs ?? DEFAULT_TIMEOUT_MS$1))
|
|
83
95
|
});
|
|
84
96
|
} catch (error) {
|
|
85
97
|
lastError = new FleetTransportError(`world-celld: fleet unreachable at ${url}`, error);
|
|
@@ -99,7 +111,7 @@ async function callDO(transport, binding, name, method, args, opts) {
|
|
|
99
111
|
} catch {
|
|
100
112
|
wire = { message: text || `HTTP ${response.status}` };
|
|
101
113
|
}
|
|
102
|
-
if (RETRYABLE_STATUSES.has(response.status) && attempt < attempts) {
|
|
114
|
+
if (RETRYABLE_STATUSES$1.has(response.status) && attempt < attempts) {
|
|
103
115
|
lastError = reconstructError(wire, response.status);
|
|
104
116
|
continue;
|
|
105
117
|
}
|
|
@@ -108,6 +120,140 @@ async function callDO(transport, binding, name, method, args, opts) {
|
|
|
108
120
|
throw lastError instanceof Error ? lastError : new FleetTransportError("world-celld: rpc failed", lastError);
|
|
109
121
|
}
|
|
110
122
|
//#endregion
|
|
123
|
+
//#region src/remote/stream-client.ts
|
|
124
|
+
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
125
|
+
502,
|
|
126
|
+
503,
|
|
127
|
+
504
|
|
128
|
+
]);
|
|
129
|
+
const READ_ATTEMPTS = 3;
|
|
130
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
131
|
+
const MAX_WRITE_RESPONSE_BYTES = 64;
|
|
132
|
+
const MAX_READ_RESPONSE_BYTES = MAX_STREAM_READ_BYTES + 65536;
|
|
133
|
+
const MAX_ERROR_RESPONSE_BYTES = 65536;
|
|
134
|
+
function delayMs(ms) {
|
|
135
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
136
|
+
}
|
|
137
|
+
function requestSignal(timeoutMs, signal) {
|
|
138
|
+
const timeout = AbortSignal.timeout(Math.max(1, timeoutMs));
|
|
139
|
+
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
140
|
+
}
|
|
141
|
+
function aborted(signal) {
|
|
142
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
143
|
+
}
|
|
144
|
+
async function readBoundedResponse(response, maxBytes) {
|
|
145
|
+
if (Number(response.headers.get("content-length") ?? "0") > maxBytes) {
|
|
146
|
+
await response.body?.cancel("stream response exceeds configured limit").catch(() => void 0);
|
|
147
|
+
throw new FleetTransportError(`world-celld: stream response exceeds ${maxBytes} bytes`);
|
|
148
|
+
}
|
|
149
|
+
if (!response.body) return /* @__PURE__ */ new Uint8Array();
|
|
150
|
+
const reader = response.body.getReader();
|
|
151
|
+
const chunks = [];
|
|
152
|
+
let total = 0;
|
|
153
|
+
try {
|
|
154
|
+
for (;;) {
|
|
155
|
+
const { done, value } = await reader.read();
|
|
156
|
+
if (done) break;
|
|
157
|
+
total += value.byteLength;
|
|
158
|
+
if (total > maxBytes) {
|
|
159
|
+
await reader.cancel("stream response exceeds configured limit").catch(() => void 0);
|
|
160
|
+
throw new FleetTransportError(`world-celld: stream response exceeds ${maxBytes} bytes`);
|
|
161
|
+
}
|
|
162
|
+
chunks.push(value);
|
|
163
|
+
}
|
|
164
|
+
} finally {
|
|
165
|
+
reader.releaseLock();
|
|
166
|
+
}
|
|
167
|
+
const body = new Uint8Array(total);
|
|
168
|
+
let offset = 0;
|
|
169
|
+
for (const chunk of chunks) {
|
|
170
|
+
body.set(chunk, offset);
|
|
171
|
+
offset += chunk.byteLength;
|
|
172
|
+
}
|
|
173
|
+
return body;
|
|
174
|
+
}
|
|
175
|
+
async function errorFromResponse(response) {
|
|
176
|
+
const text = new TextDecoder().decode(await readBoundedResponse(response, MAX_ERROR_RESPONSE_BYTES));
|
|
177
|
+
let wire;
|
|
178
|
+
try {
|
|
179
|
+
wire = JSON.parse(text).error;
|
|
180
|
+
} catch {
|
|
181
|
+
wire = { message: text || `HTTP ${response.status}` };
|
|
182
|
+
}
|
|
183
|
+
return reconstructError(wire, response.status);
|
|
184
|
+
}
|
|
185
|
+
function streamUrl(transport, name) {
|
|
186
|
+
return `${transport.fleetUrl.replace(/\/$/, "")}/v1/streams/${encodeURIComponent(name)}/chunks`;
|
|
187
|
+
}
|
|
188
|
+
async function writeStreamChunks(transport, name, runId, chunks) {
|
|
189
|
+
const body = encodeStreamWriteBatch(chunks);
|
|
190
|
+
if (body.byteLength > 8389632) throw new Error("world-celld: encoded stream batch exceeds configured limit");
|
|
191
|
+
const url = new URL(streamUrl(transport, name));
|
|
192
|
+
url.searchParams.set("runId", runId);
|
|
193
|
+
const doFetch = transport.fetchImpl ?? fetch;
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
196
|
+
response = await doFetch(url, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: {
|
|
199
|
+
"content-type": STREAM_BATCH_CONTENT_TYPE,
|
|
200
|
+
authorization: `Bearer ${transport.secret}`
|
|
201
|
+
},
|
|
202
|
+
body,
|
|
203
|
+
signal: requestSignal(transport.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw new FleetTransportError(`world-celld: fleet unreachable at ${url.href}`, error);
|
|
207
|
+
}
|
|
208
|
+
if (!response.ok) throw await errorFromResponse(response);
|
|
209
|
+
try {
|
|
210
|
+
return decodeStreamWriteResult(await readBoundedResponse(response, MAX_WRITE_RESPONSE_BYTES));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof FleetTransportError) throw error;
|
|
213
|
+
throw new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function readStreamChunks(transport, name, request, signal) {
|
|
217
|
+
const url = new URL(streamUrl(transport, name));
|
|
218
|
+
url.searchParams.set("runId", request.runId);
|
|
219
|
+
url.searchParams.set("startIndex", String(request.startIndex));
|
|
220
|
+
url.searchParams.set("maxChunks", String(request.maxChunks));
|
|
221
|
+
url.searchParams.set("maxBytes", String(request.maxBytes));
|
|
222
|
+
url.searchParams.set("waitMs", String(request.waitMs));
|
|
223
|
+
const doFetch = transport.fetchImpl ?? fetch;
|
|
224
|
+
let lastError;
|
|
225
|
+
for (let attempt = 1; attempt <= READ_ATTEMPTS; attempt++) {
|
|
226
|
+
if (signal?.aborted) throw aborted(signal);
|
|
227
|
+
if (attempt > 1) await delayMs(100 * attempt + Math.random() * 200);
|
|
228
|
+
let response;
|
|
229
|
+
try {
|
|
230
|
+
response = await doFetch(url, {
|
|
231
|
+
method: "GET",
|
|
232
|
+
headers: { authorization: `Bearer ${transport.secret}` },
|
|
233
|
+
signal: requestSignal(transport.timeoutMs ?? DEFAULT_TIMEOUT_MS, signal)
|
|
234
|
+
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (signal?.aborted) throw aborted(signal);
|
|
237
|
+
lastError = new FleetTransportError(`world-celld: fleet unreachable at ${url.href}`, error);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (response.ok) try {
|
|
241
|
+
return decodeStreamReadResult(await readBoundedResponse(response, MAX_READ_RESPONSE_BYTES));
|
|
242
|
+
} catch (error) {
|
|
243
|
+
lastError = new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
|
|
244
|
+
if (attempt < READ_ATTEMPTS) continue;
|
|
245
|
+
throw lastError;
|
|
246
|
+
}
|
|
247
|
+
const error = await errorFromResponse(response);
|
|
248
|
+
if (RETRYABLE_STATUSES.has(response.status) && attempt < READ_ATTEMPTS) {
|
|
249
|
+
lastError = error;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
throw lastError instanceof Error ? lastError : new FleetTransportError("world-celld: stream read failed", lastError);
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
111
257
|
//#region src/remote/namespaces.ts
|
|
112
258
|
const RUNS = {
|
|
113
259
|
methods: [
|
|
@@ -121,7 +267,8 @@ const RUNS = {
|
|
|
121
267
|
"getCleanupStatus",
|
|
122
268
|
"scheduleCleanup",
|
|
123
269
|
"cleanupNow",
|
|
124
|
-
"rearmCleanup"
|
|
270
|
+
"rearmCleanup",
|
|
271
|
+
"resolveHookTokenClaim"
|
|
125
272
|
],
|
|
126
273
|
mutating: /* @__PURE__ */ new Set([
|
|
127
274
|
"applyEvent",
|
|
@@ -132,10 +279,8 @@ const RUNS = {
|
|
|
132
279
|
};
|
|
133
280
|
const STREAMS = {
|
|
134
281
|
methods: [
|
|
135
|
-
"writeChunk",
|
|
136
282
|
"closeStream",
|
|
137
|
-
"
|
|
138
|
-
"getInfo",
|
|
283
|
+
"failStream",
|
|
139
284
|
"registerStream",
|
|
140
285
|
"listStreams",
|
|
141
286
|
"expireRegistry",
|
|
@@ -143,14 +288,28 @@ const STREAMS = {
|
|
|
143
288
|
"expireStream"
|
|
144
289
|
],
|
|
145
290
|
mutating: /* @__PURE__ */ new Set([
|
|
146
|
-
"writeChunk",
|
|
147
291
|
"closeStream",
|
|
292
|
+
"failStream",
|
|
148
293
|
"registerStream",
|
|
149
294
|
"expireRegistry",
|
|
150
295
|
"finalizeRegistry",
|
|
151
296
|
"expireStream"
|
|
152
297
|
])
|
|
153
298
|
};
|
|
299
|
+
function makeStreamNamespace(transport) {
|
|
300
|
+
return {
|
|
301
|
+
idFromName(name) {
|
|
302
|
+
return { toString: () => name };
|
|
303
|
+
},
|
|
304
|
+
get(id) {
|
|
305
|
+
const name = id.toString();
|
|
306
|
+
const control = makeStub(transport, "streams", name, STREAMS);
|
|
307
|
+
control.writeChunks = (runId, chunks) => writeStreamChunks(transport, name, runId, chunks);
|
|
308
|
+
control.readChunks = (request, signal) => readStreamChunks(transport, name, request, signal);
|
|
309
|
+
return control;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
154
313
|
const QUEUE = {
|
|
155
314
|
methods: [
|
|
156
315
|
"enqueue",
|
|
@@ -184,41 +343,76 @@ function makeNamespace(transport, binding, spec) {
|
|
|
184
343
|
}
|
|
185
344
|
};
|
|
186
345
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
346
|
+
const RUN_CATALOG = {
|
|
347
|
+
methods: [
|
|
348
|
+
"upsertRun",
|
|
349
|
+
"list",
|
|
350
|
+
"expireRun"
|
|
351
|
+
],
|
|
352
|
+
mutating: /* @__PURE__ */ new Set()
|
|
353
|
+
};
|
|
354
|
+
const RUN_FENCES = {
|
|
355
|
+
methods: [
|
|
356
|
+
"getStatus",
|
|
357
|
+
"fenceTerminal",
|
|
358
|
+
"fenceExpired"
|
|
359
|
+
],
|
|
360
|
+
mutating: /* @__PURE__ */ new Set()
|
|
361
|
+
};
|
|
362
|
+
const HOOK_TOKENS = {
|
|
363
|
+
methods: [
|
|
364
|
+
"get",
|
|
365
|
+
"reserve",
|
|
366
|
+
"finalize",
|
|
367
|
+
"releaseClaim",
|
|
368
|
+
"releaseBatch"
|
|
369
|
+
],
|
|
370
|
+
mutating: /* @__PURE__ */ new Set()
|
|
371
|
+
};
|
|
372
|
+
const HOOK_IDS = {
|
|
373
|
+
methods: [
|
|
374
|
+
"get",
|
|
375
|
+
"reserve",
|
|
376
|
+
"publish",
|
|
377
|
+
"releaseClaim",
|
|
378
|
+
"releaseBatch"
|
|
379
|
+
],
|
|
380
|
+
mutating: /* @__PURE__ */ new Set()
|
|
381
|
+
};
|
|
217
382
|
function createRemoteEnv(transport) {
|
|
383
|
+
const runCatalog = makeNamespace(transport, "run-catalog", RUN_CATALOG);
|
|
384
|
+
const runFences = makeNamespace(transport, "run-fences", RUN_FENCES);
|
|
385
|
+
const hookTokens = makeNamespace(transport, "hook-tokens", HOOK_TOKENS);
|
|
386
|
+
const hookIds = makeNamespace(transport, "hook-ids", HOOK_IDS);
|
|
387
|
+
const workflowIndex = createWorkflowIndex({
|
|
388
|
+
runCatalog,
|
|
389
|
+
runFences,
|
|
390
|
+
hookTokens,
|
|
391
|
+
hookIds
|
|
392
|
+
});
|
|
218
393
|
return {
|
|
219
394
|
WORKFLOW_DB: makeNamespace(transport, "runs", RUNS),
|
|
220
|
-
WORKFLOW_STREAMS:
|
|
221
|
-
WORKFLOW_INDEX:
|
|
395
|
+
WORKFLOW_STREAMS: makeStreamNamespace(transport),
|
|
396
|
+
WORKFLOW_INDEX: {
|
|
397
|
+
...workflowIndex,
|
|
398
|
+
commitRun: (run, serializedMetadata) => callFleetRoute(transport, "/v1/index/runs/commit", [run, serializedMetadata], { idempotent: true }),
|
|
399
|
+
listRuns: (options) => callFleetRoute(transport, "/v1/index/runs/list", [options], { idempotent: true }),
|
|
400
|
+
expireRun: (request) => callFleetRoute(transport, "/v1/index/runs/expire", [request], { idempotent: true }),
|
|
401
|
+
reserveHook: (token, owner) => callFleetRoute(transport, "/v1/index/hooks/reserve", [token, owner], { idempotent: true }),
|
|
402
|
+
finalizeHookIndexes: (token, hookId, serializedHook, owner, reservation) => callFleetRoute(transport, "/v1/index/hooks/finalize", [
|
|
403
|
+
token,
|
|
404
|
+
hookId,
|
|
405
|
+
serializedHook,
|
|
406
|
+
owner,
|
|
407
|
+
reservation
|
|
408
|
+
], { idempotent: true }),
|
|
409
|
+
releaseHookReservation: (token, owner, reservation) => callFleetRoute(transport, "/v1/index/hooks/release-reservation", [
|
|
410
|
+
token,
|
|
411
|
+
owner,
|
|
412
|
+
reservation
|
|
413
|
+
], { idempotent: true }),
|
|
414
|
+
releaseHookIndexes: (request) => callFleetRoute(transport, "/v1/index/hooks/release", [request], { idempotent: true })
|
|
415
|
+
},
|
|
222
416
|
WORKFLOW_QUEUE: makeNamespace(transport, "queue", QUEUE)
|
|
223
417
|
};
|
|
224
418
|
}
|
|
@@ -366,6 +560,9 @@ function createTestPump(config) {
|
|
|
366
560
|
signal: AbortSignal.timeout(httpTimeoutMs)
|
|
367
561
|
});
|
|
368
562
|
if (response.ok) {
|
|
563
|
+
try {
|
|
564
|
+
await response.body?.cancel();
|
|
565
|
+
} catch {}
|
|
369
566
|
release(envelope);
|
|
370
567
|
return;
|
|
371
568
|
}
|
|
@@ -499,7 +696,7 @@ function createQueue(config) {
|
|
|
499
696
|
status: 503,
|
|
500
697
|
headers: { "Retry-After": String(result.timeoutSeconds) }
|
|
501
698
|
});
|
|
502
|
-
return Response
|
|
699
|
+
return new Response(null, { status: 204 });
|
|
503
700
|
} catch (error) {
|
|
504
701
|
if (isPermanentError(error)) {
|
|
505
702
|
debug("[world-celld queue handler] permanent error:", {
|
|
@@ -580,6 +777,7 @@ function throwOutcomeError(outcome, runId, data) {
|
|
|
580
777
|
case "HOOK_NOT_FOUND": throw new HookNotFoundError(data.correlationId ?? runId);
|
|
581
778
|
case "WAIT_NOT_FOUND": throw new WorkflowWorldError(outcome.message, { status: 404 });
|
|
582
779
|
case "ENTITY_CONFLICT": throw new EntityConflictError(outcome.message);
|
|
780
|
+
case "HOOK_CLAIM_CANCELLED": throw new WorkflowWorldError(outcome.message, { status: 503 });
|
|
583
781
|
case "RUN_EXPIRED": throw new RunExpiredError(outcome.message);
|
|
584
782
|
case "TOO_EARLY": throw new TooEarlyError(outcome.message, { retryAfter: outcome.retryAfterSeconds });
|
|
585
783
|
case "RUN_NOT_SUPPORTED": throw new RunNotSupportedError(outcome.runSpecVersion ?? 0, SPEC_VERSION_CURRENT);
|
|
@@ -593,6 +791,19 @@ function unwrapRead(outcome) {
|
|
|
593
791
|
if (!outcome.ok) throw new RunExpiredError(outcome.message);
|
|
594
792
|
return outcome.value;
|
|
595
793
|
}
|
|
794
|
+
/** Bound cross-run fanout so large list pages cannot create an RPC burst. */
|
|
795
|
+
const RUN_LIST_CONCURRENCY = 8;
|
|
796
|
+
/**
|
|
797
|
+
* Run statuses only move pending -> running -> terminal, and terminal statuses
|
|
798
|
+
* are immutable. An older index value may therefore safely exclude a filter
|
|
799
|
+
* only when it is already terminal, or when the caller asks for pending and
|
|
800
|
+
* the index has advanced beyond pending. Earlier non-terminal metadata cannot
|
|
801
|
+
* exclude a later status because a post-commit index write may need replay.
|
|
802
|
+
*/
|
|
803
|
+
function indexStatusExcludes(indexed, requested) {
|
|
804
|
+
if (requested === void 0 || indexed === requested) return false;
|
|
805
|
+
return isTerminalWorkflowRunStatus(indexed) || requested === "pending";
|
|
806
|
+
}
|
|
596
807
|
function createStorage(config) {
|
|
597
808
|
const { env } = config;
|
|
598
809
|
const cleanup = {
|
|
@@ -620,7 +831,7 @@ function createStorage(config) {
|
|
|
620
831
|
let scanCursor = params?.pagination?.cursor;
|
|
621
832
|
let exhausted = false;
|
|
622
833
|
while (matches.length <= limit && !exhausted) {
|
|
623
|
-
const kvList = await env.WORKFLOW_INDEX.
|
|
834
|
+
const kvList = await env.WORKFLOW_INDEX.listRuns({
|
|
624
835
|
prefix,
|
|
625
836
|
limit: Math.min(1e3, Math.max(50, limit)),
|
|
626
837
|
cursor: scanCursor,
|
|
@@ -630,22 +841,32 @@ function createStorage(config) {
|
|
|
630
841
|
exhausted = true;
|
|
631
842
|
break;
|
|
632
843
|
}
|
|
844
|
+
const candidates = [];
|
|
633
845
|
for (const key of kvList.keys) {
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
846
|
+
const metadata = JSON.parse(key.value);
|
|
847
|
+
if (indexStatusExcludes(metadata.status, params?.status)) continue;
|
|
848
|
+
candidates.push({
|
|
849
|
+
key: key.name,
|
|
850
|
+
metadata
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
for (let offset = 0; offset < candidates.length && matches.length <= limit;) {
|
|
854
|
+
const needed = limit + 1 - matches.length;
|
|
855
|
+
const batch = candidates.slice(offset, offset + Math.min(RUN_LIST_CONCURRENCY, needed));
|
|
856
|
+
const resolved = await Promise.all(batch.map(async ({ key, metadata }) => {
|
|
857
|
+
try {
|
|
858
|
+
const run = await runsGet(metadata.runId, { resolveData: params?.resolveData });
|
|
859
|
+
return !params?.status || run.status === params.status ? {
|
|
860
|
+
key,
|
|
642
861
|
run
|
|
643
|
-
}
|
|
644
|
-
|
|
862
|
+
} : null;
|
|
863
|
+
} catch (error) {
|
|
864
|
+
if (!WorkflowRunNotFoundError.is(error) && !RunExpiredError.is(error)) throw error;
|
|
865
|
+
return null;
|
|
645
866
|
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
867
|
+
}));
|
|
868
|
+
for (const match of resolved) if (match) matches.push(match);
|
|
869
|
+
offset += batch.length;
|
|
649
870
|
}
|
|
650
871
|
exhausted = kvList.list_complete;
|
|
651
872
|
scanCursor = kvList.cursor ?? kvList.keys.at(-1)?.name;
|
|
@@ -667,14 +888,21 @@ function createStorage(config) {
|
|
|
667
888
|
else effectiveRunId = runId;
|
|
668
889
|
const stub = getRunDO(effectiveRunId);
|
|
669
890
|
let tokenHolder;
|
|
670
|
-
let
|
|
891
|
+
let hookAdmission;
|
|
671
892
|
if (data.eventType === "hook_created") {
|
|
672
|
-
|
|
893
|
+
const owner = {
|
|
673
894
|
runId: effectiveRunId,
|
|
674
895
|
hookId: data.correlationId
|
|
675
896
|
};
|
|
676
|
-
const
|
|
677
|
-
|
|
897
|
+
const admission = await env.WORKFLOW_INDEX.reserveHook(data.eventData.token, owner);
|
|
898
|
+
if (admission.admitted) {
|
|
899
|
+
tokenHolder = null;
|
|
900
|
+
hookAdmission = {
|
|
901
|
+
owner,
|
|
902
|
+
reservation: admission.reservation,
|
|
903
|
+
token: data.eventData.token
|
|
904
|
+
};
|
|
905
|
+
} else tokenHolder = admission.holder;
|
|
678
906
|
}
|
|
679
907
|
let outcome;
|
|
680
908
|
try {
|
|
@@ -683,14 +911,27 @@ function createStorage(config) {
|
|
|
683
911
|
data,
|
|
684
912
|
params,
|
|
685
913
|
tokenHolder,
|
|
914
|
+
hookClaimId: hookAdmission?.reservation.claimId,
|
|
686
915
|
cleanup
|
|
687
916
|
});
|
|
688
917
|
} catch (error) {
|
|
689
|
-
if (
|
|
918
|
+
if (hookAdmission) {
|
|
919
|
+
let resolution;
|
|
920
|
+
try {
|
|
921
|
+
resolution = await stub.resolveHookTokenClaim({
|
|
922
|
+
hookId: hookAdmission.owner.hookId,
|
|
923
|
+
token: hookAdmission.token,
|
|
924
|
+
claimId: hookAdmission.reservation.claimId
|
|
925
|
+
});
|
|
926
|
+
} catch {
|
|
927
|
+
throw error;
|
|
928
|
+
}
|
|
929
|
+
if (!resolution.committed) await env.WORKFLOW_INDEX.releaseHookReservation(hookAdmission.token, hookAdmission.owner, hookAdmission.reservation);
|
|
930
|
+
}
|
|
690
931
|
throw error;
|
|
691
932
|
}
|
|
692
933
|
if (!outcome.ok) {
|
|
693
|
-
if (
|
|
934
|
+
if (hookAdmission) await env.WORKFLOW_INDEX.releaseHookReservation(hookAdmission.token, hookAdmission.owner, hookAdmission.reservation);
|
|
694
935
|
throwOutcomeError(outcome, effectiveRunId, data);
|
|
695
936
|
}
|
|
696
937
|
if (outcome.run) {
|
|
@@ -699,24 +940,18 @@ function createStorage(config) {
|
|
|
699
940
|
createdAt: outcome.run.createdAt.toISOString(),
|
|
700
941
|
status: outcome.run.status
|
|
701
942
|
});
|
|
702
|
-
await env.WORKFLOW_INDEX.
|
|
703
|
-
await env.WORKFLOW_INDEX.putOwned(effectiveRunId, globalRunIndexKey(outcome.run), meta);
|
|
943
|
+
await env.WORKFLOW_INDEX.commitRun(outcome.run, meta);
|
|
704
944
|
}
|
|
705
945
|
if (outcome.hookToIndex) {
|
|
706
946
|
const serialized = stringify(outcome.hookToIndex);
|
|
707
|
-
await env.WORKFLOW_INDEX.finalizeHookIndexes(outcome.hookToIndex.token, outcome.hookToIndex.hookId, serialized, hookOwner(outcome.hookToIndex));
|
|
708
|
-
} else if (
|
|
709
|
-
|
|
947
|
+
await env.WORKFLOW_INDEX.finalizeHookIndexes(outcome.hookToIndex.token, outcome.hookToIndex.hookId, serialized, hookOwner(outcome.hookToIndex), hookAdmission?.reservation);
|
|
948
|
+
} else if (hookAdmission) await env.WORKFLOW_INDEX.releaseHookReservation(hookAdmission.token, hookAdmission.owner, hookAdmission.reservation);
|
|
949
|
+
const terminal = Boolean(outcome.run && isTerminalWorkflowRunStatus(outcome.run.status));
|
|
950
|
+
if (outcome.releasedHooks.length > 0) await env.WORKFLOW_INDEX.releaseHookIndexes({
|
|
710
951
|
runId: effectiveRunId,
|
|
711
|
-
|
|
952
|
+
hooks: outcome.releasedHooks,
|
|
953
|
+
terminal
|
|
712
954
|
});
|
|
713
|
-
if (outcome.event?.correlationId) {
|
|
714
|
-
const correlationKey = correlationIndexKey(outcome.event.correlationId, outcome.event.createdAt, outcome.event.eventId, effectiveRunId);
|
|
715
|
-
await env.WORKFLOW_INDEX.putOwned(effectiveRunId, correlationKey, JSON.stringify({
|
|
716
|
-
runId: effectiveRunId,
|
|
717
|
-
eventId: outcome.event.eventId
|
|
718
|
-
}));
|
|
719
|
-
}
|
|
720
955
|
const eventPage = outcome.events === void 0 ? {} : {
|
|
721
956
|
events: outcome.events,
|
|
722
957
|
cursor: outcome.cursor ?? null,
|
|
@@ -805,12 +1040,12 @@ function createStorage(config) {
|
|
|
805
1040
|
},
|
|
806
1041
|
hooks: {
|
|
807
1042
|
async get(hookId, params) {
|
|
808
|
-
const raw = await env.WORKFLOW_INDEX.
|
|
1043
|
+
const raw = await env.WORKFLOW_INDEX.getHookById(hookId);
|
|
809
1044
|
if (!raw) throw new HookNotFoundError(hookId);
|
|
810
1045
|
return filterHookData(parseHook(parse(raw)), params?.resolveData ?? "all");
|
|
811
1046
|
},
|
|
812
1047
|
async getByToken(token, params) {
|
|
813
|
-
const raw = await env.WORKFLOW_INDEX.
|
|
1048
|
+
const raw = await env.WORKFLOW_INDEX.getHookByToken(token);
|
|
814
1049
|
if (!raw) throw new HookNotFoundError(token);
|
|
815
1050
|
return filterHookData(parseHook(parse(raw)), params?.resolveData ?? "all");
|
|
816
1051
|
},
|
|
@@ -834,19 +1069,27 @@ function createStorage(config) {
|
|
|
834
1069
|
}
|
|
835
1070
|
//#endregion
|
|
836
1071
|
//#region src/streamer.ts
|
|
837
|
-
|
|
838
|
-
const READ_POLL_MS = 100;
|
|
839
|
-
/** Chunks fetched per DO round-trip while reading. */
|
|
840
|
-
const READ_BATCH_SIZE = 32;
|
|
841
|
-
/** Default / maximum page sizes for getStreamChunks. */
|
|
1072
|
+
const DEFAULT_LONG_POLL_MS = MAX_STREAM_LONG_POLL_MS;
|
|
842
1073
|
const DEFAULT_CHUNK_PAGE_SIZE = 32;
|
|
843
1074
|
const MAX_CHUNK_PAGE_SIZE = 32;
|
|
1075
|
+
const REGISTERED_STREAM_CACHE_SIZE = 4096;
|
|
844
1076
|
function toBytes(chunk) {
|
|
845
1077
|
return typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
|
|
846
1078
|
}
|
|
1079
|
+
function throwTerminal(result) {
|
|
1080
|
+
if (result.state === "expired") throw new Error("Stream has expired");
|
|
1081
|
+
if (result.state === "errored") {
|
|
1082
|
+
const error = new Error(result.error?.message ?? "Stream failed");
|
|
1083
|
+
error.name = result.error?.name ?? "Error";
|
|
1084
|
+
throw error;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
847
1087
|
function createStreamer(config) {
|
|
848
1088
|
const { env } = config;
|
|
849
|
-
const
|
|
1089
|
+
const streamLongPollMs = config.streamLongPollMs ?? DEFAULT_LONG_POLL_MS;
|
|
1090
|
+
if (!Number.isSafeInteger(streamLongPollMs) || streamLongPollMs < 1 || streamLongPollMs > 2e4) throw new Error(`streamLongPollMs must be between 1 and ${MAX_STREAM_LONG_POLL_MS}`);
|
|
1091
|
+
const streamFlushIntervalMs = config.streamFlushIntervalMs ?? 0;
|
|
1092
|
+
if (!Number.isSafeInteger(streamFlushIntervalMs) || streamFlushIntervalMs < 0) throw new Error("streamFlushIntervalMs must be a non-negative safe integer");
|
|
850
1093
|
const getStreamDO = (streamName) => {
|
|
851
1094
|
const id = env.WORKFLOW_STREAMS.idFromName(`stream:${streamName}`);
|
|
852
1095
|
return env.WORKFLOW_STREAMS.get(id);
|
|
@@ -855,91 +1098,157 @@ function createStreamer(config) {
|
|
|
855
1098
|
const id = env.WORKFLOW_STREAMS.idFromName(`run-streams:${runId}`);
|
|
856
1099
|
return env.WORKFLOW_STREAMS.get(id);
|
|
857
1100
|
};
|
|
858
|
-
/**
|
|
1101
|
+
/** Bounded per-isolate cache; eviction only repeats idempotent registration. */
|
|
859
1102
|
const registeredStreams = /* @__PURE__ */ new Set();
|
|
860
1103
|
async function registerStreamForRun(name, runId) {
|
|
861
1104
|
const cacheKey = `${runId}\0${name}`;
|
|
862
1105
|
if (registeredStreams.has(cacheKey)) return;
|
|
863
1106
|
await getRunRegistryDO(runId).registerStream(runId, name);
|
|
1107
|
+
if (registeredStreams.size >= REGISTERED_STREAM_CACHE_SIZE) {
|
|
1108
|
+
const oldest = registeredStreams.values().next().value;
|
|
1109
|
+
if (oldest !== void 0) registeredStreams.delete(oldest);
|
|
1110
|
+
}
|
|
864
1111
|
registeredStreams.add(cacheKey);
|
|
865
1112
|
}
|
|
866
|
-
const
|
|
1113
|
+
const writeChunksToStream = async (name, runId, input) => {
|
|
1114
|
+
if (input.length === 0) return;
|
|
867
1115
|
const resolvedRunId = await runId;
|
|
1116
|
+
const chunks = input.map(toBytes);
|
|
1117
|
+
for (const chunk of chunks) if (chunk.byteLength > 1048576) throw new Error(`Stream chunk exceeds ${MAX_STREAM_CHUNK_BYTES} bytes`);
|
|
868
1118
|
await registerStreamForRun(name, resolvedRunId);
|
|
869
|
-
|
|
1119
|
+
let batch = [];
|
|
1120
|
+
let batchBytes = 0;
|
|
1121
|
+
for (const chunk of chunks) {
|
|
1122
|
+
if (batch.length > 0 && (batch.length === 32 || batchBytes + chunk.byteLength > 8388608)) {
|
|
1123
|
+
await getStreamDO(name).writeChunks(resolvedRunId, batch);
|
|
1124
|
+
batch = [];
|
|
1125
|
+
batchBytes = 0;
|
|
1126
|
+
}
|
|
1127
|
+
batch.push(chunk);
|
|
1128
|
+
batchBytes += chunk.byteLength;
|
|
1129
|
+
}
|
|
1130
|
+
if (batch.length > 0) await getStreamDO(name).writeChunks(resolvedRunId, batch);
|
|
870
1131
|
};
|
|
1132
|
+
const writeToStream = (name, runId, chunk) => writeChunksToStream(name, runId, [chunk]);
|
|
871
1133
|
const closeStream = async (name, runId) => {
|
|
872
1134
|
const resolvedRunId = await runId;
|
|
873
1135
|
await registerStreamForRun(name, resolvedRunId);
|
|
874
1136
|
await getStreamDO(name).closeStream(resolvedRunId);
|
|
875
1137
|
};
|
|
876
|
-
const
|
|
1138
|
+
const errorStream = async (name, runId, error) => {
|
|
1139
|
+
const resolvedRunId = await runId;
|
|
1140
|
+
await registerStreamForRun(name, resolvedRunId);
|
|
1141
|
+
await getStreamDO(name).failStream(resolvedRunId, normalizeStreamError(error));
|
|
1142
|
+
};
|
|
1143
|
+
const readFromStream = async (name, runId, startIndex = 0) => {
|
|
877
1144
|
const stub = getStreamDO(name);
|
|
878
1145
|
let nextIndex;
|
|
879
1146
|
if (startIndex < 0) {
|
|
880
|
-
const info = await stub.
|
|
1147
|
+
const info = await stub.readChunks({
|
|
1148
|
+
runId,
|
|
1149
|
+
startIndex: 0,
|
|
1150
|
+
maxChunks: 0,
|
|
1151
|
+
maxBytes: MAX_STREAM_READ_BYTES,
|
|
1152
|
+
waitMs: 0
|
|
1153
|
+
});
|
|
1154
|
+
throwTerminal(info);
|
|
881
1155
|
nextIndex = Math.max(0, info.tailIndex + 1 + startIndex);
|
|
882
1156
|
} else nextIndex = startIndex;
|
|
883
1157
|
let cancelled = false;
|
|
1158
|
+
const abortController = new AbortController();
|
|
884
1159
|
return new ReadableStream({
|
|
885
1160
|
async pull(controller) {
|
|
886
|
-
|
|
1161
|
+
for (;;) {
|
|
887
1162
|
if (cancelled) return;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1163
|
+
let result;
|
|
1164
|
+
try {
|
|
1165
|
+
result = await stub.readChunks({
|
|
1166
|
+
runId,
|
|
1167
|
+
startIndex: nextIndex,
|
|
1168
|
+
maxChunks: 32,
|
|
1169
|
+
maxBytes: MAX_STREAM_READ_BYTES,
|
|
1170
|
+
waitMs: streamLongPollMs
|
|
1171
|
+
}, abortController.signal);
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
if (cancelled || abortController.signal.aborted) return;
|
|
1174
|
+
throw error;
|
|
1175
|
+
}
|
|
1176
|
+
if (result.chunks.length > 0) {
|
|
1177
|
+
for (const chunk of result.chunks) controller.enqueue(chunk);
|
|
1178
|
+
nextIndex += result.chunks.length;
|
|
895
1179
|
return;
|
|
896
1180
|
}
|
|
897
|
-
|
|
1181
|
+
throwTerminal(result);
|
|
1182
|
+
if (result.state === "closed") {
|
|
898
1183
|
controller.close();
|
|
899
1184
|
return;
|
|
900
1185
|
}
|
|
901
|
-
await new Promise((resolve) => setTimeout(resolve, readPollMs));
|
|
902
1186
|
}
|
|
903
1187
|
},
|
|
904
1188
|
cancel() {
|
|
905
1189
|
cancelled = true;
|
|
1190
|
+
abortController.abort(new DOMException("Stream reader cancelled", "AbortError"));
|
|
906
1191
|
}
|
|
907
1192
|
});
|
|
908
1193
|
};
|
|
909
1194
|
const listStreamsByRunId = (runId) => getRunRegistryDO(runId).listStreams();
|
|
910
|
-
const getStreamChunks = async (name,
|
|
911
|
-
const
|
|
1195
|
+
const getStreamChunks = async (name, runId, options) => {
|
|
1196
|
+
const requestedLimit = options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE;
|
|
1197
|
+
if (!Number.isSafeInteger(requestedLimit) || requestedLimit < 1) throw new Error("Stream chunk limit must be a positive safe integer");
|
|
1198
|
+
const limit = Math.min(requestedLimit, MAX_CHUNK_PAGE_SIZE);
|
|
912
1199
|
const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
|
|
913
1200
|
if (Number.isNaN(startIndex) || startIndex < 0) throw new Error(`Invalid stream cursor: ${options?.cursor}`);
|
|
914
|
-
const result = await getStreamDO(name).
|
|
1201
|
+
const result = await getStreamDO(name).readChunks({
|
|
1202
|
+
runId,
|
|
915
1203
|
startIndex,
|
|
916
|
-
|
|
1204
|
+
maxChunks: limit,
|
|
1205
|
+
maxBytes: MAX_STREAM_READ_BYTES,
|
|
1206
|
+
waitMs: 0
|
|
917
1207
|
});
|
|
918
|
-
|
|
1208
|
+
throwTerminal(result);
|
|
1209
|
+
const data = result.chunks.map((chunk, offset) => ({
|
|
1210
|
+
index: startIndex + offset,
|
|
1211
|
+
data: chunk
|
|
1212
|
+
}));
|
|
1213
|
+
const nextIndex = startIndex + data.length;
|
|
1214
|
+
const hasMore = nextIndex <= result.tailIndex;
|
|
919
1215
|
return {
|
|
920
|
-
data
|
|
921
|
-
|
|
922
|
-
data: chunk
|
|
923
|
-
})),
|
|
924
|
-
cursor: hasMore ? String(startIndex + limit) : null,
|
|
1216
|
+
data,
|
|
1217
|
+
cursor: hasMore ? String(nextIndex) : null,
|
|
925
1218
|
hasMore,
|
|
926
|
-
done: result.
|
|
1219
|
+
done: result.state === "closed"
|
|
1220
|
+
};
|
|
1221
|
+
};
|
|
1222
|
+
const getStreamInfo = async (name, runId) => {
|
|
1223
|
+
const result = await getStreamDO(name).readChunks({
|
|
1224
|
+
runId,
|
|
1225
|
+
startIndex: 0,
|
|
1226
|
+
maxChunks: 0,
|
|
1227
|
+
maxBytes: MAX_STREAM_READ_BYTES,
|
|
1228
|
+
waitMs: 0
|
|
1229
|
+
});
|
|
1230
|
+
throwTerminal(result);
|
|
1231
|
+
return {
|
|
1232
|
+
tailIndex: result.tailIndex,
|
|
1233
|
+
done: result.state === "closed"
|
|
927
1234
|
};
|
|
928
1235
|
};
|
|
929
|
-
const getStreamInfo = (name, _runId) => getStreamDO(name).getInfo();
|
|
930
1236
|
return {
|
|
1237
|
+
streamFlushIntervalMs,
|
|
931
1238
|
streams: {
|
|
932
1239
|
write: (runId, name, chunk) => writeToStream(name, runId, chunk),
|
|
1240
|
+
writeMulti: (runId, name, chunks) => writeChunksToStream(name, runId, chunks),
|
|
933
1241
|
close: (runId, name) => closeStream(name, runId),
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
},
|
|
1242
|
+
fail: (runId, name, error) => errorStream(name, runId, error),
|
|
1243
|
+
get: (runId, name, startIndex) => readFromStream(name, runId, startIndex),
|
|
937
1244
|
list: listStreamsByRunId,
|
|
938
1245
|
getChunks: (runId, name, options) => getStreamChunks(name, runId, options),
|
|
939
1246
|
getInfo: (runId, name) => getStreamInfo(name, runId)
|
|
940
1247
|
},
|
|
941
1248
|
writeToStream,
|
|
1249
|
+
writeChunksToStream,
|
|
942
1250
|
closeStream,
|
|
1251
|
+
errorStream,
|
|
943
1252
|
readFromStream,
|
|
944
1253
|
listStreamsByRunId,
|
|
945
1254
|
getStreamChunks,
|
|
@@ -988,7 +1297,8 @@ function createCelldWorld(config) {
|
|
|
988
1297
|
});
|
|
989
1298
|
const streamer = createStreamer({
|
|
990
1299
|
env: { WORKFLOW_STREAMS: env.WORKFLOW_STREAMS },
|
|
991
|
-
|
|
1300
|
+
streamLongPollMs: resolved.streamLongPollMs,
|
|
1301
|
+
streamFlushIntervalMs: resolved.streamFlushIntervalMs
|
|
992
1302
|
});
|
|
993
1303
|
const runStub = (runId) => env.WORKFLOW_DB.get(env.WORKFLOW_DB.idFromName(runId));
|
|
994
1304
|
const retention = {
|
|
@@ -1015,6 +1325,6 @@ function createCelldWorld(config) {
|
|
|
1015
1325
|
};
|
|
1016
1326
|
}
|
|
1017
1327
|
//#endregion
|
|
1018
|
-
export { createCelldWorld, createCelldWorld as createWorld };
|
|
1328
|
+
export { MAX_STREAM_BATCH_BYTES, MAX_STREAM_CHUNK_BYTES, MAX_STREAM_ERROR_BYTES, MAX_STREAM_LONG_POLL_MS, MAX_STREAM_READ_BYTES, MAX_STREAM_READ_CHUNKS, MAX_STREAM_WRITE_CHUNKS, createCelldWorld, createCelldWorld as createWorld };
|
|
1019
1329
|
|
|
1020
1330
|
//# sourceMappingURL=index.js.map
|