@oh-my-pi/pi-utils 17.2.13 → 17.2.15

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.15] - 2026-08-12
6
+
7
+ ### Changed
8
+
9
+ - Extended parsed Server-Sent Events (SSE) to include optional id and retry fields, enabling reconnecting transports to retain stream cursors and respect server-requested retry intervals.
10
+
5
11
  ## [17.2.13] - 2026-08-11
6
12
 
7
13
  ### Changed
@@ -27,11 +27,16 @@ export declare function readSseJson<T>(stream: ReadableStream<Uint8Array>, signa
27
27
  * - `raw` is the list of decoded non-empty lines that made up the event,
28
28
  * preserved for diagnostic context (error reporting, debugging). The
29
29
  * dispatching blank line is not included.
30
+ * - `id` and `retry` are present only when the event carried valid fields with
31
+ * those names. Control-only events are yielded so reconnecting transports can
32
+ * retain the cursor and server-requested retry interval.
30
33
  */
31
34
  export interface ServerSentEvent {
32
35
  event: string | null;
33
36
  data: string;
34
37
  raw: string[];
38
+ id?: string;
39
+ retry?: number;
35
40
  }
36
41
  /**
37
42
  * Stream raw Server-Sent Events from an HTTP response body.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "17.2.13",
4
+ "version": "17.2.15",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "17.2.13"
34
+ "@oh-my-pi/pi-natives": "17.2.15"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/bun": "^1.3.14"
package/src/stream.ts CHANGED
@@ -281,11 +281,16 @@ export async function* readSseJson<T>(
281
281
  * - `raw` is the list of decoded non-empty lines that made up the event,
282
282
  * preserved for diagnostic context (error reporting, debugging). The
283
283
  * dispatching blank line is not included.
284
+ * - `id` and `retry` are present only when the event carried valid fields with
285
+ * those names. Control-only events are yielded so reconnecting transports can
286
+ * retain the cursor and server-requested retry interval.
284
287
  */
285
288
  export interface ServerSentEvent {
286
289
  event: string | null;
287
290
  data: string;
288
291
  raw: string[];
292
+ id?: string;
293
+ retry?: number;
289
294
  }
290
295
 
291
296
  interface SseEventState {
@@ -296,6 +301,8 @@ interface SseEventState {
296
301
  // seen yet" (distinct from a `data:` field with an empty value).
297
302
  data: string | null;
298
303
  raw: string[];
304
+ id?: string;
305
+ retry?: number;
299
306
  }
300
307
 
301
308
  // Complete lines are decoded in one batch per source chunk. Each batch ends on
@@ -303,7 +310,7 @@ interface SseEventState {
303
310
  const SSE_DECODER = new TextDecoder("utf-8");
304
311
 
305
312
  function flushSseEvent(state: SseEventState): ServerSentEvent | null {
306
- if (state.event === null && state.data === null) {
313
+ if (state.event === null && state.data === null && state.id === undefined && state.retry === undefined) {
307
314
  state.raw = [];
308
315
  return null;
309
316
  }
@@ -312,9 +319,13 @@ function flushSseEvent(state: SseEventState): ServerSentEvent | null {
312
319
  data: state.data ?? "",
313
320
  raw: state.raw,
314
321
  };
322
+ if (state.id !== undefined) event.id = state.id;
323
+ if (state.retry !== undefined) event.retry = state.retry;
315
324
  state.event = null;
316
325
  state.data = null;
317
326
  state.raw = [];
327
+ state.id = undefined;
328
+ state.retry = undefined;
318
329
  return event;
319
330
  }
320
331
 
@@ -348,9 +359,22 @@ function pushSseLine(line: string, state: SseEventState): ServerSentEvent | null
348
359
  state.data += "\n";
349
360
  state.data += value;
350
361
  }
362
+ } else if (fieldName === "id") {
363
+ if (!value.includes("\0")) state.id = value;
364
+ } else if (fieldName === "retry" && value.length > 0) {
365
+ let valid = true;
366
+ for (let index = 0; index < value.length; index++) {
367
+ const code = value.charCodeAt(index);
368
+ if (code < 0x30 || code > 0x39) {
369
+ valid = false;
370
+ break;
371
+ }
372
+ }
373
+ if (valid) {
374
+ const retry = Number(value);
375
+ if (Number.isSafeInteger(retry)) state.retry = retry;
376
+ }
351
377
  }
352
- // `id` and `retry` are intentionally ignored — the providers we consume
353
- // don't use them, and the underlying transport handles reconnects itself.
354
378
  return null;
355
379
  }
356
380