@dvina/sdk 4.0.210 → 4.0.214

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