@durable-streams/client 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -168,11 +168,11 @@ type ParamsRecord = {
168
168
  /**
169
169
  * Live mode for reading from a stream.
170
170
  * - false: Catch-up only, stop at first `upToDate`
171
- * - "auto": Behavior driven by consumption method (default)
171
+ * - true: Auto-select best mode (SSE for JSON streams, long-poll for binary)
172
172
  * - "long-poll": Explicit long-poll mode for live updates
173
173
  * - "sse": Explicit server-sent events for live updates
174
174
  */
175
- type LiveMode = false | `auto` | `long-poll` | `sse`;
175
+ type LiveMode = boolean | `long-poll` | `sse`;
176
176
  /**
177
177
  * Options for the stream() function (read-only API).
178
178
  */
@@ -230,7 +230,7 @@ interface StreamOptions {
230
230
  /**
231
231
  * Live mode behavior:
232
232
  * - false: Catch-up only, stop at first `upToDate`
233
- * - "auto" (default): Behavior driven by consumption method
233
+ * - true (default): Auto-select best mode (SSE for JSON, long-poll for binary)
234
234
  * - "long-poll": Explicit long-poll mode for live updates
235
235
  * - "sse": Explicit server-sent events for live updates
236
236
  */
@@ -306,6 +306,11 @@ interface JsonBatchMeta {
306
306
  * Last Stream-Cursor / streamCursor, if present.
307
307
  */
308
308
  cursor?: string;
309
+ /**
310
+ * Whether the stream is closed and this batch contains the final data.
311
+ * When true, no more data will ever be appended to the stream.
312
+ */
313
+ streamClosed: boolean;
309
314
  }
310
315
  /**
311
316
  * A batch of parsed JSON items with metadata.
@@ -415,6 +420,17 @@ interface CreateOptions extends StreamHandleOptions {
415
420
  * @default true
416
421
  */
417
422
  batching?: boolean;
423
+ /**
424
+ * If true, create the stream in the closed state.
425
+ * Any body provided becomes the complete and final content.
426
+ *
427
+ * Useful for:
428
+ * - Cached responses
429
+ * - Placeholder errors
430
+ * - Pre-computed results
431
+ * - Single-message streams that are immediately complete
432
+ */
433
+ closed?: boolean;
418
434
  }
419
435
  /**
420
436
  * Options for appending data to a stream.
@@ -455,6 +471,37 @@ interface AppendOptions {
455
471
  producerSeq?: number;
456
472
  }
457
473
  /**
474
+ * Result of a close operation.
475
+ */
476
+ interface CloseResult {
477
+ /**
478
+ * The final offset of the stream.
479
+ * This is the offset after the last byte (including any final message).
480
+ * Returned via the `Stream-Next-Offset` header.
481
+ */
482
+ finalOffset: Offset;
483
+ }
484
+ /**
485
+ * Options for closing a stream.
486
+ */
487
+ interface CloseOptions {
488
+ /**
489
+ * Optional final message to append atomically with close.
490
+ * For JSON streams, pass a pre-serialized JSON string.
491
+ * Strings are UTF-8 encoded.
492
+ */
493
+ body?: Uint8Array | string;
494
+ /**
495
+ * Content type for the final message.
496
+ * Defaults to the stream's content type. Must match if provided.
497
+ */
498
+ contentType?: string;
499
+ /**
500
+ * AbortSignal for this operation.
501
+ */
502
+ signal?: AbortSignal;
503
+ }
504
+ /**
458
505
  * Legacy live mode type (internal use only).
459
506
  * @internal
460
507
  */
@@ -512,6 +559,11 @@ interface HeadResult {
512
559
  * Cache-Control header value.
513
560
  */
514
561
  cacheControl?: string;
562
+ /**
563
+ * Whether the stream is closed.
564
+ * When true, no further appends are permitted.
565
+ */
566
+ streamClosed: boolean;
515
567
  }
516
568
  /**
517
569
  * Metadata extracted from a stream response.
@@ -521,7 +573,7 @@ interface HeadResult {
521
573
  /**
522
574
  * Error codes for DurableStreamError.
523
575
  */
524
- type DurableStreamErrorCode = `NOT_FOUND` | `CONFLICT_SEQ` | `CONFLICT_EXISTS` | `BAD_REQUEST` | `BUSY` | `SSE_NOT_SUPPORTED` | `UNAUTHORIZED` | `FORBIDDEN` | `RATE_LIMITED` | `ALREADY_CONSUMED` | `ALREADY_CLOSED` | `UNKNOWN`;
576
+ type DurableStreamErrorCode = `NOT_FOUND` | `CONFLICT_SEQ` | `CONFLICT_EXISTS` | `BAD_REQUEST` | `BUSY` | `SSE_NOT_SUPPORTED` | `UNAUTHORIZED` | `FORBIDDEN` | `RATE_LIMITED` | `ALREADY_CONSUMED` | `ALREADY_CLOSED` | `PARSE_ERROR` | `STREAM_CLOSED` | `UNKNOWN`;
525
577
  /**
526
578
  * Options returned from onError handler to retry with modified params/headers.
527
579
  * Following the Electric client pattern.
@@ -638,33 +690,45 @@ interface StreamResponse<TJson = unknown> {
638
690
  *
639
691
  * Use this for resuming reads after a disconnect or saving checkpoints.
640
692
  */
641
- offset: Offset;
693
+ readonly offset: Offset;
642
694
  /**
643
695
  * Stream cursor for CDN collapsing (stream-cursor header).
644
696
  *
645
697
  * Updated after each chunk is delivered to the consumer.
646
698
  */
647
- cursor?: string;
699
+ readonly cursor?: string;
648
700
  /**
649
701
  * Whether we've reached the current end of the stream (stream-up-to-date header).
650
702
  *
651
703
  * Updated after each chunk is delivered to the consumer.
652
704
  */
653
- upToDate: boolean;
705
+ readonly upToDate: boolean;
706
+ /**
707
+ * Whether the stream is closed (EOF).
708
+ *
709
+ * When true, no more data will ever be appended to the stream.
710
+ * This is updated after each chunk is delivered to the consumer.
711
+ *
712
+ * In live mode, when streamClosed becomes true:
713
+ * - Long-poll requests return immediately (no waiting)
714
+ * - SSE connections are closed by the server
715
+ * - Clients stop reconnecting automatically
716
+ */
717
+ readonly streamClosed: boolean;
654
718
  /**
655
719
  * Accumulate raw bytes until first `upToDate` batch, then resolve.
656
- * When used with `live: "auto"`, signals the session to stop after upToDate.
720
+ * When used with `live: true`, signals the session to stop after upToDate.
657
721
  */
658
722
  body: () => Promise<Uint8Array>;
659
723
  /**
660
724
  * Accumulate JSON *items* across batches into a single array, resolve at `upToDate`.
661
725
  * Only valid in JSON-mode; throws otherwise.
662
- * When used with `live: "auto"`, signals the session to stop after upToDate.
726
+ * When used with `live: true`, signals the session to stop after upToDate.
663
727
  */
664
728
  json: <T = TJson>() => Promise<Array<T>>;
665
729
  /**
666
730
  * Accumulate text chunks into a single string, resolve at `upToDate`.
667
- * When used with `live: "auto"`, signals the session to stop after upToDate.
731
+ * When used with `live: true`, signals the session to stop after upToDate.
668
732
  */
669
733
  text: () => Promise<string>;
670
734
  /**
@@ -692,18 +756,27 @@ interface StreamResponse<TJson = unknown> {
692
756
  /**
693
757
  * Subscribe to JSON batches as they arrive.
694
758
  * Returns unsubscribe function.
759
+ *
760
+ * The subscriber can be sync or async. If async, backpressure is applied
761
+ * (the next batch waits for the previous callback to complete).
695
762
  */
696
- subscribeJson: <T = TJson>(subscriber: (batch: JsonBatch<T>) => Promise<void>) => () => void;
763
+ subscribeJson: <T = TJson>(subscriber: (batch: JsonBatch<T>) => void | Promise<void>) => () => void;
697
764
  /**
698
765
  * Subscribe to raw byte chunks as they arrive.
699
766
  * Returns unsubscribe function.
767
+ *
768
+ * The subscriber can be sync or async. If async, backpressure is applied
769
+ * (the next chunk waits for the previous callback to complete).
700
770
  */
701
- subscribeBytes: (subscriber: (chunk: ByteChunk) => Promise<void>) => () => void;
771
+ subscribeBytes: (subscriber: (chunk: ByteChunk) => void | Promise<void>) => () => void;
702
772
  /**
703
773
  * Subscribe to text chunks as they arrive.
704
774
  * Returns unsubscribe function.
775
+ *
776
+ * The subscriber can be sync or async. If async, backpressure is applied
777
+ * (the next chunk waits for the previous callback to complete).
705
778
  */
706
- subscribeText: (subscriber: (chunk: TextChunk) => Promise<void>) => () => void;
779
+ subscribeText: (subscriber: (chunk: TextChunk) => void | Promise<void>) => () => void;
707
780
  /**
708
781
  * Cancel the underlying session (abort HTTP, close SSE, stop long-polls).
709
782
  */
@@ -800,7 +873,7 @@ interface IdempotentAppendResult {
800
873
  * url,
801
874
  * auth,
802
875
  * offset: savedOffset,
803
- * live: "auto",
876
+ * live: true,
804
877
  * })
