@prisma/composer-prisma-cloud 0.1.0-dev.3 → 0.1.0-dev.5

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.
@@ -465,7 +465,7 @@ function storageService(opts) {
465
465
  }
466
466
  storageService({ bucket: "storage" });
467
467
  //#endregion
468
- //#region ../../1-prisma-cloud/2-shared-modules/streams/dist/streams-service-Mz48Yi0V.mjs
468
+ //#region ../../1-prisma-cloud/2-shared-modules/streams/dist/streams-service-Br5Tj3AY.mjs
469
469
  var __create = Object.create;
470
470
  var __defProp = Object.defineProperty;
471
471
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -864,7 +864,7 @@ var FetchError = class FetchError extends Error {
864
864
  let text = void 0;
865
865
  let json = void 0;
866
866
  const contentType = response.headers.get(`content-type`);
867
- if (!response.bodyUsed) if (contentType && contentType.includes(`application/json`)) try {
867
+ if (!response.bodyUsed && response.body !== null) if (contentType && contentType.includes(`application/json`)) try {
868
868
  json = await response.json();
869
869
  } catch {
870
870
  text = await response.text();
@@ -913,7 +913,7 @@ var DurableStreamError = class DurableStreamError extends Error {
913
913
  const status = response.status;
914
914
  let details;
915
915
  const contentType = response.headers.get(`content-type`);
916
- if (!response.bodyUsed) if (contentType && contentType.includes(`application/json`)) try {
916
+ if (!response.bodyUsed && response.body !== null) if (contentType && contentType.includes(`application/json`)) try {
917
917
  details = await response.json();
918
918
  } catch {
919
919
  details = await response.text();
@@ -1282,6 +1282,204 @@ async function* parseSSEStream(stream$1, signal) {
1282
1282
  }
1283
1283
  }
1284
1284
  /**
1285
+ * Abstract base class for stream response state.
1286
+ * All state transitions return new immutable state objects.
1287
+ */
1288
+ var StreamResponseState = class {
1289
+ shouldContinueLive(stopAfterUpToDate, liveMode) {
1290
+ if (stopAfterUpToDate && this.upToDate) return false;
1291
+ if (liveMode === false) return false;
1292
+ if (this.streamClosed) return false;
1293
+ return true;
1294
+ }
1295
+ };
1296
+ /**
1297
+ * State for long-poll mode. shouldUseSse() returns false.
1298
+ */
1299
+ var LongPollState = class LongPollState extends StreamResponseState {
1300
+ offset;
1301
+ cursor;
1302
+ upToDate;
1303
+ streamClosed;
1304
+ constructor(fields) {
1305
+ super();
1306
+ this.offset = fields.offset;
1307
+ this.cursor = fields.cursor;
1308
+ this.upToDate = fields.upToDate;
1309
+ this.streamClosed = fields.streamClosed;
1310
+ }
1311
+ shouldUseSse() {
1312
+ return false;
1313
+ }
1314
+ withResponseMetadata(update) {
1315
+ return new LongPollState({
1316
+ offset: update.offset ?? this.offset,
1317
+ cursor: update.cursor ?? this.cursor,
1318
+ upToDate: update.upToDate,
1319
+ streamClosed: this.streamClosed || update.streamClosed
1320
+ });
1321
+ }
1322
+ withSSEControl(event) {
1323
+ const streamClosed = this.streamClosed || (event.streamClosed ?? false);
1324
+ return new LongPollState({
1325
+ offset: event.streamNextOffset,
1326
+ cursor: event.streamCursor || this.cursor,
1327
+ upToDate: event.streamClosed ?? false ? true : event.upToDate ?? this.upToDate,
1328
+ streamClosed
1329
+ });
1330
+ }
1331
+ pause() {
1332
+ return new PausedState(this);
1333
+ }
1334
+ };
1335
+ /**
1336
+ * State for SSE mode. shouldUseSse() returns true.
1337
+ * Tracks SSE connection resilience (short connection detection).
1338
+ */
1339
+ var SSEState = class SSEState extends StreamResponseState {
1340
+ offset;
1341
+ cursor;
1342
+ upToDate;
1343
+ streamClosed;
1344
+ consecutiveShortConnections;
1345
+ connectionStartTime;
1346
+ constructor(fields) {
1347
+ super();
1348
+ this.offset = fields.offset;
1349
+ this.cursor = fields.cursor;
1350
+ this.upToDate = fields.upToDate;
1351
+ this.streamClosed = fields.streamClosed;
1352
+ this.consecutiveShortConnections = fields.consecutiveShortConnections ?? 0;
1353
+ this.connectionStartTime = fields.connectionStartTime;
1354
+ }
1355
+ shouldUseSse() {
1356
+ return true;
1357
+ }
1358
+ withResponseMetadata(update) {
1359
+ return new SSEState({
1360
+ offset: update.offset ?? this.offset,
1361
+ cursor: update.cursor ?? this.cursor,
1362
+ upToDate: update.upToDate,
1363
+ streamClosed: this.streamClosed || update.streamClosed,
1364
+ consecutiveShortConnections: this.consecutiveShortConnections,
1365
+ connectionStartTime: this.connectionStartTime
1366
+ });
1367
+ }
1368
+ withSSEControl(event) {
1369
+ const streamClosed = this.streamClosed || (event.streamClosed ?? false);
1370
+ return new SSEState({
1371
+ offset: event.streamNextOffset,
1372
+ cursor: event.streamCursor || this.cursor,
1373
+ upToDate: event.streamClosed ?? false ? true : event.upToDate ?? this.upToDate,
1374
+ streamClosed,
1375
+ consecutiveShortConnections: this.consecutiveShortConnections,
1376
+ connectionStartTime: this.connectionStartTime
1377
+ });
1378
+ }
1379
+ startConnection(now) {
1380
+ return new SSEState({
1381
+ offset: this.offset,
1382
+ cursor: this.cursor,
1383
+ upToDate: this.upToDate,
1384
+ streamClosed: this.streamClosed,
1385
+ consecutiveShortConnections: this.consecutiveShortConnections,
1386
+ connectionStartTime: now
1387
+ });
1388
+ }
1389
+ handleConnectionEnd(now, wasAborted, config) {
1390
+ if (this.connectionStartTime === void 0) return {
1391
+ action: `healthy`,
1392
+ state: this
1393
+ };
1394
+ const duration = now - this.connectionStartTime;
1395
+ if (duration < config.minConnectionDuration && !wasAborted) {
1396
+ const newCount = this.consecutiveShortConnections + 1;
1397
+ if (newCount >= config.maxShortConnections) return {
1398
+ action: `fallback`,
1399
+ state: new LongPollState({
1400
+ offset: this.offset,
1401
+ cursor: this.cursor,
1402
+ upToDate: this.upToDate,
1403
+ streamClosed: this.streamClosed
1404
+ })
1405
+ };
1406
+ return {
1407
+ action: `reconnect`,
1408
+ state: new SSEState({
1409
+ offset: this.offset,
1410
+ cursor: this.cursor,
1411
+ upToDate: this.upToDate,
1412
+ streamClosed: this.streamClosed,
1413
+ consecutiveShortConnections: newCount,
1414
+ connectionStartTime: this.connectionStartTime
1415
+ }),
1416
+ backoffAttempt: newCount
1417
+ };
1418
+ }
1419
+ if (duration >= config.minConnectionDuration) return {
1420
+ action: `healthy`,
1421
+ state: new SSEState({
1422
+ offset: this.offset,
1423
+ cursor: this.cursor,
1424
+ upToDate: this.upToDate,
1425
+ streamClosed: this.streamClosed,
1426
+ consecutiveShortConnections: 0,
1427
+ connectionStartTime: this.connectionStartTime
1428
+ })
1429
+ };
1430
+ return {
1431
+ action: `healthy`,
1432
+ state: this
1433
+ };
1434
+ }
1435
+ pause() {
1436
+ return new PausedState(this);
1437
+ }
1438
+ };
1439
+ /**
1440
+ * Paused state wrapper. Delegates all sync field access to the inner state.
1441
+ * resume() returns the wrapped state unchanged (identity preserved).
1442
+ */
1443
+ var PausedState = class PausedState extends StreamResponseState {
1444
+ #inner;
1445
+ constructor(inner) {
1446
+ super();
1447
+ this.#inner = inner;
1448
+ }
1449
+ get offset() {
1450
+ return this.#inner.offset;
1451
+ }
1452
+ get cursor() {
1453
+ return this.#inner.cursor;
1454
+ }
1455
+ get upToDate() {
1456
+ return this.#inner.upToDate;
1457
+ }
1458
+ get streamClosed() {
1459
+ return this.#inner.streamClosed;
1460
+ }
1461
+ shouldUseSse() {
1462
+ return this.#inner.shouldUseSse();
1463
+ }
1464
+ withResponseMetadata(update) {
1465
+ const newInner = this.#inner.withResponseMetadata(update);
1466
+ return new PausedState(newInner);
1467
+ }
1468
+ withSSEControl(event) {
1469
+ const newInner = this.#inner.withSSEControl(event);
1470
+ return new PausedState(newInner);
1471
+ }
1472
+ pause() {
1473
+ return this;
1474
+ }
1475
+ resume() {
1476
+ return {
1477
+ state: this.#inner,
1478
+ justResumed: true
1479
+ };
1480
+ }
1481
+ };
1482
+ /**
1285
1483
  * Constant used as abort reason when pausing the stream due to visibility change.
1286
1484
  */
1287
1485
  const PAUSE_STREAM = `PAUSE_STREAM`;
@@ -1298,10 +1496,7 @@ var StreamResponseImpl = class {
1298
1496
  #statusText;
1299
1497
  #ok;
1300
1498
  #isLoading;
1301
- #offset;
1302
- #cursor;
1303
- #upToDate;
1304
- #streamClosed;
1499
+ #syncState;
1305
1500
  #isJsonMode;
1306
1501
  #abortController;
1307
1502
  #fetchNext;
@@ -1316,11 +1511,7 @@ var StreamResponseImpl = class {
1316
1511
  #unsubscribeFromVisibilityChanges;
1317
1512
  #pausePromise;
1318
1513
  #pauseResolve;
1319
- #justResumedFromPause = false;
1320
1514
  #sseResilience;
1321
- #lastSSEConnectionStartTime;
1322
- #consecutiveShortSSEConnections = 0;
1323
- #sseFallbackToLongPoll = false;
1324
1515
  #encoding;
1325
1516
  #responseStream;
1326
1517
  constructor(config) {
@@ -1328,10 +1519,13 @@ var StreamResponseImpl = class {
1328
1519
  this.contentType = config.contentType;
1329
1520
  this.live = config.live;
1330
1521
  this.startOffset = config.startOffset;
1331
- this.#offset = config.initialOffset;
1332
- this.#cursor = config.initialCursor;
1333
- this.#upToDate = config.initialUpToDate;
1334
- this.#streamClosed = config.initialStreamClosed;
1522
+ const syncFields = {
1523
+ offset: config.initialOffset,
1524
+ cursor: config.initialCursor,
1525
+ upToDate: config.initialUpToDate,
1526
+ streamClosed: config.initialStreamClosed
1527
+ };
1528
+ this.#syncState = config.startSSE ? new SSEState(syncFields) : new LongPollState(syncFields);
1335
1529
  this.#headers = config.firstResponse.headers;
1336
1530
  this.#status = config.firstResponse.status;
1337
1531
  this.#statusText = config.firstResponse.statusText;
@@ -1388,6 +1582,7 @@ var StreamResponseImpl = class {
1388
1582
  #pause() {
1389
1583
  if (this.#state === `active`) {
1390
1584
  this.#state = `pause-requested`;
1585
+ this.#syncState = this.#syncState.pause();
1391
1586
  this.#pausePromise = new Promise((resolve) => {
1392
1587
  this.#pauseResolve = resolve;
1393
1588
  });
@@ -1401,8 +1596,8 @@ var StreamResponseImpl = class {
1401
1596
  #resume() {
1402
1597
  if (this.#state === `paused` || this.#state === `pause-requested`) {
1403
1598
  if (this.#abortController.signal.aborted) return;
1599
+ if (this.#syncState instanceof PausedState) this.#syncState = this.#syncState.resume().state;
1404
1600
  this.#state = `active`;
1405
- this.#justResumedFromPause = true;
1406
1601
  this.#pauseResolve?.();
1407
1602
  this.#pausePromise = void 0;
1408
1603
  this.#pauseResolve = void 0;
@@ -1424,16 +1619,16 @@ var StreamResponseImpl = class {
1424
1619
  return this.#isLoading;
1425
1620
  }
1426
1621
  get offset() {
1427
- return this.#offset;
1622
+ return this.#syncState.offset;
1428
1623
  }
1429
1624
  get cursor() {
1430
- return this.#cursor;
1625
+ return this.#syncState.cursor;
1431
1626
  }
1432
1627
  get upToDate() {
1433
- return this.#upToDate;
1628
+ return this.#syncState.upToDate;
1434
1629
  }
1435
1630
  get streamClosed() {
1436
- return this.#streamClosed;
1631
+ return this.#syncState.streamClosed;
1437
1632
  }
1438
1633
  #ensureJsonMode() {
1439
1634
  if (!this.#isJsonMode) throw new DurableStreamError(`JSON methods are only valid for JSON-mode streams. Content-Type is "${this.contentType}" and json hint was not set.`, `BAD_REQUEST`);
@@ -1459,160 +1654,68 @@ var StreamResponseImpl = class {
1459
1654
  * and whether we've received upToDate or streamClosed.
1460
1655
  */
1461
1656
  #shouldContinueLive() {
1462
- if (this.#stopAfterUpToDate && this.upToDate) return false;
1463
- if (this.live === false) return false;
1464
- if (this.#streamClosed) return false;
1465
- return true;
1657
+ return this.#syncState.shouldContinueLive(this.#stopAfterUpToDate, this.live);
1466
1658
  }
1467
1659
  /**
1468
1660
  * Update state from response headers.
1469
1661
  */
1470
1662
  #updateStateFromResponse(response) {
1471
- const offset = response.headers.get(STREAM_OFFSET_HEADER);
1472
- if (offset) this.#offset = offset;
1473
- const cursor = response.headers.get(STREAM_CURSOR_HEADER);
1474
- if (cursor) this.#cursor = cursor;
1475
- this.#upToDate = response.headers.has(STREAM_UP_TO_DATE_HEADER);
1476
- if (response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`) this.#streamClosed = true;
1663
+ this.#syncState = this.#syncState.withResponseMetadata({
1664
+ offset: response.headers.get(STREAM_OFFSET_HEADER) || void 0,
1665
+ cursor: response.headers.get(STREAM_CURSOR_HEADER) || void 0,
1666
+ upToDate: response.headers.has(STREAM_UP_TO_DATE_HEADER),
1667
+ streamClosed: response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`
1668
+ });
1477
1669
  this.#headers = response.headers;
1478
1670
  this.#status = response.status;
1479
1671
  this.#statusText = response.statusText;
1480
1672
  this.#ok = response.ok;
1481
1673
  }
1482
1674
  /**
1483
- * Extract stream metadata from Response headers.
1484
- * Used by subscriber APIs to get the correct offset/cursor/upToDate/streamClosed for each
1485
- * specific Response, rather than reading from `this` which may be stale due to
1486
- * ReadableStream prefetching or timing issues.
1487
- */
1488
- #getMetadataFromResponse(response) {
1489
- const offset = response.headers.get(STREAM_OFFSET_HEADER);
1490
- const cursor = response.headers.get(STREAM_CURSOR_HEADER);
1491
- const upToDate = response.headers.has(STREAM_UP_TO_DATE_HEADER);
1492
- const streamClosed = response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
1493
- return {
1494
- offset: offset ?? this.offset,
1495
- cursor: cursor ?? this.cursor,
1496
- upToDate,
1497
- streamClosed: streamClosed || this.streamClosed
1498
- };
1499
- }
1500
- /**
1501
- * Decode base64 string to Uint8Array.
1502
- * Per protocol: concatenate data lines, remove \n and \r, then decode.
1503
- */
1504
- #decodeBase64(base64Str) {
1505
- const cleaned = base64Str.replace(/[\n\r]/g, ``);
1506
- if (cleaned.length === 0) return /* @__PURE__ */ new Uint8Array(0);
1507
- if (cleaned.length % 4 !== 0) throw new DurableStreamError(`Invalid base64 data: length ${cleaned.length} is not a multiple of 4`, `PARSE_ERROR`);
1508
- try {
1509
- if (typeof Buffer !== `undefined`) return new Uint8Array(Buffer.from(cleaned, `base64`));
1510
- else {
1511
- const binaryStr = atob(cleaned);
1512
- const bytes = new Uint8Array(binaryStr.length);
1513
- for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
1514
- return bytes;
1515
- }
1516
- } catch (err) {
1517
- throw new DurableStreamError(`Failed to decode base64 data: ${err instanceof Error ? err.message : String(err)}`, `PARSE_ERROR`);
1518
- }
1519
- }
1520
- /**
1521
- * Create a synthetic Response from SSE data with proper headers.
1522
- * Includes offset/cursor/upToDate/streamClosed in headers so subscribers can read them.
1523
- */
1524
- #createSSESyntheticResponse(data, offset, cursor, upToDate, streamClosed) {
1525
- return this.#createSSESyntheticResponseFromParts([data], offset, cursor, upToDate, streamClosed);
1526
- }
1527
- /**
1528
- * Create a synthetic Response from multiple SSE data parts.
1529
- * For base64 mode, each part is independently encoded, so we decode each
1530
- * separately and concatenate the binary results.
1531
- * For text mode, parts are simply concatenated as strings.
1532
- */
1533
- #createSSESyntheticResponseFromParts(dataParts, offset, cursor, upToDate, streamClosed) {
1534
- const headers = {
1535
- "content-type": this.contentType ?? `application/json`,
1536
- [STREAM_OFFSET_HEADER]: String(offset)
1537
- };
1538
- if (cursor) headers[STREAM_CURSOR_HEADER] = cursor;
1539
- if (upToDate) headers[STREAM_UP_TO_DATE_HEADER] = `true`;
1540
- if (streamClosed) headers[STREAM_CLOSED_HEADER] = `true`;
1541
- let body;
1542
- if (this.#encoding === `base64`) {
1543
- const decodedParts = dataParts.filter((part) => part.length > 0).map((part) => this.#decodeBase64(part));
1544
- if (decodedParts.length === 0) body = /* @__PURE__ */ new ArrayBuffer(0);
1545
- else if (decodedParts.length === 1) {
1546
- const decoded = decodedParts[0];
1547
- body = decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength);
1548
- } else {
1549
- const totalLength = decodedParts.reduce((sum, part) => sum + part.length, 0);
1550
- const combined = new Uint8Array(totalLength);
1551
- let offset$1 = 0;
1552
- for (const part of decodedParts) {
1553
- combined.set(part, offset$1);
1554
- offset$1 += part.length;
1555
- }
1556
- body = combined.buffer;
1557
- }
1558
- } else body = dataParts.join(``);
1559
- return new Response(body, {
1560
- status: 200,
1561
- headers
1562
- });
1563
- }
1564
- /**
1565
1675
  * Update instance state from an SSE control event.
1566
1676
  */
1567
1677
  #updateStateFromSSEControl(controlEvent) {
1568
- this.#offset = controlEvent.streamNextOffset;
1569
- if (controlEvent.streamCursor) this.#cursor = controlEvent.streamCursor;
1570
- if (controlEvent.upToDate !== void 0) this.#upToDate = controlEvent.upToDate;
1571
- if (controlEvent.streamClosed) {
1572
- this.#streamClosed = true;
1573
- this.#upToDate = true;
1574
- }
1678
+ this.#syncState = this.#syncState.withSSEControl(controlEvent);
1679
+ }
1680
+ #updateEncodingFromSSEResponse(response) {
1681
+ this.#encoding = response.headers.get(STREAM_SSE_DATA_ENCODING_HEADER) === `base64` ? `base64` : void 0;
1575
1682
  }
1576
1683
  /**
1577
1684
  * Mark the start of an SSE connection for duration tracking.
1685
+ * If the state is not SSEState (e.g., auto-detected SSE from content-type),
1686
+ * transitions to SSEState first.
1578
1687
  */
1579
1688
  #markSSEConnectionStart() {
1580
- this.#lastSSEConnectionStartTime = Date.now();
1581
- }
1582
- /**
1583
- * Handle SSE connection end - check duration and manage fallback state.
1584
- * Returns a delay to wait before reconnecting, or null if should not reconnect.
1585
- */
1586
- async #handleSSEConnectionEnd() {
1587
- if (this.#lastSSEConnectionStartTime === void 0) return 0;
1588
- const connectionDuration = Date.now() - this.#lastSSEConnectionStartTime;
1589
- const wasAborted = this.#abortController.signal.aborted;
1590
- if (connectionDuration < this.#sseResilience.minConnectionDuration && !wasAborted) {
1591
- this.#consecutiveShortSSEConnections++;
1592
- if (this.#consecutiveShortSSEConnections >= this.#sseResilience.maxShortConnections) {
1593
- this.#sseFallbackToLongPoll = true;
1594
- if (this.#sseResilience.logWarnings) console.warn("[Durable Streams] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy.");
1595
- return null;
1596
- } else {
1597
- const maxDelay = Math.min(this.#sseResilience.backoffMaxDelay, this.#sseResilience.backoffBaseDelay * Math.pow(2, this.#consecutiveShortSSEConnections));
1598
- const delayMs = Math.floor(Math.random() * maxDelay);
1599
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1600
- return delayMs;
1601
- }
1602
- } else if (connectionDuration >= this.#sseResilience.minConnectionDuration) this.#consecutiveShortSSEConnections = 0;
1603
- return 0;
1689
+ if (!(this.#syncState instanceof SSEState)) this.#syncState = new SSEState({
1690
+ offset: this.#syncState.offset,
1691
+ cursor: this.#syncState.cursor,
1692
+ upToDate: this.#syncState.upToDate,
1693
+ streamClosed: this.#syncState.streamClosed
1694
+ });
1695
+ this.#syncState = this.#syncState.startConnection(Date.now());
1604
1696
  }
1605
1697
  /**
1606
1698
  * Try to reconnect SSE and return the new iterator, or null if reconnection
1607
1699
  * is not possible or fails.
1608
1700
  */
1609
1701
  async #trySSEReconnect() {
1610
- if (this.#sseFallbackToLongPoll) return null;
1702
+ if (!this.#syncState.shouldUseSse()) return null;
1611
1703
  if (!this.#shouldContinueLive() || !this.#startSSE) return null;
1612
- if (await this.#handleSSEConnectionEnd() === null) return null;
1704
+ const result = this.#syncState.handleConnectionEnd(Date.now(), this.#abortController.signal.aborted, this.#sseResilience);
1705
+ this.#syncState = result.state;
1706
+ if (result.action === `fallback`) {
1707
+ if (this.#sseResilience.logWarnings) console.warn("[Durable Streams] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy.");
1708
+ return null;
1709
+ }
1710
+ if (result.action === `reconnect`) {
1711
+ const maxDelay = Math.min(this.#sseResilience.backoffMaxDelay, this.#sseResilience.backoffBaseDelay * Math.pow(2, result.backoffAttempt));
1712
+ const delayMs = Math.floor(Math.random() * maxDelay);
1713
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1714
+ }
1613
1715
  this.#markSSEConnectionStart();
1614
1716
  this.#requestAbortController = new AbortController();
1615
1717
  const newSSEResponse = await this.#startSSE(this.offset, this.cursor, this.#requestAbortController.signal);
1718
+ this.#updateEncodingFromSSEResponse(newSSEResponse);
1616
1719
  if (newSSEResponse.body) return parseSSEStream(newSSEResponse.body, this.#requestAbortController.signal);
1617
1720
  return null;
1618
1721
  }
@@ -1645,7 +1748,7 @@ var StreamResponseImpl = class {
1645
1748
  this.#updateStateFromSSEControl(event);
1646
1749
  if (event.upToDate) return {
1647
1750
  type: `response`,
1648
- response: this.#createSSESyntheticResponse(``, event.streamNextOffset, event.streamCursor, true, event.streamClosed ?? false)
1751
+ response: createSSESyntheticResponse(``, event.streamNextOffset, event.streamCursor, true, event.streamClosed ?? false, this.contentType, this.#encoding)
1649
1752
  };
1650
1753
  return { type: `continue` };
1651
1754
  }
@@ -1662,7 +1765,7 @@ var StreamResponseImpl = class {
1662
1765
  while (true) {
1663
1766
  const { done: controlDone, value: controlEvent } = await sseEventIterator.next();
1664
1767
  if (controlDone) {
1665
- const response = this.#createSSESyntheticResponseFromParts(bufferedDataParts, this.offset, this.cursor, this.upToDate, this.streamClosed);
1768
+ const response = createSSESyntheticResponseFromParts(bufferedDataParts, this.offset, this.cursor, this.upToDate, this.streamClosed, this.contentType, this.#encoding, this.#isJsonMode);
1666
1769
  try {
1667
1770
  return {
1668
1771
  type: `response`,
@@ -1680,7 +1783,7 @@ var StreamResponseImpl = class {
1680
1783
  this.#updateStateFromSSEControl(controlEvent);
1681
1784
  return {
1682
1785
  type: `response`,
1683
- response: this.#createSSESyntheticResponseFromParts(bufferedDataParts, controlEvent.streamNextOffset, controlEvent.streamCursor, controlEvent.upToDate ?? false, controlEvent.streamClosed ?? false)
1786
+ response: createSSESyntheticResponseFromParts(bufferedDataParts, controlEvent.streamNextOffset, controlEvent.streamCursor, controlEvent.upToDate ?? false, controlEvent.streamClosed ?? false, this.contentType, this.#encoding, this.#isJsonMode)
1684
1787
  };
1685
1788
  }
1686
1789
  bufferedDataParts.push(controlEvent.data);
@@ -1703,6 +1806,7 @@ var StreamResponseImpl = class {
1703
1806
  firstResponseYielded = true;
1704
1807
  if ((firstResponse.headers.get(`content-type`)?.includes(`text/event-stream`) ?? false) && firstResponse.body) {
1705
1808
  this.#markSSEConnectionStart();
1809
+ this.#updateEncodingFromSSEResponse(firstResponse);
1706
1810
  this.#requestAbortController = new AbortController();
1707
1811
  sseEventIterator = parseSSEStream(firstResponse.body, this.#requestAbortController.signal);
1708
1812
  } else {
@@ -1715,6 +1819,22 @@ var StreamResponseImpl = class {
1715
1819
  return;
1716
1820
  }
1717
1821
  }
1822
+ if (!sseEventIterator && this.upToDate && this.#startSSE && this.#shouldContinueLive()) {
1823
+ if (this.#state === `pause-requested` || this.#state === `paused`) {
1824
+ this.#state = `paused`;
1825
+ if (this.#pausePromise) await this.#pausePromise;
1826
+ if (this.#abortController.signal.aborted) {
1827
+ this.#markClosed();
1828
+ controller.close();
1829
+ return;
1830
+ }
1831
+ }
1832
+ this.#markSSEConnectionStart();
1833
+ this.#requestAbortController = new AbortController();
1834
+ const sseResponse = await this.#startSSE(this.offset, this.cursor, this.#requestAbortController.signal);
1835
+ this.#updateEncodingFromSSEResponse(sseResponse);
1836
+ if (sseResponse.body) sseEventIterator = parseSSEStream(sseResponse.body, this.#requestAbortController.signal);
1837
+ }
1718
1838
  if (sseEventIterator) {
1719
1839
  if (this.#state === `pause-requested` || this.#state === `paused`) {
1720
1840
  this.#state = `paused`;
@@ -1754,6 +1874,7 @@ var StreamResponseImpl = class {
1754
1874
  }
1755
1875
  }
1756
1876
  if (this.#shouldContinueLive()) {
1877
+ let resumingFromPause = false;
1757
1878
  if (this.#state === `pause-requested` || this.#state === `paused`) {
1758
1879
  this.#state = `paused`;
1759
1880
  if (this.#pausePromise) await this.#pausePromise;
@@ -1762,16 +1883,15 @@ var StreamResponseImpl = class {
1762
1883
  controller.close();
1763
1884
  return;
1764
1885
  }
1886
+ resumingFromPause = true;
1765
1887
  }
1766
1888
  if (this.#abortController.signal.aborted) {
1767
1889
  this.#markClosed();
1768
1890
  controller.close();
1769
1891
  return;
1770
1892
  }
1771
- const resumingFromPause = this.#justResumedFromPause;
1772
- this.#justResumedFromPause = false;
1773
1893
  this.#requestAbortController = new AbortController();
1774
- const response = await this.#fetchNext(this.offset, this.cursor, this.#requestAbortController.signal, resumingFromPause);
1894
+ const response = await this.#fetchNext(this.offset, this.cursor, this.#requestAbortController.signal, this.upToDate, resumingFromPause);
1775
1895
  this.#updateStateFromResponse(response);
1776
1896
  controller.enqueue(response);
1777
1897
  return;
@@ -1934,22 +2054,25 @@ var StreamResponseImpl = class {
1934
2054
  controller.enqueue(pendingItems.shift());
1935
2055
  return;
1936
2056
  }
1937
- const { done, value: response } = await reader.read();
1938
- if (done) {
1939
- this.#markClosed();
1940
- controller.close();
1941
- return;
1942
- }
1943
- const content = (await response.text()).trim() || `[]`;
1944
- let parsed;
1945
- try {
1946
- parsed = JSON.parse(content);
1947
- } catch (err) {
1948
- const preview = content.length > 100 ? content.slice(0, 100) + `...` : content;
1949
- throw new DurableStreamError(`Failed to parse JSON response: ${err instanceof Error ? err.message : String(err)}. Data: ${preview}`, `PARSE_ERROR`);
2057
+ let result = await reader.read();
2058
+ while (!result.done) {
2059
+ const content = (await result.value.text()).trim() || `[]`;
2060
+ let parsed;
2061
+ try {
2062
+ parsed = JSON.parse(content);
2063
+ } catch (err) {
2064
+ const preview = content.length > 100 ? content.slice(0, 100) + `...` : content;
2065
+ throw new DurableStreamError(`Failed to parse JSON response: ${err instanceof Error ? err.message : String(err)}. Data: ${preview}`, `PARSE_ERROR`);
2066
+ }
2067
+ pendingItems = Array.isArray(parsed) ? parsed : [parsed];
2068
+ if (pendingItems.length > 0) {
2069
+ controller.enqueue(pendingItems.shift());
2070
+ return;
2071
+ }
2072
+ result = await reader.read();
1950
2073
  }
1951
- pendingItems = Array.isArray(parsed) ? parsed : [parsed];
1952
- if (pendingItems.length > 0) controller.enqueue(pendingItems.shift());
2074
+ this.#markClosed();
2075
+ controller.close();
1953
2076
  },
1954
2077
  cancel: () => {
1955
2078
  reader.releaseLock();
@@ -1981,7 +2104,7 @@ var StreamResponseImpl = class {
1981
2104
  while (!result.done) {
1982
2105
  if (abortController.signal.aborted) break;
1983
2106
  const response = result.value;
1984
- const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2107
+ const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
1985
2108
  const content = (await response.text()).trim() || `[]`;
1986
2109
  let parsed;
1987
2110
  try {
@@ -2025,7 +2148,7 @@ var StreamResponseImpl = class {
2025
2148
  while (!result.done) {
2026
2149
  if (abortController.signal.aborted) break;
2027
2150
  const response = result.value;
2028
- const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2151
+ const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
2029
2152
  const buffer = await response.arrayBuffer();
2030
2153
  await subscriber({
2031
2154
  data: new Uint8Array(buffer),
@@ -2062,7 +2185,7 @@ var StreamResponseImpl = class {
2062
2185
  while (!result.done) {
2063
2186
  if (abortController.signal.aborted) break;
2064
2187
  const response = result.value;
2065
- const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2188
+ const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
2066
2189
  await subscriber({
2067
2190
  text: await response.text(),
2068
2191
  offset,
@@ -2098,6 +2221,97 @@ var StreamResponseImpl = class {
2098
2221
  }
2099
2222
  };
2100
2223
  /**
2224
+ * Extract stream metadata from Response headers.
2225
+ * Falls back to the provided defaults when headers are absent.
2226
+ */
2227
+ function getMetadataFromResponse(response, fallbackOffset, fallbackCursor, fallbackStreamClosed) {
2228
+ const offset = response.headers.get(STREAM_OFFSET_HEADER);
2229
+ const cursor = response.headers.get(STREAM_CURSOR_HEADER);
2230
+ const upToDate = response.headers.has(STREAM_UP_TO_DATE_HEADER);
2231
+ const streamClosed = response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
2232
+ return {
2233
+ offset: offset ?? fallbackOffset,
2234
+ cursor: cursor ?? fallbackCursor,
2235
+ upToDate,
2236
+ streamClosed: streamClosed || fallbackStreamClosed
2237
+ };
2238
+ }
2239
+ /**
2240
+ * Decode base64 string to Uint8Array.
2241
+ * Per protocol: concatenate data lines, remove \n and \r, then decode.
2242
+ */
2243
+ function decodeBase64(base64Str) {
2244
+ const cleaned = base64Str.replace(/[\n\r]/g, ``);
2245
+ if (cleaned.length === 0) return /* @__PURE__ */ new Uint8Array(0);
2246
+ if (cleaned.length % 4 !== 0) throw new DurableStreamError(`Invalid base64 data: length ${cleaned.length} is not a multiple of 4`, `PARSE_ERROR`);
2247
+ try {
2248
+ if (typeof Buffer !== `undefined`) return new Uint8Array(Buffer.from(cleaned, `base64`));
2249
+ else {
2250
+ const binaryStr = atob(cleaned);
2251
+ const bytes = new Uint8Array(binaryStr.length);
2252
+ for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
2253
+ return bytes;
2254
+ }
2255
+ } catch (err) {
2256
+ throw new DurableStreamError(`Failed to decode base64 data: ${err instanceof Error ? err.message : String(err)}`, `PARSE_ERROR`);
2257
+ }
2258
+ }
2259
+ /**
2260
+ * Create a synthetic Response from SSE data with proper headers.
2261
+ * Includes offset/cursor/upToDate/streamClosed in headers so subscribers can read them.
2262
+ */
2263
+ function createSSESyntheticResponse(data, offset, cursor, upToDate, streamClosed, contentType, encoding) {
2264
+ return createSSESyntheticResponseFromParts([data], offset, cursor, upToDate, streamClosed, contentType, encoding);
2265
+ }
2266
+ /**
2267
+ * Create a synthetic Response from multiple SSE data parts.
2268
+ * For base64 mode, each part is independently encoded, so we decode each
2269
+ * separately and concatenate the binary results.
2270
+ * For text mode, parts are simply concatenated as strings.
2271
+ */
2272
+ function createSSESyntheticResponseFromParts(dataParts, offset, cursor, upToDate, streamClosed, contentType, encoding, isJsonMode) {
2273
+ const headers = {
2274
+ "content-type": contentType ?? `application/json`,
2275
+ [STREAM_OFFSET_HEADER]: String(offset)
2276
+ };
2277
+ if (cursor) headers[STREAM_CURSOR_HEADER] = cursor;
2278
+ if (upToDate) headers[STREAM_UP_TO_DATE_HEADER] = `true`;
2279
+ if (streamClosed) headers[STREAM_CLOSED_HEADER] = `true`;
2280
+ let body;
2281
+ if (encoding === `base64`) {
2282
+ const decodedParts = dataParts.filter((part) => part.length > 0).map((part) => decodeBase64(part));
2283
+ if (decodedParts.length === 0) body = /* @__PURE__ */ new ArrayBuffer(0);
2284
+ else if (decodedParts.length === 1) {
2285
+ const decoded = decodedParts[0];
2286
+ body = decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength);
2287
+ } else {
2288
+ const totalLength = decodedParts.reduce((sum, part) => sum + part.length, 0);
2289
+ const combined = new Uint8Array(totalLength);
2290
+ let offset$1 = 0;
2291
+ for (const part of decodedParts) {
2292
+ combined.set(part, offset$1);
2293
+ offset$1 += part.length;
2294
+ }
2295
+ body = combined.buffer;
2296
+ }
2297
+ } else if (isJsonMode) {
2298
+ const mergedParts = [];
2299
+ for (const part of dataParts) {
2300
+ const trimmed = part.trim();
2301
+ if (trimmed.length === 0) continue;
2302
+ if (trimmed.startsWith(`[`) && trimmed.endsWith(`]`)) {
2303
+ const inner = trimmed.slice(1, -1).trim();
2304
+ if (inner.length > 0) mergedParts.push(inner);
2305
+ } else mergedParts.push(trimmed);
2306
+ }
2307
+ body = `[${mergedParts.join(`,`)}]`;
2308
+ } else body = dataParts.join(``);
2309
+ return new Response(body, {
2310
+ status: 200,
2311
+ headers
2312
+ });
2313
+ }
2314
+ /**
2101
2315
  * Resolve headers from HeadersRecord (supports async functions).
2102
2316
  * Unified implementation used by both stream() and DurableStream.
2103
2317
  */
@@ -2264,7 +2478,6 @@ async function streamInternal(options) {
2264
2478
  const startOffset = options.offset ?? `-1`;
2265
2479
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, startOffset);
2266
2480
  const live = options.live ?? true;
2267
- if (live === `long-poll` || live === `sse`) fetchUrl.searchParams.set(LIVE_QUERY_PARAM, live);
2268
2481
  const params = await resolveParams(options.params);
2269
2482
  for (const [key, value] of Object.entries(params)) fetchUrl.searchParams.set(key, value);
2270
2483
  const headers = await resolveHeaders(options.headers);
@@ -2289,12 +2502,11 @@ async function streamInternal(options) {
2289
2502
  const initialStreamClosed = firstResponse.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
2290
2503
  const isJsonMode = options.json === true || (contentType?.includes(`application/json`) ?? false);
2291
2504
  const encoding = firstResponse.headers.get(STREAM_SSE_DATA_ENCODING_HEADER) === `base64` ? `base64` : void 0;
2292
- const fetchNext = async (offset, cursor, signal, resumingFromPause) => {
2505
+ const fetchNext = async (offset, cursor, signal, upToDate, resumingFromPause) => {
2293
2506
  const nextUrl = new URL(url);
2294
2507
  nextUrl.searchParams.set(OFFSET_QUERY_PARAM, offset);
2295
- if (!resumingFromPause) {
2296
- if (live === `sse`) nextUrl.searchParams.set(LIVE_QUERY_PARAM, `sse`);
2297
- else if (live === true || live === `long-poll`) nextUrl.searchParams.set(LIVE_QUERY_PARAM, `long-poll`);
2508
+ if (upToDate && !resumingFromPause) {
2509
+ if (live === true || live === `long-poll`) nextUrl.searchParams.set(LIVE_QUERY_PARAM, `long-poll`);
2298
2510
  }
2299
2511
  if (cursor) nextUrl.searchParams.set(`cursor`, cursor);
2300
2512
  const nextParams = await resolveParams(options.params);
@@ -2418,6 +2630,7 @@ var IdempotentProducer = class {
2418
2630
  #maxBatchBytes;
2419
2631
  #lingerMs;
2420
2632
  #fetchClient;
2633
+ #headers;
2421
2634
  #signal;
2422
2635
  #onError;
2423
2636
  #pendingBatch = [];
@@ -2425,9 +2638,11 @@ var IdempotentProducer = class {
2425
2638
  #lingerTimeout = null;
2426
2639
  #queue;
2427
2640
  #maxInFlight;
2641
+ #deferredEnqueues = /* @__PURE__ */ new Set();
2428
2642
  #closed = false;
2429
2643
  #closeResult = null;
2430
2644
  #pendingFinalMessage;
2645
+ #lastSuccessfulOffset;
2431
2646
  #epochClaimed;
2432
2647
  #seqState = /* @__PURE__ */ new Map();
2433
2648
  /**
@@ -2453,6 +2668,7 @@ var IdempotentProducer = class {
2453
2668
  this.#maxBatchBytes = maxBatchBytes;
2454
2669
  this.#lingerMs = lingerMs;
2455
2670
  this.#signal = opts?.signal;
2671
+ this.#headers = opts?.headers;
2456
2672
  this.#onError = opts?.onError;
2457
2673
  this.#fetchClient = opts?.fetch ?? ((...args) => fetch(...args));
2458
2674
  this.#maxInFlight = maxInFlight;
@@ -2514,7 +2730,10 @@ var IdempotentProducer = class {
2514
2730
  this.#lingerTimeout = null;
2515
2731
  }
2516
2732
  if (this.#pendingBatch.length > 0) this.#enqueuePendingBatch();
2517
- await this.#queue.drained();
2733
+ do {
2734
+ await this.#queue.drained();
2735
+ await Promise.all(this.#deferredEnqueues);
2736
+ } while (this.#deferredEnqueues.size > 0 || this.inFlightCount > 0);
2518
2737
  }
2519
2738
  /**
2520
2739
  * Stop the producer without closing the underlying stream.
@@ -2578,13 +2797,13 @@ var IdempotentProducer = class {
2578
2797
  else body = bodyBytes;
2579
2798
  }
2580
2799
  const seqForThisRequest = this.#nextSeq;
2581
- const headers = {
2800
+ const headers = await this.#buildHeaders({
2582
2801
  "content-type": contentType,
2583
2802
  [PRODUCER_ID_HEADER]: this.#producerId,
2584
2803
  [PRODUCER_EPOCH_HEADER]: this.#epoch.toString(),
2585
2804
  [PRODUCER_SEQ_HEADER]: seqForThisRequest.toString(),
2586
2805
  [STREAM_CLOSED_HEADER]: `true`
2587
- };
2806
+ });
2588
2807
  const response = await this.#fetchClient(this.#stream.url, {
2589
2808
  method: `POST`,
2590
2809
  headers,
@@ -2593,11 +2812,15 @@ var IdempotentProducer = class {
2593
2812
  });
2594
2813
  if (response.status === 204) {
2595
2814
  this.#nextSeq = seqForThisRequest + 1;
2596
- return { finalOffset: response.headers.get(STREAM_OFFSET_HEADER) ?? `` };
2815
+ const finalOffset = response.headers.get(STREAM_OFFSET_HEADER) ?? ``;
2816
+ this.#recordSuccessfulOffset(finalOffset);
2817
+ return { finalOffset };
2597
2818
  }
2598
2819
  if (response.status === 200) {
2599
2820
  this.#nextSeq = seqForThisRequest + 1;
2600
- return { finalOffset: response.headers.get(STREAM_OFFSET_HEADER) ?? `` };
2821
+ const finalOffset = response.headers.get(STREAM_OFFSET_HEADER) ?? ``;
2822
+ this.#recordSuccessfulOffset(finalOffset);
2823
+ return { finalOffset };
2601
2824
  }
2602
2825
  if (response.status === 403) {
2603
2826
  const currentEpochStr = response.headers.get(PRODUCER_EPOCH_HEADER);
@@ -2645,7 +2868,14 @@ var IdempotentProducer = class {
2645
2868
  * Number of batches currently in flight.
2646
2869
  */
2647
2870
  get inFlightCount() {
2648
- return this.#queue.length();
2871
+ return this.#queue.length() + this.#queue.running();
2872
+ }
2873
+ /**
2874
+ * The greatest non-empty stream offset returned by a successful producer
2875
+ * append or close request.
2876
+ */
2877
+ get lastSuccessfulOffset() {
2878
+ return this.#lastSuccessfulOffset;
2649
2879
  }
2650
2880
  /**
2651
2881
  * Enqueue the current pending batch for processing.
@@ -2653,17 +2883,22 @@ var IdempotentProducer = class {
2653
2883
  #enqueuePendingBatch() {
2654
2884
  if (this.#pendingBatch.length === 0) return;
2655
2885
  const batch = this.#pendingBatch;
2656
- const seq = this.#nextSeq;
2657
2886
  this.#pendingBatch = [];
2658
2887
  this.#batchBytes = 0;
2888
+ if (this.#autoClaim && !this.#epochClaimed && this.inFlightCount > 0) {
2889
+ const deferred = this.#queue.drained().then(() => {
2890
+ this.#pushBatch(batch);
2891
+ }).finally(() => {
2892
+ this.#deferredEnqueues.delete(deferred);
2893
+ });
2894
+ this.#deferredEnqueues.add(deferred);
2895
+ deferred.catch(() => {});
2896
+ } else this.#pushBatch(batch);
2897
+ }
2898
+ #pushBatch(batch) {
2899
+ const seq = this.#nextSeq;
2659
2900
  this.#nextSeq++;
2660
- if (this.#autoClaim && !this.#epochClaimed && this.#queue.length() > 0) this.#queue.drained().then(() => {
2661
- this.#queue.push({
2662
- batch,
2663
- seq
2664
- }).catch(() => {});
2665
- });
2666
- else this.#queue.push({
2901
+ this.#queue.push({
2667
2902
  batch,
2668
2903
  seq
2669
2904
  }).catch(() => {});
@@ -2675,7 +2910,8 @@ var IdempotentProducer = class {
2675
2910
  const { batch, seq } = task;
2676
2911
  const epoch = this.#epoch;
2677
2912
  try {
2678
- await this.#doSendBatch(batch, seq, epoch);
2913
+ const result = await this.#doSendBatch(batch, seq, epoch);
2914
+ this.#recordSuccessfulOffset(result.offset);
2679
2915
  if (!this.#epochClaimed) this.#epochClaimed = true;
2680
2916
  this.#signalSeqComplete(epoch, seq, void 0);
2681
2917
  } catch (error) {
@@ -2684,6 +2920,9 @@ var IdempotentProducer = class {
2684
2920
  throw error;
2685
2921
  }
2686
2922
  }
2923
+ #recordSuccessfulOffset(offset) {
2924
+ if (offset && (!this.#lastSuccessfulOffset || offset > this.#lastSuccessfulOffset)) this.#lastSuccessfulOffset = offset;
2925
+ }
2687
2926
  /**
2688
2927
  * Signal that a sequence has completed (success or failure).
2689
2928
  */
@@ -2758,12 +2997,12 @@ var IdempotentProducer = class {
2758
2997
  batchedBody = concatenated;
2759
2998
  }
2760
2999
  const url = this.#stream.url;
2761
- const headers = {
3000
+ const headers = await this.#buildHeaders({
2762
3001
  "content-type": contentType,
2763
3002
  [PRODUCER_ID_HEADER]: this.#producerId,
2764
3003
  [PRODUCER_EPOCH_HEADER]: epoch.toString(),
2765
3004
  [PRODUCER_SEQ_HEADER]: seq.toString()
2766
- };
3005
+ });
2767
3006
  const response = await this.#fetchClient(url, {
2768
3007
  method: `POST`,
2769
3008
  headers,
@@ -2804,6 +3043,15 @@ var IdempotentProducer = class {
2804
3043
  if (response.status === 400) throw await DurableStreamError.fromResponse(response, url);
2805
3044
  throw await FetchError.fromResponse(response, url);
2806
3045
  }
3046
+ async #buildHeaders(protocolHeaders) {
3047
+ const streamHeaders = await this.#stream.resolveHeaders();
3048
+ const producerHeaders = await resolveHeaders(this.#headers);
3049
+ return {
3050
+ ...streamHeaders,
3051
+ ...producerHeaders,
3052
+ ...protocolHeaders
3053
+ };
3054
+ }
2807
3055
  /**
2808
3056
  * Clear pending batch and report error.
2809
3057
  */
@@ -2847,7 +3095,7 @@ function isPromiseLike(value) {
2847
3095
  * contentType: "application/json"
2848
3096
  * });
2849
3097
  *
2850
- * // Write data
3098
+ * // Single write
2851
3099
  * await stream.append(JSON.stringify({ message: "hello" }));
2852
3100
  *
2853
3101
  * // Read with the new API
@@ -2870,6 +3118,7 @@ var DurableStream = class DurableStream {
2870
3118
  contentType;
2871
3119
  #options;
2872
3120
  #fetchClient;
3121
+ #baseFetchClient;
2873
3122
  #onError;
2874
3123
  #batchingEnabled;
2875
3124
  #queue;
@@ -2890,7 +3139,9 @@ var DurableStream = class DurableStream {
2890
3139
  if (opts.contentType) this.contentType = opts.contentType;
2891
3140
  this.#batchingEnabled = opts.batching !== false;
2892
3141
  if (this.#batchingEnabled) this.#queue = import_queue.default.promise(this.#batchWorker.bind(this), 1);
2893
- const fetchWithBackoffClient = createFetchWithBackoff(opts.fetch ?? ((...args) => fetch(...args)), { ...opts.backoffOptions ?? BackoffDefaults });
3142
+ this.#baseFetchClient = opts.fetch ?? ((...args) => fetch(...args));
3143
+ const backOffOpts = { ...opts.backoffOptions ?? BackoffDefaults };
3144
+ const fetchWithBackoffClient = createFetchWithBackoff(this.#baseFetchClient, backOffOpts);
2894
3145
  this.#fetchClient = createFetchWithConsumedBody(fetchWithBackoffClient);
2895
3146
  }
2896
3147
  /**
@@ -2945,12 +3196,15 @@ var DurableStream = class DurableStream {
2945
3196
  */
2946
3197
  async head(opts) {
2947
3198
  const { requestHeaders, fetchUrl } = await this.#buildRequest();
2948
- const response = await this.#fetchClient(fetchUrl.toString(), {
3199
+ const response = await this.#baseFetchClient(fetchUrl.toString(), {
2949
3200
  method: `HEAD`,
2950
3201
  headers: requestHeaders,
2951
3202
  signal: opts?.signal ?? this.#options.signal
2952
3203
  });
2953
- if (!response.ok) await handleErrorResponse(response, this.url);
3204
+ if (!response.ok) {
3205
+ if (response.status === 404) return { exists: false };
3206
+ await handleErrorResponse(response, this.url);
3207
+ }
2954
3208
  const contentType = response.headers.get(`content-type`) ?? void 0;
2955
3209
  const offset = response.headers.get(STREAM_OFFSET_HEADER) ?? void 0;
2956
3210
  const etag = response.headers.get(`etag`) ?? void 0;
@@ -3048,9 +3302,14 @@ var DurableStream = class DurableStream {
3048
3302
  /**
3049
3303
  * Append a single payload to the stream.
3050
3304
  *
3051
- * When batching is enabled (default), multiple append() calls made while
3052
- * a POST is in-flight will be batched together into a single request.
3053
- * This significantly improves throughput for high-frequency writes.
3305
+ * Batching: when batching is enabled (default), append() calls that overlap
3306
+ * in time (e.g. fired without awaiting each one) are coalesced into a
3307
+ * single POST while a prior POST is in flight. If every call is awaited
3308
+ * before the next is issued, no batching happens — each call becomes its
3309
+ * own roundtrip. For tight loops driving an async iterable (e.g. LLM
3310
+ * token streams), prefer `appendStream()` / `writable()` which pipe the
3311
+ * source over a single POST, or fire `append()` calls without awaiting
3312
+ * each one and await the last promise (and `close()`) at the end.
3054
3313
  *
3055
3314
  * - `body` must be string or Uint8Array.
3056
3315
  * - For JSON streams, pass pre-serialized JSON strings.
@@ -3060,7 +3319,7 @@ var DurableStream = class DurableStream {
3060
3319
  *
3061
3320
  * @example
3062
3321
  * ```typescript
3063
- * // JSON stream - pass pre-serialized JSON
3322
+ * // JSON stream - pass pre-serialized JSON (single write)
3064
3323
  * await stream.append(JSON.stringify({ message: "hello" }));
3065
3324
  *
3066
3325
  * // Byte stream
@@ -3069,6 +3328,14 @@ var DurableStream = class DurableStream {
3069
3328
  *
3070
3329
  * // Promise value - awaited before buffering
3071
3330
  * await stream.append(fetchData());
3331
+ *
3332
+ * // High-frequency writes from an async iterable - fire-and-track-last
3333
+ * let last: Promise<void> = Promise.resolve();
3334
+ * for await (const chunk of source) {
3335
+ * last = stream.append(JSON.stringify(chunk));
3336
+ * }
3337
+ * await last;
3338
+ * await stream.close();
3072
3339
  * ```
3073
3340
  */
3074
3341
  async append(body, opts) {
@@ -3275,6 +3542,7 @@ var DurableStream = class DurableStream {
3275
3542
  let writeError = null;
3276
3543
  const producer = new IdempotentProducer(this, producerId, {
3277
3544
  autoClaim: true,
3545
+ headers: opts?.headers,
3278
3546
  lingerMs: opts?.lingerMs,
3279
3547
  maxBatchBytes: opts?.maxBatchBytes,
3280
3548
  onError: (error) => {
@@ -3357,6 +3625,14 @@ var DurableStream = class DurableStream {
3357
3625
  });
3358
3626
  }
3359
3627
  /**
3628
+ * Resolve the stream's configured headers.
3629
+ * Used by IdempotentProducer to merge auth headers into its requests.
3630
+ * @internal
3631
+ */
3632
+ async resolveHeaders() {
3633
+ return resolveHeaders(this.#options.headers);
3634
+ }
3635
+ /**
3360
3636
  * Build request headers and URL.
3361
3637
  */
3362
3638
  async #buildRequest() {