@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 +6 -0
- package/dist/types/stream.d.ts +5 -0
- package/package.json +2 -2
- package/src/stream.ts +27 -3
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
|
package/dist/types/stream.d.ts
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.
|
|
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.
|
|
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
|
|