@durable-streams/client 0.1.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/README.md +799 -0
- package/dist/index.cjs +1172 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +627 -0
- package/dist/index.d.ts +1072 -0
- package/dist/index.js +1830 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/asyncIterableReadableStream.ts +220 -0
- package/src/constants.ts +105 -0
- package/src/error.ts +189 -0
- package/src/fetch.ts +267 -0
- package/src/index.ts +103 -0
- package/src/response.ts +1053 -0
- package/src/sse.ts +130 -0
- package/src/stream-api.ts +284 -0
- package/src/stream.ts +867 -0
- package/src/types.ts +737 -0
- package/src/utils.ts +104 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Streams TypeScript Client Types
|
|
3
|
+
*
|
|
4
|
+
* Following the Electric Durable Stream Protocol specification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReadableStreamAsyncIterable } from "./asyncIterableReadableStream"
|
|
8
|
+
import type { BackoffOptions } from "./fetch"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Offset string - opaque to the client.
|
|
12
|
+
* Format: "<read-seq>_<byte-offset>"
|
|
13
|
+
*
|
|
14
|
+
* **Special value**: `-1` means "start of stream" - use this to read from the beginning.
|
|
15
|
+
*
|
|
16
|
+
* Always use the returned `offset` field from reads/follows as the next `offset` you pass in.
|
|
17
|
+
*/
|
|
18
|
+
export type Offset = string
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Type for values that can be provided immediately or resolved asynchronously.
|
|
22
|
+
*/
|
|
23
|
+
export type MaybePromise<T> = T | Promise<T>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Headers record where values can be static strings or async functions.
|
|
27
|
+
* Following the @electric-sql/client pattern for dynamic headers.
|
|
28
|
+
*
|
|
29
|
+
* **Important**: Functions are called **for each request**, not once per session.
|
|
30
|
+
* In live mode with long-polling, the same function may be called many times
|
|
31
|
+
* to fetch fresh values (e.g., refreshed auth tokens) for each poll.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* headers: {
|
|
36
|
+
* Authorization: `Bearer ${token}`, // Static - same for all requests
|
|
37
|
+
* 'X-Tenant-Id': () => getCurrentTenant(), // Called per-request
|
|
38
|
+
* 'X-Auth': async () => await refreshToken() // Called per-request (can refresh)
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export type HeadersRecord = {
|
|
43
|
+
[key: string]: string | (() => MaybePromise<string>)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Params record where values can be static or async functions.
|
|
48
|
+
* Following the @electric-sql/client pattern for dynamic params.
|
|
49
|
+
*
|
|
50
|
+
* **Important**: Functions are called **for each request**, not once per session.
|
|
51
|
+
* In live mode, the same function may be called multiple times to fetch
|
|
52
|
+
* fresh parameter values for each poll.
|
|
53
|
+
*/
|
|
54
|
+
export type ParamsRecord = {
|
|
55
|
+
[key: string]: string | (() => MaybePromise<string>) | undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Live Mode Types
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Live mode for reading from a stream.
|
|
64
|
+
* - false: Catch-up only, stop at first `upToDate`
|
|
65
|
+
* - "auto": Behavior driven by consumption method (default)
|
|
66
|
+
* - "long-poll": Explicit long-poll mode for live updates
|
|
67
|
+
* - "sse": Explicit server-sent events for live updates
|
|
68
|
+
*/
|
|
69
|
+
export type LiveMode = false | `auto` | `long-poll` | `sse`
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Stream Options (Read API)
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Options for the stream() function (read-only API).
|
|
77
|
+
*/
|
|
78
|
+
export interface StreamOptions {
|
|
79
|
+
/**
|
|
80
|
+
* The full URL to the durable stream.
|
|
81
|
+
* E.g., "https://streams.example.com/my-account/chat/room-1"
|
|
82
|
+
*/
|
|
83
|
+
url: string | URL
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* HTTP headers to include in requests.
|
|
87
|
+
* Values can be strings or functions (sync or async) that return strings.
|
|
88
|
+
*
|
|
89
|
+
* **Important**: Functions are evaluated **per-request** (not per-session).
|
|
90
|
+
* In live mode, functions are called for each poll, allowing fresh values
|
|
91
|
+
* like refreshed auth tokens.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* headers: {
|
|
96
|
+
* Authorization: `Bearer ${token}`, // Static
|
|
97
|
+
* 'X-Tenant-Id': () => getCurrentTenant(), // Evaluated per-request
|
|
98
|
+
* 'X-Auth': async () => await refreshToken() // Evaluated per-request
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
headers?: HeadersRecord
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Query parameters to include in requests.
|
|
106
|
+
* Values can be strings or functions (sync or async) that return strings.
|
|
107
|
+
*
|
|
108
|
+
* **Important**: Functions are evaluated **per-request** (not per-session).
|
|
109
|
+
*/
|
|
110
|
+
params?: ParamsRecord
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* AbortSignal for cancellation.
|
|
114
|
+
*/
|
|
115
|
+
signal?: AbortSignal
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Custom fetch implementation (for auth layers, proxies, etc.).
|
|
119
|
+
* Defaults to globalThis.fetch.
|
|
120
|
+
*/
|
|
121
|
+
fetch?: typeof globalThis.fetch
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Backoff options for retry behavior.
|
|
125
|
+
* Defaults to exponential backoff with jitter.
|
|
126
|
+
*/
|
|
127
|
+
backoffOptions?: BackoffOptions
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Starting offset (query param ?offset=...).
|
|
131
|
+
* If omitted, defaults to "-1" (start of stream).
|
|
132
|
+
* You can also explicitly pass "-1" to read from the beginning.
|
|
133
|
+
*/
|
|
134
|
+
offset?: Offset
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Live mode behavior:
|
|
138
|
+
* - false: Catch-up only, stop at first `upToDate`
|
|
139
|
+
* - "auto" (default): Behavior driven by consumption method
|
|
140
|
+
* - "long-poll": Explicit long-poll mode for live updates
|
|
141
|
+
* - "sse": Explicit server-sent events for live updates
|
|
142
|
+
*/
|
|
143
|
+
live?: LiveMode
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Hint: treat content as JSON even if Content-Type doesn't say so.
|
|
147
|
+
*/
|
|
148
|
+
json?: boolean
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Error handler for recoverable errors (following Electric client pattern).
|
|
152
|
+
*/
|
|
153
|
+
onError?: StreamErrorHandler
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* SSE resilience options.
|
|
157
|
+
* When SSE connections fail repeatedly, the client can automatically
|
|
158
|
+
* fall back to long-polling mode.
|
|
159
|
+
*/
|
|
160
|
+
sseResilience?: SSEResilienceOptions
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Options for SSE connection resilience.
|
|
165
|
+
*/
|
|
166
|
+
export interface SSEResilienceOptions {
|
|
167
|
+
/**
|
|
168
|
+
* Minimum expected SSE connection duration in milliseconds.
|
|
169
|
+
* Connections shorter than this are considered "short" and may indicate
|
|
170
|
+
* proxy buffering or server misconfiguration.
|
|
171
|
+
* @default 1000
|
|
172
|
+
*/
|
|
173
|
+
minConnectionDuration?: number
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Maximum number of consecutive short connections before falling back to long-poll.
|
|
177
|
+
* @default 3
|
|
178
|
+
*/
|
|
179
|
+
maxShortConnections?: number
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Base delay for exponential backoff between short connection retries (ms).
|
|
183
|
+
* @default 100
|
|
184
|
+
*/
|
|
185
|
+
backoffBaseDelay?: number
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Maximum delay cap for exponential backoff (ms).
|
|
189
|
+
* @default 5000
|
|
190
|
+
*/
|
|
191
|
+
backoffMaxDelay?: number
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Whether to log warnings when falling back to long-poll.
|
|
195
|
+
* @default true
|
|
196
|
+
*/
|
|
197
|
+
logWarnings?: boolean
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Chunk & Batch Types
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Metadata for a JSON batch or chunk.
|
|
206
|
+
*/
|
|
207
|
+
export interface JsonBatchMeta {
|
|
208
|
+
/**
|
|
209
|
+
* Last Stream-Next-Offset for this batch.
|
|
210
|
+
*/
|
|
211
|
+
offset: Offset
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* True if this batch ends at the current end of the stream.
|
|
215
|
+
*/
|
|
216
|
+
upToDate: boolean
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Last Stream-Cursor / streamCursor, if present.
|
|
220
|
+
*/
|
|
221
|
+
cursor?: string
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A batch of parsed JSON items with metadata.
|
|
226
|
+
*/
|
|
227
|
+
export interface JsonBatch<T = unknown> extends JsonBatchMeta {
|
|
228
|
+
/**
|
|
229
|
+
* The parsed JSON items in this batch.
|
|
230
|
+
*/
|
|
231
|
+
items: ReadonlyArray<T>
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A chunk of raw bytes with metadata.
|
|
236
|
+
*/
|
|
237
|
+
export interface ByteChunk extends JsonBatchMeta {
|
|
238
|
+
/**
|
|
239
|
+
* The raw byte data.
|
|
240
|
+
*/
|
|
241
|
+
data: Uint8Array
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A chunk of text with metadata.
|
|
246
|
+
*/
|
|
247
|
+
export interface TextChunk extends JsonBatchMeta {
|
|
248
|
+
/**
|
|
249
|
+
* The text content.
|
|
250
|
+
*/
|
|
251
|
+
text: string
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// StreamHandle Options (Read/Write API)
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Base options for StreamHandle operations.
|
|
260
|
+
*/
|
|
261
|
+
export interface StreamHandleOptions {
|
|
262
|
+
/**
|
|
263
|
+
* The full URL to the durable stream.
|
|
264
|
+
* E.g., "https://streams.example.com/my-account/chat/room-1"
|
|
265
|
+
*/
|
|
266
|
+
url: string | URL
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* HTTP headers to include in requests.
|
|
270
|
+
* Values can be strings or functions (sync or async) that return strings.
|
|
271
|
+
*
|
|
272
|
+
* Functions are evaluated **per-request** (not per-session).
|
|
273
|
+
*/
|
|
274
|
+
headers?: HeadersRecord
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Query parameters to include in requests.
|
|
278
|
+
* Values can be strings or functions (sync or async) that return strings.
|
|
279
|
+
*
|
|
280
|
+
* Functions are evaluated **per-request** (not per-session).
|
|
281
|
+
*/
|
|
282
|
+
params?: ParamsRecord
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Custom fetch implementation.
|
|
286
|
+
* Defaults to globalThis.fetch.
|
|
287
|
+
*/
|
|
288
|
+
fetch?: typeof globalThis.fetch
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Default AbortSignal for operations.
|
|
292
|
+
*/
|
|
293
|
+
signal?: AbortSignal
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The content type for the stream.
|
|
297
|
+
*/
|
|
298
|
+
contentType?: string
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Error handler for recoverable errors.
|
|
302
|
+
*/
|
|
303
|
+
onError?: StreamErrorHandler
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Enable automatic batching for append() calls.
|
|
307
|
+
* When true, multiple append() calls made while a POST is in-flight
|
|
308
|
+
* will be batched together into a single request.
|
|
309
|
+
*
|
|
310
|
+
* @default true
|
|
311
|
+
*/
|
|
312
|
+
batching?: boolean
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Options for creating a new stream.
|
|
317
|
+
*/
|
|
318
|
+
export interface CreateOptions extends StreamHandleOptions {
|
|
319
|
+
/**
|
|
320
|
+
* Time-to-live in seconds (relative TTL).
|
|
321
|
+
*/
|
|
322
|
+
ttlSeconds?: number
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Absolute expiry time (RFC3339 format).
|
|
326
|
+
*/
|
|
327
|
+
expiresAt?: string
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Initial body to append on creation.
|
|
331
|
+
*/
|
|
332
|
+
body?: BodyInit | Uint8Array | string
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Enable automatic batching for append() calls.
|
|
336
|
+
* When true, multiple append() calls made while a POST is in-flight
|
|
337
|
+
* will be batched together into a single request.
|
|
338
|
+
*
|
|
339
|
+
* @default true
|
|
340
|
+
*/
|
|
341
|
+
batching?: boolean
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Options for appending data to a stream.
|
|
346
|
+
*/
|
|
347
|
+
export interface AppendOptions {
|
|
348
|
+
/**
|
|
349
|
+
* Writer coordination sequence (stream-seq header).
|
|
350
|
+
* Monotonic, lexicographic sequence for coordinating multiple writers.
|
|
351
|
+
* If lower than last appended seq, server returns 409 Conflict.
|
|
352
|
+
* Not related to read offsets.
|
|
353
|
+
*/
|
|
354
|
+
seq?: string
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Content type for this append.
|
|
358
|
+
* Must match the stream's content type.
|
|
359
|
+
*/
|
|
360
|
+
contentType?: string
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* AbortSignal for this operation.
|
|
364
|
+
*/
|
|
365
|
+
signal?: AbortSignal
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Legacy live mode type (internal use only).
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
export type LegacyLiveMode = `long-poll` | `sse`
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Options for reading from a stream (internal iterator options).
|
|
376
|
+
* @internal
|
|
377
|
+
*/
|
|
378
|
+
export interface ReadOptions {
|
|
379
|
+
/**
|
|
380
|
+
* Starting offset, passed as ?offset=...
|
|
381
|
+
* If omitted, defaults to "-1" (start of stream).
|
|
382
|
+
*/
|
|
383
|
+
offset?: Offset
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Live mode behavior:
|
|
387
|
+
* - undefined/true (default): Catch-up then auto-select SSE or long-poll for live updates
|
|
388
|
+
* - false: Only catch-up, stop after up-to-date (no live updates)
|
|
389
|
+
* - "long-poll": Use long-polling for live updates
|
|
390
|
+
* - "sse": Use SSE for live updates (throws if unsupported)
|
|
391
|
+
*/
|
|
392
|
+
live?: boolean | LegacyLiveMode
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Override cursor for the request.
|
|
396
|
+
* By default, the client echoes the last stream-cursor value.
|
|
397
|
+
*/
|
|
398
|
+
cursor?: string
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* AbortSignal for this operation.
|
|
402
|
+
*/
|
|
403
|
+
signal?: AbortSignal
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Result from a HEAD request on a stream.
|
|
408
|
+
*/
|
|
409
|
+
export interface HeadResult {
|
|
410
|
+
/**
|
|
411
|
+
* Whether the stream exists.
|
|
412
|
+
*/
|
|
413
|
+
exists: true
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* The stream's content type.
|
|
417
|
+
*/
|
|
418
|
+
contentType?: string
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* The tail offset (next offset after current end of stream).
|
|
422
|
+
* Provided by server as stream-offset header on HEAD.
|
|
423
|
+
*/
|
|
424
|
+
offset?: Offset
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* ETag for the stream (format: {internal_stream_id}:{end_offset}).
|
|
428
|
+
*/
|
|
429
|
+
etag?: string
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Cache-Control header value.
|
|
433
|
+
*/
|
|
434
|
+
cacheControl?: string
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Metadata extracted from a stream response.
|
|
439
|
+
* Contains headers and control information from the stream server.
|
|
440
|
+
*/
|
|
441
|
+
export interface ResponseMetadata {
|
|
442
|
+
/**
|
|
443
|
+
* Next offset to read from (stream-offset header).
|
|
444
|
+
*/
|
|
445
|
+
offset: string
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Cursor for CDN collapsing (stream-cursor header).
|
|
449
|
+
*/
|
|
450
|
+
cursor?: string
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* True if stream-up-to-date header was present.
|
|
454
|
+
*/
|
|
455
|
+
upToDate: boolean
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* ETag for caching.
|
|
459
|
+
*/
|
|
460
|
+
etag?: string
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Content type of the stream.
|
|
464
|
+
*/
|
|
465
|
+
contentType?: string
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Error codes for DurableStreamError.
|
|
470
|
+
*/
|
|
471
|
+
export type DurableStreamErrorCode =
|
|
472
|
+
| `NOT_FOUND`
|
|
473
|
+
| `CONFLICT_SEQ`
|
|
474
|
+
| `CONFLICT_EXISTS`
|
|
475
|
+
| `BAD_REQUEST`
|
|
476
|
+
| `BUSY`
|
|
477
|
+
| `SSE_NOT_SUPPORTED`
|
|
478
|
+
| `UNAUTHORIZED`
|
|
479
|
+
| `FORBIDDEN`
|
|
480
|
+
| `RATE_LIMITED`
|
|
481
|
+
| `ALREADY_CONSUMED`
|
|
482
|
+
| `ALREADY_CLOSED`
|
|
483
|
+
| `UNKNOWN`
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Options returned from onError handler to retry with modified params/headers.
|
|
487
|
+
* Following the Electric client pattern.
|
|
488
|
+
*/
|
|
489
|
+
export type RetryOpts = {
|
|
490
|
+
params?: ParamsRecord
|
|
491
|
+
headers?: HeadersRecord
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Error handler callback type.
|
|
496
|
+
*
|
|
497
|
+
* Called when a recoverable error occurs during streaming.
|
|
498
|
+
*
|
|
499
|
+
* **Return value behavior** (following Electric client pattern):
|
|
500
|
+
* - Return `{}` (empty object) → Retry immediately with same params/headers
|
|
501
|
+
* - Return `{ params }` → Retry with merged params (existing params preserved)
|
|
502
|
+
* - Return `{ headers }` → Retry with merged headers (existing headers preserved)
|
|
503
|
+
* - Return `void` or `undefined` → Stop stream and propagate the error
|
|
504
|
+
* - Return `null` → INVALID (will cause error - use `{}` instead)
|
|
505
|
+
*
|
|
506
|
+
* **Important**: To retry, you MUST return an object (can be empty `{}`).
|
|
507
|
+
* Returning nothing (`void`), explicitly returning `undefined`, or omitting
|
|
508
|
+
* a return statement all stop the stream. Do NOT return `null`.
|
|
509
|
+
*
|
|
510
|
+
* Note: Automatic retries with exponential backoff are already applied
|
|
511
|
+
* for 5xx server errors, network errors, and 429 rate limits before
|
|
512
|
+
* this handler is called.
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* // Retry on any error (returns empty object)
|
|
517
|
+
* onError: (error) => ({})
|
|
518
|
+
*
|
|
519
|
+
* // Refresh auth token on 401, propagate other errors
|
|
520
|
+
* onError: async (error) => {
|
|
521
|
+
* if (error instanceof FetchError && error.status === 401) {
|
|
522
|
+
* const newToken = await refreshAuthToken()
|
|
523
|
+
* return { headers: { Authorization: `Bearer ${newToken}` } }
|
|
524
|
+
* }
|
|
525
|
+
* // Implicitly returns undefined - error will propagate
|
|
526
|
+
* }
|
|
527
|
+
*
|
|
528
|
+
* // Conditionally retry with explicit propagation
|
|
529
|
+
* onError: (error) => {
|
|
530
|
+
* if (shouldRetry(error)) {
|
|
531
|
+
* return {} // Retry
|
|
532
|
+
* }
|
|
533
|
+
* return undefined // Explicitly propagate error
|
|
534
|
+
* }
|
|
535
|
+
* ```
|
|
536
|
+
*/
|
|
537
|
+
export type StreamErrorHandler = (
|
|
538
|
+
error: Error
|
|
539
|
+
) => void | RetryOpts | Promise<void | RetryOpts>
|
|
540
|
+
|
|
541
|
+
// ============================================================================
|
|
542
|
+
// StreamResponse Interface
|
|
543
|
+
// ============================================================================
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* A streaming session returned by stream() or DurableStream.stream().
|
|
547
|
+
*
|
|
548
|
+
* Represents a live session with fixed `url`, `offset`, and `live` parameters.
|
|
549
|
+
* Supports multiple consumption styles: Promise helpers, ReadableStreams,
|
|
550
|
+
* and Subscribers.
|
|
551
|
+
*
|
|
552
|
+
* @typeParam TJson - The type of JSON items in the stream.
|
|
553
|
+
*/
|
|
554
|
+
export interface StreamResponse<TJson = unknown> {
|
|
555
|
+
// --- Static session info (known after first response) ---
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The stream URL.
|
|
559
|
+
*/
|
|
560
|
+
readonly url: string
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* The stream's content type (from first response).
|
|
564
|
+
*/
|
|
565
|
+
readonly contentType?: string
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* The live mode for this session.
|
|
569
|
+
*/
|
|
570
|
+
readonly live: LiveMode
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* The starting offset for this session.
|
|
574
|
+
*/
|
|
575
|
+
readonly startOffset: Offset
|
|
576
|
+
|
|
577
|
+
// --- Response metadata (updated on each response) ---
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* HTTP response headers from the most recent server response.
|
|
581
|
+
* Updated on each long-poll/SSE response.
|
|
582
|
+
*/
|
|
583
|
+
readonly headers: Headers
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* HTTP status code from the most recent server response.
|
|
587
|
+
* Updated on each long-poll/SSE response.
|
|
588
|
+
*/
|
|
589
|
+
readonly status: number
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* HTTP status text from the most recent server response.
|
|
593
|
+
* Updated on each long-poll/SSE response.
|
|
594
|
+
*/
|
|
595
|
+
readonly statusText: string
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Whether the most recent response was successful (status 200-299).
|
|
599
|
+
* Always true for active streams (errors are thrown).
|
|
600
|
+
*/
|
|
601
|
+
readonly ok: boolean
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Whether the stream is waiting for initial data.
|
|
605
|
+
*
|
|
606
|
+
* Note: Always false in current implementation because stream() awaits
|
|
607
|
+
* the first response before returning. A future async iterator API
|
|
608
|
+
* could expose this as true during initial connection.
|
|
609
|
+
*/
|
|
610
|
+
readonly isLoading: boolean
|
|
611
|
+
|
|
612
|
+
// --- Evolving state as data arrives ---
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* The next offset to read from (Stream-Next-Offset header).
|
|
616
|
+
*
|
|
617
|
+
* **Important**: This value advances **after data is delivered to the consumer**,
|
|
618
|
+
* not just after fetching from the server. The offset represents the position
|
|
619
|
+
* in the stream that follows the data most recently provided to your consumption
|
|
620
|
+
* method (body(), json(), bodyStream(), subscriber callback, etc.).
|
|
621
|
+
*
|
|
622
|
+
* Use this for resuming reads after a disconnect or saving checkpoints.
|
|
623
|
+
*/
|
|
624
|
+
offset: Offset
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Stream cursor for CDN collapsing (stream-cursor header).
|
|
628
|
+
*
|
|
629
|
+
* Updated after each chunk is delivered to the consumer.
|
|
630
|
+
*/
|
|
631
|
+
cursor?: string
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Whether we've reached the current end of the stream (stream-up-to-date header).
|
|
635
|
+
*
|
|
636
|
+
* Updated after each chunk is delivered to the consumer.
|
|
637
|
+
*/
|
|
638
|
+
upToDate: boolean
|
|
639
|
+
|
|
640
|
+
// =================================
|
|
641
|
+
// 1) Accumulating helpers (Promise)
|
|
642
|
+
// =================================
|
|
643
|
+
// Accumulate until first `upToDate`, then resolve and stop.
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Accumulate raw bytes until first `upToDate` batch, then resolve.
|
|
647
|
+
* When used with `live: "auto"`, signals the session to stop after upToDate.
|
|
648
|
+
*/
|
|
649
|
+
body: () => Promise<Uint8Array>
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Accumulate JSON *items* across batches into a single array, resolve at `upToDate`.
|
|
653
|
+
* Only valid in JSON-mode; throws otherwise.
|
|
654
|
+
* When used with `live: "auto"`, signals the session to stop after upToDate.
|
|
655
|
+
*/
|
|
656
|
+
json: <T = TJson>() => Promise<Array<T>>
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Accumulate text chunks into a single string, resolve at `upToDate`.
|
|
660
|
+
* When used with `live: "auto"`, signals the session to stop after upToDate.
|
|
661
|
+
*/
|
|
662
|
+
text: () => Promise<string>
|
|
663
|
+
|
|
664
|
+
// =====================
|
|
665
|
+
// 2) ReadableStreams
|
|
666
|
+
// =====================
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Raw bytes as a ReadableStream<Uint8Array>.
|
|
670
|
+
*
|
|
671
|
+
* The returned stream is guaranteed to be async-iterable, so you can use
|
|
672
|
+
* `for await...of` syntax even on Safari/iOS which may lack native support.
|
|
673
|
+
*/
|
|
674
|
+
bodyStream: () => ReadableStreamAsyncIterable<Uint8Array>
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Individual JSON items (flattened) as a ReadableStream<TJson>.
|
|
678
|
+
* Built on jsonBatches().
|
|
679
|
+
*
|
|
680
|
+
* The returned stream is guaranteed to be async-iterable, so you can use
|
|
681
|
+
* `for await...of` syntax even on Safari/iOS which may lack native support.
|
|
682
|
+
*/
|
|
683
|
+
jsonStream: () => ReadableStreamAsyncIterable<TJson>
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Text chunks as ReadableStream<string>.
|
|
687
|
+
*
|
|
688
|
+
* The returned stream is guaranteed to be async-iterable, so you can use
|
|
689
|
+
* `for await...of` syntax even on Safari/iOS which may lack native support.
|
|
690
|
+
*/
|
|
691
|
+
textStream: () => ReadableStreamAsyncIterable<string>
|
|
692
|
+
|
|
693
|
+
// =====================
|
|
694
|
+
// 3) Subscriber APIs
|
|
695
|
+
// =====================
|
|
696
|
+
// Subscribers return Promise<void> for backpressure control.
|
|
697
|
+
// Note: Only one consumption method can be used per StreamResponse.
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Subscribe to JSON batches as they arrive.
|
|
701
|
+
* Returns unsubscribe function.
|
|
702
|
+
*/
|
|
703
|
+
subscribeJson: <T = TJson>(
|
|
704
|
+
subscriber: (batch: JsonBatch<T>) => Promise<void>
|
|
705
|
+
) => () => void
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Subscribe to raw byte chunks as they arrive.
|
|
709
|
+
* Returns unsubscribe function.
|
|
710
|
+
*/
|
|
711
|
+
subscribeBytes: (
|
|
712
|
+
subscriber: (chunk: ByteChunk) => Promise<void>
|
|
713
|
+
) => () => void
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Subscribe to text chunks as they arrive.
|
|
717
|
+
* Returns unsubscribe function.
|
|
718
|
+
*/
|
|
719
|
+
subscribeText: (subscriber: (chunk: TextChunk) => Promise<void>) => () => void
|
|
720
|
+
|
|
721
|
+
// =====================
|
|
722
|
+
// 4) Lifecycle
|
|
723
|
+
// =====================
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Cancel the underlying session (abort HTTP, close SSE, stop long-polls).
|
|
727
|
+
*/
|
|
728
|
+
cancel: (reason?: unknown) => void
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Resolves when the session has fully closed:
|
|
732
|
+
* - `live:false` and up-to-date reached,
|
|
733
|
+
* - manual cancellation,
|
|
734
|
+
* - terminal error.
|
|
735
|
+
*/
|
|
736
|
+
readonly closed: Promise<void>
|
|
737
|
+
}
|