@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/src/types.ts CHANGED
@@ -62,11 +62,11 @@ export type ParamsRecord = {
62
62
  /**
63
63
  * Live mode for reading from a stream.
64
64
  * - false: Catch-up only, stop at first `upToDate`
65
- * - "auto": Behavior driven by consumption method (default)
65
+ * - true: Auto-select best mode (SSE for JSON streams, long-poll for binary)
66
66
  * - "long-poll": Explicit long-poll mode for live updates
67
67
  * - "sse": Explicit server-sent events for live updates
68
68
  */
69
- export type LiveMode = false | `auto` | `long-poll` | `sse`
69
+ export type LiveMode = boolean | `long-poll` | `sse`
70
70
 
71
71
  // ============================================================================
72
72
  // Stream Options (Read API)
@@ -136,7 +136,7 @@ export interface StreamOptions {
136
136
  /**
137
137
  * Live mode behavior:
138
138
  * - false: Catch-up only, stop at first `upToDate`
139
- * - "auto" (default): Behavior driven by consumption method
139
+ * - true (default): Auto-select best mode (SSE for JSON, long-poll for binary)
140
140
  * - "long-poll": Explicit long-poll mode for live updates
141
141
  * - "sse": Explicit server-sent events for live updates
142
142
  */
@@ -228,6 +228,12 @@ export interface JsonBatchMeta {
228
228
  * Last Stream-Cursor / streamCursor, if present.
229
229
  */
230
230
  cursor?: string
231
+
232
+ /**
233
+ * Whether the stream is closed and this batch contains the final data.
234
+ * When true, no more data will ever be appended to the stream.
235
+ */
236
+ streamClosed: boolean
231
237
  }
232
238
 
233
239
  /**
@@ -357,6 +363,18 @@ export interface CreateOptions extends StreamHandleOptions {
357
363
  * @default true
358
364
  */
359
365
  batching?: boolean
366
+
367
+ /**
368
+ * If true, create the stream in the closed state.
369
+ * Any body provided becomes the complete and final content.
370
+ *
371
+ * Useful for:
372
+ * - Cached responses
373
+ * - Placeholder errors
374
+ * - Pre-computed results
375
+ * - Single-message streams that are immediately complete
376
+ */
377
+ closed?: boolean
360
378
  }
361
379
 
362
380
  /**
@@ -403,6 +421,41 @@ export interface AppendOptions {
403
421
  producerSeq?: number
404
422
  }
405
423
 
424
+ /**
425
+ * Result of a close operation.
426
+ */
427
+ export interface CloseResult {
428
+ /**
429
+ * The final offset of the stream.
430
+ * This is the offset after the last byte (including any final message).
431
+ * Returned via the `Stream-Next-Offset` header.
432
+ */
433
+ finalOffset: Offset
434
+ }
435
+
436
+ /**
437
+ * Options for closing a stream.
438
+ */
439
+ export interface CloseOptions {
440
+ /**
441
+ * Optional final message to append atomically with close.
442
+ * For JSON streams, pass a pre-serialized JSON string.
443
+ * Strings are UTF-8 encoded.
444
+ */
445
+ body?: Uint8Array | string
446
+
447
+ /**
448
+ * Content type for the final message.
449
+ * Defaults to the stream's content type. Must match if provided.
450
+ */
451
+ contentType?: string
452
+
453
+ /**
454
+ * AbortSignal for this operation.
455
+ */
456
+ signal?: AbortSignal
457
+ }
458
+
406
459
  /**
407
460
  * Legacy live mode type (internal use only).
408
461
  * @internal
@@ -470,6 +523,12 @@ export interface HeadResult {
470
523
  * Cache-Control header value.
471
524
  */
472
525
  cacheControl?: string
526
+
527
+ /**
528
+ * Whether the stream is closed.
529
+ * When true, no further appends are permitted.
530
+ */
531
+ streamClosed: boolean
473
532
  }
474
533
 
475
534
  /**
@@ -518,6 +577,8 @@ export type DurableStreamErrorCode =
518
577
  | `RATE_LIMITED`
519
578
  | `ALREADY_CONSUMED`
520
579
  | `ALREADY_CLOSED`
580
+ | `PARSE_ERROR`
581
+ | `STREAM_CLOSED`
521
582
  | `UNKNOWN`
522
583
 
523
584
  /**
@@ -659,21 +720,34 @@ export interface StreamResponse<TJson = unknown> {
659
720
  *
660
721
  * Use this for resuming reads after a disconnect or saving checkpoints.
661
722
  */
662
- offset: Offset
723
+ readonly offset: Offset
663
724
 
664
725
  /**
665
726
  * Stream cursor for CDN collapsing (stream-cursor header).
666
727
  *
667
728
  * Updated after each chunk is delivered to the consumer.
668
729
  */
669
- cursor?: string
730
+ readonly cursor?: string
670
731
 
