@ewhauser/world-celld 0.6.0 → 0.7.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 +18 -0
- package/README.md +32 -15
- package/celld-queue-worker/README.md +2 -2
- package/celld-queue-worker/wrangler.jsonc +3 -1
- package/celld-worker/README.md +3 -3
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -100
- package/dist/index.js.map +1 -1
- package/dist/queue-consumer.js +1 -1
- package/dist/{queue-protocol-Ds44uuND.js → queue-protocol-CtH7UUCO.js} +2 -2
- package/dist/{queue-protocol-Ds44uuND.js.map → queue-protocol-CtH7UUCO.js.map} +1 -1
- package/dist/{queue-protocol-CmJNMH_5.js → run-list-B__jKuE7.js} +124 -18
- package/dist/run-list-B__jKuE7.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +211 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +19 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +313 -33
- package/dist/worker.js.map +1 -1
- package/docs/celld-v0.6-upgrade.md +121 -0
- package/package.json +4 -2
- package/dist/queue-protocol-CmJNMH_5.js.map +0 -1
- package/docs/celld-v0.5-upgrade.md +0 -130
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.7.0 (2026-09-27)
|
|
6
|
+
|
|
7
|
+
## What's Changed
|
|
8
|
+
* test(perf): cover mixed workflows, sustained load, and node failover by @ewhauser in https://github.com/ewhauser/world-celld/pull/64
|
|
9
|
+
* feat!: require celld v0.6.0 by @ewhauser in https://github.com/ewhauser/world-celld/pull/67
|
|
10
|
+
* perf(queue): dispatch consumer batches without a fill wait by @ewhauser in https://github.com/ewhauser/world-celld/pull/69
|
|
11
|
+
* perf(codec): use native base64 and a faster fallback by @ewhauser in https://github.com/ewhauser/world-celld/pull/70
|
|
12
|
+
* perf(runs): list runs inside the fleet in one round trip by @ewhauser in https://github.com/ewhauser/world-celld/pull/74
|
|
13
|
+
* perf(indexes): publish run mutations to indexes inside the fleet by @ewhauser in https://github.com/ewhauser/world-celld/pull/72
|
|
14
|
+
* perf(runs): keep idempotent run replays read-only by @ewhauser in https://github.com/ewhauser/world-celld/pull/75
|
|
15
|
+
* perf(events): honor preloadEvents on lazy hook resume by @ewhauser in https://github.com/ewhauser/world-celld/pull/71
|
|
16
|
+
* perf(streams): negotiate 512-chunk stream batches by @ewhauser in https://github.com/ewhauser/world-celld/pull/73
|
|
17
|
+
* perf(retention): finish small terminal cleanups in one pass by @ewhauser in https://github.com/ewhauser/world-celld/pull/76
|
|
18
|
+
* perf(retention): expire streams in pages of up to 256 chunks by @ewhauser in https://github.com/ewhauser/world-celld/pull/77
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.6.0...v0.7.0
|
|
22
|
+
|
|
5
23
|
## 0.6.0 (2026-09-16)
|
|
6
24
|
|
|
7
25
|
## What's Changed
|
package/README.md
CHANGED
|
@@ -87,16 +87,17 @@ requests. It must be reachable from every celld node.
|
|
|
87
87
|
|
|
88
88
|
## Deploy the worker
|
|
89
89
|
|
|
90
|
-
Before deploying, you need a celld v0.
|
|
90
|
+
Before deploying, you need a celld v0.6.0 fleet, `esbuild` on `PATH`, and an
|
|
91
91
|
object store that meets celld's conditional-write requirements. Refer to the
|
|
92
92
|
[celld documentation](https://github.com/denoland/celld) for fleet and storage
|
|
93
93
|
setup.
|
|
94
94
|
|
|
95
|
-
**world-celld
|
|
96
|
-
|
|
95
|
+
**world-celld requires celld v0.6.0.** Older runtimes and node-level
|
|
96
|
+
worker-variable overrides are unsupported.
|
|
97
97
|
|
|
98
|
-
For an existing fleet, follow the [v0.
|
|
99
|
-
|
|
98
|
+
For an existing fleet, follow the [v0.6.0 upgrade and feature review](docs/celld-v0.6-upgrade.md).
|
|
99
|
+
A multi-node fleet uses fleet durability by default; stop every old celld node
|
|
100
|
+
before starting v0.6.0, because that is not a rolling upgrade.
|
|
100
101
|
|
|
101
102
|
Before either deploy, set `vars.WORLD_SECRET` in both copied Wrangler configs
|
|
102
103
|
to the same secret from your secret manager. Set optional
|
|
@@ -104,10 +105,10 @@ to the same secret from your secret manager. Set optional
|
|
|
104
105
|
`vars.WORKFLOW_RETENTION_MS` there to the desired maximum workflow age
|
|
105
106
|
(for example, `"7776000000"` for 90 days). Use private deployment copies outside
|
|
106
107
|
source control: celld persists these variables in the deployment stored in the
|
|
107
|
-
fleet bucket.
|
|
108
|
+
fleet bucket. celld rejects `CELLD_VAR_*`; `.dev.vars` is read only by
|
|
108
109
|
`celld dev`, not by `celld deploy`.
|
|
109
110
|
|
|
110
|
-
No second storage service is required for queue payloads. celld v0.
|
|
111
|
+
No second storage service is required for queue payloads. celld v0.6.0 serves
|
|
111
112
|
the `WORKFLOW_QUEUE_PAYLOADS` binding from the existing fleet bucket under
|
|
112
113
|
`r2/workflow-world-queue-payloads/`. `r2_buckets` is the Wrangler-compatible
|
|
113
114
|
configuration key for that binding; it does not require Cloudflare R2.
|
|
@@ -163,7 +164,7 @@ More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md
|
|
|
163
164
|
and [`celld-queue-worker/README.md`](./celld-queue-worker/README.md).
|
|
164
165
|
|
|
165
166
|
For opt-in bucket or collector telemetry and local `.dev.vars` setup, see
|
|
166
|
-
the [operational examples](docs/celld-v0.
|
|
167
|
+
the [operational examples](docs/celld-v0.6-upgrade.md#adopt-telemetry).
|
|
167
168
|
|
|
168
169
|
## How it works
|
|
169
170
|
|
|
@@ -347,7 +348,7 @@ pnpm test:integration
|
|
|
347
348
|
|
|
348
349
|
### Real celld restart smoke
|
|
349
350
|
|
|
350
|
-
The required CI smoke owns native celld v0.
|
|
351
|
+
The required CI smoke owns native celld v0.6.0 and MinIO processes on loopback,
|
|
351
352
|
uses fresh temporary bucket and runtime state, and kills celld with `SIGKILL`
|
|
352
353
|
before deleting its local working state and starting a new process against the
|
|
353
354
|
bucket-backed state. It checks that:
|
|
@@ -382,12 +383,11 @@ failover correctness.
|
|
|
382
383
|
|
|
383
384
|
The opt-in performance harness starts a fresh MinIO bucket and a single celld
|
|
384
385
|
node with Docker Compose. Its queue workload verifies that every accepted
|
|
385
|
-
message reaches a successful callback, including forced `503` redeliveries.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
two-script native Queue topology as the restart smoke.
|
|
386
|
+
message reaches a successful callback, including forced `503` redeliveries. It
|
|
387
|
+
also measures a mixed run/step/hook/stream/queue lifecycle and terminal-run
|
|
388
|
+
cleanup. Results include throughput plus p50, p95, p99, and maximum latency and
|
|
389
|
+
are saved under `.perf-results/`. The harness pins celld v0.6.0 and deploys the
|
|
390
|
+
same two-script native Queue topology as the restart smoke.
|
|
391
391
|
|
|
392
392
|
> This harness runs a single celld node and leaves the required storage-contract
|
|
393
393
|
> probe enabled. Passing it does not establish multi-node fencing, ownership
|
|
@@ -422,6 +422,23 @@ Useful controls are `PERF_PAYLOAD_BYTES`, `PERF_RETRY_EVERY`,
|
|
|
422
422
|
because local machines vary. Message-loss, message-ID, callback validity, and
|
|
423
423
|
duplicate-success checks are always enforced.
|
|
424
424
|
|
|
425
|
+
`PERF_WORKFLOW_RUNS` and `PERF_WORKFLOW_CONCURRENCY` control the mixed workload.
|
|
426
|
+
Set `PERF_STEADY_SECONDS` to enable a duration-based queue run at
|
|
427
|
+
`PERF_STEADY_RATE` arrivals per second; its JSON records schedule lag and
|
|
428
|
+
one-second backlog samples. For a repeatable concurrency/payload sweep, run
|
|
429
|
+
`pnpm test:perf:minio:matrix`. It runs four isolated profiles and writes a
|
|
430
|
+
manifest with the Git commit, hardware, workload settings, and result paths.
|
|
431
|
+
`PERF_MATRIX_SOAK_SECONDS` changes the final steady profile's duration (60
|
|
432
|
+
seconds by default). Compare runs on the same host with the same profile and
|
|
433
|
+
without unrelated load; the harness does not set portable latency budgets.
|
|
434
|
+
|
|
435
|
+
`pnpm test:perf:minio:failover` starts one celld node, seeds a run, stream, and
|
|
436
|
+
queue workload (including accepted deliveries forced to retry), starts a peer
|
|
437
|
+
against the same bucket, kills the first node, then measures peer recovery and
|
|
438
|
+
delivery of both pending and new messages. Its output is
|
|
439
|
+
`minio-failover-latest.json`. This is a two-node local handoff test, not a
|
|
440
|
+
production object-store or fleet qualification.
|
|
441
|
+
|
|
425
442
|
Bug reports and focused pull requests are welcome. Please include a regression
|
|
426
443
|
test for behavior changes and run the checks above before submitting a PR.
|
|
427
444
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# world-celld Queue consumer
|
|
2
2
|
|
|
3
|
-
This companion celld v0.
|
|
3
|
+
This companion celld v0.6.0 script consumes the native `workflow-world` Queue
|
|
4
4
|
and forwards each delivery to the primary `workflow-world` script through a
|
|
5
5
|
`QueueDeliveryRpc` named service binding. The consumer calls
|
|
6
6
|
`deliver(secret, envelope, attempt)` and handles `complete`, `suspend`, or `retry`
|
|
@@ -16,7 +16,7 @@ celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
|
|
|
16
16
|
|
|
17
17
|
Before deploying, set `vars.WORLD_SECRET` in the copied Wrangler config to the
|
|
18
18
|
same bearer secret used by the primary worker. Keep that private deployment
|
|
19
|
-
config out of source control.
|
|
19
|
+
config out of source control. celld rejects `CELLD_VAR_*` node overrides.
|
|
20
20
|
Deploy the primary worker after this script so it remains the fleet's
|
|
21
21
|
public application. Native Queue operations are available through `celld queue
|
|
22
22
|
info`, `peek`, `pause`, `resume`, `purge`, and `redrive`.
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
{
|
|
12
12
|
"queue": "workflow-world",
|
|
13
13
|
"max_batch_size": 10,
|
|
14
|
-
|
|
14
|
+
// Dispatch as soon as a message is ready. Every workflow step hop
|
|
15
|
+
// goes through this queue, so a batch-fill wait adds to each hop.
|
|
16
|
+
"max_batch_timeout": 0,
|
|
15
17
|
"max_retries": 100,
|
|
16
18
|
"dead_letter_queue": "workflow-world-dlq",
|
|
17
19
|
"max_concurrency": 25,
|
package/celld-worker/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Copy this directory and the companion Queue consumer out of `node_modules` so
|
|
|
15
15
|
worker last. Before deploying, set `vars.WORLD_SECRET` in both copied
|
|
16
16
|
Wrangler configs to the same secret from your secret manager. Keep those
|
|
17
17
|
private deployment copies out of source control; celld stores their variables
|
|
18
|
-
in the fleet bucket. Stop the whole old fleet before starting v0.
|
|
18
|
+
in the fleet bucket. Stop the whole old fleet before starting v0.6.0 nodes:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
cp -r node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
|
|
@@ -30,7 +30,7 @@ to the native Queue.
|
|
|
30
30
|
|
|
31
31
|
Requirements:
|
|
32
32
|
|
|
33
|
-
- celld v0.
|
|
33
|
+
- celld v0.6.0 (the currently tested runtime baseline).
|
|
34
34
|
- `esbuild` on PATH (celld shells out to it).
|
|
35
35
|
- A bucket with conditional-write support (celld's fencing requirement).
|
|
36
36
|
- `WORLD_SECRET` set in the deployment config (`vars.WORLD_SECRET`) —
|
|
@@ -46,7 +46,7 @@ names the Workers-compatible binding API; it does not require Cloudflare R2.
|
|
|
46
46
|
The bundled `wrangler.jsonc` declares an hourly UTC cron trigger. It does no
|
|
47
47
|
catalog work by default. Set `vars.WORKFLOW_RETENTION_MS` to `"7776000000"`
|
|
48
48
|
in the primary Wrangler config and redeploy to enable a maximum workflow age
|
|
49
|
-
measured from run creation.
|
|
49
|
+
measured from run creation. celld rejects `CELLD_VAR_*` node overrides.
|
|
50
50
|
`.dev.vars` overrides apply only to `celld dev`, not production deployments.
|
|
51
51
|
|
|
52
52
|
`7776000000` is 90 days. The policy includes pending and running workflows as
|
package/dist/index.d.ts
CHANGED
|
@@ -149,6 +149,15 @@ interface ApplyEventSuccess {
|
|
|
149
149
|
maxEvents?: number;
|
|
150
150
|
/** Deadline for publishing this authoritative mutation to derivative indexes. */
|
|
151
151
|
indexPublicationExpiresAt?: number;
|
|
152
|
+
/** Set by the worker router after it published `run` to the run catalog. */
|
|
153
|
+
runIndexPublished?: true;
|
|
154
|
+
/** Set by the worker router after it released `releasedHooks` from the hook indexes. */
|
|
155
|
+
hookIndexesReleased?: true;
|
|
156
|
+
/**
|
|
157
|
+
* The unchanged run returned with a `preloadEvents` hook_received replay
|
|
158
|
+
* log. It is not a run mutation, so it carries no index publication.
|
|
159
|
+
*/
|
|
160
|
+
replayRun?: WorkflowRun;
|
|
152
161
|
}
|
|
153
162
|
type ApplyEventOutcome = ApplyEventSuccess | ApplyEventFailure;
|
|
154
163
|
//#endregion
|
|
@@ -211,12 +220,16 @@ interface DurableObjectIdLike {
|
|
|
211
220
|
}
|
|
212
221
|
//#endregion
|
|
213
222
|
//#region src/stream-protocol.d.ts
|
|
214
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* Chunks per write request that every worker accepts. A client sends larger
|
|
225
|
+
* batches only after a worker advertises {@link STREAM_CHUNK_LIMIT_HEADER}.
|
|
226
|
+
*/
|
|
215
227
|
declare const MAX_STREAM_WRITE_CHUNKS = 32;
|
|
216
228
|
/** A chunk remains below the 2 MiB SQLite-backed DO key/value limit. */
|
|
217
229
|
declare const MAX_STREAM_CHUNK_BYTES: number;
|
|
218
230
|
/** Keeps serialized RPC bodies well below the 32 MiB Workers RPC limit. */
|
|
219
231
|
declare const MAX_STREAM_BATCH_BYTES: number;
|
|
232
|
+
/** Chunks per read request that every worker accepts; see {@link MAX_STREAM_WRITE_CHUNKS}. */
|
|
220
233
|
declare const MAX_STREAM_READ_CHUNKS = 32;
|
|
221
234
|
declare const MAX_STREAM_READ_BYTES: number;
|
|
222
235
|
declare const MAX_STREAM_ERROR_BYTES: number;
|
|
@@ -264,6 +277,13 @@ interface CelldStreamer extends Omit<Streamer, 'streams'> {
|
|
|
264
277
|
}
|
|
265
278
|
/** Direct StreamDO RPC surface. Remote stubs specialize read/write as binary HTTP. */
|
|
266
279
|
interface StreamDOStub {
|
|
280
|
+
/**
|
|
281
|
+
* Remote stubs only: the per-request chunk limit the fleet has advertised.
|
|
282
|
+
* Stubs without it are held to the baseline every worker accepts.
|
|
283
|
+
*/
|
|
284
|
+
streamChunkLimit?(): number;
|
|
285
|
+
/** Remote stubs only: drop an advertised limit an older worker rejected. */
|
|
286
|
+
resetStreamChunkLimit?(): void;
|
|
267
287
|
writeChunks(runId: string, chunks: Uint8Array[]): Promise<StreamWriteResult>;
|
|
268
288
|
readChunks(request: StreamReadRequest, signal?: AbortSignal): Promise<StreamReadResult>;
|
|
269
289
|
closeStream(runId: string): Promise<void>;
|
|
@@ -323,6 +343,24 @@ interface CelldQueueProducer {
|
|
|
323
343
|
send(envelope: NativeQueueEnvelope, options?: NativeQueueSendOptions): Promise<NativeQueueSendResult>;
|
|
324
344
|
}
|
|
325
345
|
//#endregion
|
|
346
|
+
//#region src/run-list.d.ts
|
|
347
|
+
type RunStatus = WorkflowRun['status'];
|
|
348
|
+
/** Wire shape of a run listing, shared by the client and the worker router. */
|
|
349
|
+
interface RunListRequest {
|
|
350
|
+
workflowName?: string;
|
|
351
|
+
statuses?: RunStatus[];
|
|
352
|
+
limit: number;
|
|
353
|
+
cursor?: string;
|
|
354
|
+
sortOrder?: 'asc' | 'desc';
|
|
355
|
+
/** 'none' strips run input and output from the returned page. */
|
|
356
|
+
resolveData?: 'none' | 'all';
|
|
357
|
+
}
|
|
358
|
+
interface RunListPage {
|
|
359
|
+
data: WorkflowRun[];
|
|
360
|
+
cursor: string | null;
|
|
361
|
+
hasMore: boolean;
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
326
364
|
//#region src/indexes.d.ts
|
|
327
365
|
interface IndexListOptions {
|
|
328
366
|
prefix?: string;
|
|
@@ -360,6 +398,11 @@ interface WorkflowIndex {
|
|
|
360
398
|
stored: boolean;
|
|
361
399
|
}>;
|
|
362
400
|
listRuns(options?: IndexListOptions): Promise<IndexListPage>;
|
|
401
|
+
/**
|
|
402
|
+
* Remote only: the complete run listing performed inside the fleet. Resolves
|
|
403
|
+
* to null when the worker predates the route; the caller then lists itself.
|
|
404
|
+
*/
|
|
405
|
+
listResolvedRuns?(request: RunListRequest): Promise<RunListPage | null>;
|
|
363
406
|
getHookByToken(token: string): Promise<string | null>;
|
|
364
407
|
getHookById(hookId: string): Promise<string | null>;
|
|
365
408
|
reserveHook(token: string, owner: HookTokenOwner): Promise<HookReservationResult>;
|
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-protocol.ts","../src/queue.ts","../src/indexes.ts","../src/config.ts","../src/index.ts"],"mappings":";;KAOY;UAEK;EACf;EACA;EACA;EACA,WAAW;;EAEX,SAAS;;EAET,cAAc;;EAEd,iBAAiB;EACjB;EACA,OAAO;EACP,OAAO;;EAEP;EACA;EACA;EACA,gBAAgB;EAChB,eAAe;EACf;EACA;;EAEA;;UAGe;EACf;EACA;;EAEA,SAAS;EACT,cAAc;EACd,iBAAiB;EACjB,WAAW;EACX,cAAc;;EAEd,SAAS;;KAGC;KAEA,eAAe;EACrB;EAAU,OAAO;;EACjB;EAAW;EAAqB;EAAiB,WAAW;;UAEjD;EACf;EACA;;UAae;EACf;EACA,OAAO;;UAGQ;EACf;;UAGe;EACf;EACA,OAAO;EACP;;UAGe;EACf;;UAGe;EACf;;UAGe;;EAEf;EACA;;UAGe;;EAEf;;EAEA;;EAEA;EACA;;UAGe;EACf;;;;UC0Ce;;EAEf;EACA,MAAM,qBAAqB;;EAE3B,SAAS;;;;;EAKT;IAAgB;IAAe;;;EAE/B;;EAEA,UAAU;;KAiFA;UAWK;EACf;EACA,MAAM;EACN;;EAEA;;EAEA;;UAGe;EACf;;EAEA,QAAQ;EACR,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;;EAEP;;EAEA,eAAe;IAAQ;IAAgB;;;EAEvC;IAAe;IAAsB,WAAW;;;EAEhD,cAAc;;EAEd,SAAS;;EAET;EACA;;EAEA;;EAEA;;
|
|
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-protocol.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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CreateEventSchema, EventSchema, HookSchema, MessageId, QueuePayloadSchema, SPEC_VERSION_CURRENT, StepSchema, WaitSchema, WorkflowRunSchema,
|
|
1
|
+
import { B as STREAM_BATCH_CONTENT_TYPE, C as MAX_QUEUE_DELIVERY_TIMEOUT_MS, D as checkedQueueTimestampAdd, E as boundedIntegerOption, F as MAX_STREAM_ERROR_BYTES, H as decodeStreamReadResult, I as MAX_STREAM_LONG_POLL_MS, J as isChunkLimitRejection, K as encodeStreamWriteBatch, L as MAX_STREAM_READ_BYTES, M as strictIntegerSetting, N as MAX_STREAM_BATCH_BYTES, P as MAX_STREAM_CHUNK_BYTES, R as MAX_STREAM_READ_CHUNKS, S as MAX_FLEET_RPC_TIMEOUT_MS, V as STREAM_CHUNK_LIMIT_HEADER, W as decodeStreamWriteResult, Y as normalizeStreamError, _ as createWorkflowIndex, a as nativeQueueDelaySeconds, f as compact, g as stringify, h as parse, j as queueDelayDeadline, m as rpcStringify, n as listRunsPage, p as rpcParse, v as runIndexMetadata, w as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, z as MAX_STREAM_WRITE_CHUNKS } from "./run-list-B__jKuE7.js";
|
|
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";
|
|
5
5
|
//#region src/config.ts
|
|
@@ -132,6 +132,23 @@ const READ_ATTEMPTS = 3;
|
|
|
132
132
|
const MAX_WRITE_RESPONSE_BYTES = 64;
|
|
133
133
|
const MAX_READ_RESPONSE_BYTES = MAX_STREAM_READ_BYTES + 65536;
|
|
134
134
|
const MAX_ERROR_RESPONSE_BYTES = 65536;
|
|
135
|
+
/**
|
|
136
|
+
* Per-request chunk limit the fleet has advertised, per transport. Absent
|
|
137
|
+
* until a worker advertises one, so a client never sends an older worker more
|
|
138
|
+
* than the baseline it accepts; a response without the header drops back.
|
|
139
|
+
*/
|
|
140
|
+
const advertisedChunkLimits = /* @__PURE__ */ new WeakMap();
|
|
141
|
+
function streamChunkLimit(transport) {
|
|
142
|
+
return advertisedChunkLimits.get(transport) ?? 32;
|
|
143
|
+
}
|
|
144
|
+
function resetStreamChunkLimit(transport) {
|
|
145
|
+
advertisedChunkLimits.delete(transport);
|
|
146
|
+
}
|
|
147
|
+
function recordStreamChunkLimit(transport, response) {
|
|
148
|
+
const advertised = Number(response.headers.get(STREAM_CHUNK_LIMIT_HEADER));
|
|
149
|
+
if (Number.isSafeInteger(advertised) && advertised > 32) advertisedChunkLimits.set(transport, Math.min(advertised, 512));
|
|
150
|
+
else advertisedChunkLimits.delete(transport);
|
|
151
|
+
}
|
|
135
152
|
function delayMs(ms, signal) {
|
|
136
153
|
if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
|
|
137
154
|
if (signal.aborted) return Promise.reject(aborted(signal));
|
|
@@ -202,7 +219,7 @@ function streamUrl(transport, name) {
|
|
|
202
219
|
async function writeStreamChunks(transport, name, runId, chunks) {
|
|
203
220
|
const timeoutMs = resolveFleetTimeoutMs(transport);
|
|
204
221
|
const body = encodeStreamWriteBatch(chunks);
|
|
205
|
-
if (body.byteLength >
|
|
222
|
+
if (body.byteLength > 8391680) throw new Error("world-celld: encoded stream batch exceeds configured limit");
|
|
206
223
|
const url = new URL(streamUrl(transport, name));
|
|
207
224
|
url.searchParams.set("runId", runId);
|
|
208
225
|
const doFetch = transport.fetchImpl ?? fetch;
|
|
@@ -221,6 +238,7 @@ async function writeStreamChunks(transport, name, runId, chunks) {
|
|
|
221
238
|
throw new FleetTransportError(`world-celld: fleet unreachable at ${url.href}`, error);
|
|
222
239
|
}
|
|
223
240
|
if (!response.ok) throw await errorFromResponse(response);
|
|
241
|
+
recordStreamChunkLimit(transport, response);
|
|
224
242
|
try {
|
|
225
243
|
return decodeStreamWriteResult(await readBoundedResponse(response, MAX_WRITE_RESPONSE_BYTES));
|
|
226
244
|
} catch (error) {
|
|
@@ -257,13 +275,16 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
257
275
|
await response.body?.cancel(aborted(signal)).catch(() => void 0);
|
|
258
276
|
throw aborted(signal);
|
|
259
277
|
}
|
|
260
|
-
if (response.ok)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
278
|
+
if (response.ok) {
|
|
279
|
+
recordStreamChunkLimit(transport, response);
|
|
280
|
+
try {
|
|
281
|
+
return decodeStreamReadResult(await readBoundedResponse(response, MAX_READ_RESPONSE_BYTES));
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (signal?.aborted) throw aborted(signal);
|
|
284
|
+
lastError = error instanceof FleetTransportError ? error : new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
|
|
285
|
+
if (attempt < READ_ATTEMPTS) continue;
|
|
286
|
+
throw lastError;
|
|
287
|
+
}
|
|
267
288
|
}
|
|
268
289
|
let error;
|
|
269
290
|
try {
|
|
@@ -276,6 +297,12 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
276
297
|
lastError = error;
|
|
277
298
|
continue;
|
|
278
299
|
}
|
|
300
|
+
if (request.maxChunks > 32 && isChunkLimitRejection(error) && attempt < READ_ATTEMPTS) {
|
|
301
|
+
resetStreamChunkLimit(transport);
|
|
302
|
+
url.searchParams.set("maxChunks", String(32));
|
|
303
|
+
lastError = error;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
279
306
|
throw error;
|
|
280
307
|
}
|
|
281
308
|
throw lastError instanceof Error ? lastError : new FleetTransportError("world-celld: stream read failed", lastError);
|
|
@@ -335,6 +362,8 @@ function makeStreamNamespace(transport) {
|
|
|
335
362
|
const control = makeStub(transport, "streams", name, STREAMS);
|
|
336
363
|
control.writeChunks = (runId, chunks) => writeStreamChunks(transport, name, runId, chunks);
|
|
337
364
|
control.readChunks = (request, signal) => readStreamChunks(transport, name, request, signal);
|
|
365
|
+
control.streamChunkLimit = () => streamChunkLimit(transport);
|
|
366
|
+
control.resetStreamChunkLimit = () => resetStreamChunkLimit(transport);
|
|
338
367
|
return control;
|
|
339
368
|
}
|
|
340
369
|
};
|
|
@@ -382,6 +411,20 @@ const HOOK_IDS = {
|
|
|
382
411
|
],
|
|
383
412
|
mutating: /* @__PURE__ */ new Set()
|
|
384
413
|
};
|
|
414
|
+
/** Transports whose worker answered the fleet-side run listing with 404. */
|
|
415
|
+
const legacyRunListing = /* @__PURE__ */ new WeakSet();
|
|
416
|
+
async function listResolvedRuns(transport, request) {
|
|
417
|
+
if (legacyRunListing.has(transport)) return null;
|
|
418
|
+
try {
|
|
419
|
+
return await callFleetRoute(transport, "/v1/index/runs/list-resolved", [request], { idempotent: true });
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error.status === 404) {
|
|
422
|
+
legacyRunListing.add(transport);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
385
428
|
function createRemoteEnv(transport) {
|
|
386
429
|
const runCatalog = makeNamespace(transport, "run-catalog", RUN_CATALOG);
|
|
387
430
|
const hookTokens = makeNamespace(transport, "hook-tokens", HOOK_TOKENS);
|
|
@@ -402,6 +445,7 @@ function createRemoteEnv(transport) {
|
|
|
402
445
|
publicationExpiresAt
|
|
403
446
|
], { idempotent: true }),
|
|
404
447
|
listRuns: (options) => callFleetRoute(transport, "/v1/index/runs/list", [options], { idempotent: true }),
|
|
448
|
+
listResolvedRuns: (request) => listResolvedRuns(transport, request),
|
|
405
449
|
expireRun: (request) => callFleetRoute(transport, "/v1/index/runs/expire", [request], { idempotent: true }),
|
|
406
450
|
reserveHook: (token, owner) => callFleetRoute(transport, "/v1/index/hooks/reserve", [token, owner], { idempotent: true }),
|
|
407
451
|
finalizeHookIndexes: (token, hookId, serializedHook, owner, reservation) => callFleetRoute(transport, "/v1/index/hooks/finalize", [
|
|
@@ -458,7 +502,7 @@ const debug = createDebugLogger("celld-world");
|
|
|
458
502
|
* Vendored from vinnymac/worlds packages/world-cloudflare/src/queue.ts
|
|
459
503
|
* (Apache-2.0, see NOTICE), modified for celld:
|
|
460
504
|
*
|
|
461
|
-
* - Production publishes a small pointer through celld v0.
|
|
505
|
+
* - Production publishes a small pointer through celld v0.6.0 native Queues.
|
|
462
506
|
* The deployed Queue consumer forwards deliveries to the same HTTP handler
|
|
463
507
|
* used by the in-process test pump.
|
|
464
508
|
* - Run-bearing message bodies live in the fleet object store. This preserves
|
|
@@ -828,6 +872,7 @@ function parseApplyEventOutcome(value) {
|
|
|
828
872
|
for (const releasedHook of value.releasedHooks) if (!isRecord(releasedHook) || typeof releasedHook.hookId !== "string" || releasedHook.hookId.length === 0 || typeof releasedHook.token !== "string" || releasedHook.token.length === 0) malformedApplyEventOutcome("releasedHooks entries must contain hookId and token strings");
|
|
829
873
|
const event = parseSuccessEntity(value, "event", EventSchema);
|
|
830
874
|
const run = parseSuccessEntity(value, "run", WorkflowRunSchema);
|
|
875
|
+
const replayRun = parseSuccessEntity(value, "replayRun", WorkflowRunSchema);
|
|
831
876
|
const step = parseSuccessEntity(value, "step", StepSchema);
|
|
832
877
|
const hook = parseSuccessEntity(value, "hook", HookSchema);
|
|
833
878
|
const wait = parseSuccessEntity(value, "wait", WaitSchema);
|
|
@@ -851,6 +896,7 @@ function parseApplyEventOutcome(value) {
|
|
|
851
896
|
if (value.cursor !== void 0 && value.cursor !== null && typeof value.cursor !== "string") malformedApplyEventOutcome("cursor must be a string or null");
|
|
852
897
|
if (value.hasMore !== void 0 && typeof value.hasMore !== "boolean") malformedApplyEventOutcome("hasMore must be a boolean");
|
|
853
898
|
if (value.maxEvents !== void 0 && (typeof value.maxEvents !== "number" || !Number.isSafeInteger(value.maxEvents) || value.maxEvents < 0)) malformedApplyEventOutcome("maxEvents must be a non-negative safe integer");
|
|
899
|
+
for (const flag of ["runIndexPublished", "hookIndexesReleased"]) if (value[flag] !== void 0 && value[flag] !== true) malformedApplyEventOutcome(`${flag} must be true when present`);
|
|
854
900
|
if (run !== void 0 && value.indexPublicationExpiresAt === void 0) malformedApplyEventOutcome("indexPublicationExpiresAt is required when run is present");
|
|
855
901
|
if (value.indexPublicationExpiresAt !== void 0 && (typeof value.indexPublicationExpiresAt !== "number" || !Number.isFinite(value.indexPublicationExpiresAt))) malformedApplyEventOutcome("indexPublicationExpiresAt must be a finite number");
|
|
856
902
|
return {
|
|
@@ -859,6 +905,7 @@ function parseApplyEventOutcome(value) {
|
|
|
859
905
|
...value,
|
|
860
906
|
event,
|
|
861
907
|
run,
|
|
908
|
+
replayRun,
|
|
862
909
|
step,
|
|
863
910
|
hook,
|
|
864
911
|
wait,
|
|
@@ -951,19 +998,6 @@ function unwrapRead(outcome) {
|
|
|
951
998
|
if (!outcome.ok) throw new RunExpiredError(outcome.message);
|
|
952
999
|
return outcome.value;
|
|
953
1000
|
}
|
|
954
|
-
/** Bound cross-run fanout so large list pages cannot create an RPC burst. */
|
|
955
|
-
const RUN_LIST_CONCURRENCY = 8;
|
|
956
|
-
/**
|
|
957
|
-
* Run statuses only move pending -> running -> terminal, and terminal statuses
|
|
958
|
-
* are immutable. An older index value may therefore safely exclude a filter
|
|
959
|
-
* only when it is already terminal, or when the caller asks for pending and
|
|
960
|
-
* the index has advanced beyond pending. Earlier non-terminal metadata cannot
|
|
961
|
-
* exclude a later status because a post-commit index write may need replay.
|
|
962
|
-
*/
|
|
963
|
-
function indexStatusExcludes(indexed, requested) {
|
|
964
|
-
if (requested === void 0 || indexed === requested) return false;
|
|
965
|
-
return isTerminalWorkflowRunStatus(indexed) || requested === "pending";
|
|
966
|
-
}
|
|
967
1001
|
function createStorage(config) {
|
|
968
1002
|
const { env } = config;
|
|
969
1003
|
const cleanup = { retentionMs: config.runRetentionMs ?? 0 };
|
|
@@ -981,61 +1015,31 @@ function createStorage(config) {
|
|
|
981
1015
|
runs: {
|
|
982
1016
|
get: runsGet,
|
|
983
1017
|
async list(params) {
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
});
|
|
998
|
-
if (kvList.keys.length === 0) {
|
|
999
|
-
exhausted = true;
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
const candidates = [];
|
|
1003
|
-
for (const key of kvList.keys) {
|
|
1004
|
-
const metadata = JSON.parse(key.value);
|
|
1005
|
-
if (statuses?.every((status) => indexStatusExcludes(metadata.status, status))) continue;
|
|
1006
|
-
candidates.push({
|
|
1007
|
-
key: key.name,
|
|
1008
|
-
metadata
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
for (let offset = 0; offset < candidates.length && matches.length <= limit;) {
|
|
1012
|
-
const needed = limit + 1 - matches.length;
|
|
1013
|
-
const batch = candidates.slice(offset, offset + Math.min(RUN_LIST_CONCURRENCY, needed));
|
|
1014
|
-
const resolved = await Promise.all(batch.map(async ({ key, metadata }) => {
|
|
1015
|
-
try {
|
|
1016
|
-
const run = await runsGet(metadata.runId, { resolveData: params?.resolveData });
|
|
1017
|
-
return statuses === void 0 || statuses.includes(run.status) ? {
|
|
1018
|
-
key,
|
|
1019
|
-
run
|
|
1020
|
-
} : null;
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
if (!WorkflowRunNotFoundError.is(error) && !RunExpiredError.is(error)) throw error;
|
|
1023
|
-
return null;
|
|
1024
|
-
}
|
|
1025
|
-
}));
|
|
1026
|
-
for (const match of resolved) if (match) matches.push(match);
|
|
1027
|
-
offset += batch.length;
|
|
1028
|
-
}
|
|
1029
|
-
exhausted = kvList.list_complete;
|
|
1030
|
-
scanCursor = kvList.cursor ?? kvList.keys.at(-1)?.name;
|
|
1031
|
-
}
|
|
1032
|
-
const hasMore = matches.length > limit;
|
|
1033
|
-
const page = matches.slice(0, limit);
|
|
1034
|
-
return {
|
|
1035
|
-
data: page.map(({ run }) => run),
|
|
1036
|
-
cursor: hasMore ? page.at(-1)?.key ?? null : null,
|
|
1037
|
-
hasMore
|
|
1018
|
+
const request = {
|
|
1019
|
+
workflowName: params?.workflowName,
|
|
1020
|
+
statuses: params?.status === void 0 ? void 0 : Array.isArray(params.status) ? params.status : [params.status],
|
|
1021
|
+
limit: params?.pagination?.limit ?? 20,
|
|
1022
|
+
cursor: params?.pagination?.cursor,
|
|
1023
|
+
sortOrder: params?.pagination?.sortOrder,
|
|
1024
|
+
resolveData: params?.resolveData
|
|
1025
|
+
};
|
|
1026
|
+
const fleetPage = Number.isSafeInteger(request.limit) && request.limit >= 1 && request.limit <= 1e3 ? await env.WORKFLOW_INDEX.listResolvedRuns?.(compact(request)) : void 0;
|
|
1027
|
+
if (fleetPage) return {
|
|
1028
|
+
data: fleetPage.data.map((run) => filterData(parseRun(run), params?.resolveData, ["input", "output"])),
|
|
1029
|
+
cursor: fleetPage.cursor,
|
|
1030
|
+
hasMore: fleetPage.hasMore
|
|
1038
1031
|
};
|
|
1032
|
+
return await listRunsPage({
|
|
1033
|
+
listRuns: (options) => env.WORKFLOW_INDEX.listRuns(options),
|
|
1034
|
+
readRun: async (runId) => {
|
|
1035
|
+
try {
|
|
1036
|
+
return await runsGet(runId);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
if (WorkflowRunNotFoundError.is(error) || RunExpiredError.is(error)) return null;
|
|
1039
|
+
throw error;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}, request);
|
|
1039
1043
|
}
|
|
1040
1044
|
},
|
|
1041
1045
|
events: {
|
|
@@ -1095,20 +1099,18 @@ function createStorage(config) {
|
|
|
1095
1099
|
throwOutcomeError(parsedOutcome.outcome, effectiveRunId, data);
|
|
1096
1100
|
}
|
|
1097
1101
|
const outcome = parsedOutcome.outcome;
|
|
1098
|
-
if (outcome.run) {
|
|
1099
|
-
const meta = JSON.stringify({
|
|
1100
|
-
runId: effectiveRunId,
|
|
1101
|
-
createdAt: outcome.run.createdAt.toISOString(),
|
|
1102
|
-
status: outcome.run.status
|
|
1103
|
-
});
|
|
1102
|
+
if (outcome.run && !outcome.runIndexPublished) {
|
|
1104
1103
|
if (outcome.indexPublicationExpiresAt === void 0) throw new Error("world-celld: authoritative run mutation omitted its index lease");
|
|
1105
|
-
await env.WORKFLOW_INDEX.commitRun(outcome.run,
|
|
1104
|
+
await env.WORKFLOW_INDEX.commitRun(outcome.run, runIndexMetadata({
|
|
1105
|
+
...outcome.run,
|
|
1106
|
+
runId: effectiveRunId
|
|
1107
|
+
}), outcome.indexPublicationExpiresAt);
|
|
1106
1108
|
}
|
|
1107
1109
|
if (outcome.hookToIndex) {
|
|
1108
1110
|
const serialized = stringify(outcome.hookToIndex);
|
|
1109
1111
|
await env.WORKFLOW_INDEX.finalizeHookIndexes(outcome.hookToIndex.token, outcome.hookToIndex.hookId, serialized, hookOwner(outcome.hookToIndex), hookAdmission?.reservation);
|
|
1110
1112
|
} else if (hookAdmission) await env.WORKFLOW_INDEX.releaseHookReservation(hookAdmission.token, hookAdmission.owner, hookAdmission.reservation);
|
|
1111
|
-
if (outcome.releasedHooks.length > 0) await env.WORKFLOW_INDEX.releaseHookIndexes({
|
|
1113
|
+
if (outcome.releasedHooks.length > 0 && !outcome.hookIndexesReleased) await env.WORKFLOW_INDEX.releaseHookIndexes({
|
|
1112
1114
|
runId: effectiveRunId,
|
|
1113
1115
|
hooks: outcome.releasedHooks
|
|
1114
1116
|
});
|
|
@@ -1119,7 +1121,7 @@ function createStorage(config) {
|
|
|
1119
1121
|
};
|
|
1120
1122
|
return {
|
|
1121
1123
|
event: outcome.event,
|
|
1122
|
-
run: outcome.run,
|
|
1124
|
+
run: outcome.run ?? outcome.replayRun,
|
|
1123
1125
|
step: outcome.step,
|
|
1124
1126
|
hook: outcome.hook,
|
|
1125
1127
|
wait: outcome.wait,
|
|
@@ -1276,18 +1278,28 @@ function createStreamer(config) {
|
|
|
1276
1278
|
const chunks = input.map(toBytes);
|
|
1277
1279
|
for (const chunk of chunks) if (chunk.byteLength > 1048576) throw new Error(`Stream chunk exceeds ${MAX_STREAM_CHUNK_BYTES} bytes`);
|
|
1278
1280
|
await registerStreamForRun(name, resolvedRunId);
|
|
1279
|
-
|
|
1280
|
-
let
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1281
|
+
const stub = getStreamDO(name);
|
|
1282
|
+
let next = 0;
|
|
1283
|
+
while (next < chunks.length) {
|
|
1284
|
+
const chunkLimit = stub.streamChunkLimit?.() ?? 32;
|
|
1285
|
+
let end = next;
|
|
1286
|
+
let batchBytes = 0;
|
|
1287
|
+
while (end < chunks.length && end - next < chunkLimit && (end === next || batchBytes + chunks[end].byteLength <= 8388608)) {
|
|
1288
|
+
batchBytes += chunks[end].byteLength;
|
|
1289
|
+
end++;
|
|
1290
|
+
}
|
|
1291
|
+
const batch = chunks.slice(next, end);
|
|
1292
|
+
try {
|
|
1293
|
+
await stub.writeChunks(resolvedRunId, batch);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
if (batch.length > 32 && isChunkLimitRejection(error)) {
|
|
1296
|
+
stub.resetStreamChunkLimit?.();
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
throw error;
|
|
1286
1300
|
}
|
|
1287
|
-
|
|
1288
|
-
batchBytes += chunk.byteLength;
|
|
1301
|
+
next = end;
|
|
1289
1302
|
}
|
|
1290
|
-
if (batch.length > 0) await getStreamDO(name).writeChunks(resolvedRunId, batch);
|
|
1291
1303
|
};
|
|
1292
1304
|
const writeToStream = (name, runId, chunk) => writeChunksToStream(name, runId, [chunk]);
|
|
1293
1305
|
const closeStream = async (name, runId) => {
|
|
@@ -1325,7 +1337,7 @@ function createStreamer(config) {
|
|
|
1325
1337
|
result = await stub.readChunks({
|
|
1326
1338
|
runId,
|
|
1327
1339
|
startIndex: nextIndex,
|
|
1328
|
-
maxChunks: 32,
|
|
1340
|
+
maxChunks: stub.streamChunkLimit?.() ?? 32,
|
|
1329
1341
|
maxBytes: MAX_STREAM_READ_BYTES,
|
|
1330
1342
|
waitMs: streamLongPollMs
|
|
1331
1343
|
}, abortController.signal);
|
|
@@ -1355,10 +1367,11 @@ function createStreamer(config) {
|
|
|
1355
1367
|
const getStreamChunks = async (name, runId, options) => {
|
|
1356
1368
|
const requestedLimit = options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE;
|
|
1357
1369
|
if (!Number.isSafeInteger(requestedLimit) || requestedLimit < 1) throw new Error("Stream chunk limit must be a positive safe integer");
|
|
1358
|
-
const
|
|
1370
|
+
const stub = getStreamDO(name);
|
|
1371
|
+
const limit = Math.min(requestedLimit, stub.streamChunkLimit?.() ?? MAX_CHUNK_PAGE_SIZE);
|
|
1359
1372
|
const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
|
|
1360
1373
|
if (Number.isNaN(startIndex) || startIndex < 0) throw new Error(`Invalid stream cursor: ${options?.cursor}`);
|
|
1361
|
-
const result = await
|
|
1374
|
+
const result = await stub.readChunks({
|
|
1362
1375
|
runId,
|
|
1363
1376
|
startIndex,
|
|
1364
1377
|
maxChunks: limit,
|