@durable-streams/client 0.2.0 → 0.2.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/src/types.ts CHANGED
@@ -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
  /**
@@ -519,6 +578,7 @@ export type DurableStreamErrorCode =
519
578
  | `ALREADY_CONSUMED`
520
579
  | `ALREADY_CLOSED`
521
580
  | `PARSE_ERROR`
581
+ | `STREAM_CLOSED`
522
582
  | `UNKNOWN`
523
583
 
524
584
  /**
@@ -676,6 +736,19 @@ export interface StreamResponse<TJson = unknown> {
676
736
  */
677
737
  readonly upToDate: boolean
678
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
751
+
679
752
  // =================================
680
753
  // 1) Accumulating helpers (Promise)
681
754
  // =================================
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`