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