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

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.
Files changed (40) hide show
  1. package/dist/control.d.mts +29 -6
  2. package/dist/control.mjs +67 -454
  3. package/dist/control.mjs.map +1 -1
  4. package/dist/cron/index.mjs +6 -14
  5. package/dist/cron/index.mjs.map +1 -1
  6. package/dist/cron/scheduler-entrypoint.mjs +21 -70
  7. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  8. package/dist/cron/scheduler-service.mjs +6 -14
  9. package/dist/cron/scheduler-service.mjs.map +1 -1
  10. package/dist/index.d.mts +1 -40
  11. package/dist/index.mjs +2 -42
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/{prisma-next-DlU01kXJ-2sP5mQb6.mjs → prisma-next-qPB8_Az6.mjs} +3 -3
  14. package/dist/prisma-next-qPB8_Az6.mjs.map +1 -0
  15. package/dist/prisma-next.d.mts +1 -1
  16. package/dist/prisma-next.mjs +1 -1
  17. package/dist/{provisioned-edges-DIQAR4q4-Bn9op-JG.mjs → provisioned-edges-Bb7WiV49-DeSPi3Ax.mjs} +4 -4
  18. package/dist/provisioned-edges-Bb7WiV49-DeSPi3Ax.mjs.map +1 -0
  19. package/dist/storage/index.d.mts +10 -11
  20. package/dist/storage/index.mjs +6 -14
  21. package/dist/storage/index.mjs.map +1 -1
  22. package/dist/storage/storage-entrypoint.mjs +1 -11
  23. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  24. package/dist/storage/storage-service.mjs +6 -14
  25. package/dist/storage/storage-service.mjs.map +1 -1
  26. package/dist/storage/testing.mjs.map +1 -1
  27. package/dist/streams/index.d.mts +12 -13
  28. package/dist/streams/index.mjs +205 -489
  29. package/dist/streams/index.mjs.map +1 -1
  30. package/dist/streams/streams-entrypoint.mjs +1 -11
  31. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  32. package/dist/streams/streams-service.mjs +6 -14
  33. package/dist/streams/streams-service.mjs.map +1 -1
  34. package/dist/streams/testing.d.mts +1 -1
  35. package/dist/streams/testing.mjs.map +1 -1
  36. package/dist/testing.d.mts +1 -1
  37. package/dist/testing.mjs.map +1 -1
  38. package/package.json +15 -15
  39. package/dist/prisma-next-DlU01kXJ-2sP5mQb6.mjs.map +0 -1
  40. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { dependency, hydrateSecrets, hydrateSync, module, number, provisionNeed, resource, service, string } from "@prisma/composer";
2
2
  import { blindCast } from "@prisma/composer/casts";
3
- import { RPC_PEER_KEY } from "@prisma/composer/service-rpc";
3
+ import { RPC_PEER_KEY } from "@prisma/composer/rpc";
4
4
  import { type } from "arktype";
5
5
  import node from "@prisma/composer/node";
6
6
  blindCast(Symbol.for("prisma:prisma-cloud-secret-source"));
@@ -199,7 +199,7 @@ function standardValidateSync(schema, value) {
199
199
  return result.value;
200
200
  }
201
201
  //#endregion
202
- //#region ../../1-prisma-cloud/1-extensions/target/dist/provisioned-edges-DIQAR4q4.mjs
202
+ //#region ../../1-prisma-cloud/1-extensions/target/dist/provisioned-edges-Bb7WiV49.mjs
203
203
  /**
204
204
  * RPC's reserved provider param (ADR-0030/ADR-0031): the declaration —
205
205
  * name + schema + brand — for the accepted-keys set a provider stores, shared
@@ -218,7 +218,7 @@ function standardValidateSync(schema, value) {
218
218
  * The reserved provider param for RPC's accepted-keys set: the var name is
219
219
  * `RPC_ACCEPTED_KEYS`, derived through `configKey` at both ends
220
220
  * (`configKey(address, …)` at deploy, `configKey('', …)` at boot — the
221
- * address-free form is `@internal/service-rpc`'s `RPC_ACCEPTED_KEYS_ENV`). `brand` is
221
+ * address-free form is `@internal/rpc`'s `RPC_ACCEPTED_KEYS_ENV`). `brand` is
222
222
  * `RPC_PEER_KEY`, the same brand `perBindingToken()`'s need carries — control.ts
223
223
  * looks its `value(refs)` up by this field.
224
224
  */
@@ -254,16 +254,8 @@ configKey("", {
254
254
  });
255
255
  const RESERVED_PROVIDER_PARAMS = [RPC_ACCEPTED_KEYS_PARAM, STREAMS_API_KEY_PARAM];
256
256
  blindCast(Symbol.for("prisma:prisma-cloud-param-source"));
257
- Object.freeze({
258
- kind: "s3",
259
- __cmp: {
260
- url: "",
261
- bucket: "",
262
- accessKeyId: "",
263
- secretAccessKey: ""
264
- },
265
- satisfies: (required) => required.kind === "s3"
266
- });
257
+ //#endregion
258
+ //#region ../../1-prisma-cloud/1-extensions/target/dist/index.mjs
267
259
  const reservedParams = { port: number({ default: 3e3 }) };
