@dvina/sdk 4.0.212 → 4.0.215

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 (33) hide show
  1. package/dist/adapters/angular/index.cjs +4 -4
  2. package/dist/adapters/angular/index.d.cts +2 -2
  3. package/dist/adapters/angular/index.d.ts +2 -2
  4. package/dist/adapters/angular/index.js +2 -2
  5. package/dist/billing/index.cjs +5 -5
  6. package/dist/billing/index.d.cts +2 -12
  7. package/dist/billing/index.d.ts +2 -12
  8. package/dist/billing/index.js +1 -1
  9. package/dist/{chunk-CW5W3LEG.js → chunk-BJDU4UX4.js} +290 -408
  10. package/dist/chunk-BJDU4UX4.js.map +1 -0
  11. package/dist/{chunk-5TAY5I34.js → chunk-Q5XGHH7D.js} +471 -209
  12. package/dist/chunk-Q5XGHH7D.js.map +1 -0
  13. package/dist/{chunk-4EESBP3A.cjs → chunk-XK5PW7IX.cjs} +311 -429
  14. package/dist/chunk-XK5PW7IX.cjs.map +1 -0
  15. package/dist/{chunk-BL3GFOZR.cjs → chunk-Y4OD5SDF.cjs} +473 -208
  16. package/dist/chunk-Y4OD5SDF.cjs.map +1 -0
  17. package/dist/{client-mOcqT66b.d.ts → client-Bn-0nJ8y.d.ts} +9 -2
  18. package/dist/{client-DRS_ycOv.d.cts → client-Ca2wlmC4.d.cts} +9 -2
  19. package/dist/{index-BKyI_YQS.d.cts → index-Ct2Mh0zl.d.cts} +1 -1
  20. package/dist/{index-BrsbEcIx.d.ts → index-D50r0Gl0.d.ts} +1 -1
  21. package/dist/index.cjs +387 -387
  22. package/dist/index.d.cts +9 -7
  23. package/dist/index.d.ts +9 -7
  24. package/dist/index.js +2 -2
  25. package/dist/pagination/index.d.cts +2 -2
  26. package/dist/pagination/index.d.ts +2 -2
  27. package/dist/{sync-engine-DN0tSqtg.d.cts → sync-engine-21OlgS5_.d.cts} +5 -16
  28. package/dist/{sync-engine-BmE8-rhX.d.ts → sync-engine-D35aV8Zc.d.ts} +5 -16
  29. package/package.json +1 -1
  30. package/dist/chunk-4EESBP3A.cjs.map +0 -1
  31. package/dist/chunk-5TAY5I34.js.map +0 -1
  32. package/dist/chunk-BL3GFOZR.cjs.map +0 -1
  33. package/dist/chunk-CW5W3LEG.js.map +0 -1
@@ -852,6 +852,26 @@ function hasMultilineItems(maybeArray) {
852
852
  return (_maybeArray$some = maybeArray === null || maybeArray === void 0 ? void 0 : maybeArray.some((str) => str.includes("\n"))) !== null && _maybeArray$some !== void 0 ? _maybeArray$some : false;
853
853
  }
854
854
 
855
+ // src/transport/core/token-refresh.ts
856
+ var inFlightRefreshes = /* @__PURE__ */ new WeakMap();
857
+ async function refreshTokenSingleFlight(getToken, failedToken) {
858
+ let inFlight = inFlightRefreshes.get(getToken);
859
+ if (!inFlight) {
860
+ const refreshPromise = getToken({ forceRefresh: true }).finally(() => {
861
+ if (inFlightRefreshes.get(getToken) === refreshPromise) {
862
+ inFlightRefreshes.delete(getToken);
863
+ }
864
+ });
865
+ inFlightRefreshes.set(getToken, refreshPromise);
866
+ inFlight = refreshPromise;
867
+ }
868
+ const refreshedToken = await inFlight;
869
+ if (failedToken && refreshedToken === failedToken) {
870
+ throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Token refresh did not produce a new token", failedToken);
871
+ }
872
+ return refreshedToken;
873
+ }
874
+
855
875
  // src/transport/http.ts
856
876
  var printCache = /* @__PURE__ */ new WeakMap();
857
877
  function printDocument(document) {
@@ -902,11 +922,22 @@ async function doFetch(options, document, variables) {
902
922
  query: printDocument(document),
903
923
  variables
904
924
  });