671
732
  /**
672
733
  * Whether we've reached the current end of the stream (stream-up-to-date header).
673
734
  *
674
735
  * Updated after each chunk is delivered to the consumer.
675
736
  */
676
- upToDate: boolean
737
+ readonly upToDate: boolean
738
+
739
+ /**
740
+ * Whether the stream is closed (EOF).
741
+ *
742
+ * When true, no more data will ever be appended to the stream.
743
+ * This is updated after each chunk is delivered to the consumer.
744
+ *
745
+ * In live mode, when streamClosed becomes true:
746
+ * - Long-poll requests return immediately (no waiting)
747
+ * - SSE connections are closed by the server
748
+ * - Clients stop reconnecting automatically
749
+ */
750
+ readonly streamClosed: boolean
677
751
 
678
752
  // =================================
679
753
  // 1) Accumulating helpers (Promise)
@@ -682,20 +756,20 @@ export interface StreamResponse<TJson = unknown> {
682
756
 
683
757
  /**
684
758
  * Accumulate raw bytes until first `upToDate` batch, then resolve.
685
- * When used with `live: "auto"`, signals the session to stop after upToDate.
759
+ * When used with `live: true`, signals the session to stop after upToDate.
686
760
  */
687
761
  body: () => Promise<Uint8Array>
688
762
 
689
763
  /**
690
764
  * Accumulate JSON *items* across batches into a single array, resolve at `upToDate`.
691
765
  * Only valid in JSON-mode; throws otherwise.
692
- * When used with `live: "auto"`, signals the session to stop after upToDate.
766
+ * When used with `live: true`, signals the session to stop after upToDate.
693
767
  */
694
768
  json: <T = TJson>() => Promise<Array<T>>
695
769
 
696
770
  /**
697
771
  * Accumulate text chunks into a single string, resolve at `upToDate`.
698
- * When used with `live: "auto"`, signals the session to stop after upToDate.
772
+ * When used with `live: true`, signals the session to stop after upToDate.
699
773
  */
700
774
  text: () => Promise<string>
701
775
 
@@ -737,24 +811,35 @@ export interface StreamResponse<TJson = unknown> {
737
811
  /**
738
812
  * Subscribe to JSON batches as they arrive.
739
813
  * Returns unsubscribe function.
814
+ *
815
+ * The subscriber can be sync or async. If async, backpressure is applied
816
+ * (the next batch waits for the previous callback to complete).
740
817
  */
741
818
  subscribeJson: <T = TJson>(
742
- subscriber: (batch: JsonBatch<T>) => Promise<void>
819
+ subscriber: (batch: JsonBatch<T>) => void | Promise<void>
743
820
  ) => () => void
744
821
 
745
822
  /**
746
823
  * Subscribe to raw byte chunks as they arrive.
747
824
  * Returns unsubscribe function.
825
+ *
826
+ * The subscriber can be sync or async. If async, backpressure is applied
827
+ * (the next chunk waits for the previous callback to complete).
748
828
  */
749
829
  subscribeBytes: (
750
- subscriber: (chunk: ByteChunk) => Promise<void>
830
+ subscriber: (chunk: ByteChunk) => void | Promise<void>
751
831
  ) => () => void
752
832
 
753
833
  /**
754
834
  * Subscribe to text chunks as they arrive.
755
835
  * Returns unsubscribe function.
836
+ *
837
+ * The subscriber can be sync or async. If async, backpressure is applied
838
+ * (the next chunk waits for the previous callback to complete).
756
839
  */
757
- subscribeText: (subscriber: (chunk: TextChunk) => Promise<void>) => () => void
840
+ subscribeText: (
841
+ subscriber: (chunk: TextChunk) => void | Promise<void>
842
+ ) => () => void
758
843
 
759
844
  // =====================
760
845
  // 4) Lifecycle
package/src/utils.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  * Shared utility functions for the Durable Streams client.
3
3
  */
4
4
 
5
- import { DurableStreamError } from "./error"
5
+ import { STREAM_CLOSED_HEADER, STREAM_OFFSET_HEADER } from "./constants"
6
+ import { DurableStreamError, StreamClosedError } from "./error"
6
7
  import type { HeadersRecord, MaybePromise } from "./types"
7
8
 
8
9
  /**
@@ -45,6 +46,14 @@ export async function handleErrorResponse(
45
46
  }
46
47
 
47
48
  if (status === 409) {
49
+ // Check if this is a stream closed error
50
+ const streamClosedHeader = response.headers.get(STREAM_CLOSED_HEADER)
51
+ if (streamClosedHeader?.toLowerCase() === `true`) {
52
+ const finalOffset =
53
+ response.headers.get(STREAM_OFFSET_HEADER) ?? undefined
54
+ throw new StreamClosedError(url, finalOffset)
55
+ }
56
+
48
57
  // Context-specific 409 messages
49
58
  const message =
50
59
  context?.operation === `create`