268
260
  /**
269
261
  * A Prisma Compute service — declarations only (deps + params + build + the
@@ -416,7 +408,7 @@ function s3StoreService(def) {
416
408
  }));
417
409
  }
418
410
  //#endregion
419
- //#region ../../1-prisma-cloud/2-shared-modules/storage/dist/storage-service-DSaZsAC4.mjs
411
+ //#region ../../1-prisma-cloud/2-shared-modules/storage/dist/storage-service-BQdOiMsW.mjs
420
412
  const s3Contract = Object.freeze({
421
413
  kind: "s3",
422
414
  __cmp: {
@@ -473,7 +465,7 @@ function storageService(opts) {
473
465
  }
474
466
  storageService({ bucket: "storage" });
475
467
  //#endregion
476
- //#region ../../1-prisma-cloud/2-shared-modules/streams/dist/streams-service-Cg8rwrZa.mjs
468
+ //#region ../../1-prisma-cloud/2-shared-modules/streams/dist/streams-service-Mz48Yi0V.mjs
477
469
  var __create = Object.create;
478
470
  var __defProp = Object.defineProperty;
479
471
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -872,7 +864,7 @@ var FetchError = class FetchError extends Error {
872
864
  let text = void 0;
873
865
  let json = void 0;
874
866
  const contentType = response.headers.get(`content-type`);
875
- if (!response.bodyUsed && response.body !== null) if (contentType && contentType.includes(`application/json`)) try {
867
+ if (!response.bodyUsed) if (contentType && contentType.includes(`application/json`)) try {
876
868
  json = await response.json();
877
869
  } catch {
878
870
  text = await response.text();
@@ -921,7 +913,7 @@ var DurableStreamError = class DurableStreamError extends Error {
921
913
  const status = response.status;
922
914
  let details;
923
915
  const contentType = response.headers.get(`content-type`);
924
- if (!response.bodyUsed && response.body !== null) if (contentType && contentType.includes(`application/json`)) try {
916
+ if (!response.bodyUsed) if (contentType && contentType.includes(`application/json`)) try {
925
917
  details = await response.json();
926
918
  } catch {
927
919
  details = await response.text();
@@ -1290,204 +1282,6 @@ async function* parseSSEStream(stream$1, signal) {
1290
1282
  }
1291
1283
  }
1292
1284
  /**
1293
- * Abstract base class for stream response state.
1294
- * All state transitions return new immutable state objects.
1295
- */
1296
- var StreamResponseState = class {
1297
- shouldContinueLive(stopAfterUpToDate, liveMode) {
1298
- if (stopAfterUpToDate && this.upToDate) return false;
1299
- if (liveMode === false) return false;
1300
- if (this.streamClosed) return false;
1301
- return true;
1302
- }
1303
- };
1304
- /**
1305
- * State for long-poll mode. shouldUseSse() returns false.
1306
- */
1307
- var LongPollState = class LongPollState extends StreamResponseState {
1308
- offset;
1309
- cursor;
1310
- upToDate;
1311
- streamClosed;
1312
- constructor(fields) {
1313
- super();
1314
- this.offset = fields.offset;
1315
- this.cursor = fields.cursor;
1316
- this.upToDate = fields.upToDate;
1317
- this.streamClosed = fields.streamClosed;
1318
- }
1319
- shouldUseSse() {
1320
- return false;
1321
- }
1322
- withResponseMetadata(update) {
1323
- return new LongPollState({
1324
- offset: update.offset ?? this.offset,
1325
- cursor: update.cursor ?? this.cursor,
1326
- upToDate: update.upToDate,
1327
- streamClosed: this.streamClosed || update.streamClosed
1328
- });
1329
- }
1330
- withSSEControl(event) {
1331
- const streamClosed = this.streamClosed || (event.streamClosed ?? false);
1332
- return new LongPollState({
1333
- offset: event.streamNextOffset,
1334
- cursor: event.streamCursor || this.cursor,
1335
- upToDate: event.streamClosed ?? false ? true : event.upToDate ?? this.upToDate,
1336
- streamClosed
1337
- });
1338
- }
1339
- pause() {
1340
- return new PausedState(this);
1341
- }
1342
- };
1343
- /**
1344
- * State for SSE mode. shouldUseSse() returns true.
1345
- * Tracks SSE connection resilience (short connection detection).
1346
- */
1347
- var SSEState = class SSEState extends StreamResponseState {
1348
- offset;
1349
- cursor;
1350
- upToDate;
1351
- streamClosed;
1352
- consecutiveShortConnections;
1353
- connectionStartTime;
1354
- constructor(fields) {
1355
- super();
1356
- this.offset = fields.offset;
1357
- this.cursor = fields.cursor;
1358
- this.upToDate = fields.upToDate;
1359
- this.streamClosed = fields.streamClosed;
1360
- this.consecutiveShortConnections = fields.consecutiveShortConnections ?? 0;
1361
- this.connectionStartTime = fields.connectionStartTime;
1362
- }
1363
- shouldUseSse() {
1364
- return true;
1365
- }
1366
- withResponseMetadata(update) {
1367
- return new SSEState({
1368
- offset: update.offset ?? this.offset,
1369
- cursor: update.cursor ?? this.cursor,
1370
- upToDate: update.upToDate,
1371
- streamClosed: this.streamClosed || update.streamClosed,
1372
- consecutiveShortConnections: this.consecutiveShortConnections,
1373
- connectionStartTime: this.connectionStartTime
1374
- });
1375
- }
1376
- withSSEControl(event) {
1377
- const streamClosed = this.streamClosed || (event.streamClosed ?? false);
1378
- return new SSEState({
1379
- offset: event.streamNextOffset,
1380
- cursor: event.streamCursor || this.cursor,
1381
- upToDate: event.streamClosed ?? false ? true : event.upToDate ?? this.upToDate,
1382
- streamClosed,
1383
- consecutiveShortConnections: this.consecutiveShortConnections,
1384
- connectionStartTime: this.connectionStartTime
1385
- });
1386
- }
1387
- startConnection(now) {
1388
- return new SSEState({
1389
- offset: this.offset,
1390
- cursor: this.cursor,
1391
- upToDate: this.upToDate,
1392
- streamClosed: this.streamClosed,
1393
- consecutiveShortConnections: this.consecutiveShortConnections,
1394
- connectionStartTime: now
1395
- });
1396
- }
1397
- handleConnectionEnd(now, wasAborted, config) {
1398
- if (this.connectionStartTime === void 0) return {
1399
- action: `healthy`,
1400
- state: this
1401
- };
1402
- const duration = now - this.connectionStartTime;
1403
- if (duration < config.minConnectionDuration && !wasAborted) {
1404
- const newCount = this.consecutiveShortConnections + 1;
1405
- if (newCount >= config.maxShortConnections) return {
1406
- action: `fallback`,
1407
- state: new LongPollState({
1408
- offset: this.offset,
1409
- cursor: this.cursor,
1410
- upToDate: this.upToDate,
1411
- streamClosed: this.streamClosed
1412
- })
1413
- };
1414
- return {
1415
- action: `reconnect`,
1416
- state: new SSEState({
1417
- offset: this.offset,
1418
- cursor: this.cursor,
1419
- upToDate: this.upToDate,
1420
- streamClosed: this.streamClosed,
1421
- consecutiveShortConnections: newCount,
1422
- connectionStartTime: this.connectionStartTime
1423
- }),
1424
- backoffAttempt: newCount
1425
- };
1426
- }
1427
- if (duration >= config.minConnectionDuration) return {
1428
- action: `healthy`,
1429
- state: new SSEState({
1430
- offset: this.offset,
1431
- cursor: this.cursor,
1432
- upToDate: this.upToDate,
1433
- streamClosed: this.streamClosed,
1434
- consecutiveShortConnections: 0,
1435
- connectionStartTime: this.connectionStartTime
1436
- })
1437
- };
1438
- return {
1439
- action: `healthy`,
1440
- state: this
1441
- };
1442
- }
1443
- pause() {
1444
- return new PausedState(this);
1445
- }
1446
- };
1447
- /**
1448
- * Paused state wrapper. Delegates all sync field access to the inner state.
1449
- * resume() returns the wrapped state unchanged (identity preserved).
1450
- */
1451
- var PausedState = class PausedState extends StreamResponseState {
1452
- #inner;
1453
- constructor(inner) {
1454
- super();
1455
- this.#inner = inner;
1456
- }
1457
- get offset() {
1458
- return this.#inner.offset;
1459
- }
1460
- get cursor() {
1461
- return this.#inner.cursor;
1462
- }
1463
- get upToDate() {
1464
- return this.#inner.upToDate;
1465
- }
1466
- get streamClosed() {
1467
- return this.#inner.streamClosed;
1468
- }
1469
- shouldUseSse() {
1470
- return this.#inner.shouldUseSse();
1471
- }
1472
- withResponseMetadata(update) {
1473
- const newInner = this.#inner.withResponseMetadata(update);
1474
- return new PausedState(newInner);
1475
- }
1476
- withSSEControl(event) {
1477
- const newInner = this.#inner.withSSEControl(event);
1478
- return new PausedState(newInner);
1479
- }
1480
- pause() {
1481
- return this;
1482
- }
1483
- resume() {
1484
- return {
1485
- state: this.#inner,
1486
- justResumed: true
1487
- };
1488
- }
1489
- };
1490
- /**
1491
1285
  * Constant used as abort reason when pausing the stream due to visibility change.
1492
1286
  */