905
- const response = await fetch(options.url, {
906
- method: "POST",
907
- headers,
908
- body
909
- });
925
+ let response;
926
+ try {
927
+ response = await fetch(options.url, {
928
+ method: "POST",
929
+ headers,
930
+ body
931
+ });
932
+ } catch (error) {
933
+ if (error instanceof Error && error.name === "AbortError") {
934
+ throw error;
935
+ }
936
+ throw new chunkKV5SP7RP_cjs.DvinaNetworkError(
937
+ error instanceof Error ? error.message : "Network request failed before reaching the server",
938
+ 0
939
+ );
940
+ }
910
941
  if (!response.ok) {
911
942
  if (response.status === 401 || response.status === 403) {
912
943
  throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError(`Authentication failed with status ${response.status}`, token);
@@ -965,10 +996,7 @@ async function handleAuthRefresh(document, variables, refreshState, options, fai
965
996
  if (!refreshState.isRefreshing) {
966
997
  refreshState.isRefreshing = true;
967
998
  try {
968
- const newToken = await options.getToken({ forceRefresh: true });
969
- if (failedToken && newToken === failedToken) {
970
- throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Token refresh did not produce a new token");
971
- }
999
+ await refreshTokenSingleFlight(options.getToken, failedToken);
972
1000
  refreshState.pendingRequests.forEach((req) => req.resolve());
973
1001
  refreshState.pendingRequests = [];
974
1002
  refreshState.isRefreshing = false;
@@ -1005,15 +1033,430 @@ function sleep(ms) {
1005
1033
  return new Promise((resolve) => setTimeout(resolve, ms));
1006
1034
  }
1007
1035
 
1036
+ // src/transport/core/connection-supervisor.ts
1037
+ var DEFAULT_AUTH_RETRY_DELAY_MS = 1e3;
1038
+ var DEFAULT_BASE_RECONNECT_DELAY_MS = 1e3;
1039
+ var DEFAULT_MAX_CONSECUTIVE_AUTH_FAILURES = 3;
1040
+ var DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
1041
+ function isAbortError(error) {
1042
+ return error instanceof Error && error.name === "AbortError";
1043
+ }
1044
+ function now() {
1045
+ return Date.now();
1046
+ }
1047
+ var ConnectionSupervisor = class {
1048
+ constructor(options) {
1049
+ this.options = options;
1050
+ }
1051
+ abortController;
1052
+ abortReason;
1053
+ attemptPromise;
1054
+ closed = false;
1055
+ consecutiveAuthFailures = 0;
1056
+ forceRefreshNext = false;
1057
+ lastActivityAt = null;
1058
+ lastConnectedAt = null;
1059
+ lastError;
1060
+ reconnectAttempt = 0;
1061
+ reconnectTimer;
1062
+ restartAfterCurrentAttempt = false;
1063
+ state = "idle";
1064
+ staleTimer;
1065
+ shouldRun = false;
1066
+ get snapshot() {
1067
+ return {
1068
+ consecutiveAuthFailures: this.consecutiveAuthFailures,
1069
+ id: this.options.id,
1070
+ kind: this.options.kind,
1071
+ lastActivityAt: this.lastActivityAt,
1072
+ lastConnectedAt: this.lastConnectedAt,
1073
+ lastError: this.lastError,
1074
+ reconnectAttempt: this.reconnectAttempt,
1075
+ state: this.state
1076
+ };
1077
+ }
1078
+ async start() {
1079
+ if (this.closed) {
1080
+ throw new Error(`Connection '${this.options.kind}:${this.options.id}' is closed`);
1081
+ }
1082
+ this.shouldRun = true;
1083
+ this.clearReconnectTimer();
1084
+ if (this.attemptPromise && this.abortReason) {
1085
+ this.restartAfterCurrentAttempt = true;
1086
+ }
1087
+ return this.ensureAttempt();
1088
+ }
1089
+ stop() {
1090
+ this.shouldRun = false;
1091
+ this.restartAfterCurrentAttempt = false;
1092
+ this.clearReconnectTimer();
1093
+ this.clearStaleTimer();
1094
+ this.abort("manual");
1095
+ this.setState("idle");
1096
+ }
1097
+ close() {
1098
+ this.closed = true;
1099
+ this.shouldRun = false;
1100
+ this.restartAfterCurrentAttempt = false;
1101
+ this.clearReconnectTimer();
1102
+ this.clearStaleTimer();
1103
+ this.abort("manual");
1104
+ this.setState("closed");
1105
+ }
1106
+ reconnect() {
1107
+ if (this.closed) {
1108
+ return;
1109
+ }
1110
+ this.shouldRun = true;
1111
+ this.clearReconnectTimer();
1112
+ this.clearStaleTimer();
1113
+ if (this.abortController) {
1114
+ this.restartAfterCurrentAttempt = true;
1115
+ this.abort("restart");
1116
+ return;
1117
+ }
1118
+ void this.ensureAttempt();
1119
+ }
1120
+ ensureAttempt() {
1121
+ if (!this.shouldRun || this.closed) {
1122
+ return Promise.resolve();
1123
+ }
1124
+ if (!this.attemptPromise) {
1125
+ const attemptPromise = this.runAttempt().finally(() => {
1126
+ if (this.attemptPromise === attemptPromise) {
1127
+ this.attemptPromise = void 0;
1128
+ }
1129
+ });
1130
+ this.attemptPromise = attemptPromise;
1131
+ }
1132
+ return this.attemptPromise;
1133
+ }
1134
+ async runAttempt() {
1135
+ if (!this.shouldRun || this.closed) {
1136
+ return;
1137
+ }
1138
+ const isReconnect = this.lastConnectedAt !== null || this.reconnectAttempt > 0;
1139
+ this.setState("connecting");
1140
+ this.lastError = void 0;
1141
+ this.abortReason = void 0;
1142
+ const abortController = new AbortController();
1143
+ this.abortController = abortController;
1144
+ try {
1145
+ const token = this.forceRefreshNext ? await refreshTokenSingleFlight(this.options.getToken) : await this.options.getToken(void 0);
1146
+ this.forceRefreshNext = false;
1147
+ await this.options.dial({
1148
+ isReconnect,
1149
+ markActivity: () => this.markActivity(),
1150
+ markConnected: () => this.markConnected(),
1151
+ requestTokenRefresh: () => {
1152
+ this.forceRefreshNext = true;
1153
+ },
1154
+ signal: abortController.signal,
1155
+ token
1156
+ });
1157
+ if (!this.shouldRun || this.closed) {
1158
+ return;
1159
+ }
1160
+ this.scheduleReconnect("end");
1161
+ } catch (error) {
1162
+ if (isAbortError(error)) {
1163
+ if (this.abortReason === "stale" && this.shouldRun && !this.closed) {
1164
+ this.scheduleReconnect("end");
1165
+ }
1166
+ return;
1167
+ }
1168
+ if (!this.shouldRun || this.closed) {
1169
+ return;
1170
+ }
1171
+ this.handleFailure(error);
1172
+ } finally {
1173
+ if (this.abortController === abortController) {
1174
+ this.abortController = void 0;
1175
+ }
1176
+ this.abortReason = void 0;
1177
+ if (this.restartAfterCurrentAttempt && this.shouldRun && !this.closed) {
1178
+ this.restartAfterCurrentAttempt = false;
1179
+ queueMicrotask(() => {
1180
+ if (!this.closed && this.shouldRun) {
1181
+ void this.ensureAttempt();
1182
+ }
1183
+ });
1184
+ }
1185
+ }
1186
+ }
1187
+ handleFailure(error) {
1188
+ this.lastError = error;
1189
+ const isAuthError2 = this.options.isAuthError?.(error) ?? false;
1190
+ if (isAuthError2) {
1191
+ this.consecutiveAuthFailures += 1;
1192
+ this.forceRefreshNext = true;
1193
+ this.setState("reconnecting");
1194
+ if (this.consecutiveAuthFailures >= (this.options.maxConsecutiveAuthFailures ?? DEFAULT_MAX_CONSECUTIVE_AUTH_FAILURES)) {
1195
+ this.shouldRun = false;
1196
+ this.clearReconnectTimer();
1197
+ this.clearStaleTimer();
1198
+ this.setState("failed");
1199
+ this.options.onFatalError?.(error);
1200
+ return;
1201
+ }
1202
+ this.scheduleReconnect("auth");
1203
+ return;
1204
+ }
1205
+ this.consecutiveAuthFailures = 0;
1206
+ if (typeof navigator !== "undefined" && "onLine" in navigator && navigator.onLine === false) {
1207
+ this.setState("offline");
1208
+ } else {
1209
+ this.setState("reconnecting");
1210
+ }
1211
+ this.scheduleReconnect("failure");
1212
+ }
1213
+ scheduleReconnect(reason) {
1214
+ if (!this.shouldRun || this.closed) {
1215
+ return;
1216
+ }
1217
+ this.clearReconnectTimer();
1218
+ this.clearStaleTimer();
1219
+ const delay = reason === "auth" ? this.options.authRetryDelayMs ?? DEFAULT_AUTH_RETRY_DELAY_MS : reason === "failure" ? this.nextReconnectDelay() : this.options.baseReconnectDelayMs ?? DEFAULT_BASE_RECONNECT_DELAY_MS;
1220
+ this.reconnectTimer = setTimeout(() => {
1221
+ this.reconnectTimer = void 0;
1222
+ void this.ensureAttempt();
1223
+ }, delay);
1224
+ }
1225
+ nextReconnectDelay() {
1226
+ const baseDelay = this.options.baseReconnectDelayMs ?? DEFAULT_BASE_RECONNECT_DELAY_MS;
1227
+ const maxDelay = this.options.maxReconnectDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
1228
+ const cappedDelay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempt), maxDelay);
1229
+ this.reconnectAttempt += 1;
1230
+ if (this.options.jitter === "full") {
1231
+ return Math.floor(Math.random() * cappedDelay);
1232
+ }
1233
+ return cappedDelay;
1234
+ }
1235
+ markConnected() {
1236
+ this.consecutiveAuthFailures = 0;
1237
+ this.reconnectAttempt = 0;
1238
+ this.lastConnectedAt = now();
1239
+ this.setState("connected");
1240
+ this.markActivity();
1241
+ }
1242
+ markActivity() {
1243
+ this.lastActivityAt = now();
1244
+ this.armStaleTimer();
1245
+ }
1246
+ armStaleTimer() {
1247
+ this.clearStaleTimer();
1248
+ const staleTimeoutMs = this.options.staleTimeoutMs;
1249
+ if (!staleTimeoutMs || staleTimeoutMs <= 0 || this.state !== "connected") {
1250
+ return;
1251
+ }
1252
+ this.staleTimer = setTimeout(() => {
1253
+ if (!this.shouldRun || this.closed || !this.abortController) {
1254
+ return;
1255
+ }
1256
+ this.setState("stale");
1257
+ this.abort("stale");
1258
+ }, staleTimeoutMs);
1259
+ }
1260
+ clearReconnectTimer() {
1261
+ if (!this.reconnectTimer) {
1262
+ return;
1263
+ }
1264
+ clearTimeout(this.reconnectTimer);
1265
+ this.reconnectTimer = void 0;
1266
+ }
1267
+ clearStaleTimer() {
1268
+ if (!this.staleTimer) {
1269
+ return;
1270
+ }
1271
+ clearTimeout(this.staleTimer);
1272
+ this.staleTimer = void 0;
1273
+ }
1274
+ abort(reason) {
1275
+ if (!this.abortController) {
1276
+ return;
1277
+ }
1278
+ this.abortReason = reason;
1279
+ this.abortController.abort();
1280
+ this.abortController = void 0;
1281
+ }
1282
+ setState(nextState) {
1283
+ if (this.state === nextState) {
1284
+ return;
1285
+ }
1286
+ this.state = nextState;
1287
+ this.options.onStateChange?.(this.snapshot);
1288
+ }
1289
+ };
1290
+
1291
+ // src/transport/core/sse-stream.ts
1292
+ async function consumeSseStream(body, signal, handlers) {
1293
+ const reader = body.getReader();
1294
+ const decoder = new TextDecoder();
1295
+ let buffer = "";
1296
+ try {
1297
+ while (!signal.aborted) {
1298
+ const { done, value } = await reader.read();
1299
+ if (done) {
1300
+ break;
1301
+ }
1302
+ handlers.onChunk?.();
1303
+ buffer += decoder.decode(value, { stream: true });
1304
+ const parts = buffer.split(/(?:\r\n|\r|\n){2}/);
1305
+ buffer = parts.pop() ?? "";
1306
+ for (const part of parts) {
1307
+ handlers.onEventBlock(part.trim());
1308
+ }
1309
+ }
1310
+ } finally {
1311
+ reader.releaseLock();
1312
+ }
1313
+ }
1314
+
1008
1315
  // src/billing/client.ts
1009
1316
  var INITIAL_RECONNECT_DELAY_MS = 1e3;
1010
1317
  var MAX_RECONNECT_DELAY_MS = 3e4;
1011
- var RECONNECT_BACKOFF_MULTIPLIER = 2;
1012
1318
  var MAX_CONSECUTIVE_AUTH_FAILURES = 3;
1319
+ var STALE_CONNECTION_TIMEOUT_MS = 6e4;
1320
+ var BillingAuthStreamError = class extends Error {
1321
+ constructor(status) {
1322
+ super(`Billing SSE auth failed: ${status}`);
1323
+ this.status = status;
1324
+ this.name = "BillingAuthStreamError";
1325
+ }
1326
+ };
1327
+ var BillingSubscriptionStream = class {
1328
+ constructor(options, onClose) {
1329
+ this.options = options;
1330
+ this.onClose = onClose;
1331
+ this.supervisor = new ConnectionSupervisor({
1332
+ authRetryDelayMs: INITIAL_RECONNECT_DELAY_MS,
1333
+ baseReconnectDelayMs: INITIAL_RECONNECT_DELAY_MS,
1334
+ getToken: options.getToken,
1335
+ id: `billing-${Math.random().toString(36).slice(2)}`,
1336
+ isAuthError: (error) => error instanceof BillingAuthStreamError,
1337
+ kind: "billing-sse",
1338
+ maxConsecutiveAuthFailures: MAX_CONSECUTIVE_AUTH_FAILURES,
1339
+ maxReconnectDelayMs: MAX_RECONNECT_DELAY_MS,
1340
+ onFatalError: () => {
1341
+ this.options.onError?.(
1342
+ new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Billing SSE stream authentication failed permanently")
1343
+ );
1344
+ this.close();
1345
+ },
1346
+ staleTimeoutMs: STALE_CONNECTION_TIMEOUT_MS,
1347
+ dial: async (context) => {
1348
+ const response = await fetch(this.options.billingSseUrl, {
1349
+ method: "GET",
1350
+ headers: {
1351
+ Authorization: `Bearer ${context.token}`,
1352
+ Accept: "text/event-stream",
1353
+ "Cache-Control": "no-cache"
1354
+ },
1355
+ signal: context.signal
1356
+ });
1357
+ if (!response.ok) {
1358
+ if (response.status === 401 || response.status === 403) {
1359
+ throw new BillingAuthStreamError(response.status);
1360
+ }
1361
+ throw new Error(`Billing SSE failed: ${response.status} ${response.statusText}`);
1362
+ }
1363
+ if (!response.body) {
1364
+ throw new Error("Billing SSE response has no body");
1365
+ }
1366
+ context.markConnected();
1367
+ await consumeSseStream(response.body, context.signal, {
1368
+ onChunk: () => context.markActivity(),
1369
+ onEventBlock: (block2) => this.processEventBlock(block2, context.requestTokenRefresh)
1370
+ });
1371
+ }
1372
+ });
1373
+ }
1374
+ abortFinished = false;
1375
+ queue = [];
1376
+ pendingResolve = null;
1377
+ supervisor;
1378
+ start() {
1379
+ void this.supervisor.start();
1380
+ }
1381
+ reconnect() {
1382
+ if (this.abortFinished) {
1383
+ return;
1384
+ }
1385
+ this.supervisor.reconnect();
1386
+ }
1387
+ next() {
1388
+ if (this.queue.length > 0) {
1389
+ return Promise.resolve({ value: this.queue.shift(), done: false });
1390
+ }
1391
+ if (this.abortFinished) {
1392
+ return Promise.resolve({ value: void 0, done: true });
1393
+ }
1394
+ return new Promise((resolve) => {
1395
+ this.pendingResolve = resolve;
1396
+ });
1397
+ }
1398
+ return() {
1399
+ this.close();
1400
+ return Promise.resolve({ value: void 0, done: true });
1401
+ }
1402
+ [Symbol.asyncIterator]() {
1403
+ return this;
1404
+ }
1405
+ close() {
1406
+ if (this.abortFinished) {
1407
+ return;
1408
+ }
1409
+ this.abortFinished = true;
1410
+ this.supervisor.close();
1411
+ if (this.pendingResolve) {
1412
+ this.pendingResolve({ value: void 0, done: true });
1413
+ this.pendingResolve = null;
1414
+ }
1415
+ this.onClose(this);
1416
+ }
1417
+ push(value) {
1418
+ if (this.abortFinished) {
1419
+ return;
1420
+ }
1421
+ if (this.pendingResolve) {
1422
+ this.pendingResolve({ value, done: false });
1423
+ this.pendingResolve = null;
1424
+ return;
1425
+ }
1426
+ this.queue.length = 0;
1427
+ this.queue.push(value);
1428
+ }
1429
+ processEventBlock(block2, requestTokenRefresh) {
1430
+ if (!block2 || block2.startsWith(":")) {
1431
+ return;
1432
+ }
1433
+ let eventType;
1434
+ const dataLines = [];
1435
+ for (const line of block2.split(/\r\n|\r|\n/)) {
1436
+ if (line.startsWith("event: ")) {
1437
+ eventType = line.slice(7).trim();
1438
+ } else if (line.startsWith("data: ")) {
1439
+ dataLines.push(line.slice(6));
1440
+ }
1441
+ }
1442
+ if (eventType === "token-expired") {
1443
+ requestTokenRefresh();
1444
+ throw new BillingAuthStreamError(401);
1445
+ }
1446
+ if (eventType !== "subscription" || dataLines.length === 0) {
1447
+ return;
1448
+ }
1449
+ try {
1450
+ const parsed = JSON.parse(dataLines.join("\n"));
1451
+ this.push(parsed);
1452
+ } catch {
1453
+ }
1454
+ }
1455
+ };
1013
1456
  var BillingClient = class {
1014
1457
  http;
1015
1458
  options;
1016
- activeAbortControllers = /* @__PURE__ */ new Set();
1459
+ activeUpdateStreams = /* @__PURE__ */ new Set();
1017
1460
  constructor(options) {
1018
1461
  this.options = options;
1019
1462
  this.http = createHttpTransport({
@@ -1028,12 +1471,16 @@ var BillingClient = class {
1028
1471
  * Call this on logout or SDK teardown to prevent lingering connections.
1029
1472
  */
1030
1473
  destroy() {
1031
- for (const controller of this.activeAbortControllers) {
1032
- controller.abort();
1474
+ for (const stream of Array.from(this.activeUpdateStreams)) {
1475
+ stream.close();
1476
+ }
1477
+ this.activeUpdateStreams.clear();
1478
+ }
1479
+ reconnectAll() {
1480
+ for (const stream of this.activeUpdateStreams) {
1481
+ stream.reconnect();
1033
1482
  }
1034
- this.activeAbortControllers.clear();
1035
1483
  }
1036
- // ─── Plans ───────────────────────────────────────────────────────────────
1037
1484
  get plans() {
1038
1485
  return {
1039
1486
  list: async () => {
@@ -1054,7 +1501,6 @@ var BillingClient = class {
1054
1501
  }
1055
1502
  };
1056
1503
  }
1057
- // ─── Subscription ────────────────────────────────────────────────────────
1058
1504
  get subscription() {
1059
1505
  return {
1060
1506
  get: async () => {
@@ -1093,17 +1539,16 @@ var BillingClient = class {
1093
1539
  const result = await this.request(ReleaseScheduleDocument);
1094
1540
  return result.releaseSchedule;
1095
1541
  },
1096
- /**
1097
- * Subscribe to real-time subscription updates via SSE.
1098
- * Returns an AsyncIterable that yields WorkspaceSubscription snapshots.
1099
- * Automatically reconnects with exponential backoff and token refresh on auth errors.
1100
- */
1101
1542
  updates: () => {
1102
- return this.createSseStream();
1543
+ const stream = new BillingSubscriptionStream(this.options, (closedStream) => {
1544
+ this.activeUpdateStreams.delete(closedStream);
1545
+ });
1546
+ this.activeUpdateStreams.add(stream);
1547
+ stream.start();
1548
+ return stream;
1103
1549
  }
1104
1550
  };
1105
1551
  }
1106
- // ─── Seats ───────────────────────────────────────────────────────────────
1107
1552
  get seats() {
1108
1553
  return {
1109
1554
  add: async (variables) => {
@@ -1116,7 +1561,6 @@ var BillingClient = class {
1116
1561
  }
1117
1562
  };
1118
1563
  }
1119
- // ─── Sessions ────────────────────────────────────────────────────────────
1120
1564
  get session() {
1121
1565
  return {
1122
1566
  createSession: async (variables) => {
@@ -1129,7 +1573,6 @@ var BillingClient = class {
1129
1573
  }
1130
1574
  };
1131
1575
  }
1132
- // ─── Management ──────────────────────────────────────────────────────────
1133
1576
  get management() {
1134
1577
  return {
1135
1578
  subscription: async (variables) => {
@@ -1154,197 +1597,19 @@ var BillingClient = class {
1154
1597
  }
1155
1598
  };
1156
1599
  }
1157
- // ─── Private ─────────────────────────────────────────────────────────────
1158
1600
  request(document, variables) {
1159
1601
  return this.http.request(document, variables);
1160
1602
  }
1161
- /**
1162
- * Create an SSE stream that connects to the billing SSE endpoint.
1163
- * Reconnects automatically with exponential backoff.
1164
- * On 401/403, refreshes the token and retries with a short delay.
1165
- */
1166
- createSseStream() {
1167
- const options = this.options;
1168
- const activeAbortControllers = this.activeAbortControllers;
1169
- return {
1170
- [Symbol.asyncIterator]() {
1171
- const queue = [];
1172
- let resolve = null;
1173
- let done = false;
1174
- let abortController;
1175
- let reconnectTimer;
1176
- let reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
1177
- let shouldReconnect = true;
1178
- let needsForceRefresh = false;
1179
- let consecutiveAuthFailures = 0;
1180
- function push(value) {
1181
- if (resolve) {
1182
- resolve({ value, done: false });
1183
- resolve = null;
1184
- } else {
1185
- queue.length = 0;
1186
- queue.push(value);
1187
- }
1188
- }
1189
- async function connect() {
1190
- if (!shouldReconnect) return;
1191
- if (abortController) {
1192
- activeAbortControllers.delete(abortController);
1193
- }
1194
- abortController = new AbortController();
1195
- activeAbortControllers.add(abortController);
1196
- try {
1197
- const forceRefresh = needsForceRefresh;
1198
- needsForceRefresh = false;
1199
- const token = await options.getToken(forceRefresh ? { forceRefresh: true } : void 0);
1200
- const response = await fetch(options.billingSseUrl, {
1201
- method: "GET",
1202
- headers: {
1203
- Authorization: `Bearer ${token}`,
1204
- Accept: "text/event-stream",
1205
- "Cache-Control": "no-cache"
1206
- },
1207
- signal: abortController.signal
1208
- });
1209
- if (!response.ok) {
1210
- if (response.status === 401 || response.status === 403) {
1211
- consecutiveAuthFailures++;
1212
- if (consecutiveAuthFailures >= MAX_CONSECUTIVE_AUTH_FAILURES) {
1213
- done = true;
1214
- shouldReconnect = false;
1215
- options.onError?.(
1216
- new chunkKV5SP7RP_cjs.DvinaAuthenticationError(
1217
- "Billing SSE stream authentication failed permanently"
1218
- )
1219
- );
1220
- if (resolve) {
1221
- resolve({ value: void 0, done: true });
1222
- resolve = null;
1223
- }
1224
- return;
1225
- }
1226
- needsForceRefresh = true;
1227
- scheduleReconnect(INITIAL_RECONNECT_DELAY_MS);
1228
- return;
1229
- }
1230
- throw new Error(`Billing SSE failed: ${response.status} ${response.statusText}`);
1231
- }
1232
- if (!response.body) {
1233
- throw new Error("Billing SSE response has no body");
1234
- }
1235
- consecutiveAuthFailures = 0;
1236
- reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
1237
- await parseSseStream(response.body, abortController.signal);
1238
- } catch (err) {
1239
- if (err.name === "AbortError") return;
1240
- if (shouldReconnect) {
1241
- scheduleReconnect(reconnectDelay);
1242
- reconnectDelay = Math.min(
1243
- reconnectDelay * RECONNECT_BACKOFF_MULTIPLIER,
1244
- MAX_RECONNECT_DELAY_MS
1245
- );
1246
- }
1247
- }
1248
- }
1249
- async function parseSseStream(body, signal) {
1250
- const reader = body.getReader();
1251
- const decoder = new TextDecoder();
1252
- let buffer = "";
1253
- try {
1254
- while (!signal.aborted && shouldReconnect) {
1255
- const { done: streamDone, value } = await reader.read();
1256
- if (streamDone) break;
1257
- buffer += decoder.decode(value, { stream: true });
1258
- const parts = buffer.split(/(?:\r\n|\r|\n){2}/);
1259
- buffer = parts.pop() ?? "";
1260
- for (const part of parts) {
1261
- processEventBlock(part.trim());
1262
- }
1263
- }
1264
- } finally {
1265
- reader.releaseLock();
1266
- }
1267
- if (shouldReconnect) {
1268
- scheduleReconnect(reconnectDelay);
1269
- }
1270
- }
1271
- function processEventBlock(block2) {
1272
- if (!block2 || block2.startsWith(":")) return;
1273
- let eventType;
1274
- let eventData;
1275
- for (const line of block2.split(/\r\n|\r|\n/)) {
1276
- if (line.startsWith("event: ")) {
1277
- eventType = line.slice(7).trim();
1278
- } else if (line.startsWith("data: ")) {
1279
- eventData = line.slice(6);
1280
- }
1281
- }
1282
- if (eventType === "token-expired") {
1283
- needsForceRefresh = true;
1284
- return;
1285
- }
1286
- if (eventType === "subscription" && eventData) {
1287
- try {
1288
- const parsed = JSON.parse(eventData);
1289
- push(parsed);
1290
- } catch {
1291
- }
1292
- }
1293
- }
1294
- function scheduleReconnect(delay) {
1295
- if (!shouldReconnect) return;
1296
- reconnectTimer = setTimeout(() => {
1297
- connect();
1298
- }, delay);
1299
- }
1300
- connect();
1301
- return {
1302
- next() {
1303
- if (queue.length > 0) {
1304
- return Promise.resolve({ value: queue.shift(), done: false });
1305
- }
1306
- if (done) {
1307
- return Promise.resolve({
1308
- value: void 0,
1309
- done: true
1310
- });
1311
- }
1312
- return new Promise((r) => {
1313
- resolve = r;
1314
- });
1315
- },
1316
- return() {
1317
- shouldReconnect = false;
1318
- done = true;
1319
- if (reconnectTimer) {
1320
- clearTimeout(reconnectTimer);
1321
- reconnectTimer = void 0;
1322
- }
1323
- if (abortController) {
1324
- activeAbortControllers.delete(abortController);
1325
- abortController.abort();
1326
- abortController = void 0;
1327
- }
1328
- if (resolve) {
1329
- resolve({ value: void 0, done: true });
1330
- resolve = null;
1331
- }
1332
- return Promise.resolve({ value: void 0, done: true });
1333
- },
1334
- [Symbol.asyncIterator]() {
1335
- return this;
1336
- }
1337
- };
1338
- }
1339
- };
1340
- }
1341
1603
  };
1342
1604
 
1343
1605
  exports.BillingClient = BillingClient;
1606
+ exports.ConnectionSupervisor = ConnectionSupervisor;
1344
1607
  exports.EntitlementTier = EntitlementTier;
1345
1608
  exports.SubscriptionCycle = SubscriptionCycle;
1346
1609
  exports.SubscriptionProvider = SubscriptionProvider;
1610
+ exports.consumeSseStream = consumeSseStream;
1347
1611
  exports.createHttpTransport = createHttpTransport;
1348
1612
  exports.print = print;
1349
- //# sourceMappingURL=chunk-BL3GFOZR.cjs.map
1350
- //# sourceMappingURL=chunk-BL3GFOZR.cjs.map
1613
+ exports.refreshTokenSingleFlight = refreshTokenSingleFlight;
1614
+ //# sourceMappingURL=chunk-Y4OD5SDF.cjs.map
1615
+ //# sourceMappingURL=chunk-Y4OD5SDF.cjs.map