@ewhauser/world-celld 0.2.0 → 0.3.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 +10 -0
- package/README.md +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -105
- package/dist/index.js.map +1 -1
- package/dist/testing.js +255 -106
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +14 -6
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +255 -106
- package/dist/worker.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.3.0 (2026-08-16)
|
|
6
|
+
|
|
7
|
+
## What's Changed
|
|
8
|
+
* fix: split Release Please release and PR phases by @ewhauser in https://github.com/ewhauser/world-celld/pull/16
|
|
9
|
+
* feat: target Workflow 5 beta by @ewhauser in https://github.com/ewhauser/world-celld/pull/17
|
|
10
|
+
* fix: enforce Workflow 5 storage contracts by @ewhauser in https://github.com/ewhauser/world-celld/pull/19
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.2.0...v0.3.0
|
|
14
|
+
|
|
5
15
|
## 0.2.0 (2026-08-16)
|
|
6
16
|
|
|
7
17
|
## What's Changed
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ celld worker router
|
|
|
125
125
|
|
|
|
126
126
|
| HTTP callbacks
|
|
127
127
|
v
|
|
128
|
-
Workflow application /.well-known/workflow/v1/
|
|
128
|
+
Workflow application /.well-known/workflow/v1/flow
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
The application-side package implements the Workflow `World` interface and
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CreateEventRequest, Event, Hook, RunCreatedEventRequest, Step, TerminalWorkflowRunStatus, ValidQueueName, WorkflowRun, World } from "@workflow/world";
|
|
1
|
+
import { CreateEventParams, CreateEventRequest, Event, Hook, RunCreatedEventRequest, Step, TerminalWorkflowRunStatus, ValidQueueName, Wait, WorkflowRun, World } from "@workflow/world";
|
|
2
2
|
//#region src/retention.d.ts
|
|
3
3
|
type CleanupPhase = 'retained' | 'index' | 'streams' | 'queues' | 'payload' | 'tombstoned';
|
|
4
4
|
interface CleanupRecord {
|
|
@@ -69,6 +69,8 @@ interface ApplyEventRequest {
|
|
|
69
69
|
/** The effective run id (already resolved / generated by the caller). */
|
|
70
70
|
runId: string;
|
|
71
71
|
data: CreateEventRequest | RunCreatedEventRequest;
|
|
72
|
+
/** Workflow 5 event-log position and preload options. */
|
|
73
|
+
params?: CreateEventParams;
|
|
72
74
|
/**
|
|
73
75
|
* Current holder of the hook token per the global KV token index
|
|
74
76
|
* (hook_created only). `null` means the token is unclaimed.
|
|
@@ -80,7 +82,7 @@ interface ApplyEventRequest {
|
|
|
80
82
|
/** Internal world-celld retention policy captured with the event. */
|
|
81
83
|
cleanup?: ScheduleCleanupRequest;
|
|
82
84
|
}
|
|
83
|
-
type ApplyEventErrorCode = 'RUN_NOT_FOUND' | 'STEP_NOT_FOUND' | 'HOOK_NOT_FOUND' | '
|
|
85
|
+
type ApplyEventErrorCode = 'RUN_NOT_FOUND' | 'STEP_NOT_FOUND' | 'HOOK_NOT_FOUND' | 'WAIT_NOT_FOUND' | 'ENTITY_CONFLICT' | 'RUN_EXPIRED' | 'TOO_EARLY' | 'RUN_NOT_SUPPORTED';
|
|
84
86
|
interface ApplyEventFailure {
|
|
85
87
|
ok: false;
|
|
86
88
|
code: ApplyEventErrorCode;
|
|
@@ -97,6 +99,9 @@ interface ApplyEventSuccess {
|
|
|
97
99
|
run?: WorkflowRun;
|
|
98
100
|
step?: Step;
|
|
99
101
|
hook?: Hook;
|
|
102
|
+
wait?: Wait;
|
|
103
|
+
/** True when a lazy step_started atomically created its step. */
|
|
104
|
+
stepCreated?: true;
|
|
100
105
|
/** Hooks whose tokens must be released from the global KV index. */
|
|
101
106
|
releasedHooks: Array<{
|
|
102
107
|
hookId: string;
|
|
@@ -111,6 +116,11 @@ interface ApplyEventSuccess {
|
|
|
111
116
|
hookToIndex?: Hook;
|
|
112
117
|
/** All events (ascending), preloaded for run_started responses. */
|
|
113
118
|
events?: Event[];
|
|
119
|
+
/** Pagination metadata required whenever events are returned. */
|
|
120
|
+
cursor?: string | null;
|
|
121
|
+
hasMore?: boolean;
|
|
122
|
+
/** Server-owned event ceiling advertised on run lifecycle writes. */
|
|
123
|
+
maxEvents?: number;
|
|
114
124
|
}
|
|
115
125
|
type ApplyEventOutcome = ApplyEventSuccess | ApplyEventFailure;
|
|
116
126
|
//#endregion
|
|
@@ -200,7 +210,7 @@ interface StreamDOId {
|
|
|
200
210
|
}
|
|
201
211
|
//#endregion
|
|
202
212
|
//#region src/queue.d.ts
|
|
203
|
-
type Pathname = 'flow'
|
|
213
|
+
type Pathname = 'flow';
|
|
204
214
|
/**
|
|
205
215
|
* Enqueue request accepted by a QueueDO cell. The message payload travels
|
|
206
216
|
* pre-encoded with the shared tagged-JSON codec (`body`), so the cell can
|
|
@@ -223,7 +233,7 @@ interface EnqueueRequest {
|
|
|
223
233
|
config: QueueCellConfig;
|
|
224
234
|
}
|
|
225
235
|
interface QueueCellConfig {
|
|
226
|
-
/** Base URL of the app; deliveries POST to `${baseUrl}/.well-known/workflow/v1/
|
|
236
|
+
/** Base URL of the app; deliveries POST to `${baseUrl}/.well-known/workflow/v1/flow`. */
|
|
227
237
|
targetBaseUrl: string;
|
|
228
238
|
queueShards: number;
|
|
229
239
|
}
|
|
@@ -312,7 +322,7 @@ interface CelldWorldConfig {
|
|
|
312
322
|
deploymentId?: string;
|
|
313
323
|
/**
|
|
314
324
|
* Base URL the app's workflow endpoints are mounted on; QueueDO cells
|
|
315
|
-
* deliver to `${baseUrl}/.well-known/workflow/v1/
|
|
325
|
+
* deliver to `${baseUrl}/.well-known/workflow/v1/flow`.
|
|
316
326
|
* Default: process.env.WORKFLOW_BASE_URL || `http://localhost:${PORT ?? 3000}`
|
|
317
327
|
*/
|
|
318
328
|
baseUrl?: string;
|
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/streamer.ts","../src/queue.ts","../src/config.ts","../src/index.ts"],"mappings":";;KAOY;UAEK;EACf;EACA;EACA;EACA,WAAW;EACX,aAAa;EACb,gBAAgB;EAChB,OAAO;EACP;EACA,OAAO;EACP;EACA;EACA,gBAAgB;EAChB,eAAe;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA,gBAAgB;EAChB,aAAa;EACb,WAAW;EACX,cAAc;;KAGJ,eAAe;EACrB;EAAU,OAAO;;EACjB;EAAW;EAAqB;EAAiB,WAAW;;UAEjD;EACf;EACA;;UAGe;EACf;EACA;EACA,OAAO;EACP;;UAGe;EACf;;UAGe;EACf;;UAGe;EACf;EACA;;UAGe;EACf;;UAGe;EACf;EACA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/streamer.ts","../src/queue.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -294,10 +294,7 @@ function isPermanentError(err) {
|
|
|
294
294
|
function computeBackoff(attempt) {
|
|
295
295
|
return Math.min(60, 2 ** attempt);
|
|
296
296
|
}
|
|
297
|
-
const
|
|
298
|
-
workflow: "flow",
|
|
299
|
-
step: "step"
|
|
300
|
-
};
|
|
297
|
+
const QUEUE_PATHNAME = "flow";
|
|
301
298
|
function isTestMode() {
|
|
302
299
|
if (process.env.CELLD_QUEUE_MODE === "cells") return false;
|
|
303
300
|
return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
@@ -321,9 +318,8 @@ function shardFor(key, shards) {
|
|
|
321
318
|
return (hash >>> 0) % shards;
|
|
322
319
|
}
|
|
323
320
|
/**
|
|
324
|
-
* In-process test pump. Holds
|
|
325
|
-
*
|
|
326
|
-
* `${baseUrl}/.well-known/workflow/v1/{flow|step}`.
|
|
321
|
+
* In-process test pump. Holds an in-memory FIFO and HTTP-dispatches envelopes
|
|
322
|
+
* to the user's server at `${baseUrl}/.well-known/workflow/v1/flow`.
|
|
327
323
|
*
|
|
328
324
|
* Mirrors world-local's idempotency semantics: messages are deduplicated on
|
|
329
325
|
* `idempotencyKey` while a message with the same key is in flight, and the
|
|
@@ -337,14 +333,8 @@ function createTestPump(config) {
|
|
|
337
333
|
const httpTimeoutMs = config.httpTimeoutMs ?? 3e5;
|
|
338
334
|
const maxAttempts = config.maxAttempts ?? 5;
|
|
339
335
|
const baseBackoffMs = config.backoffDelayMs ?? 1e3;
|
|
340
|
-
const queues = {
|
|
341
|
-
|
|
342
|
-
step: []
|
|
343
|
-
};
|
|
344
|
-
const wakers = {
|
|
345
|
-
flow: [],
|
|
346
|
-
step: []
|
|
347
|
-
};
|
|
336
|
+
const queues = { flow: [] };
|
|
337
|
+
const wakers = { flow: [] };
|
|
348
338
|
/** Inflight messageIds by idempotencyKey (world-local queue.js semantics). */
|
|
349
339
|
const inflightMessages = /* @__PURE__ */ new Map();
|
|
350
340
|
let running = false;
|
|
@@ -437,7 +427,6 @@ function createTestPump(config) {
|
|
|
437
427
|
if (running) return;
|
|
438
428
|
running = true;
|
|
439
429
|
loop("flow");
|
|
440
|
-
loop("step");
|
|
441
430
|
},
|
|
442
431
|
stop() {
|
|
443
432
|
running = false;
|
|
@@ -456,13 +445,13 @@ function createQueue(config) {
|
|
|
456
445
|
};
|
|
457
446
|
return {
|
|
458
447
|
async queue(queueName, message, opts) {
|
|
459
|
-
|
|
460
|
-
const runId = "runId" in message
|
|
448
|
+
parseQueueName(queueName);
|
|
449
|
+
const runId = "runId" in message && typeof message.runId === "string" ? message.runId : void 0;
|
|
461
450
|
if (isTestMode()) {
|
|
462
451
|
const existing = testPump.inflight(opts?.idempotencyKey);
|
|
463
452
|
if (existing) return { messageId: existing };
|
|
464
453
|
const messageId = MessageId.parse(`msg_${generateMessageId()}`);
|
|
465
|
-
testPump.push(
|
|
454
|
+
testPump.push(QUEUE_PATHNAME, {
|
|
466
455
|
messageId,
|
|
467
456
|
queueName,
|
|
468
457
|
attempt: 1,
|
|
@@ -476,7 +465,7 @@ function createQueue(config) {
|
|
|
476
465
|
const outcome = await getQueueStub(shard).enqueue({
|
|
477
466
|
messageId,
|
|
478
467
|
queueName,
|
|
479
|
-
pathname:
|
|
468
|
+
pathname: QUEUE_PATHNAME,
|
|
480
469
|
body: stringify(message),
|
|
481
470
|
runId,
|
|
482
471
|
idempotencyKey: opts?.idempotencyKey,
|
|
@@ -589,11 +578,11 @@ function throwOutcomeError(outcome, runId, data) {
|
|
|
589
578
|
case "RUN_NOT_FOUND": throw new WorkflowRunNotFoundError(runId);
|
|
590
579
|
case "STEP_NOT_FOUND": throw new WorkflowWorldError(outcome.message, { status: 404 });
|
|
591
580
|
case "HOOK_NOT_FOUND": throw new HookNotFoundError(data.correlationId ?? runId);
|
|
581
|
+
case "WAIT_NOT_FOUND": throw new WorkflowWorldError(outcome.message, { status: 404 });
|
|
592
582
|
case "ENTITY_CONFLICT": throw new EntityConflictError(outcome.message);
|
|
593
583
|
case "RUN_EXPIRED": throw new RunExpiredError(outcome.message);
|
|
594
584
|
case "TOO_EARLY": throw new TooEarlyError(outcome.message, { retryAfter: outcome.retryAfterSeconds });
|
|
595
585
|
case "RUN_NOT_SUPPORTED": throw new RunNotSupportedError(outcome.runSpecVersion ?? 0, SPEC_VERSION_CURRENT);
|
|
596
|
-
case "LEGACY_RUN_NOT_SUPPORTED": throw new WorkflowWorldError(outcome.message, { status: 422 });
|
|
597
586
|
}
|
|
598
587
|
}
|
|
599
588
|
const parseRun = (run) => WorkflowRunSchema.parse(compact(run));
|
|
@@ -671,7 +660,7 @@ function createStorage(config) {
|
|
|
671
660
|
}
|
|
672
661
|
},
|
|
673
662
|
events: {
|
|
674
|
-
async create(runId, data,
|
|
663
|
+
async create(runId, data, params) {
|
|
675
664
|
let effectiveRunId;
|
|
676
665
|
if (data.eventType === "run_created" && (!runId || runId === "")) effectiveRunId = `wrun_${ulid()}`;
|
|
677
666
|
else if (!runId) throw new WorkflowWorldError("runId is required for non-run_created events", { status: 400 });
|
|
@@ -692,6 +681,7 @@ function createStorage(config) {
|
|
|
692
681
|
outcome = await stub.applyEvent({
|
|
693
682
|
runId: effectiveRunId,
|
|
694
683
|
data,
|
|
684
|
+
params,
|
|
695
685
|
tokenHolder,
|
|
696
686
|
cleanup
|
|
697
687
|
});
|
|
@@ -727,12 +717,20 @@ function createStorage(config) {
|
|
|
727
717
|
eventId: outcome.event.eventId
|
|
728
718
|
}));
|
|
729
719
|
}
|
|
720
|
+
const eventPage = outcome.events === void 0 ? {} : {
|
|
721
|
+
events: outcome.events,
|
|
722
|
+
cursor: outcome.cursor ?? null,
|
|
723
|
+
hasMore: outcome.hasMore ?? false
|
|
724
|
+
};
|
|
730
725
|
return {
|
|
731
726
|
event: outcome.event,
|
|
732
727
|
run: outcome.run,
|
|
733
728
|
step: outcome.step,
|
|
734
729
|
hook: outcome.hook,
|
|
735
|
-
|
|
730
|
+
wait: outcome.wait,
|
|
731
|
+
stepCreated: outcome.stepCreated,
|
|
732
|
+
maxEvents: outcome.maxEvents,
|
|
733
|
+
...eventPage
|
|
736
734
|
};
|
|
737
735
|
},
|
|
738
736
|
async get(runId, eventId, _params) {
|
|
@@ -756,23 +754,30 @@ function createStorage(config) {
|
|
|
756
754
|
},
|
|
757
755
|
async listByCorrelationId(params) {
|
|
758
756
|
const limit = params.pagination?.limit ?? 100;
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
757
|
+
const stub = getRunDO(params.runId);
|
|
758
|
+
const matches = [];
|
|
759
|
+
let scanCursor = params.pagination?.cursor;
|
|
760
|
+
let exhausted = false;
|
|
761
|
+
while (matches.length <= limit && !exhausted) {
|
|
762
|
+
const page = unwrapRead(await stub.listEvents({
|
|
763
|
+
limit: Math.min(1e3, Math.max(50, limit + 1)),
|
|
764
|
+
cursor: scanCursor,
|
|
765
|
+
sortOrder: params.pagination?.sortOrder ?? "asc"
|
|
766
|
+
}));
|
|
767
|
+
for (const event of page.data) if (event.correlationId === params.correlationId) {
|
|
768
|
+
matches.push(event);
|
|
769
|
+
if (matches.length > limit) break;
|
|
770
|
+
}
|
|
771
|
+
exhausted = !page.hasMore;
|
|
772
|
+
scanCursor = page.cursor ?? page.data.at(-1)?.eventId;
|
|
773
|
+
if (page.data.length === 0) exhausted = true;
|
|
774
|
+
}
|
|
775
|
+
const hasMore = matches.length > limit;
|
|
776
|
+
const data = matches.slice(0, limit);
|
|
766
777
|
return {
|
|
767
|
-
data:
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const { runId, eventId } = JSON.parse(raw);
|
|
771
|
-
const outcome = await getRunDO(runId).getEvent(eventId);
|
|
772
|
-
return outcome.ok ? outcome.value : null;
|
|
773
|
-
}))).filter((event) => event !== null).map(parseEvent),
|
|
774
|
-
cursor: listed.list_complete ? null : listed.cursor ?? null,
|
|
775
|
-
hasMore: !listed.list_complete
|
|
778
|
+
data: data.map(parseEvent),
|
|
779
|
+
cursor: hasMore ? data.at(-1)?.eventId ?? null : null,
|
|
780
|
+
hasMore
|
|
776
781
|
};
|
|
777
782
|
}
|
|
778
783
|
},
|
|
@@ -858,75 +863,87 @@ function createStreamer(config) {
|
|
|
858
863
|
await getRunRegistryDO(runId).registerStream(runId, name);
|
|
859
864
|
registeredStreams.add(cacheKey);
|
|
860
865
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
if (done) {
|
|
894
|
-
controller.close();
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
await new Promise((resolve) => setTimeout(resolve, readPollMs));
|
|
866
|
+
const writeToStream = async (name, runId, chunk) => {
|
|
867
|
+
const resolvedRunId = await runId;
|
|
868
|
+
await registerStreamForRun(name, resolvedRunId);
|
|
869
|
+
await getStreamDO(name).writeChunk(resolvedRunId, toBytes(chunk));
|
|
870
|
+
};
|
|
871
|
+
const closeStream = async (name, runId) => {
|
|
872
|
+
const resolvedRunId = await runId;
|
|
873
|
+
await registerStreamForRun(name, resolvedRunId);
|
|
874
|
+
await getStreamDO(name).closeStream(resolvedRunId);
|
|
875
|
+
};
|
|
876
|
+
const readFromStream = async (name, startIndex = 0) => {
|
|
877
|
+
const stub = getStreamDO(name);
|
|
878
|
+
let nextIndex;
|
|
879
|
+
if (startIndex < 0) {
|
|
880
|
+
const info = await stub.getInfo();
|
|
881
|
+
nextIndex = Math.max(0, info.tailIndex + 1 + startIndex);
|
|
882
|
+
} else nextIndex = startIndex;
|
|
883
|
+
let cancelled = false;
|
|
884
|
+
return new ReadableStream({
|
|
885
|
+
async pull(controller) {
|
|
886
|
+
while (true) {
|
|
887
|
+
if (cancelled) return;
|
|
888
|
+
const { chunks, done } = await stub.getChunks({
|
|
889
|
+
startIndex: nextIndex,
|
|
890
|
+
limit: READ_BATCH_SIZE
|
|
891
|
+
});
|
|
892
|
+
if (chunks.length > 0) {
|
|
893
|
+
for (const chunk of chunks) controller.enqueue(chunk);
|
|
894
|
+
nextIndex += chunks.length;
|
|
895
|
+
return;
|
|
898
896
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
897
|
+
if (done) {
|
|
898
|
+
controller.close();
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
await new Promise((resolve) => setTimeout(resolve, readPollMs));
|
|
902
902
|
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
903
|
+
},
|
|
904
|
+
cancel() {
|
|
905
|
+
cancelled = true;
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
};
|
|
909
|
+
const listStreamsByRunId = (runId) => getRunRegistryDO(runId).listStreams();
|
|
910
|
+
const getStreamChunks = async (name, _runId, options) => {
|
|
911
|
+
const limit = Math.min(options?.limit ?? DEFAULT_CHUNK_PAGE_SIZE, MAX_CHUNK_PAGE_SIZE);
|
|
912
|
+
const startIndex = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
|
|
913
|
+
if (Number.isNaN(startIndex) || startIndex < 0) throw new Error(`Invalid stream cursor: ${options?.cursor}`);
|
|
914
|
+
const result = await getStreamDO(name).getChunks({
|
|
915
|
+
startIndex,
|
|
916
|
+
limit: limit + 1
|
|
917
|
+
});
|
|
918
|
+
const hasMore = result.chunks.length > limit;
|
|
919
|
+
return {
|
|
920
|
+
data: result.chunks.slice(0, limit).map((chunk, offset) => ({
|
|
921
|
+
index: startIndex + offset,
|
|
922
|
+
data: chunk
|
|
923
|
+
})),
|
|
924
|
+
cursor: hasMore ? String(startIndex + limit) : null,
|
|
925
|
+
hasMore,
|
|
926
|
+
done: result.done
|
|
927
|
+
};
|
|
928
|
+
};
|
|
929
|
+
const getStreamInfo = (name, _runId) => getStreamDO(name).getInfo();
|
|
930
|
+
return {
|
|
931
|
+
streams: {
|
|
932
|
+
write: (runId, name, chunk) => writeToStream(name, runId, chunk),
|
|
933
|
+
close: (runId, name) => closeStream(name, runId),
|
|
934
|
+
get: (runId, name, startIndex) => {
|
|
935
|
+
return readFromStream(name, startIndex);
|
|
936
|
+
},
|
|
937
|
+
list: listStreamsByRunId,
|
|
938
|
+
getChunks: (runId, name, options) => getStreamChunks(name, runId, options),
|
|
939
|
+
getInfo: (runId, name) => getStreamInfo(name, runId)
|
|
926
940
|
},
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
941
|
+
writeToStream,
|
|
942
|
+
closeStream,
|
|
943
|
+
readFromStream,
|
|
944
|
+
listStreamsByRunId,
|
|
945
|
+
getStreamChunks,
|
|
946
|
+
getStreamInfo
|
|
930
947
|
};
|
|
931
948
|
}
|
|
932
949
|
//#endregion
|