1493
1287
  const PAUSE_STREAM = `PAUSE_STREAM`;
@@ -1504,7 +1298,10 @@ var StreamResponseImpl = class {
1504
1298
  #statusText;
1505
1299
  #ok;
1506
1300
  #isLoading;
1507
- #syncState;
1301
+ #offset;
1302
+ #cursor;
1303
+ #upToDate;
1304
+ #streamClosed;
1508
1305
  #isJsonMode;
1509
1306
  #abortController;
1510
1307
  #fetchNext;
@@ -1519,7 +1316,11 @@ var StreamResponseImpl = class {
1519
1316
  #unsubscribeFromVisibilityChanges;
1520
1317
  #pausePromise;
1521
1318
  #pauseResolve;
1319
+ #justResumedFromPause = false;
1522
1320
  #sseResilience;
1321
+ #lastSSEConnectionStartTime;
1322
+ #consecutiveShortSSEConnections = 0;
1323
+ #sseFallbackToLongPoll = false;
1523
1324
  #encoding;
1524
1325
  #responseStream;
1525
1326
  constructor(config) {
@@ -1527,13 +1328,10 @@ var StreamResponseImpl = class {
1527
1328
  this.contentType = config.contentType;
1528
1329
  this.live = config.live;
1529
1330
  this.startOffset = config.startOffset;
1530
- const syncFields = {
1531
- offset: config.initialOffset,
1532
- cursor: config.initialCursor,
1533
- upToDate: config.initialUpToDate,
1534
- streamClosed: config.initialStreamClosed
1535
- };
1536
- this.#syncState = config.startSSE ? new SSEState(syncFields) : new LongPollState(syncFields);
1331
+ this.#offset = config.initialOffset;
1332
+ this.#cursor = config.initialCursor;
1333
+ this.#upToDate = config.initialUpToDate;
1334
+ this.#streamClosed = config.initialStreamClosed;
1537
1335
  this.#headers = config.firstResponse.headers;
1538
1336
  this.#status = config.firstResponse.status;
1539
1337
  this.#statusText = config.firstResponse.statusText;
@@ -1590,7 +1388,6 @@ var StreamResponseImpl = class {
1590
1388
  #pause() {
1591
1389
  if (this.#state === `active`) {
1592
1390
  this.#state = `pause-requested`;
1593
- this.#syncState = this.#syncState.pause();
1594
1391
  this.#pausePromise = new Promise((resolve) => {
1595
1392
  this.#pauseResolve = resolve;
1596
1393
  });
@@ -1604,8 +1401,8 @@ var StreamResponseImpl = class {
1604
1401
  #resume() {
1605
1402
  if (this.#state === `paused` || this.#state === `pause-requested`) {
1606
1403
  if (this.#abortController.signal.aborted) return;
1607
- if (this.#syncState instanceof PausedState) this.#syncState = this.#syncState.resume().state;
1608
1404
  this.#state = `active`;
1405
+ this.#justResumedFromPause = true;
1609
1406
  this.#pauseResolve?.();
1610
1407
  this.#pausePromise = void 0;
1611
1408
  this.#pauseResolve = void 0;
@@ -1627,16 +1424,16 @@ var StreamResponseImpl = class {
1627
1424
  return this.#isLoading;
1628
1425
  }
1629
1426
  get offset() {
1630
- return this.#syncState.offset;
1427
+ return this.#offset;
1631
1428
  }
1632
1429
  get cursor() {
1633
- return this.#syncState.cursor;
1430
+ return this.#cursor;
1634
1431
  }
1635
1432
  get upToDate() {
1636
- return this.#syncState.upToDate;
1433
+ return this.#upToDate;
1637
1434
  }
1638
1435
  get streamClosed() {
1639
- return this.#syncState.streamClosed;
1436
+ return this.#streamClosed;
1640
1437
  }
1641
1438
  #ensureJsonMode() {
1642
1439
  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`);
@@ -1662,68 +1459,160 @@ var StreamResponseImpl = class {
1662
1459
  * and whether we've received upToDate or streamClosed.
1663
1460
  */
1664
1461
  #shouldContinueLive() {
1665
- return this.#syncState.shouldContinueLive(this.#stopAfterUpToDate, this.live);
1462
+ if (this.#stopAfterUpToDate && this.upToDate) return false;
1463
+ if (this.live === false) return false;
1464
+ if (this.#streamClosed) return false;
1465
+ return true;
1666
1466
  }
1667
1467
  /**
1668
1468
  * Update state from response headers.
1669
1469
  */
1670
1470
  #updateStateFromResponse(response) {
1671
- this.#syncState = this.#syncState.withResponseMetadata({
1672
- offset: response.headers.get(STREAM_OFFSET_HEADER) || void 0,
1673
- cursor: response.headers.get(STREAM_CURSOR_HEADER) || void 0,
1674
- upToDate: response.headers.has(STREAM_UP_TO_DATE_HEADER),
1675
- streamClosed: response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`
1676
- });
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;
1677
1477
  this.#headers = response.headers;
1678
1478
  this.#status = response.status;
1679
1479
  this.#statusText = response.statusText;
1680
1480
  this.#ok = response.ok;
1681
1481
  }
1682
1482
  /**
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
+ /**
1683
1565
  * Update instance state from an SSE control event.
1684
1566
  */
1685
1567
  #updateStateFromSSEControl(controlEvent) {
1686
- this.#syncState = this.#syncState.withSSEControl(controlEvent);
1687
- }
1688
- #updateEncodingFromSSEResponse(response) {
1689
- this.#encoding = response.headers.get(STREAM_SSE_DATA_ENCODING_HEADER) === `base64` ? `base64` : void 0;
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
+ }
1690
1575
  }
1691
1576
  /**
1692
1577
  * Mark the start of an SSE connection for duration tracking.
1693
- * If the state is not SSEState (e.g., auto-detected SSE from content-type),
1694
- * transitions to SSEState first.
1695
1578
  */
1696
1579
  #markSSEConnectionStart() {
1697
- if (!(this.#syncState instanceof SSEState)) this.#syncState = new SSEState({
1698
- offset: this.#syncState.offset,
1699
- cursor: this.#syncState.cursor,
1700
- upToDate: this.#syncState.upToDate,
1701
- streamClosed: this.#syncState.streamClosed
1702
- });
1703
- this.#syncState = this.#syncState.startConnection(Date.now());
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;
1704
1604
  }
1705
1605
  /**
1706
1606
  * Try to reconnect SSE and return the new iterator, or null if reconnection
1707
1607
  * is not possible or fails.
1708
1608
  */
1709
1609
  async #trySSEReconnect() {
1710
- if (!this.#syncState.shouldUseSse()) return null;
1610
+ if (this.#sseFallbackToLongPoll) return null;
1711
1611
  if (!this.#shouldContinueLive() || !this.#startSSE) return null;
1712
- const result = this.#syncState.handleConnectionEnd(Date.now(), this.#abortController.signal.aborted, this.#sseResilience);
1713
- this.#syncState = result.state;
1714
- if (result.action === `fallback`) {
1715
- 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.");
1716
- return null;
1717
- }
1718
- if (result.action === `reconnect`) {
1719
- const maxDelay = Math.min(this.#sseResilience.backoffMaxDelay, this.#sseResilience.backoffBaseDelay * Math.pow(2, result.backoffAttempt));
1720
- const delayMs = Math.floor(Math.random() * maxDelay);
1721
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1722
- }
1612
+ if (await this.#handleSSEConnectionEnd() === null) return null;
1723
1613
  this.#markSSEConnectionStart();
1724
1614
  this.#requestAbortController = new AbortController();
1725
1615
  const newSSEResponse = await this.#startSSE(this.offset, this.cursor, this.#requestAbortController.signal);
1726
- this.#updateEncodingFromSSEResponse(newSSEResponse);
1727
1616
  if (newSSEResponse.body) return parseSSEStream(newSSEResponse.body, this.#requestAbortController.signal);
1728
1617
  return null;
1729
1618
  }
@@ -1756,7 +1645,7 @@ var StreamResponseImpl = class {
1756
1645
  this.#updateStateFromSSEControl(event);
1757
1646
  if (event.upToDate) return {
1758
1647
  type: `response`,
1759
- response: createSSESyntheticResponse(``, event.streamNextOffset, event.streamCursor, true, event.streamClosed ?? false, this.contentType, this.#encoding)
1648
+ response: this.#createSSESyntheticResponse(``, event.streamNextOffset, event.streamCursor, true, event.streamClosed ?? false)
1760
1649
  };
1761
1650
  return { type: `continue` };
1762
1651
  }
@@ -1773,7 +1662,7 @@ var StreamResponseImpl = class {
1773
1662
  while (true) {
1774
1663
  const { done: controlDone, value: controlEvent } = await sseEventIterator.next();
1775
1664
  if (controlDone) {
1776
- const response = createSSESyntheticResponseFromParts(bufferedDataParts, this.offset, this.cursor, this.upToDate, this.streamClosed, this.contentType, this.#encoding, this.#isJsonMode);
1665
+ const response = this.#createSSESyntheticResponseFromParts(bufferedDataParts, this.offset, this.cursor, this.upToDate, this.streamClosed);
1777
1666
  try {
1778
1667
  return {
1779
1668
  type: `response`,
@@ -1791,7 +1680,7 @@ var StreamResponseImpl = class {
1791
1680
  this.#updateStateFromSSEControl(controlEvent);
1792
1681
  return {
1793
1682
  type: `response`,
1794
- response: createSSESyntheticResponseFromParts(bufferedDataParts, controlEvent.streamNextOffset, controlEvent.streamCursor, controlEvent.upToDate ?? false, controlEvent.streamClosed ?? false, this.contentType, this.#encoding, this.#isJsonMode)
1683
+ response: this.#createSSESyntheticResponseFromParts(bufferedDataParts, controlEvent.streamNextOffset, controlEvent.streamCursor, controlEvent.upToDate ?? false, controlEvent.streamClosed ?? false)
1795
1684
  };
1796
1685
  }
1797
1686
  bufferedDataParts.push(controlEvent.data);
@@ -1814,7 +1703,6 @@ var StreamResponseImpl = class {
1814
1703
  firstResponseYielded = true;
1815
1704
  if ((firstResponse.headers.get(`content-type`)?.includes(`text/event-stream`) ?? false) && firstResponse.body) {
1816
1705
  this.#markSSEConnectionStart();
1817
- this.#updateEncodingFromSSEResponse(firstResponse);
1818
1706
  this.#requestAbortController = new AbortController();
1819
1707
  sseEventIterator = parseSSEStream(firstResponse.body, this.#requestAbortController.signal);
1820
1708
  } else {
@@ -1827,22 +1715,6 @@ var StreamResponseImpl = class {
1827
1715
  return;
1828
1716
  }
1829
1717
  }
1830
- if (!sseEventIterator && this.upToDate && this.#startSSE && this.#shouldContinueLive()) {
1831
- if (this.#state === `pause-requested` || this.#state === `paused`) {
1832
- this.#state = `paused`;
1833
- if (this.#pausePromise) await this.#pausePromise;
1834
- if (this.#abortController.signal.aborted) {
1835
- this.#markClosed();
1836
- controller.close();
1837
- return;
1838
- }
1839
- }
1840
- this.#markSSEConnectionStart();
1841
- this.#requestAbortController = new AbortController();
1842
- const sseResponse = await this.#startSSE(this.offset, this.cursor, this.#requestAbortController.signal);
1843
- this.#updateEncodingFromSSEResponse(sseResponse);
1844
- if (sseResponse.body) sseEventIterator = parseSSEStream(sseResponse.body, this.#requestAbortController.signal);
1845
- }
1846
1718
  if (sseEventIterator) {
1847
1719
  if (this.#state === `pause-requested` || this.#state === `paused`) {
1848
1720
  this.#state = `paused`;
@@ -1882,7 +1754,6 @@ var StreamResponseImpl = class {
1882
1754
  }
1883
1755
  }
1884
1756
  if (this.#shouldContinueLive()) {
1885
- let resumingFromPause = false;
1886
1757
  if (this.#state === `pause-requested` || this.#state === `paused`) {
1887
1758
  this.#state = `paused`;
1888
1759
  if (this.#pausePromise) await this.#pausePromise;
@@ -1891,15 +1762,16 @@ var StreamResponseImpl = class {
1891
1762
  controller.close();
1892
1763
  return;
1893
1764
  }
1894
- resumingFromPause = true;
1895
1765
  }
1896
1766
  if (this.#abortController.signal.aborted) {
1897
1767
  this.#markClosed();
1898
1768
  controller.close();
1899
1769
  return;
1900
1770
  }
1771
+ const resumingFromPause = this.#justResumedFromPause;
1772
+ this.#justResumedFromPause = false;
1901
1773
  this.#requestAbortController = new AbortController();
1902
- const response = await this.#fetchNext(this.offset, this.cursor, this.#requestAbortController.signal, this.upToDate, resumingFromPause);
1774
+ const response = await this.#fetchNext(this.offset, this.cursor, this.#requestAbortController.signal, resumingFromPause);
1903
1775
  this.#updateStateFromResponse(response);
1904
1776
  controller.enqueue(response);
1905
1777
  return;
@@ -2062,25 +1934,22 @@ var StreamResponseImpl = class {
2062
1934
  controller.enqueue(pendingItems.shift());
2063
1935
  return;
2064
1936
  }
2065
- let result = await reader.read();
2066
- while (!result.done) {
2067
- const content = (await result.value.text()).trim() || `[]`;
2068
- let parsed;
2069
- try {
2070
- parsed = JSON.parse(content);
2071
- } catch (err) {
2072
- const preview = content.length > 100 ? content.slice(0, 100) + `...` : content;
2073
- throw new DurableStreamError(`Failed to parse JSON response: ${err instanceof Error ? err.message : String(err)}. Data: ${preview}`, `PARSE_ERROR`);
2074
- }
2075
- pendingItems = Array.isArray(parsed) ? parsed : [parsed];
2076
- if (pendingItems.length > 0) {
2077
- controller.enqueue(pendingItems.shift());
2078
- return;
2079
- }
2080
- result = await reader.read();
1937
+ const { done, value: response } = await reader.read();
1938
+ if (done) {
1939
+ this.#markClosed();
1940
+ controller.close();
1941
+ return;
2081
1942
  }
2082
- this.#markClosed();
2083
- controller.close();
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`);
1950
+ }
1951
+ pendingItems = Array.isArray(parsed) ? parsed : [parsed];
1952
+ if (pendingItems.length > 0) controller.enqueue(pendingItems.shift());
2084
1953
  },
2085
1954
  cancel: () => {
2086
1955
  reader.releaseLock();
@@ -2112,7 +1981,7 @@ var StreamResponseImpl = class {
2112
1981
  while (!result.done) {
2113
1982
  if (abortController.signal.aborted) break;
2114
1983
  const response = result.value;
2115
- const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
1984
+ const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2116
1985
  const content = (await response.text()).trim() || `[]`;
2117
1986
  let parsed;
2118
1987
  try {
@@ -2156,7 +2025,7 @@ var StreamResponseImpl = class {
2156
2025
  while (!result.done) {
2157
2026
  if (abortController.signal.aborted) break;
2158
2027
  const response = result.value;
2159
- const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
2028
+ const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2160
2029
  const buffer = await response.arrayBuffer();
2161
2030
  await subscriber({
2162
2031
  data: new Uint8Array(buffer),
@@ -2193,7 +2062,7 @@ var StreamResponseImpl = class {
2193
2062
  while (!result.done) {
2194
2063
  if (abortController.signal.aborted) break;
2195
2064
  const response = result.value;
2196
- const { offset, cursor, upToDate, streamClosed } = getMetadataFromResponse(response, this.offset, this.cursor, this.streamClosed);
2065
+ const { offset, cursor, upToDate, streamClosed } = this.#getMetadataFromResponse(response);
2197
2066
  await subscriber({
2198
2067
  text: await response.text(),
2199
2068
  offset,
@@ -2229,97 +2098,6 @@ var StreamResponseImpl = class {
2229
2098
  }
2230
2099
  };
2231
2100
  /**
2232
- * Extract stream metadata from Response headers.
2233
- * Falls back to the provided defaults when headers are absent.
2234
- */
2235
- function getMetadataFromResponse(response, fallbackOffset, fallbackCursor, fallbackStreamClosed) {
2236
- const offset = response.headers.get(STREAM_OFFSET_HEADER);
2237
- const cursor = response.headers.get(STREAM_CURSOR_HEADER);
2238
- const upToDate = response.headers.has(STREAM_UP_TO_DATE_HEADER);
2239
- const streamClosed = response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
2240
- return {
2241
- offset: offset ?? fallbackOffset,
2242
- cursor: cursor ?? fallbackCursor,
2243
- upToDate,
2244
- streamClosed: streamClosed || fallbackStreamClosed
2245
- };
2246
- }
2247
- /**
2248
- * Decode base64 string to Uint8Array.
2249
- * Per protocol: concatenate data lines, remove \n and \r, then decode.
2250
- */
2251
- function decodeBase64(base64Str) {
2252
- const cleaned = base64Str.replace(/[\n\r]/g, ``);
2253
- if (cleaned.length === 0) return /* @__PURE__ */ new Uint8Array(0);
2254
- if (cleaned.length % 4 !== 0) throw new DurableStreamError(`Invalid base64 data: length ${cleaned.length} is not a multiple of 4`, `PARSE_ERROR`);
2255
- try {
2256
- if (typeof Buffer !== `undefined`) return new Uint8Array(Buffer.from(cleaned, `base64`));
2257
- else {
2258
- const binaryStr = atob(cleaned);
2259
- const bytes = new Uint8Array(binaryStr.length);
2260
- for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
2261
- return bytes;
2262
- }
2263
- } catch (err) {
2264
- throw new DurableStreamError(`Failed to decode base64 data: ${err instanceof Error ? err.message : String(err)}`, `PARSE_ERROR`);
2265
- }
2266
- }
2267
- /**
2268
- * Create a synthetic Response from SSE data with proper headers.
2269
- * Includes offset/cursor/upToDate/streamClosed in headers so subscribers can read them.
2270
- */
2271
- function createSSESyntheticResponse(data, offset, cursor, upToDate, streamClosed, contentType, encoding) {
2272
- return createSSESyntheticResponseFromParts([data], offset, cursor, upToDate, streamClosed, contentType, encoding);
2273
- }
2274
- /**
2275
- * Create a synthetic Response from multiple SSE data parts.
2276
- * For base64 mode, each part is independently encoded, so we decode each
2277
- * separately and concatenate the binary results.
2278
- * For text mode, parts are simply concatenated as strings.
2279
- */
2280
- function createSSESyntheticResponseFromParts(dataParts, offset, cursor, upToDate, streamClosed, contentType, encoding, isJsonMode) {
2281
- const headers = {
2282
- "content-type": contentType ?? `application/json`,
2283
- [STREAM_OFFSET_HEADER]: String(offset)
2284
- };
2285
- if (cursor) headers[STREAM_CURSOR_HEADER] = cursor;
2286
- if (upToDate) headers[STREAM_UP_TO_DATE_HEADER] = `true`;
2287
- if (streamClosed) headers[STREAM_CLOSED_HEADER] = `true`;
2288
- let body;
2289
- if (encoding === `base64`) {
2290
- const decodedParts = dataParts.filter((part) => part.length > 0).map((part) => decodeBase64(part));
2291
- if (decodedParts.length === 0) body = /* @__PURE__ */ new ArrayBuffer(0);
2292
- else if (decodedParts.length === 1) {
2293
- const decoded = decodedParts[0];
2294
- body = decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength);
2295
- } else {
2296
- const totalLength = decodedParts.reduce((sum, part) => sum + part.length, 0);
2297
- const combined = new Uint8Array(totalLength);
2298
- let offset$1 = 0;
2299
- for (const part of decodedParts) {
2300
- combined.set(part, offset$1);
2301
- offset$1 += part.length;
2302
- }
2303
- body = combined.buffer;
2304
- }
2305
- } else if (isJsonMode) {
2306
- const mergedParts = [];
2307
- for (const part of dataParts) {
2308
- const trimmed = part.trim();
2309
- if (trimmed.length === 0) continue;
2310
- if (trimmed.startsWith(`[`) && trimmed.endsWith(`]`)) {
2311
- const inner = trimmed.slice(1, -1).trim();
2312
- if (inner.length > 0) mergedParts.push(inner);
2313
- } else mergedParts.push(trimmed);
2314
- }
2315
- body = `[${mergedParts.join(`,`)}]`;
2316
- } else body = dataParts.join(``);
2317
- return new Response(body, {
2318
- status: 200,
2319
- headers
2320
- });
2321
- }
2322
- /**
2323
2101
  * Resolve headers from HeadersRecord (supports async functions).
2324
2102
  * Unified implementation used by both stream() and DurableStream.
2325
2103
  */
@@ -2486,6 +2264,7 @@ async function streamInternal(options) {
2486
2264
  const startOffset = options.offset ?? `-1`;
2487
2265
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, startOffset);
2488
2266
  const live = options.live ?? true;
2267
+ if (live === `long-poll` || live === `sse`) fetchUrl.searchParams.set(LIVE_QUERY_PARAM, live);
2489
2268
  const params = await resolveParams(options.params);
2490
2269
  for (const [key, value] of Object.entries(params)) fetchUrl.searchParams.set(key, value);
2491
2270
  const headers = await resolveHeaders(options.headers);
@@ -2510,11 +2289,12 @@ async function streamInternal(options) {
2510
2289
  const initialStreamClosed = firstResponse.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
2511
2290
  const isJsonMode = options.json === true || (contentType?.includes(`application/json`) ?? false);
2512
2291
  const encoding = firstResponse.headers.get(STREAM_SSE_DATA_ENCODING_HEADER) === `base64` ? `base64` : void 0;
2513
- const fetchNext = async (offset, cursor, signal, upToDate, resumingFromPause) => {
2292
+ const fetchNext = async (offset, cursor, signal, resumingFromPause) => {
2514
2293
  const nextUrl = new URL(url);
2515
2294
  nextUrl.searchParams.set(OFFSET_QUERY_PARAM, offset);
2516
- if (upToDate && !resumingFromPause) {
2517
- if (live === true || live === `long-poll`) nextUrl.searchParams.set(LIVE_QUERY_PARAM, `long-poll`);
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`);
2518
2298
  }
2519
2299
  if (cursor) nextUrl.searchParams.set(`cursor`, cursor);
2520
2300
  const nextParams = await resolveParams(options.params);
@@ -2638,7 +2418,6 @@ var IdempotentProducer = class {
2638
2418
  #maxBatchBytes;
2639
2419
  #lingerMs;
2640
2420
  #fetchClient;
2641
- #headers;
2642
2421
  #signal;
2643
2422
  #onError;
2644
2423
  #pendingBatch = [];
@@ -2646,11 +2425,9 @@ var IdempotentProducer = class {
2646
2425
  #lingerTimeout = null;
2647
2426
  #queue;
2648
2427
  #maxInFlight;
2649
- #deferredEnqueues = /* @__PURE__ */ new Set();
2650
2428
  #closed = false;
2651
2429
  #closeResult = null;
2652
2430
  #pendingFinalMessage;
2653
- #lastSuccessfulOffset;
2654
2431
  #epochClaimed;
2655
2432
  #seqState = /* @__PURE__ */ new Map();
2656
2433
  /**
@@ -2676,7 +2453,6 @@ var IdempotentProducer = class {
2676
2453
  this.#maxBatchBytes = maxBatchBytes;
2677
2454
  this.#lingerMs = lingerMs;
2678
2455
  this.#signal = opts?.signal;
2679
- this.#headers = opts?.headers;
2680
2456
  this.#onError = opts?.onError;
2681
2457
  this.#fetchClient = opts?.fetch ?? ((...args) => fetch(...args));
2682
2458
  this.#maxInFlight = maxInFlight;
@@ -2738,10 +2514,7 @@ var IdempotentProducer = class {
2738
2514
  this.#lingerTimeout = null;
2739
2515
  }
2740
2516
  if (this.#pendingBatch.length > 0) this.#enqueuePendingBatch();
2741
- do {
2742
- await this.#queue.drained();
2743
- await Promise.all(this.#deferredEnqueues);
2744
- } while (this.#deferredEnqueues.size > 0 || this.inFlightCount > 0);
2517
+ await this.#queue.drained();
2745
2518
  }
2746
2519
  /**
2747
2520
  * Stop the producer without closing the underlying stream.
@@ -2805,13 +2578,13 @@ var IdempotentProducer = class {
2805
2578
  else body = bodyBytes;
2806
2579
  }
2807
2580
  const seqForThisRequest = this.#nextSeq;
2808
- const headers = await this.#buildHeaders({
2581
+ const headers = {
2809
2582
  "content-type": contentType,
2810
2583
  [PRODUCER_ID_HEADER]: this.#producerId,
2811
2584
  [PRODUCER_EPOCH_HEADER]: this.#epoch.toString(),
2812
2585
  [PRODUCER_SEQ_HEADER]: seqForThisRequest.toString(),
2813
2586
  [STREAM_CLOSED_HEADER]: `true`
2814
- });
2587
+ };
2815
2588
  const response = await this.#fetchClient(this.#stream.url, {
2816
2589
  method: `POST`,
2817
2590
  headers,
@@ -2820,15 +2593,11 @@ var IdempotentProducer = class {
2820
2593
  });
2821
2594
  if (response.status === 204) {
2822
2595
  this.#nextSeq = seqForThisRequest + 1;
2823
- const finalOffset = response.headers.get(STREAM_OFFSET_HEADER) ?? ``;
2824
- this.#recordSuccessfulOffset(finalOffset);
2825
- return { finalOffset };
2596
+ return { finalOffset: response.headers.get(STREAM_OFFSET_HEADER) ?? `` };
2826
2597
  }
2827
2598
  if (response.status === 200) {
2828
2599
  this.#nextSeq = seqForThisRequest + 1;
2829
- const finalOffset = response.headers.get(STREAM_OFFSET_HEADER) ?? ``;
2830
- this.#recordSuccessfulOffset(finalOffset);
2831
- return { finalOffset };
2600
+ return { finalOffset: response.headers.get(STREAM_OFFSET_HEADER) ?? `` };
2832
2601
  }
2833
2602
  if (response.status === 403) {
2834
2603
  const currentEpochStr = response.headers.get(PRODUCER_EPOCH_HEADER);
@@ -2876,14 +2645,7 @@ var IdempotentProducer = class {
2876
2645
  * Number of batches currently in flight.
2877
2646
  */
2878
2647
  get inFlightCount() {
2879
- return this.#queue.length() + this.#queue.running();
2880
- }
2881
- /**
2882
- * The greatest non-empty stream offset returned by a successful producer
2883
- * append or close request.
2884
- */
2885
- get lastSuccessfulOffset() {
2886
- return this.#lastSuccessfulOffset;
2648
+ return this.#queue.length();
2887
2649
  }
2888
2650
  /**
2889
2651
  * Enqueue the current pending batch for processing.
@@ -2891,22 +2653,17 @@ var IdempotentProducer = class {
2891
2653
  #enqueuePendingBatch() {
2892
2654
  if (this.#pendingBatch.length === 0) return;
2893
2655
  const batch = this.#pendingBatch;
2656
+ const seq = this.#nextSeq;
2894
2657
  this.#pendingBatch = [];
2895
2658
  this.#batchBytes = 0;
2896
- if (this.#autoClaim && !this.#epochClaimed && this.inFlightCount > 0) {
2897
- const deferred = this.#queue.drained().then(() => {
2898
- this.#pushBatch(batch);
2899
- }).finally(() => {
2900
- this.#deferredEnqueues.delete(deferred);
2901
- });
2902
- this.#deferredEnqueues.add(deferred);
2903
- deferred.catch(() => {});
2904
- } else this.#pushBatch(batch);
2905
- }
2906
- #pushBatch(batch) {
2907
- const seq = this.#nextSeq;
2908
2659
  this.#nextSeq++;
2909
- this.#queue.push({
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({
2910
2667
  batch,
2911
2668
  seq
2912
2669
  }).catch(() => {});
@@ -2918,8 +2675,7 @@ var IdempotentProducer = class {
2918
2675
  const { batch, seq } = task;
2919
2676
  const epoch = this.#epoch;
2920
2677
  try {
2921
- const result = await this.#doSendBatch(batch, seq, epoch);
2922
- this.#recordSuccessfulOffset(result.offset);
2678
+ await this.#doSendBatch(batch, seq, epoch);
2923
2679
  if (!this.#epochClaimed) this.#epochClaimed = true;
2924
2680
  this.#signalSeqComplete(epoch, seq, void 0);
2925
2681
  } catch (error) {
@@ -2928,9 +2684,6 @@ var IdempotentProducer = class {
2928
2684
  throw error;
2929
2685
  }
2930
2686
  }
2931
- #recordSuccessfulOffset(offset) {
2932
- if (offset && (!this.#lastSuccessfulOffset || offset > this.#lastSuccessfulOffset)) this.#lastSuccessfulOffset = offset;
2933
- }
2934
2687
  /**
2935
2688
  * Signal that a sequence has completed (success or failure).
2936
2689
  */
@@ -3005,12 +2758,12 @@ var IdempotentProducer = class {
3005
2758
  batchedBody = concatenated;
3006
2759
  }
3007
2760
  const url = this.#stream.url;
3008
- const headers = await this.#buildHeaders({
2761
+ const headers = {
3009
2762
  "content-type": contentType,
3010
2763
  [PRODUCER_ID_HEADER]: this.#producerId,
3011
2764
  [PRODUCER_EPOCH_HEADER]: epoch.toString(),
3012
2765
  [PRODUCER_SEQ_HEADER]: seq.toString()
3013
- });
2766
+ };
3014
2767
  const response = await this.#fetchClient(url, {
3015
2768
  method: `POST`,
3016
2769
  headers,
@@ -3051,15 +2804,6 @@ var IdempotentProducer = class {
3051
2804
  if (response.status === 400) throw await DurableStreamError.fromResponse(response, url);
3052
2805
  throw await FetchError.fromResponse(response, url);
3053
2806
  }
3054
- async #buildHeaders(protocolHeaders) {
3055
- const streamHeaders = await this.#stream.resolveHeaders();
3056
- const producerHeaders = await resolveHeaders(this.#headers);
3057
- return {
3058
- ...streamHeaders,
3059
- ...producerHeaders,
3060
- ...protocolHeaders
3061
- };
3062
- }
3063
2807
  /**
3064
2808
  * Clear pending batch and report error.
3065
2809
  */
@@ -3103,7 +2847,7 @@ function isPromiseLike(value) {
3103
2847
  * contentType: "application/json"
3104
2848
  * });
3105
2849
  *
3106
- * // Single write
2850
+ * // Write data
3107
2851
  * await stream.append(JSON.stringify({ message: "hello" }));
3108
2852
  *
3109
2853
  * // Read with the new API
@@ -3126,7 +2870,6 @@ var DurableStream = class DurableStream {
3126
2870
  contentType;
3127
2871
  #options;
3128
2872
  #fetchClient;
3129
- #baseFetchClient;
3130
2873
  #onError;
3131
2874
  #batchingEnabled;
3132
2875
  #queue;
@@ -3147,9 +2890,7 @@ var DurableStream = class DurableStream {
3147
2890
  if (opts.contentType) this.contentType = opts.contentType;
3148
2891
  this.#batchingEnabled = opts.batching !== false;
3149
2892
  if (this.#batchingEnabled) this.#queue = import_queue.default.promise(this.#batchWorker.bind(this), 1);
3150
- this.#baseFetchClient = opts.fetch ?? ((...args) => fetch(...args));
3151
- const backOffOpts = { ...opts.backoffOptions ?? BackoffDefaults };
3152
- const fetchWithBackoffClient = createFetchWithBackoff(this.#baseFetchClient, backOffOpts);
2893
+ const fetchWithBackoffClient = createFetchWithBackoff(opts.fetch ?? ((...args) => fetch(...args)), { ...opts.backoffOptions ?? BackoffDefaults });
3153
2894
  this.#fetchClient = createFetchWithConsumedBody(fetchWithBackoffClient);
3154
2895
  }
3155
2896
  /**
@@ -3204,15 +2945,12 @@ var DurableStream = class DurableStream {
3204
2945
  */
3205
2946
  async head(opts) {
3206
2947
  const { requestHeaders, fetchUrl } = await this.#buildRequest();
3207
- const response = await this.#baseFetchClient(fetchUrl.toString(), {
2948
+ const response = await this.#fetchClient(fetchUrl.toString(), {
3208
2949
  method: `HEAD`,
3209
2950
  headers: requestHeaders,
3210
2951
  signal: opts?.signal ?? this.#options.signal
3211
2952
  });
3212
- if (!response.ok) {
3213
- if (response.status === 404) return { exists: false };
3214
- await handleErrorResponse(response, this.url);
3215
- }
2953
+ if (!response.ok) await handleErrorResponse(response, this.url);
3216
2954
  const contentType = response.headers.get(`content-type`) ?? void 0;
3217
2955
  const offset = response.headers.get(STREAM_OFFSET_HEADER) ?? void 0;
3218
2956
  const etag = response.headers.get(`etag`) ?? void 0;
@@ -3310,14 +3048,9 @@ var DurableStream = class DurableStream {
3310
3048
  /**
3311
3049
  * Append a single payload to the stream.
3312
3050
  *
3313
- * Batching: when batching is enabled (default), append() calls that overlap
3314
- * in time (e.g. fired without awaiting each one) are coalesced into a
3315
- * single POST while a prior POST is in flight. If every call is awaited
3316
- * before the next is issued, no batching happens — each call becomes its
3317
- * own roundtrip. For tight loops driving an async iterable (e.g. LLM
3318
- * token streams), prefer `appendStream()` / `writable()` which pipe the
3319
- * source over a single POST, or fire `append()` calls without awaiting
3320
- * each one and await the last promise (and `close()`) at the end.
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.
3321
3054
  *
3322
3055
  * - `body` must be string or Uint8Array.
3323
3056
  * - For JSON streams, pass pre-serialized JSON strings.
@@ -3327,7 +3060,7 @@ var DurableStream = class DurableStream {
3327
3060
  *
3328
3061
  * @example
3329
3062
  * ```typescript
3330
- * // JSON stream - pass pre-serialized JSON (single write)
3063
+ * // JSON stream - pass pre-serialized JSON
3331
3064
  * await stream.append(JSON.stringify({ message: "hello" }));
3332
3065
  *
3333
3066
  * // Byte stream
@@ -3336,14 +3069,6 @@ var DurableStream = class DurableStream {
3336
3069
  *
3337
3070
  * // Promise value - awaited before buffering
3338
3071
  * await stream.append(fetchData());
3339
- *
3340
- * // High-frequency writes from an async iterable - fire-and-track-last
3341
- * let last: Promise<void> = Promise.resolve();
3342
- * for await (const chunk of source) {
3343
- * last = stream.append(JSON.stringify(chunk));
3344
- * }
3345
- * await last;
3346
- * await stream.close();
3347
3072
  * ```
3348
3073
  */
3349
3074
  async append(body, opts) {
@@ -3550,7 +3275,6 @@ var DurableStream = class DurableStream {
3550
3275
  let writeError = null;
3551
3276
  const producer = new IdempotentProducer(this, producerId, {
3552
3277
  autoClaim: true,
3553
- headers: opts?.headers,
3554
3278
  lingerMs: opts?.lingerMs,
3555
3279
  maxBatchBytes: opts?.maxBatchBytes,
3556
3280
  onError: (error) => {
@@ -3633,14 +3357,6 @@ var DurableStream = class DurableStream {
3633
3357
  });
3634
3358
  }
3635
3359
  /**
3636
- * Resolve the stream's configured headers.
3637
- * Used by IdempotentProducer to merge auth headers into its requests.
3638
- * @internal
3639
- */
3640
- async resolveHeaders() {
3641
- return resolveHeaders(this.#options.headers);
3642
- }
3643
- /**
3644
3360
  * Build request headers and URL.
3645
3361
  */
3646
3362
  async #buildRequest() {