805
878
  * live.subscribeJson(async (batch) => {
806
879
  * for (const item of batch.items) {
@@ -853,7 +926,7 @@ interface DurableStreamOptions extends StreamHandleOptions {
853
926
  * });
854
927
  *
855
928
  * // Write data
856
- * await stream.append({ message: "hello" });
929
+ * await stream.append(JSON.stringify({ message: "hello" }));
857
930
  *
858
931
  * // Read with the new API
859
932
  * const res = await stream.stream<{ message: string }>();
@@ -925,28 +998,54 @@ declare class DurableStream {
925
998
  signal?: AbortSignal;
926
999
  }): Promise<void>;
927
1000
  /**
1001
+ * Close the stream, optionally with a final message.
1002
+ *
1003
+ * After closing:
1004
+ * - No further appends are permitted (server returns 409)
1005
+ * - Readers can observe the closed state and treat it as EOF
1006
+ * - The stream's data remains fully readable
1007
+ *
1008
+ * Closing is:
1009
+ * - **Durable**: The closed state is persisted
1010
+ * - **Monotonic**: Once closed, a stream cannot be reopened
1011
+ *
1012
+ * **Idempotency:**
1013
+ * - `close()` without body: Idempotent — safe to call multiple times
1014
+ * - `close({ body })` with body: NOT idempotent — throws `StreamClosedError`
1015
+ * if stream is already closed (use `IdempotentProducer.close()` for
1016
+ * idempotent close-with-body semantics)
1017
+ *
1018
+ * @returns CloseResult with the final offset
1019
+ * @throws StreamClosedError if called with body on an already-closed stream
1020
+ */
1021
+ close(opts?: CloseOptions): Promise<CloseResult>;
1022
+ /**
928
1023
  * Append a single payload to the stream.
929
1024
  *
930
1025
  * When batching is enabled (default), multiple append() calls made while
931
1026
  * a POST is in-flight will be batched together into a single request.
932
1027
  * This significantly improves throughput for high-frequency writes.
933
1028
  *
934
- * - `body` may be Uint8Array, string, or any JSON-serializable value (for JSON streams).
935
- * - `body` may also be a Promise that resolves to any of the above types.
1029
+ * - `body` must be string or Uint8Array.
1030
+ * - For JSON streams, pass pre-serialized JSON strings.
1031
+ * - `body` may also be a Promise that resolves to string or Uint8Array.
936
1032
  * - Strings are encoded as UTF-8.
937
1033
  * - `seq` (if provided) is sent as stream-seq (writer coordination).
938
1034
  *
939
1035
  * @example
940
1036
  * ```typescript
941
- * // Direct value
942
- * await stream.append({ message: "hello" });
1037
+ * // JSON stream - pass pre-serialized JSON
1038
+ * await stream.append(JSON.stringify({ message: "hello" }));
1039
+ *
1040
+ * // Byte stream
1041
+ * await stream.append("raw text data");
1042
+ * await stream.append(new Uint8Array([1, 2, 3]));
943
1043
  *
944
1044
  * // Promise value - awaited before buffering
945
1045
  * await stream.append(fetchData());
946
- * await stream.append(Promise.all([a, b, c]));
947
1046
  * ```
948
1047
  */
949
- append(body: BodyInit | Uint8Array | string | unknown, opts?: AppendOptions): Promise<void>;
1048
+ append(body: Uint8Array | string | Promise<Uint8Array | string>, opts?: AppendOptions): Promise<void>;
950
1049
  /**
951
1050
  * Append a streaming body to the stream.
952
1051
  *
@@ -986,6 +1085,11 @@ declare class DurableStream {
986
1085
  * Returns a WritableStream that can be used with `pipeTo()` or
987
1086
  * `pipeThrough()` from any ReadableStream source.
988
1087
  *
1088
+ * Uses IdempotentProducer internally for:
1089
+ * - Automatic batching (controlled by lingerMs, maxBatchBytes)
1090
+ * - Exactly-once delivery semantics
1091
+ * - Streaming writes (doesn't buffer entire content in memory)
1092
+ *
989
1093
  * @example
990
1094
  * ```typescript
991
1095
  * // Pipe from fetch response
@@ -995,9 +1099,19 @@ declare class DurableStream {
995
1099
  * // Pipe through a transform
996
1100
  * const readable = someStream.pipeThrough(new TextEncoderStream());
997
1101
  * await readable.pipeTo(stream.writable());
1102
+ *
1103
+ * // With custom producer options
1104
+ * await source.pipeTo(stream.writable({
1105
+ * producerId: "my-producer",
1106
+ * lingerMs: 10,
1107
+ * maxBatchBytes: 64 * 1024,
1108
+ * }));
998
1109
  * ```
999
1110
  */
1000
- writable(opts?: AppendOptions): WritableStream<Uint8Array | string>;
1111
+ writable(opts?: Pick<IdempotentProducerOptions, `lingerMs` | `maxBatchBytes` | `onError`> & {
1112
+ producerId?: string;
1113
+ signal?: AbortSignal;
1114
+ }): WritableStream<Uint8Array | string>;
1001
1115
  /**
1002
1116
  * Start a fetch-like streaming session against this handle's URL/headers/params.
1003
1117
  * The first request is made inside this method; it resolves when we have
@@ -1132,12 +1246,22 @@ declare class IdempotentProducer {
1132
1246
  * Errors are reported via onError callback if configured. Use flush() to
1133
1247
  * wait for all pending messages to be sent.
1134
1248
  *
1135
- * For JSON streams, pass native objects (which will be serialized internally).
1249
+ * For JSON streams, pass pre-serialized JSON strings.
1136
1250
  * For byte streams, pass string or Uint8Array.
1137
1251
  *
1138
- * @param body - Data to append (object for JSON streams, string or Uint8Array for byte streams)
1252
+ * @param body - Data to append (string or Uint8Array)
1253
+ *
1254
+ * @example
1255
+ * ```typescript
1256
+ * // JSON stream
1257
+ * producer.append(JSON.stringify({ message: "hello" }));
1258
+ *
1259
+ * // Byte stream
1260
+ * producer.append("raw text data");
1261
+ * producer.append(new Uint8Array([1, 2, 3]));
1262
+ * ```
1139
1263
  */
1140
- append(body: Uint8Array | string | unknown): void;
1264
+ append(body: Uint8Array | string): void;
1141
1265
  /**
1142
1266
  * Send any pending batch immediately and wait for all in-flight batches.
1143
1267
  *
@@ -1145,11 +1269,33 @@ declare class IdempotentProducer {
1145
1269
  */
1146
1270
  flush(): Promise<void>;
1147
1271
  /**
1148
- * Flush pending messages and close the producer.
1272
+ * Stop the producer without closing the underlying stream.
1273
+ *
1274
+ * Use this when you want to:
1275
+ * - Hand off writing to another producer
1276
+ * - Keep the stream open for future writes
1277
+ * - Stop this producer but not signal EOF to readers
1149
1278
  *
1150
- * After calling close(), further append() calls will throw.
1279
+ * Flushes any pending messages before detaching.
1280
+ * After calling detach(), further append() calls will throw.
1151
1281
  */
1152
- close(): Promise<void>;
1282
+ detach(): Promise<void>;
1283
+ /**
1284
+ * Flush pending messages and close the underlying stream (EOF).
1285
+ *
1286
+ * This is the typical way to end a producer session. It:
1287
+ * 1. Flushes all pending messages
1288
+ * 2. Optionally appends a final message
1289
+ * 3. Closes the stream (no further appends permitted)
1290
+ *
1291
+ * **Idempotent**: Unlike `DurableStream.close({ body })`, this method is
1292
+ * idempotent even with a final message because it uses producer headers
1293
+ * for deduplication. Safe to retry on network failures.
1294
+ *
1295
+ * @param finalMessage - Optional final message to append atomically with close
1296
+ * @returns CloseResult with the final offset
1297
+ */
1298
+ close(finalMessage?: Uint8Array | string): Promise<CloseResult>;
1153
1299
  /**
1154
1300
  * Increment epoch and reset sequence.
1155
1301
  *
@@ -1230,6 +1376,19 @@ declare class MissingStreamUrlError extends Error {
1230
1376
  constructor();
1231
1377
  }
1232
1378
  /**
1379
+ * Error thrown when attempting to append to a closed stream.
1380
+ */
1381
+ declare class StreamClosedError extends DurableStreamError {
1382
+ readonly code: "STREAM_CLOSED";
1383
+ readonly status = 409;
1384
+ readonly streamClosed: true;
1385
+ /**
1386
+ * The final offset of the stream, if available from the response.
1387
+ */
1388
+ readonly finalOffset?: string;
1389
+ constructor(url?: string, finalOffset?: string);
1390
+ }
1391
+ /**
1233
1392
  * Error thrown when signal option is invalid.
1234
1393
  */
1235
1394
  declare class InvalidSignalError extends Error {
@@ -1259,6 +1418,11 @@ declare const STREAM_CURSOR_HEADER = "Stream-Cursor";
1259
1418
  */
1260
1419
  declare const STREAM_UP_TO_DATE_HEADER = "Stream-Up-To-Date";
1261
1420
  /**
1421
+ * Response/request header indicating stream is closed (EOF).
1422
+ * When present with value "true", the stream is permanently closed.
1423
+ */
1424
+ declare const STREAM_CLOSED_HEADER = "Stream-Closed";
1425
+ /**
1262
1426
  * Request header for writer coordination sequence.
1263
1427
  * Monotonic, lexicographic. If lower than last appended seq -> 409 Conflict.
1264
1428
  */
@@ -1307,13 +1471,17 @@ declare const LIVE_QUERY_PARAM = "live";
1307
1471
  */
1308
1472
  declare const CURSOR_QUERY_PARAM = "cursor";
1309
1473
  /**
1310
- * SSE control event field for the next offset.
1311
- * Note: Different from HTTP header name (camelCase vs Header-Case).
1474
+ * Response header indicating SSE data encoding (e.g., base64 for binary streams).
1312
1475
  */
1313
1476
 
1314
1477
  /**
1315
- * Content types that support SSE mode.
1316
- * SSE is only valid for text/* or application/json streams.
1478
+ * SSE control event field for stream closed state.
1479
+ * Note: Different from HTTP header name (camelCase vs Header-Case).
1480
+ */
1481
+ declare const SSE_CLOSED_FIELD = "streamClosed";
1482
+ /**
1483
+ * Content types that are natively compatible with SSE (UTF-8 text).
1484
+ * Binary content types are also supported via automatic base64 encoding.
1317
1485
  */
1318
1486
  declare const SSE_COMPATIBLE_CONTENT_TYPES: ReadonlyArray<string>;
1319
1487
  /**
@@ -1322,4 +1490,4 @@ declare const SSE_COMPATIBLE_CONTENT_TYPES: ReadonlyArray<string>;
1322
1490
  declare const DURABLE_STREAM_PROTOCOL_QUERY_PARAMS: Array<string>;
1323
1491
 
1324
1492
  //#endregion
1325
- export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, IdempotentAppendResult, IdempotentProducer, IdempotentProducerOptions, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, PRODUCER_EPOCH_HEADER, PRODUCER_EXPECTED_SEQ_HEADER, PRODUCER_ID_HEADER, PRODUCER_RECEIVED_SEQ_HEADER, PRODUCER_SEQ_HEADER, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, SequenceGapError, StaleEpochError, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, _resetHttpWarningForTesting, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream, warnIfUsingHttpInBrowser };
1493
+ export { AppendOptions, BackoffDefaults, BackoffOptions, ByteChunk, CURSOR_QUERY_PARAM, CloseOptions, CloseResult, CreateOptions, DURABLE_STREAM_PROTOCOL_QUERY_PARAMS, DurableStream, DurableStreamError, DurableStreamErrorCode, DurableStreamOptions, FetchBackoffAbortError, FetchError, HeadResult, HeadersRecord, IdempotentAppendResult, IdempotentProducer, IdempotentProducerOptions, InvalidSignalError, JsonBatch, JsonBatchMeta, LIVE_QUERY_PARAM, LegacyLiveMode, LiveMode, MaybePromise, MissingStreamUrlError, OFFSET_QUERY_PARAM, Offset, PRODUCER_EPOCH_HEADER, PRODUCER_EXPECTED_SEQ_HEADER, PRODUCER_ID_HEADER, PRODUCER_RECEIVED_SEQ_HEADER, PRODUCER_SEQ_HEADER, ParamsRecord, ReadOptions, ReadableStreamAsyncIterable, RetryOpts, SSEResilienceOptions, SSE_CLOSED_FIELD, SSE_COMPATIBLE_CONTENT_TYPES, STREAM_CLOSED_HEADER, STREAM_CURSOR_HEADER, STREAM_EXPIRES_AT_HEADER, STREAM_OFFSET_HEADER, STREAM_SEQ_HEADER, STREAM_TTL_HEADER, STREAM_UP_TO_DATE_HEADER, SequenceGapError, StaleEpochError, StreamClosedError, StreamErrorHandler, StreamHandleOptions, StreamOptions, StreamResponse, TextChunk, _resetHttpWarningForTesting, asAsyncIterableReadableStream, createFetchWithBackoff, createFetchWithConsumedBody, stream, warnIfUsingHttpInBrowser };