@bagelink/sdk 1.15.543 → 1.15.549

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/dist/index.cjs CHANGED
@@ -1312,6 +1312,7 @@ function createSSEStream(url, options = {}) {
1312
1312
  let lastEventId = initialLastEventId;
1313
1313
  let reconnectAttempts = 0;
1314
1314
  async function connect() {
1315
+ if (controller.signal.aborted) return;
1315
1316
  const requestHeaders = {
1316
1317
  "Accept": "text/event-stream",
1317
1318
  "Cache-Control": "no-cache",
@@ -1341,10 +1342,10 @@ function createSSEStream(url, options = {}) {
1341
1342
  stream.emit("error", new Error("Response body is null"));
1342
1343
  return;
1343
1344
  }
1344
- reconnectAttempts = 0;
1345
1345
  const reader = response.body.getReader();
1346
1346
  const decoder = new TextDecoder();
1347
1347
  let buffer = "";
1348
+ let terminalEventReceived = false;
1348
1349
  let currentEvent = "message";
1349
1350
  let currentData = "";
1350
1351
  let currentId;
@@ -1352,11 +1353,10 @@ function createSSEStream(url, options = {}) {
1352
1353
  while (true) {
1353
1354
  const { done, value } = await reader.read();
1354
1355
  if (done) {
1355
- if (!controller.signal.aborted) {
1356
- stream.emit("done", {});
1357
- stream.emit("complete", {});
1356
+ if (!controller.signal.aborted && !terminalEventReceived) {
1357
+ return handleDisconnect(new Error("Stream ended before a terminal event"));
1358
1358
  }
1359
- break;
1359
+ return;
1360
1360
  }
1361
1361
  buffer += decoder.decode(value, { stream: true });
1362
1362
  const lines = buffer.split("\n");
@@ -1370,12 +1370,21 @@ function createSSEStream(url, options = {}) {
1370
1370
  currentData = currentData ? `${currentData}
1371
1371
  ${line.slice(5).trim()}` : line.slice(5).trim();
1372
1372
  } else if (line === "" && currentData) {
1373
- if (currentId !== void 0) lastEventId = currentId;
1373
+ if (currentId !== void 0) {
1374
+ if (currentId !== lastEventId) reconnectAttempts = 0;
1375
+ lastEventId = currentId;
1376
+ }
1374
1377
  try {
1375
1378
  const parsed = JSON.parse(currentData);
1376
1379
  stream.emit(currentEvent, parsed);
1377
1380
  if (currentEvent === "done" || currentEvent === "error") {
1381
+ terminalEventReceived = true;
1378
1382
  stream.emit("complete", {});
1383
+ controller.abort();
1384
+ try {
1385
+ await reader.cancel();
1386
+ } catch {
1387
+ }
1379
1388
  return;
1380
1389
  }
1381
1390
  } catch {
package/dist/index.mjs CHANGED
@@ -1306,6 +1306,7 @@ function createSSEStream(url, options = {}) {
1306
1306
  let lastEventId = initialLastEventId;
1307
1307
  let reconnectAttempts = 0;
1308
1308
  async function connect() {
1309
+ if (controller.signal.aborted) return;
1309
1310
  const requestHeaders = {
1310
1311
  "Accept": "text/event-stream",
1311
1312
  "Cache-Control": "no-cache",
@@ -1335,10 +1336,10 @@ function createSSEStream(url, options = {}) {
1335
1336
  stream.emit("error", new Error("Response body is null"));
1336
1337
  return;
1337
1338
  }
1338
- reconnectAttempts = 0;
1339
1339
  const reader = response.body.getReader();
1340
1340
  const decoder = new TextDecoder();
1341
1341
  let buffer = "";
1342
+ let terminalEventReceived = false;
1342
1343
  let currentEvent = "message";
1343
1344
  let currentData = "";
1344
1345
  let currentId;
@@ -1346,11 +1347,10 @@ function createSSEStream(url, options = {}) {
1346
1347
  while (true) {
1347
1348
  const { done, value } = await reader.read();
1348
1349
  if (done) {
1349
- if (!controller.signal.aborted) {
1350
- stream.emit("done", {});
1351
- stream.emit("complete", {});
1350
+ if (!controller.signal.aborted && !terminalEventReceived) {
1351
+ return handleDisconnect(new Error("Stream ended before a terminal event"));
1352
1352
  }
1353
- break;
1353
+ return;
1354
1354
  }
1355
1355
  buffer += decoder.decode(value, { stream: true });
1356
1356
  const lines = buffer.split("\n");
@@ -1364,12 +1364,21 @@ function createSSEStream(url, options = {}) {
1364
1364
  currentData = currentData ? `${currentData}
1365
1365
  ${line.slice(5).trim()}` : line.slice(5).trim();
1366
1366
  } else if (line === "" && currentData) {
1367
- if (currentId !== void 0) lastEventId = currentId;
1367
+ if (currentId !== void 0) {
1368
+ if (currentId !== lastEventId) reconnectAttempts = 0;
1369
+ lastEventId = currentId;
1370
+ }
1368
1371
  try {
1369
1372
  const parsed = JSON.parse(currentData);
1370
1373
  stream.emit(currentEvent, parsed);
1371
1374
  if (currentEvent === "done" || currentEvent === "error") {
1375
+ terminalEventReceived = true;
1372
1376
  stream.emit("complete", {});
1377
+ controller.abort();
1378
+ try {
1379
+ await reader.cancel();
1380
+ } catch {
1381
+ }
1373
1382
  return;
1374
1383
  }
1375
1384
  } catch {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/sdk",
3
3
  "type": "module",
4
- "version": "1.15.543",
4
+ "version": "1.15.549",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -66,6 +66,7 @@ export function createSSEStream<TEventMap extends StreamEventMap = StreamEventMa
66
66
  let reconnectAttempts = 0
67
67
 
68
68
  async function connect(): Promise<void> {
69
+ if (controller.signal.aborted) return
69
70
  const requestHeaders: Record<string, string> = {
70
71
  'Accept': 'text/event-stream',
71
72
  'Cache-Control': 'no-cache',
@@ -98,12 +99,10 @@ export function createSSEStream<TEventMap extends StreamEventMap = StreamEventMa
98
99
  return
99
100
  }
100
101
 
101
- // Successful connection — reset reconnect counter
102
- reconnectAttempts = 0
103
-
104
102
  const reader = response.body.getReader()
105
103
  const decoder = new TextDecoder()
106
104
  let buffer = ''
105
+ let terminalEventReceived = false
107
106
 
108
107
  // SSE parser state MUST live across read() chunks: a single event's
109
108
  // `event:`/`data:` lines and its terminating blank line can be split over
@@ -120,12 +119,10 @@ export function createSSEStream<TEventMap extends StreamEventMap = StreamEventMa
120
119
  const { done, value } = await reader.read()
121
120
 
122
121
  if (done) {
123
- // Server closed the connection — emit done only if not aborted
124
- if (!controller.signal.aborted) {
125
- stream.emit('done', {})
126
- stream.emit('complete', {})
122
+ if (!controller.signal.aborted && !terminalEventReceived) {
123
+ return handleDisconnect(new Error('Stream ended before a terminal event'))
127
124
  }
128
- break
125
+ return
129
126
  }
130
127
 
131
128
  buffer += decoder.decode(value, { stream: true })
@@ -143,7 +140,10 @@ export function createSSEStream<TEventMap extends StreamEventMap = StreamEventMa
143
140
  currentData = currentData ? `${currentData}\n${line.slice(5).trim()}` : line.slice(5).trim()
144
141
  } else if (line === '' && currentData) {
145
142
  // Track last seen ID for reconnect
146
- if (currentId !== undefined) lastEventId = currentId
143
+ if (currentId !== undefined) {
144
+ if (currentId !== lastEventId) reconnectAttempts = 0
145
+ lastEventId = currentId
146
+ }
147
147
 
148
148
  try {
149
149
  const parsed = JSON.parse(currentData)
@@ -151,7 +151,10 @@ export function createSSEStream<TEventMap extends StreamEventMap = StreamEventMa
151
151
 
152
152
  // Auto-close stream on terminal events
153
153
  if (currentEvent === 'done' || currentEvent === 'error') {
154
+ terminalEventReceived = true
154
155
  stream.emit('complete', {})
156
+ controller.abort()
157
+ try { await reader.cancel() } catch { /* already closed */ }
155
158
  return
156
159
  }
157
160
  } catch {