@0xinsider/sdk 0.14.0-bootstrap.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.
@@ -0,0 +1,509 @@
1
+ /**
2
+ * SSE consumer for `GET /api/v1/stream`.
3
+ *
4
+ * The endpoint forwards the platform's live feed envelopes as Server-Sent
5
+ * Events. Source of truth: `web/public/api/v1/openapi.json` -> paths./api/v1/stream.
6
+ *
7
+ * Wire format (per the spec's response description):
8
+ * - Each data frame is `id: <seq>\ndata: <json-envelope>\n\n`, where the JSON
9
+ * envelope is `{ seq, published_at, type, ...event-specific }`. The SSE id
10
+ * equals the envelope `seq`.
11
+ * - A resync marker is `event: resync\nid: <seq>\ndata: <json>\n\n`, where the
12
+ * JSON is `{ type: "resync", completeness, from_sequence, to_sequence }`.
13
+ * It means the requested resume point is outside the retained window or a
14
+ * live sequence gap was observed; treat it as "refetch current state".
15
+ * - Idle connections emit `: keep-alive` comment lines (ignored).
16
+ *
17
+ * Resume is via the `Last-Event-ID` header set to the last `seq` you processed;
18
+ * the spec also accepts `last_event_id` / `seq` query fallbacks. This consumer
19
+ * sends the header and auto-tracks the last seen seq so a caller-driven
20
+ * reconnect resumes from the right place.
21
+ *
22
+ * Filters (per-connection, subscribe-time): `event` (comma-separated frame
23
+ * types), `condition_id`, and `min_grade` (S|A|B|C|D|F).
24
+ *
25
+ * Protocol validity (#16248). The decoder is bounded and fails visibly:
26
+ * - A successful response whose media type is not `text/event-stream` is
27
+ * `StreamProtocolError` (`unexpected_media_type`), not an empty stream.
28
+ * - A data frame whose payload is not JSON, not an object, or has no usable
29
+ * sequence (a finite `seq` in the envelope, or a finite SSE `id`) is a
30
+ * `StreamProtocolError` (`invalid_json`, `invalid_envelope`,
31
+ * `unusable_sequence`); a resync frame whose payload is not an object is
32
+ * `invalid_resync`. Before #16248 such frames were skipped or yielded with
33
+ * `NaN`, and the next valid frame moved the cursor past the gap.
34
+ * - A frame larger than `maxFrameBytes` (default 1 MiB), whether or not its
35
+ * blank-line delimiter has arrived, is `frame_too_large` before it is
36
+ * parsed (#16644); the reader is released and the connection closed.
37
+ * - The error carries `lastSeq`, the last sequence delivered before it, so a
38
+ * malformed frame never moves the cursor and a caller can decide whether
39
+ * to resume from there (which replays the frame while it is retained),
40
+ * skip past `frameId`, or refetch state. No raw payload is on the error.
41
+ * - A terminal `event: error` frame (the key was revoked, the account
42
+ * lapsed, or the credential store could not confirm the key) throws the
43
+ * typed `OxinsiderApiError` it carries and is never yielded (#16546);
44
+ * `retry: false` is permanent to the resilient consumers.
45
+ * Comment lines (`: keep-alive`), LF and CRLF framing, unknown SSE fields
46
+ * and unknown-but-valid envelope `type`s are compatible as before.
47
+ */
48
+ import { type Grade, type OxinsiderApiClient } from "./client.js";
49
+ /** A live feed envelope frame: `{ seq, published_at, type, ...payload }`. */
50
+ export interface FeedEnvelope {
51
+ /** Cluster-shared monotonic sequence id; also the SSE event id. */
52
+ seq: number;
53
+ /** ISO-8601 publish time, when present. */
54
+ published_at?: string;
55
+ /** Frame wire type, e.g. `"WhaleTradesInserted"`. */
56
+ type: string;
57
+ [key: string]: unknown;
58
+ }
59
+ /** A resync marker frame (SSE `event: resync`). */
60
+ export interface ResyncMarker {
61
+ type: "resync";
62
+ completeness?: {
63
+ status?: string;
64
+ reason?: string;
65
+ [key: string]: unknown;
66
+ };
67
+ from_sequence?: number;
68
+ to_sequence?: number;
69
+ [key: string]: unknown;
70
+ }
71
+ export interface StreamFilters {
72
+ /**
73
+ * Subscribe only to these frame wire types. Sent as a comma-separated
74
+ * `event` query param. Empty/omitted = all frames.
75
+ */
76
+ event?: readonly string[];
77
+ /** Subscribe only to frames for this market (raw provider id or `mkt_*`). */
78
+ condition_id?: string;
79
+ /** Only deliver frames carrying a grade at or above this threshold. */
80
+ min_grade?: Grade;
81
+ }
82
+ export interface StreamOptions extends StreamFilters {
83
+ /**
84
+ * Resume after this cluster-shared sequence id (sets `Last-Event-ID`). It
85
+ * remains valid across replicas and process restarts while retained. When
86
+ * omitted the stream starts from the live head.
87
+ */
88
+ lastEventId?: number | string;
89
+ /** Cooperative cancellation; abort to close the stream. */
90
+ signal?: AbortSignal;
91
+ /** Invoked once for the resync marker frame, if one is emitted. */
92
+ onResync?: (marker: ResyncMarker) => void;
93
+ /**
94
+ * Optional cursor object whose `seq` is updated to the last DELIVERED
95
+ * envelope `seq` as frames arrive. Pass the same object back as
96
+ * `lastEventId: cursor.seq` on reconnect to resume from where you left off.
97
+ *
98
+ * Received, not processed (#16247). It is written before the frame reaches
99
+ * your code, so it says the frame arrived and nothing about whether your
100
+ * handling of it succeeded: resuming from it after a handler failure skips
101
+ * that event. When losing an event would lose work, drive the stream with
102
+ * `consumeStreamCheckpointed`, whose `StreamCheckpoint` advances only after
103
+ * the handler and your durable write resolve, and keep this cursor for
104
+ * transport progress.
105
+ */
106
+ cursor?: {
107
+ seq?: number;
108
+ };
109
+ /**
110
+ * Byte ceiling for one undelivered frame (#16248). Default
111
+ * `DEFAULT_MAX_STREAM_FRAME_BYTES` (1 MiB). A frame larger than this,
112
+ * delimited or not, ends the connection with `StreamProtocolError`
113
+ * (`frame_too_large`) before it is parsed or yielded, and the reader is
114
+ * released (#16644).
115
+ * A positive finite integer; the largest real frame is a few KB.
116
+ */
117
+ maxFrameBytes?: number;
118
+ }
119
+ /** Default for `StreamOptions.maxFrameBytes`: 1 MiB. */
120
+ export declare const DEFAULT_MAX_STREAM_FRAME_BYTES = 1048576;
121
+ /** What the stream did that the SSE contract does not allow. */
122
+ export type StreamProtocolErrorReason =
123
+ /** A 2xx whose `Content-Type` is not `text/event-stream`. */
124
+ "unexpected_media_type"
125
+ /** A data frame whose payload is not JSON. */
126
+ | "invalid_json"
127
+ /** A data frame whose payload is JSON but not an object (or is `null`, an array). */
128
+ | "invalid_envelope"
129
+ /** A data frame with no finite `seq` in the envelope and no finite SSE `id`. */
130
+ | "unusable_sequence"
131
+ /** A resync frame whose payload is not an object, or whose `type` is not `resync`. */
132
+ | "invalid_resync"
133
+ /** A frame, delimited or not, is larger than `maxFrameBytes`. */
134
+ | "frame_too_large";
135
+ /**
136
+ * The stream broke the SSE contract (#16248). The connection is closed and
137
+ * the reader released before this is thrown. `lastSeq` is the last sequence
138
+ * delivered on this connection, or `undefined` when none was: no malformed
139
+ * frame moves the cursor. `frameId` is the SSE `id` of the offending frame
140
+ * when it carried one, `event` its SSE event name, `bytes` its size, and
141
+ * `mediaType` the response's `Content-Type` for `unexpected_media_type`. The
142
+ * raw payload is deliberately not carried: it may hold data you would not
143
+ * want in a log, and `frameId` plus `lastSeq` name the frame exactly.
144
+ *
145
+ * `streamFeedResilient` treats this as permanent: reconnecting from
146
+ * `lastSeq` would replay the same frame while the server retains it. Decide:
147
+ * resume from `lastSeq` later, resume after `frameId` to skip it, or refetch
148
+ * state and attach live.
149
+ */
150
+ export declare class StreamProtocolError extends Error {
151
+ readonly reason: StreamProtocolErrorReason;
152
+ readonly lastSeq: number | undefined;
153
+ readonly frameId: number | undefined;
154
+ readonly event: string | undefined;
155
+ readonly bytes: number | undefined;
156
+ readonly mediaType: string | undefined;
157
+ constructor(reason: StreamProtocolErrorReason, detail: {
158
+ lastSeq: number | undefined;
159
+ frameId?: number;
160
+ event?: string;
161
+ bytes?: number;
162
+ mediaType?: string | null;
163
+ });
164
+ }
165
+ /** `Content-Type` names an event stream: `text/event-stream`, with or without parameters. */
166
+ export declare function isEventStreamMediaType(contentType: string | null): boolean;
167
+ /** One yielded item from the stream iterator. */
168
+ export type StreamEvent = {
169
+ kind: "event";
170
+ seq: number;
171
+ envelope: FeedEnvelope;
172
+ } | {
173
+ kind: "resync";
174
+ seq: number | null;
175
+ marker: ResyncMarker;
176
+ };
177
+ /**
178
+ * Open the stream and async-iterate its frames. Yields a discriminated
179
+ * `StreamEvent`: `kind: "event"` carries the `FeedEnvelope`, `kind: "resync"`
180
+ * carries the `ResyncMarker`.
181
+ *
182
+ * Pass `options.cursor` (a `{ seq?: number }` object) to have the last
183
+ * delivered `seq` written back as frames arrive; reconnect with
184
+ * `lastEventId: cursor.seq` to resume after a transport drop.
185
+ *
186
+ * @example
187
+ * for await (const frame of streamFeed(client, { event: ["WhaleTradesInserted"], min_grade: "S" })) {
188
+ * if (frame.kind === "event") console.log(frame.envelope.type, frame.seq);
189
+ * }
190
+ */
191
+ export declare function streamFeed(client: OxinsiderApiClient, options?: StreamOptions): AsyncGenerator<StreamEvent, void, undefined>;
192
+ export interface ResilientStreamOptions extends StreamOptions {
193
+ /**
194
+ * Consecutive failed connections tolerated before giving up with
195
+ * `StreamReconnectsExhaustedError`. A connection that delivers any frame
196
+ * resets the count. Default `DEFAULT_MAX_STREAM_RECONNECTS` (10).
197
+ */
198
+ maxReconnects?: number;
199
+ /**
200
+ * Called before each reconnect's backoff: the 1-based consecutive attempt,
201
+ * the seq the reconnect resumes after (`undefined` attaches live), and why
202
+ * the previous connection ended (`undefined` for a clean server close).
203
+ */
204
+ onReconnect?: (attempt: number, lastSeq: number | undefined, cause: unknown) => void;
205
+ /**
206
+ * The longest server-requested wait (`Retry-After` or `retry_at`) the loop holds
207
+ * in-process, in ms. Default `DEFAULT_MAX_STREAM_RETRY_AFTER_MS` (60 000,
208
+ * the REST client's ceiling). A refusal asking for longer ends the loop
209
+ * with `StreamRetryDeferredError`, which carries the not-before instant
210
+ * (`retryAt`) and the seq to resume after (`lastSeq`), so the wait is
211
+ * yours to schedule and no reconnect is spent on it. `Infinity` opts into
212
+ * waiting however long the server says, in timer-sized chunks; it can hold
213
+ * the process for days on a `monthly_quota_exceeded` refusal, which is why
214
+ * it is not the default (#16249).
215
+ */
216
+ maxRetryAfterMs?: number;
217
+ }
218
+ /** Default for `ResilientStreamOptions.maxReconnects`. */
219
+ export declare const DEFAULT_MAX_STREAM_RECONNECTS = 10;
220
+ /** Default for `ResilientStreamOptions.maxRetryAfterMs`. */
221
+ export declare const DEFAULT_MAX_STREAM_RETRY_AFTER_MS = 60000;
222
+ /**
223
+ * `streamFeedResilient` gave up after `maxReconnects` consecutive failed
224
+ * connections. `lastSeq` is the seq to resume after (pass it as `lastEventId`
225
+ * later); `cause` is the last connection's failure.
226
+ */
227
+ export declare class StreamReconnectsExhaustedError extends Error {
228
+ readonly lastSeq: number | undefined;
229
+ readonly attempts: number;
230
+ constructor(attempts: number, lastSeq: number | undefined, cause: unknown);
231
+ }
232
+ /**
233
+ * `streamFeedResilient` received a server-requested wait longer than
234
+ * `maxRetryAfterMs`, so the wait is yours to schedule (#16249). `retryAt` is
235
+ * the server's not-before instant as this client read it (the header's
236
+ * seconds added to the local clock, the header's HTTP-date, or terminal
237
+ * `retry_at` when no usable header exists); `cause` is
238
+ * the refusal, whose `retryAt` (from the body's `retry_at`) is the server's
239
+ * own clock reading of the same instant. `lastSeq` is the seq to resume after:
240
+ * pass it as `lastEventId` when you reconnect at `retryAt`. `attempts` is how
241
+ * many consecutive failed connections preceded this one; the refusal itself
242
+ * spent none of `maxReconnects`.
243
+ *
244
+ * Nothing here was clamped: a wait the timer cannot hold is never shortened
245
+ * into an earlier reconnect.
246
+ */
247
+ export declare class StreamRetryDeferredError extends Error {
248
+ readonly retryAt: Date;
249
+ readonly retryAfterMs: number;
250
+ readonly lastSeq: number | undefined;
251
+ readonly attempts: number;
252
+ constructor(retryAfterMs: number, lastSeq: number | undefined, attempts: number, cause: unknown);
253
+ }
254
+ /**
255
+ * `streamFeed` with the reconnect-and-resume loop built in (#14286).
256
+ *
257
+ * - A clean server close, a network error, a 429 or a 5xx reconnects,
258
+ * resuming after the last delivered seq (`Last-Event-ID`). Before any frame
259
+ * arrives it resumes from `lastEventId`, or attaches live when that is unset.
260
+ * - The wait is `Retry-After` (delta-seconds or an HTTP-date) plus jitter when
261
+ * the refusal carries one, otherwise a jittered backoff from 1 s capped at
262
+ * 30 s. A `Retry-After` past `maxRetryAfterMs` (60 s by default) is not
263
+ * waited out: the loop throws `StreamRetryDeferredError` with `retryAt` and
264
+ * `lastSeq`, spending no reconnect, and never clamps the wait to an earlier
265
+ * one (#16249).
266
+ * - Any other 4xx (400, 401, 402, 403, 423) is thrown at once: it cannot heal
267
+ * on its own. So is `StreamProtocolError` (#16248): a resume from
268
+ * `lastSeq` would replay the malformed frame, so the decision is yours.
269
+ * - After `maxReconnects` consecutive connections that delivered nothing, it
270
+ * throws `StreamReconnectsExhaustedError` carrying `lastSeq`.
271
+ * - `resync` markers are yielded unchanged, and `onResync` still fires. The
272
+ * marker's `id` becomes the resume cursor, as the server intends, so a
273
+ * reconnect after one does not request the aged window again.
274
+ * - Aborting `signal` ends the iterator without throwing, during a connection
275
+ * or a backoff. Each connection's body reader is cancelled when it ends, so
276
+ * reconnects do not accumulate sockets.
277
+ * - No request deadline applies, as with `streamFeed`.
278
+ *
279
+ * `options.cursor`, when passed, tracks the last delivered seq across every
280
+ * connection -- delivered, not processed (#16247). This loop resumes from it,
281
+ * so a consumer whose handler can fail should drive the stream with
282
+ * `consumeStreamCheckpointed` instead, which resumes from an acknowledged
283
+ * checkpoint and replays the event it failed on.
284
+ *
285
+ * @example
286
+ * const controller = new AbortController();
287
+ * for await (const frame of streamFeedResilient(client, {
288
+ * event: ["WhaleTradesInserted"],
289
+ * signal: controller.signal,
290
+ * onReconnect: (attempt, lastSeq, cause) => console.warn("reconnecting", attempt, lastSeq, cause),
291
+ * })) {
292
+ * if (frame.kind === "resync") await refetchState();
293
+ * else handle(frame.envelope);
294
+ * }
295
+ */
296
+ export declare function streamFeedResilient(client: OxinsiderApiClient, options?: ResilientStreamOptions): AsyncGenerator<StreamEvent, void, undefined>;
297
+ /**
298
+ * Callback-style stream consumer for environments where an async iterator is
299
+ * awkward. Returns a promise that resolves when the stream ends (or rejects on
300
+ * error / abort). Pass a `signal` to stop it.
301
+ *
302
+ * Delivery, not processing (#16247): `options.cursor` is written by
303
+ * `streamFeed` BEFORE the frame is yielded, so it has already moved past an
304
+ * event whose `onEvent` then throws. Reconnecting from it skips that event.
305
+ * For a consumer that must not lose work, use `consumeStreamCheckpointed`,
306
+ * which advances an acknowledged checkpoint only after the handler and your
307
+ * own durable write have both resolved.
308
+ */
309
+ export declare function consumeStream(client: OxinsiderApiClient, handlers: {
310
+ onEvent?: (envelope: FeedEnvelope, seq: number) => void | Promise<void>;
311
+ onResync?: (marker: ResyncMarker, seq: number | null) => void | Promise<void>;
312
+ }, options?: StreamOptions): Promise<void>;
313
+ /**
314
+ * An acknowledged processing checkpoint (#16247): the last `seq` whose
315
+ * handling COMPLETED, which is not the same thing as the last `seq` that
316
+ * arrived. `StreamOptions.cursor` is the received cursor -- transport
317
+ * progress, written before the frame is handed to you. This is the processed
318
+ * one, written after your handler and your own durable write have resolved.
319
+ *
320
+ * Own the object if you persist the checkpoint yourself: pass it in, read
321
+ * `seq` after the consumer returns or throws, and pass the stored value back
322
+ * as `lastEventId` when you resume in a later process.
323
+ */
324
+ export interface StreamCheckpoint {
325
+ /** The last acknowledged sequence, or `undefined` until one is acknowledged. */
326
+ seq?: number;
327
+ }
328
+ /** What the checkpoint advanced past: a feed envelope, or a resync refresh. */
329
+ export type StreamCheckpointReason = "event" | "resync";
330
+ /**
331
+ * Which awaited step rejected: your event handler, your resync refresh, or
332
+ * your `onCheckpoint` durable write.
333
+ */
334
+ export type StreamHandlerStage = "event" | "resync" | "checkpoint";
335
+ /** The recovery state around one handler failure, as it is decided. */
336
+ export interface StreamHandlerFailure {
337
+ /**
338
+ * The sequence whose processing failed; `undefined` only for a resync
339
+ * marker that carried no SSE id.
340
+ */
341
+ seq: number | undefined;
342
+ /** Which awaited step rejected. */
343
+ stage: StreamHandlerStage;
344
+ /** 1-based consecutive failures at this sequence. Resets on any success. */
345
+ attempt: number;
346
+ /** Whether replay is planned; false means exhaustion or clean cancellation. */
347
+ willRetry: boolean;
348
+ /**
349
+ * Present and true when caller cancellation has stopped the consumer at
350
+ * this observation. It does not claim cancellation caused the rejection.
351
+ */
352
+ cancelled?: boolean;
353
+ /**
354
+ * The sequence the replay resumes AFTER, so the failed frame is delivered
355
+ * again while the server retains it. `undefined` means the replay attaches
356
+ * live and the failed frame is gone: that happens only when nothing has
357
+ * been acknowledged, no `lastEventId` was given, and the failing sequence
358
+ * is not a positive integer to step back from.
359
+ */
360
+ replayFrom: number | undefined;
361
+ /** The acknowledged checkpoint, which this failure did NOT advance. */
362
+ checkpoint: number | undefined;
363
+ }
364
+ /**
365
+ * `consumeStreamCheckpointed` gave up after `maxHandlerRetries` consecutive
366
+ * failures at the same sequence (#16247). `checkpoint` is the acknowledged
367
+ * sequence, still behind the failing one, and `replayFrom` is the point a
368
+ * later resume should pass as `lastEventId` to deliver the failed frame
369
+ * again while the server retains it. `cause` is the last rejection your
370
+ * handler produced.
371
+ */
372
+ export declare class StreamHandlerFailedError extends Error {
373
+ readonly seq: number | undefined;
374
+ readonly stage: StreamHandlerStage;
375
+ readonly attempts: number;
376
+ readonly checkpoint: number | undefined;
377
+ readonly replayFrom: number | undefined;
378
+ constructor(detail: {
379
+ seq: number | undefined;
380
+ stage: StreamHandlerStage;
381
+ attempts: number;
382
+ checkpoint: number | undefined;
383
+ replayFrom: number | undefined;
384
+ }, cause: unknown);
385
+ }
386
+ /** Default for `CheckpointedStreamOptions.maxHandlerRetries`. */
387
+ export declare const DEFAULT_MAX_HANDLER_RETRIES = 3;
388
+ export interface CheckpointedStreamOptions extends ResilientStreamOptions {
389
+ /**
390
+ * Where the acknowledged checkpoint is written. Pass your own object to
391
+ * read it after the consumer returns; omit it and the consumer keeps one
392
+ * internally, which `onCheckpoint` still reports.
393
+ */
394
+ checkpoint?: StreamCheckpoint;
395
+ /**
396
+ * Consecutive failures at the SAME sequence tolerated before the consumer
397
+ * throws `StreamHandlerFailedError`. Each retry closes the connection and
398
+ * replays from the checkpoint after a jittered backoff (1 s to 30 s). `0`
399
+ * gives up on the first failure. Default `DEFAULT_MAX_HANDLER_RETRIES` (3).
400
+ */
401
+ maxHandlerRetries?: number;
402
+ /**
403
+ * Called on every handler failure with the recovery state, before backoff,
404
+ * exhaustion, or a clean cancellation return. Cancellation retains the
405
+ * original error with `willRetry: false` and `cancelled: true`, without
406
+ * spending a retry. It is not awaited and must not throw.
407
+ */
408
+ onHandlerError?: (error: unknown, failure: StreamHandlerFailure) => void;
409
+ }
410
+ export interface CheckpointedStreamHandlers {
411
+ /**
412
+ * Apply one feed envelope. Awaited: the connection is not read again until
413
+ * it settles. Reject to leave the checkpoint before this event.
414
+ */
415
+ onEvent: (envelope: FeedEnvelope, seq: number) => void | Promise<void>;
416
+ /**
417
+ * Refresh state after a resync marker. Awaited, so it is a real barrier:
418
+ * the checkpoint moves to the marker only once this resolves. Reject and
419
+ * the refresh is retried under the same bounded policy as an event.
420
+ */
421
+ onResync?: (marker: ResyncMarker, seq: number | null) => void | Promise<void>;
422
+ /**
423
+ * Persist the checkpoint. Awaited BEFORE the in-memory checkpoint advances,
424
+ * so a failed write leaves the checkpoint where it was and the event is
425
+ * replayed rather than lost.
426
+ */
427
+ onCheckpoint?: (seq: number, reason: StreamCheckpointReason) => void | Promise<void>;
428
+ }
429
+ /**
430
+ * The stream consumer for work that must not be silently dropped (#16247):
431
+ * it separates the received cursor from an acknowledged processing
432
+ * checkpoint, and only the checkpoint decides where a reconnect resumes.
433
+ *
434
+ * Ordering, per frame, for `kind: "event"`:
435
+ * 1. `onEvent` is awaited. Nothing is read from the connection while it
436
+ * runs, so a slow handler is backpressure on the socket, never a queue:
437
+ * at most one frame is ever in flight and nothing is buffered on your
438
+ * behalf.
439
+ * 2. `onCheckpoint(seq, "event")` is awaited -- your durable write.
440
+ * 3. Only then does `checkpoint.seq` become `seq`, and only then does the
441
+ * next reconnect resume after it.
442
+ * A rejection at step 1 or 2 leaves the checkpoint BEFORE the event: the
443
+ * connection is closed, a jittered backoff runs, and the consumer reconnects
444
+ * from the checkpoint, which replays the unacknowledged event while the
445
+ * server retains it. `onHandlerError` reports each failure with its attempt
446
+ * number, the replay point and whether another attempt follows. After
447
+ * `maxHandlerRetries` consecutive failures at the same sequence it throws
448
+ * `StreamHandlerFailedError`, carrying the unadvanced checkpoint.
449
+ *
450
+ * A `resync` marker is the same barrier: `onResync` is awaited, then
451
+ * `onCheckpoint(seq, "resync")`, and an interrupted refresh commits nothing,
452
+ * so the recovery is retried instead of being recorded as done. (The
453
+ * fire-and-forget `StreamOptions.onResync` notification is not awaited and is
454
+ * not a barrier -- do not use it as one.)
455
+ *
456
+ * Delivery is AT-LEAST-ONCE. A replay re-delivers every unacknowledged frame,
457
+ * and a handler that succeeded but whose `onCheckpoint` write failed sees its
458
+ * event again. Deduplicate on `seq` (it is monotonic per cluster) or make the
459
+ * side effect idempotent; nothing here can promise exactly-once side effects.
460
+ *
461
+ * `options.cursor` is untouched in meaning: it still tracks the last
462
+ * DELIVERED seq, including frames whose handler later failed, and it rewinds
463
+ * when a replay re-delivers them. Read it for transport progress; never as
464
+ * proof that the work was done.
465
+ *
466
+ * Transport recovery, `Retry-After` handling, permanent 4xx and
467
+ * `StreamProtocolError` behave exactly as in `streamFeedResilient`, which
468
+ * this drives. `StreamReconnectsExhaustedError` and
469
+ * `StreamRetryDeferredError` surface unchanged; their `lastSeq` is the
470
+ * acknowledged checkpoint, because a reconnect can only happen between
471
+ * handlers. Aborting `signal` ends the consumer without throwing: a handler
472
+ * already running is awaited (it is not cancelled for you -- pass the same
473
+ * signal into your own work if you want that), and if it succeeds its
474
+ * checkpoint is committed before the consumer returns. If an awaited event,
475
+ * resync, or checkpoint callback rejects during cancellation, the checkpoint
476
+ * stays unchanged and `onHandlerError` reports the original rejection with
477
+ * `willRetry: false` and `cancelled: true`; the consumer returns even when
478
+ * handler retries are disabled or exhausted. A coincident unrelated failure
479
+ * is still reported, without claiming cancellation caused it.
480
+ *
481
+ * @example
482
+ * const checkpoint = { seq: await loadCheckpoint() };
483
+ * await consumeStreamCheckpointed(client, {
484
+ * onEvent: async (envelope, seq) => { await applyOnce(seq, envelope); },
485
+ * onResync: async () => { await refetchCurrentState(); },
486
+ * onCheckpoint: async (seq) => { await saveCheckpoint(seq); },
487
+ * }, {
488
+ * event: ["WhaleTradesInserted"],
489
+ * lastEventId: checkpoint.seq,
490
+ * checkpoint,
491
+ * signal: controller.signal,
492
+ * onHandlerError: (error, failure) =>
493
+ * console.warn("handler failed", failure.seq, failure.attempt, failure.willRetry, error),
494
+ * });
495
+ */
496
+ export declare function consumeStreamCheckpointed(client: OxinsiderApiClient, handlers: CheckpointedStreamHandlers, options?: CheckpointedStreamOptions): Promise<void>;
497
+ export interface ParsedSseFrame {
498
+ event?: string;
499
+ id?: number;
500
+ data: string;
501
+ }
502
+ /** Parse one SSE frame (without the trailing blank line). */
503
+ export declare function parseSseFrame(raw: string): ParsedSseFrame | null;
504
+ /**
505
+ * Turn one parsed SSE frame into a `StreamEvent`, or throw
506
+ * `StreamProtocolError` (#16248). `lastSeq` is only carried onto the error.
507
+ */
508
+ export declare function decodeStreamFrame(parsed: ParsedSseFrame, lastSeq: number | undefined): StreamEvent;
509
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}