@cntryl/fitz 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/dist/index.cjs +998 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +998 -175
- package/dist/index.mjs.map +1 -1
- package/dist/types/client/client.d.ts +1 -1
- package/dist/types/client/client.d.ts.map +1 -1
- package/dist/types/client/connection.d.ts +10 -3
- package/dist/types/client/connection.d.ts.map +1 -1
- package/dist/types/client/resilience.d.ts +21 -0
- package/dist/types/client/resilience.d.ts.map +1 -0
- package/dist/types/core/errors.d.ts.map +1 -1
- package/dist/types/core/types.d.ts +13 -0
- package/dist/types/core/types.d.ts.map +1 -1
- package/dist/types/core/wake-gate.d.ts +11 -0
- package/dist/types/core/wake-gate.d.ts.map +1 -0
- package/dist/types/domains/base.d.ts +9 -2
- package/dist/types/domains/base.d.ts.map +1 -1
- package/dist/types/domains/kv/transaction.d.ts.map +1 -1
- package/dist/types/domains/lease/client.d.ts.map +1 -1
- package/dist/types/domains/lease/types.d.ts.map +1 -1
- package/dist/types/domains/queue/client.d.ts +5 -0
- package/dist/types/domains/queue/client.d.ts.map +1 -1
- package/dist/types/domains/queue/types.d.ts.map +1 -1
- package/dist/types/domains/rpc/client.d.ts.map +1 -1
- package/dist/types/domains/schedule/client.d.ts +4 -1
- package/dist/types/domains/schedule/client.d.ts.map +1 -1
- package/dist/types/domains/stream/client.d.ts +7 -0
- package/dist/types/domains/stream/client.d.ts.map +1 -1
- package/dist/types/domains/stream/session.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/transport/tcp.d.ts.map +1 -1
- package/dist/types/transport/types.d.ts +7 -0
- package/dist/types/transport/types.d.ts.map +1 -1
- package/dist/types/transport/websocket.d.ts.map +1 -1
- package/package.json +7 -3
package/dist/index.mjs
CHANGED
|
@@ -531,11 +531,18 @@ function retryableKey(error) {
|
|
|
531
531
|
if (error.domainCode === void 0) return null;
|
|
532
532
|
return `${prefix}_${error.domainCode}`;
|
|
533
533
|
}
|
|
534
|
+
function isTransientQueueCommitFailure(error) {
|
|
535
|
+
if (!error.code.startsWith("QUEUE_")) return false;
|
|
536
|
+
const message = error.message.toLowerCase();
|
|
537
|
+
if (!message.includes("failed to commit transaction:")) return false;
|
|
538
|
+
return message.includes("writestall(") || message.includes("memory budget exceeded") || message.includes("lease heartbeat reports unhealthy") || message.includes("refusing writes");
|
|
539
|
+
}
|
|
534
540
|
function isRetryable(error) {
|
|
535
541
|
if (!(error instanceof FitzError)) return false;
|
|
536
542
|
if (error instanceof TimeoutError || error instanceof TransportError) return true;
|
|
537
543
|
const key = retryableKey(error);
|
|
538
|
-
|
|
544
|
+
if (key !== null && retryableErrorCodes.has(key)) return true;
|
|
545
|
+
return isTransientQueueCommitFailure(error);
|
|
539
546
|
}
|
|
540
547
|
/**
|
|
541
548
|
* Error types for Fitz client
|
|
@@ -974,7 +981,7 @@ function createMultiplexer(observability = {}) {
|
|
|
974
981
|
const span = tracer?.startSpan("fitz.request", attributes);
|
|
975
982
|
let spanEnded = false;
|
|
976
983
|
if (signal?.aborted) {
|
|
977
|
-
const error = abortError$
|
|
984
|
+
const error = abortError$3();
|
|
978
985
|
span?.recordException(error);
|
|
979
986
|
span?.end();
|
|
980
987
|
meter?.counter("fitz.request.failed", 1, {
|
|
@@ -1048,7 +1055,7 @@ function createMultiplexer(observability = {}) {
|
|
|
1048
1055
|
heapifyUp(requestEntry.timeoutIndex);
|
|
1049
1056
|
if (signal) {
|
|
1050
1057
|
onAbort = () => {
|
|
1051
|
-
failRequest(abortError$
|
|
1058
|
+
failRequest(abortError$3());
|
|
1052
1059
|
};
|
|
1053
1060
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1054
1061
|
}
|
|
@@ -1068,7 +1075,7 @@ function createMultiplexer(observability = {}) {
|
|
|
1068
1075
|
return deferred.promise;
|
|
1069
1076
|
}, (err) => {
|
|
1070
1077
|
if (!finalize()) {
|
|
1071
|
-
if (signal?.aborted) throw abortError$
|
|
1078
|
+
if (signal?.aborted) throw abortError$3();
|
|
1072
1079
|
throw err;
|
|
1073
1080
|
}
|
|
1074
1081
|
unregisterRequest(messageType, requestEntry);
|
|
@@ -1081,7 +1088,7 @@ function createMultiplexer(observability = {}) {
|
|
|
1081
1088
|
span?.end();
|
|
1082
1089
|
spanEnded = true;
|
|
1083
1090
|
}
|
|
1084
|
-
if (signal?.aborted) throw abortError$
|
|
1091
|
+
if (signal?.aborted) throw abortError$3();
|
|
1085
1092
|
throw err;
|
|
1086
1093
|
});
|
|
1087
1094
|
};
|
|
@@ -1193,7 +1200,7 @@ function createMultiplexer(observability = {}) {
|
|
|
1193
1200
|
const Multiplexer = function(observability = {}) {
|
|
1194
1201
|
return createMultiplexer(observability);
|
|
1195
1202
|
};
|
|
1196
|
-
function abortError$
|
|
1203
|
+
function abortError$3() {
|
|
1197
1204
|
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
1198
1205
|
error.name = "AbortError";
|
|
1199
1206
|
return error;
|
|
@@ -1237,8 +1244,64 @@ function readU32BE(payload, offset) {
|
|
|
1237
1244
|
return (payload[offset] << 24 | payload[offset + 1] << 16 | payload[offset + 2] << 8 | payload[offset + 3]) >>> 0;
|
|
1238
1245
|
}
|
|
1239
1246
|
//#endregion
|
|
1247
|
+
//#region src/client/resilience.ts
|
|
1248
|
+
const resilienceMetaSymbol = Symbol("fitz.resilience.meta");
|
|
1249
|
+
function attachResilienceMeta(error, meta) {
|
|
1250
|
+
if (error && (typeof error === "object" || typeof error === "function")) Object.defineProperty(error, resilienceMetaSymbol, {
|
|
1251
|
+
value: meta,
|
|
1252
|
+
configurable: true,
|
|
1253
|
+
enumerable: false,
|
|
1254
|
+
writable: true
|
|
1255
|
+
});
|
|
1256
|
+
return error;
|
|
1257
|
+
}
|
|
1258
|
+
function getResilienceMeta(error) {
|
|
1259
|
+
if (!error || typeof error !== "object" && typeof error !== "function") return;
|
|
1260
|
+
return error[resilienceMetaSymbol];
|
|
1261
|
+
}
|
|
1262
|
+
function classifyFailureKind(error) {
|
|
1263
|
+
if (error instanceof TimeoutError) return "timeout";
|
|
1264
|
+
if (error instanceof TransportError) return "transport";
|
|
1265
|
+
if (error instanceof ConnectionError) return "connection";
|
|
1266
|
+
if (error instanceof FitzError) return "domain";
|
|
1267
|
+
return "other";
|
|
1268
|
+
}
|
|
1269
|
+
function isTransientRetryError(error) {
|
|
1270
|
+
return error instanceof TimeoutError || error instanceof TransportError || error instanceof ConnectionError || isRetryable(error);
|
|
1271
|
+
}
|
|
1272
|
+
function shouldRetryOperation(retryClass, error) {
|
|
1273
|
+
switch (retryClass) {
|
|
1274
|
+
case "wait_only": return false;
|
|
1275
|
+
case "replayable_read": return isTransientRetryError(error);
|
|
1276
|
+
case "confirmed_negative_retry": {
|
|
1277
|
+
const meta = getResilienceMeta(error);
|
|
1278
|
+
return meta?.explicitNegative === true && meta.boundary === "post-send" && isRetryable(error);
|
|
1279
|
+
}
|
|
1280
|
+
default: return false;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
//#endregion
|
|
1240
1284
|
//#region src/client/connection.ts
|
|
1241
1285
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1286
|
+
const sleepWithAbort = async (ms, signal) => {
|
|
1287
|
+
if (signal?.aborted) throw abortError$2();
|
|
1288
|
+
if (ms <= 0) return;
|
|
1289
|
+
await new Promise((resolve, reject) => {
|
|
1290
|
+
const timer = setTimeout(() => {
|
|
1291
|
+
cleanup();
|
|
1292
|
+
resolve();
|
|
1293
|
+
}, ms);
|
|
1294
|
+
const onAbort = () => {
|
|
1295
|
+
cleanup();
|
|
1296
|
+
reject(abortError$2());
|
|
1297
|
+
};
|
|
1298
|
+
const cleanup = () => {
|
|
1299
|
+
clearTimeout(timer);
|
|
1300
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1301
|
+
};
|
|
1302
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1303
|
+
});
|
|
1304
|
+
};
|
|
1242
1305
|
function createAsyncHandlerDispatcher(maxConcurrency, timeoutMs, onError) {
|
|
1243
1306
|
let activeCount = 0;
|
|
1244
1307
|
let closed = false;
|
|
@@ -1306,7 +1369,7 @@ function createRequestGate(maxConcurrency, maxQueueSize) {
|
|
|
1306
1369
|
let closed = false;
|
|
1307
1370
|
const queue = [];
|
|
1308
1371
|
const acquire = async (signal) => {
|
|
1309
|
-
if (signal?.aborted) throw abortError();
|
|
1372
|
+
if (signal?.aborted) throw abortError$2();
|
|
1310
1373
|
if (closed) throw connectionClosedError();
|
|
1311
1374
|
return await new Promise((resolve, reject) => {
|
|
1312
1375
|
const grant = () => {
|
|
@@ -1329,7 +1392,7 @@ function createRequestGate(maxConcurrency, maxQueueSize) {
|
|
|
1329
1392
|
waiter.onAbort = () => {
|
|
1330
1393
|
removeWaiter(waiter);
|
|
1331
1394
|
cleanup();
|
|
1332
|
-
reject(abortError());
|
|
1395
|
+
reject(abortError$2());
|
|
1333
1396
|
};
|
|
1334
1397
|
if (signal) signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
1335
1398
|
if (activeCount < maxConcurrency) {
|
|
@@ -1378,7 +1441,7 @@ function createRequestGate(maxConcurrency, maxQueueSize) {
|
|
|
1378
1441
|
close
|
|
1379
1442
|
};
|
|
1380
1443
|
}
|
|
1381
|
-
function abortError() {
|
|
1444
|
+
function abortError$2() {
|
|
1382
1445
|
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
1383
1446
|
error.name = "AbortError";
|
|
1384
1447
|
return error;
|
|
@@ -1386,12 +1449,37 @@ function abortError() {
|
|
|
1386
1449
|
function connectionClosedError() {
|
|
1387
1450
|
return new ConnectionError("Connection closed", { state: "CLOSED" });
|
|
1388
1451
|
}
|
|
1389
|
-
function throwIfAborted(signal) {
|
|
1390
|
-
if (signal?.aborted) throw abortError();
|
|
1452
|
+
function throwIfAborted$1(signal) {
|
|
1453
|
+
if (signal?.aborted) throw abortError$2();
|
|
1391
1454
|
}
|
|
1392
1455
|
function isAbortError$1(error) {
|
|
1393
1456
|
return error instanceof Error && error.name === "AbortError";
|
|
1394
1457
|
}
|
|
1458
|
+
const waitForSharedPromise$1 = async (promise, signal) => {
|
|
1459
|
+
if (!signal) return promise;
|
|
1460
|
+
if (signal.aborted) throw abortError$2();
|
|
1461
|
+
return await new Promise((resolve, reject) => {
|
|
1462
|
+
let settled = false;
|
|
1463
|
+
const cleanup = () => {
|
|
1464
|
+
signal.removeEventListener("abort", onAbort);
|
|
1465
|
+
};
|
|
1466
|
+
const settle = (callback) => {
|
|
1467
|
+
if (settled) return;
|
|
1468
|
+
settled = true;
|
|
1469
|
+
cleanup();
|
|
1470
|
+
callback();
|
|
1471
|
+
};
|
|
1472
|
+
const onAbort = () => {
|
|
1473
|
+
settle(() => reject(abortError$2()));
|
|
1474
|
+
};
|
|
1475
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1476
|
+
promise.then((value) => {
|
|
1477
|
+
settle(() => resolve(value));
|
|
1478
|
+
}, (error) => {
|
|
1479
|
+
settle(() => reject(error));
|
|
1480
|
+
});
|
|
1481
|
+
});
|
|
1482
|
+
};
|
|
1395
1483
|
function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
1396
1484
|
const timeout = options.timeout ?? 3e4;
|
|
1397
1485
|
const authSettleDelayMs = options.authSettleDelayMs ?? 100;
|
|
@@ -1399,6 +1487,13 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1399
1487
|
const reconnectMaxAttempts = options.reconnect?.maxAttempts ?? Infinity;
|
|
1400
1488
|
const reconnectBackoffMs = options.reconnect?.backoffMs ?? 250;
|
|
1401
1489
|
const reconnectMaxBackoffMs = options.reconnect?.maxBackoffMs ?? 5e3;
|
|
1490
|
+
const retryEnabled = options.retry?.enabled ?? true;
|
|
1491
|
+
const retryMaxAttempts = options.retry?.maxAttempts ?? 3;
|
|
1492
|
+
const retryBackoffMs = options.retry?.backoffMs ?? 100;
|
|
1493
|
+
const retryMaxBackoffMs = options.retry?.maxBackoffMs ?? 1e3;
|
|
1494
|
+
const heartbeatEnabled = options.heartbeat?.enabled ?? true;
|
|
1495
|
+
const heartbeatIntervalMs = options.heartbeat?.intervalMs ?? 1e4;
|
|
1496
|
+
const heartbeatTimeoutMs = options.heartbeat?.timeoutMs ?? 3e4;
|
|
1402
1497
|
const maxInFlightRequests = options.maxInFlightRequests ?? 256;
|
|
1403
1498
|
const maxRequestQueueSize = options.maxRequestQueueSize ?? 1024;
|
|
1404
1499
|
const observability = options.observability;
|
|
@@ -1412,10 +1507,23 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1412
1507
|
let receiveLoop = null;
|
|
1413
1508
|
let receiveLoopAbort = false;
|
|
1414
1509
|
let closeRequested = false;
|
|
1510
|
+
let permanentlyClosed = false;
|
|
1511
|
+
let connectPromise = null;
|
|
1415
1512
|
let reconnectPromise = null;
|
|
1513
|
+
let connectionLossPromise = null;
|
|
1514
|
+
let reconnectRestoreActive = false;
|
|
1416
1515
|
let authOutcome = null;
|
|
1417
1516
|
let authRejected = false;
|
|
1517
|
+
let hasEstablishedSession = false;
|
|
1518
|
+
let reconnectExhausted = false;
|
|
1519
|
+
let readyWaiterCount = 0;
|
|
1520
|
+
const closeAbortController = new AbortController();
|
|
1418
1521
|
const connectionScope = createScope("connection");
|
|
1522
|
+
const readyListeners = /* @__PURE__ */ new Set();
|
|
1523
|
+
let heartbeatTimer = null;
|
|
1524
|
+
let heartbeatTransport = null;
|
|
1525
|
+
let heartbeatPending = false;
|
|
1526
|
+
let lastActivityAt = Date.now();
|
|
1419
1527
|
const log = (level, event, fields) => {
|
|
1420
1528
|
observability?.logger?.log(level, event, fields);
|
|
1421
1529
|
};
|
|
@@ -1446,6 +1554,8 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1446
1554
|
};
|
|
1447
1555
|
const handlePossibleTransportFailure = (error) => {
|
|
1448
1556
|
if (closeRequested) return;
|
|
1557
|
+
if (state !== "AUTHENTICATED") return;
|
|
1558
|
+
if (getResilienceMeta(error)?.boundary === "pre-send") return;
|
|
1449
1559
|
if (error instanceof TransportError || error instanceof ConnectionError || error instanceof AuthenticationError) handleConnectionLoss(error);
|
|
1450
1560
|
};
|
|
1451
1561
|
const multiplexer = new Multiplexer({
|
|
@@ -1456,17 +1566,35 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1456
1566
|
log("warn", "fitz.connection.handler_failed", { error: describeError(error) });
|
|
1457
1567
|
});
|
|
1458
1568
|
const connect = async (options = {}) => {
|
|
1459
|
-
closeRequested
|
|
1569
|
+
if (permanentlyClosed || closeRequested) throw connectionClosedError();
|
|
1570
|
+
throwIfAborted$1(options.signal);
|
|
1571
|
+
if (state === "AUTHENTICATED") return;
|
|
1572
|
+
if (connectPromise) {
|
|
1573
|
+
await waitForSharedPromise$1(connectPromise, options.signal);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
if (reconnectPromise || state === "CONNECTING" || state === "CONNECTED" || state === "AUTHENTICATING" || state === "RECONNECTING" || state === "DISCONNECTED" && canWaitForReconnect()) {
|
|
1577
|
+
await waitForReady(options.signal, timeout);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1460
1580
|
authRejected = false;
|
|
1461
|
-
|
|
1581
|
+
reconnectExhausted = false;
|
|
1582
|
+
const sharedConnectPromise = openAndAuthenticate(false, options.signal).finally(() => {
|
|
1583
|
+
if (connectPromise === sharedConnectPromise) connectPromise = null;
|
|
1584
|
+
});
|
|
1585
|
+
connectPromise = sharedConnectPromise;
|
|
1586
|
+
await sharedConnectPromise;
|
|
1462
1587
|
};
|
|
1463
1588
|
const close = async () => {
|
|
1464
1589
|
if (state === "CLOSED" && !transport) {
|
|
1465
1590
|
await connectionScope.dispose();
|
|
1466
1591
|
return;
|
|
1467
1592
|
}
|
|
1593
|
+
permanentlyClosed = true;
|
|
1468
1594
|
closeRequested = true;
|
|
1469
1595
|
receiveLoopAbort = true;
|
|
1596
|
+
stopHeartbeat();
|
|
1597
|
+
closeAbortController.abort();
|
|
1470
1598
|
asyncHandlerDispatcher.close();
|
|
1471
1599
|
const scopeDisposePromise = connectionScope.dispose();
|
|
1472
1600
|
setState("CLOSED");
|
|
@@ -1487,15 +1615,34 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1487
1615
|
await asyncHandlerDispatcher.drain();
|
|
1488
1616
|
await scopeDisposePromise;
|
|
1489
1617
|
};
|
|
1490
|
-
const
|
|
1618
|
+
const waitForRequestReady = async (signal, allowReconnectRestore = false) => {
|
|
1619
|
+
if (allowReconnectRestore && reconnectRestoreActive && state === "AUTHENTICATING" && !closeRequested && !authRejected && transport) return;
|
|
1620
|
+
const releaseReadyWaitSlot = acquireReadyWaitSlot();
|
|
1621
|
+
try {
|
|
1622
|
+
await waitForReady(signal, timeout);
|
|
1623
|
+
} finally {
|
|
1624
|
+
releaseReadyWaitSlot?.();
|
|
1625
|
+
}
|
|
1491
1626
|
ensureAuthenticated();
|
|
1627
|
+
};
|
|
1628
|
+
const requestInternal = async (messageType, requestPayload, signal, allowReconnectRestore = false) => {
|
|
1629
|
+
let sendStarted = false;
|
|
1630
|
+
await waitForRequestReady(signal, allowReconnectRestore);
|
|
1492
1631
|
const releaseRequestSlot = await requestGate.acquire(signal);
|
|
1493
1632
|
const startedAt = Date.now();
|
|
1494
1633
|
try {
|
|
1495
1634
|
const activeTransport = ensureTransport();
|
|
1496
1635
|
const frame = FrameCodec.encodeFrame(messageType, requestPayload);
|
|
1497
|
-
return await multiplexer.request(messageType, frame, (data) =>
|
|
1636
|
+
return await multiplexer.request(messageType, frame, (data) => {
|
|
1637
|
+
sendStarted = true;
|
|
1638
|
+
return sendSerialized(activeTransport, data);
|
|
1639
|
+
}, timeout, signal);
|
|
1498
1640
|
} catch (error) {
|
|
1641
|
+
attachResilienceMeta(error, {
|
|
1642
|
+
boundary: sendStarted ? "post-send" : "pre-send",
|
|
1643
|
+
failureKind: classifyFailureKind(error),
|
|
1644
|
+
explicitNegative: false
|
|
1645
|
+
});
|
|
1499
1646
|
log("error", "fitz.connection.request_failed", {
|
|
1500
1647
|
operation: "request",
|
|
1501
1648
|
state,
|
|
@@ -1510,13 +1657,34 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1510
1657
|
releaseRequestSlot();
|
|
1511
1658
|
}
|
|
1512
1659
|
};
|
|
1513
|
-
const
|
|
1660
|
+
const request = async (messageType, requestPayload, signal) => {
|
|
1661
|
+
return await requestInternal(messageType, requestPayload, signal);
|
|
1662
|
+
};
|
|
1663
|
+
const requestDuringReconnectRestore = async (messageType, requestPayload, signal) => {
|
|
1664
|
+
return await requestInternal(messageType, requestPayload, signal, true);
|
|
1665
|
+
};
|
|
1666
|
+
const send = async (messageType, requestPayload, signal) => {
|
|
1667
|
+
let sendStarted = false;
|
|
1668
|
+
const releaseReadyWaitSlot = acquireReadyWaitSlot();
|
|
1669
|
+
try {
|
|
1670
|
+
await waitForReady(signal, timeout);
|
|
1671
|
+
} finally {
|
|
1672
|
+
releaseReadyWaitSlot?.();
|
|
1673
|
+
}
|
|
1514
1674
|
ensureAuthenticated();
|
|
1515
|
-
const releaseRequestSlot = await requestGate.acquire();
|
|
1675
|
+
const releaseRequestSlot = await requestGate.acquire(signal);
|
|
1516
1676
|
const startedAt = Date.now();
|
|
1517
1677
|
try {
|
|
1518
|
-
|
|
1678
|
+
const activeTransport = ensureTransport();
|
|
1679
|
+
const frame = FrameCodec.encodeFrame(messageType, requestPayload);
|
|
1680
|
+
sendStarted = true;
|
|
1681
|
+
await sendSerialized(activeTransport, frame);
|
|
1519
1682
|
} catch (error) {
|
|
1683
|
+
attachResilienceMeta(error, {
|
|
1684
|
+
boundary: sendStarted ? "post-send" : "pre-send",
|
|
1685
|
+
failureKind: classifyFailureKind(error),
|
|
1686
|
+
explicitNegative: false
|
|
1687
|
+
});
|
|
1520
1688
|
log("error", "fitz.connection.send_failed", {
|
|
1521
1689
|
operation: "send",
|
|
1522
1690
|
state,
|
|
@@ -1531,8 +1699,8 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1531
1699
|
releaseRequestSlot();
|
|
1532
1700
|
}
|
|
1533
1701
|
};
|
|
1534
|
-
const sendFireAndForget = async (messageType, requestPayload) => {
|
|
1535
|
-
await send(messageType, requestPayload);
|
|
1702
|
+
const sendFireAndForget = async (messageType, requestPayload, signal) => {
|
|
1703
|
+
await send(messageType, requestPayload, signal);
|
|
1536
1704
|
};
|
|
1537
1705
|
const registerNotificationHandler = (messageType, handler) => {
|
|
1538
1706
|
multiplexer.registerNotificationHandler(messageType, handler);
|
|
@@ -1560,22 +1728,224 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1560
1728
|
const getState = () => state;
|
|
1561
1729
|
const isConnected = () => state === "AUTHENTICATED";
|
|
1562
1730
|
const getUrl = () => ensureTransport().getUrl();
|
|
1731
|
+
const canWaitForReconnect = () => {
|
|
1732
|
+
return reconnectEnabled && hasEstablishedSession && !reconnectExhausted && !authRejected;
|
|
1733
|
+
};
|
|
1734
|
+
const readyFailure = () => {
|
|
1735
|
+
if (state === "AUTHENTICATED") return null;
|
|
1736
|
+
if (closeRequested || state === "CLOSED") return connectionClosedError();
|
|
1737
|
+
if (authRejected) return new AuthenticationError("Authentication rejected", { state });
|
|
1738
|
+
if (state === "CONNECTING" || state === "CONNECTED" || state === "AUTHENTICATING" || state === "RECONNECTING") return null;
|
|
1739
|
+
if (state === "DISCONNECTED" && canWaitForReconnect()) return null;
|
|
1740
|
+
return new ConnectionError(`Cannot use connection while state is ${state}`, { state });
|
|
1741
|
+
};
|
|
1742
|
+
const notifyReadyListeners = () => {
|
|
1743
|
+
for (const listener of readyListeners) listener();
|
|
1744
|
+
};
|
|
1745
|
+
const acquireReadyWaitSlot = () => {
|
|
1746
|
+
const failure = readyFailure();
|
|
1747
|
+
if (state === "AUTHENTICATED" || failure) return null;
|
|
1748
|
+
if (readyWaiterCount >= maxRequestQueueSize) throw new RequestQueueFullError();
|
|
1749
|
+
readyWaiterCount += 1;
|
|
1750
|
+
let released = false;
|
|
1751
|
+
return () => {
|
|
1752
|
+
if (released) return;
|
|
1753
|
+
released = true;
|
|
1754
|
+
readyWaiterCount = Math.max(readyWaiterCount - 1, 0);
|
|
1755
|
+
};
|
|
1756
|
+
};
|
|
1757
|
+
const waitForReady = async (signal, waitTimeoutMs = timeout) => {
|
|
1758
|
+
throwIfAborted$1(signal);
|
|
1759
|
+
const immediateFailure = readyFailure();
|
|
1760
|
+
if (!immediateFailure) {
|
|
1761
|
+
if (state === "AUTHENTICATED") return;
|
|
1762
|
+
} else throw immediateFailure;
|
|
1763
|
+
await new Promise((resolve, reject) => {
|
|
1764
|
+
let settled = false;
|
|
1765
|
+
let timeoutId = null;
|
|
1766
|
+
const cleanup = () => {
|
|
1767
|
+
readyListeners.delete(onStateChange);
|
|
1768
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1769
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1770
|
+
};
|
|
1771
|
+
const settle = (cb) => {
|
|
1772
|
+
if (settled) return;
|
|
1773
|
+
settled = true;
|
|
1774
|
+
cleanup();
|
|
1775
|
+
cb();
|
|
1776
|
+
};
|
|
1777
|
+
const onAbort = () => {
|
|
1778
|
+
settle(() => reject(abortError$2()));
|
|
1779
|
+
};
|
|
1780
|
+
const onStateChange = () => {
|
|
1781
|
+
const failure = readyFailure();
|
|
1782
|
+
if (state === "AUTHENTICATED") {
|
|
1783
|
+
settle(resolve);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
if (failure) settle(() => reject(failure));
|
|
1787
|
+
};
|
|
1788
|
+
readyListeners.add(onStateChange);
|
|
1789
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1790
|
+
timeoutId = setTimeout(() => {
|
|
1791
|
+
settle(() => reject(new ConnectionError("Timed out waiting for connection to become ready", { state })));
|
|
1792
|
+
}, waitTimeoutMs);
|
|
1793
|
+
onStateChange();
|
|
1794
|
+
});
|
|
1795
|
+
};
|
|
1796
|
+
const waitUntilReady = async (signal, waitTimeoutMs = timeout) => {
|
|
1797
|
+
await waitForReady(signal, waitTimeoutMs);
|
|
1798
|
+
};
|
|
1799
|
+
const shouldWaitForReconnect = () => {
|
|
1800
|
+
return reconnectPromise !== null || state === "RECONNECTING" || state === "DISCONNECTED" && canWaitForReconnect();
|
|
1801
|
+
};
|
|
1802
|
+
const getRetryDelayMs = (baseDelayMs) => {
|
|
1803
|
+
const jitter = Math.floor(Math.random() * baseDelayMs * .5);
|
|
1804
|
+
return Math.min(Math.max(baseDelayMs + jitter, 1), retryMaxBackoffMs);
|
|
1805
|
+
};
|
|
1806
|
+
const recordRetry = (operation, attempt, delayMs, error) => {
|
|
1807
|
+
const meta = getResilienceMeta(error);
|
|
1808
|
+
log("warn", "fitz.request.retry", {
|
|
1809
|
+
domain: operation.domain,
|
|
1810
|
+
operation: operation.operation,
|
|
1811
|
+
attempt,
|
|
1812
|
+
delayMs,
|
|
1813
|
+
boundary: meta?.boundary ?? "unknown",
|
|
1814
|
+
error: describeError(error),
|
|
1815
|
+
...describeErrorFields(error)
|
|
1816
|
+
});
|
|
1817
|
+
observability?.meter?.counter("fitz.request.retry", 1, {
|
|
1818
|
+
domain: operation.domain,
|
|
1819
|
+
operation: operation.operation,
|
|
1820
|
+
boundary: meta?.boundary ?? "unknown"
|
|
1821
|
+
});
|
|
1822
|
+
};
|
|
1823
|
+
const recordRetryExhausted = (operation, attempt, error) => {
|
|
1824
|
+
const meta = getResilienceMeta(error);
|
|
1825
|
+
log("warn", "fitz.request.retry_exhausted", {
|
|
1826
|
+
domain: operation.domain,
|
|
1827
|
+
operation: operation.operation,
|
|
1828
|
+
attempt,
|
|
1829
|
+
boundary: meta?.boundary ?? "unknown",
|
|
1830
|
+
error: describeError(error),
|
|
1831
|
+
...describeErrorFields(error)
|
|
1832
|
+
});
|
|
1833
|
+
observability?.meter?.counter("fitz.request.retry_exhausted", 1, {
|
|
1834
|
+
domain: operation.domain,
|
|
1835
|
+
operation: operation.operation,
|
|
1836
|
+
boundary: meta?.boundary ?? "unknown"
|
|
1837
|
+
});
|
|
1838
|
+
};
|
|
1839
|
+
const executeWithRetry = async (operation, task) => {
|
|
1840
|
+
if (!retryEnabled || operation.retryClass === "wait_only") return task();
|
|
1841
|
+
let attempt = 0;
|
|
1842
|
+
let delayMs = retryBackoffMs;
|
|
1843
|
+
while (true) {
|
|
1844
|
+
attempt += 1;
|
|
1845
|
+
try {
|
|
1846
|
+
return await task();
|
|
1847
|
+
} catch (error) {
|
|
1848
|
+
if (isAbortError$1(error)) throw error;
|
|
1849
|
+
if (!shouldRetryOperation(operation.retryClass, error)) throw error;
|
|
1850
|
+
if (attempt >= retryMaxAttempts) {
|
|
1851
|
+
recordRetryExhausted(operation, attempt, error);
|
|
1852
|
+
throw error;
|
|
1853
|
+
}
|
|
1854
|
+
const actualDelayMs = getRetryDelayMs(delayMs);
|
|
1855
|
+
recordRetry(operation, attempt, actualDelayMs, error);
|
|
1856
|
+
await sleepWithAbort(actualDelayMs, operation.signal);
|
|
1857
|
+
delayMs = Math.min(delayMs * 2, retryMaxBackoffMs);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
const withWriteLock = async (operation) => {
|
|
1862
|
+
const prior = writeChain;
|
|
1863
|
+
let release;
|
|
1864
|
+
writeChain = new Promise((resolve) => {
|
|
1865
|
+
release = resolve;
|
|
1866
|
+
});
|
|
1867
|
+
await prior;
|
|
1868
|
+
try {
|
|
1869
|
+
return await operation();
|
|
1870
|
+
} finally {
|
|
1871
|
+
release();
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
const markOutboundActivity = () => {
|
|
1875
|
+
lastActivityAt = Date.now();
|
|
1876
|
+
};
|
|
1877
|
+
const markRemoteActivity = () => {
|
|
1878
|
+
lastActivityAt = Date.now();
|
|
1879
|
+
};
|
|
1880
|
+
const stopHeartbeat = () => {
|
|
1881
|
+
if (heartbeatTimer) {
|
|
1882
|
+
clearTimeout(heartbeatTimer);
|
|
1883
|
+
heartbeatTimer = null;
|
|
1884
|
+
}
|
|
1885
|
+
heartbeatTransport = null;
|
|
1886
|
+
heartbeatPending = false;
|
|
1887
|
+
};
|
|
1888
|
+
const startHeartbeat = (activeTransport) => {
|
|
1889
|
+
if (!heartbeatEnabled) return;
|
|
1890
|
+
stopHeartbeat();
|
|
1891
|
+
activeTransport.enableKeepAlive?.(heartbeatIntervalMs);
|
|
1892
|
+
heartbeatTransport = activeTransport;
|
|
1893
|
+
markRemoteActivity();
|
|
1894
|
+
const scheduleNext = () => {
|
|
1895
|
+
if (closeRequested || receiveLoopAbort || heartbeatTransport !== activeTransport) return;
|
|
1896
|
+
heartbeatTimer = setTimeout(tick, heartbeatIntervalMs);
|
|
1897
|
+
};
|
|
1898
|
+
const tick = () => {
|
|
1899
|
+
heartbeatTimer = null;
|
|
1900
|
+
if (closeRequested || receiveLoopAbort || heartbeatTransport !== activeTransport) return;
|
|
1901
|
+
const now = Date.now();
|
|
1902
|
+
if (now - lastActivityAt < heartbeatIntervalMs) {
|
|
1903
|
+
scheduleNext();
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
const supportsHeartbeat = activeTransport.supportsHeartbeat?.() ?? true;
|
|
1907
|
+
if (!heartbeatPending && supportsHeartbeat && activeTransport.sendHeartbeat) {
|
|
1908
|
+
heartbeatPending = true;
|
|
1909
|
+
const heartbeatSentAt = now;
|
|
1910
|
+
const dispatchHeartbeat = (heartbeat) => activeTransport.sendHeartbeat(heartbeat);
|
|
1911
|
+
withWriteLock(async () => {
|
|
1912
|
+
await dispatchHeartbeat({ timeoutMs: heartbeatTimeoutMs });
|
|
1913
|
+
}).then(() => {
|
|
1914
|
+
if (heartbeatTransport !== activeTransport) return;
|
|
1915
|
+
heartbeatPending = false;
|
|
1916
|
+
markRemoteActivity();
|
|
1917
|
+
}).catch((error) => {
|
|
1918
|
+
if (heartbeatTransport !== activeTransport) return;
|
|
1919
|
+
heartbeatPending = false;
|
|
1920
|
+
if (lastActivityAt > heartbeatSentAt) return;
|
|
1921
|
+
const heartbeatError = new TransportError(`Heartbeat failed: ${describeError(error)}`);
|
|
1922
|
+
activeTransport.close().catch(() => void 0);
|
|
1923
|
+
handleConnectionLoss(heartbeatError);
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
scheduleNext();
|
|
1927
|
+
};
|
|
1928
|
+
scheduleNext();
|
|
1929
|
+
};
|
|
1563
1930
|
const openAndAuthenticate = async (isReconnect, signal) => {
|
|
1564
|
-
throwIfAborted(signal);
|
|
1931
|
+
throwIfAborted$1(signal);
|
|
1565
1932
|
receiveLoopAbort = false;
|
|
1566
1933
|
frameParser.parseFrames(new Uint8Array(0));
|
|
1567
1934
|
requestGate = createRequestGate(maxInFlightRequests, maxRequestQueueSize);
|
|
1568
1935
|
const activeTransport = transportFactory();
|
|
1569
1936
|
transport = activeTransport;
|
|
1937
|
+
stopHeartbeat();
|
|
1570
1938
|
setState(isReconnect ? "RECONNECTING" : "CONNECTING");
|
|
1571
1939
|
emitLifecycleEvent(isReconnect ? "reconnect_start" : "connect_start");
|
|
1572
1940
|
await activeTransport.connect();
|
|
1941
|
+
markRemoteActivity();
|
|
1573
1942
|
if (closeRequested) {
|
|
1943
|
+
stopHeartbeat();
|
|
1574
1944
|
await activeTransport.close().catch(() => void 0);
|
|
1575
1945
|
if (transport === activeTransport) transport = null;
|
|
1576
1946
|
throw connectionClosedError();
|
|
1577
1947
|
}
|
|
1578
|
-
throwIfAborted(signal);
|
|
1948
|
+
throwIfAborted$1(signal);
|
|
1579
1949
|
receiveLoop = startReceiveLoop();
|
|
1580
1950
|
setState("CONNECTED");
|
|
1581
1951
|
setState("AUTHENTICATING");
|
|
@@ -1584,23 +1954,34 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1584
1954
|
try {
|
|
1585
1955
|
await sendConnect();
|
|
1586
1956
|
if (closeRequested) throw connectionClosedError();
|
|
1587
|
-
throwIfAborted(signal);
|
|
1957
|
+
throwIfAborted$1(signal);
|
|
1588
1958
|
await Promise.race([authOutcome.promise, sleep(authSettleDelayMs)]);
|
|
1589
1959
|
if (closeRequested) throw connectionClosedError();
|
|
1590
|
-
throwIfAborted(signal);
|
|
1960
|
+
throwIfAborted$1(signal);
|
|
1591
1961
|
authOutcome?.resolve();
|
|
1592
1962
|
authOutcome = null;
|
|
1593
1963
|
if (isReconnect) {
|
|
1594
|
-
|
|
1595
|
-
|
|
1964
|
+
multiplexer.setConnected();
|
|
1965
|
+
reconnectRestoreActive = true;
|
|
1966
|
+
try {
|
|
1967
|
+
await restoreReconnectState();
|
|
1968
|
+
if (closeRequested) throw connectionClosedError();
|
|
1969
|
+
} finally {
|
|
1970
|
+
reconnectRestoreActive = false;
|
|
1971
|
+
}
|
|
1596
1972
|
}
|
|
1973
|
+
hasEstablishedSession = true;
|
|
1974
|
+
reconnectExhausted = false;
|
|
1597
1975
|
setState("AUTHENTICATED");
|
|
1598
|
-
|
|
1976
|
+
startHeartbeat(activeTransport);
|
|
1977
|
+
if (!isReconnect) multiplexer.setConnected();
|
|
1599
1978
|
emitLifecycleEvent(isReconnect ? "reconnect_succeeded" : "connect_succeeded");
|
|
1600
1979
|
} catch (error) {
|
|
1601
1980
|
authOutcome = null;
|
|
1981
|
+
reconnectRestoreActive = false;
|
|
1602
1982
|
multiplexer.setDisconnected();
|
|
1603
1983
|
emitDisconnect();
|
|
1984
|
+
stopHeartbeat();
|
|
1604
1985
|
if (activeTransport) await activeTransport.close().catch(() => void 0);
|
|
1605
1986
|
if (transport === activeTransport) transport = null;
|
|
1606
1987
|
const rejectedAuth = error instanceof AuthenticationError;
|
|
@@ -1608,7 +1989,7 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1608
1989
|
if (closeRequested) setState("CLOSED");
|
|
1609
1990
|
else setState(rejectedAuth ? "CLOSED" : "DISCONNECTED");
|
|
1610
1991
|
emitLifecycleEvent(isReconnect ? "reconnect_failed" : "connect_failed", error);
|
|
1611
|
-
if (isAbortError$1(error)) throw abortError();
|
|
1992
|
+
if (isAbortError$1(error)) throw abortError$2();
|
|
1612
1993
|
throw error;
|
|
1613
1994
|
}
|
|
1614
1995
|
};
|
|
@@ -1616,10 +1997,12 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1616
1997
|
const token = await tokenProvider();
|
|
1617
1998
|
const frame = FrameCodec.encodeFrame(1, utf8Encoder.encode(token));
|
|
1618
1999
|
await ensureTransport().send(frame);
|
|
2000
|
+
markOutboundActivity();
|
|
1619
2001
|
};
|
|
1620
2002
|
const startReceiveLoop = async () => {
|
|
1621
2003
|
while (!receiveLoopAbort && !closeRequested) try {
|
|
1622
2004
|
const data = await ensureTransport().receive();
|
|
2005
|
+
markRemoteActivity();
|
|
1623
2006
|
const frames = frameParser.parseFrames(data);
|
|
1624
2007
|
for (const frame of frames) multiplexer.dispatch(frame.messageType, frame.payload);
|
|
1625
2008
|
} catch (error) {
|
|
@@ -1629,6 +2012,17 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1629
2012
|
}
|
|
1630
2013
|
};
|
|
1631
2014
|
const handleConnectionLoss = async (error) => {
|
|
2015
|
+
if (connectionLossPromise) {
|
|
2016
|
+
await connectionLossPromise;
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
connectionLossPromise = handleConnectionLossOnce(error).finally(() => {
|
|
2020
|
+
connectionLossPromise = null;
|
|
2021
|
+
});
|
|
2022
|
+
await connectionLossPromise;
|
|
2023
|
+
};
|
|
2024
|
+
const handleConnectionLossOnce = async (error) => {
|
|
2025
|
+
stopHeartbeat();
|
|
1632
2026
|
multiplexer.setDisconnected();
|
|
1633
2027
|
requestGate.close();
|
|
1634
2028
|
emitDisconnect();
|
|
@@ -1649,6 +2043,7 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1649
2043
|
emitLifecycleEvent("auth_rejected", error);
|
|
1650
2044
|
return;
|
|
1651
2045
|
}
|
|
2046
|
+
reconnectExhausted = false;
|
|
1652
2047
|
setState("DISCONNECTED");
|
|
1653
2048
|
emitLifecycleEvent("connection_lost", error);
|
|
1654
2049
|
if (!reconnectEnabled) return;
|
|
@@ -1670,12 +2065,13 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1670
2065
|
emitLifecycleEvent("reconnect_scheduled", void 0, attempts);
|
|
1671
2066
|
const actualDelayMs = getReconnectDelayMs(delayMs);
|
|
1672
2067
|
try {
|
|
1673
|
-
await
|
|
2068
|
+
await sleepWithAbort(actualDelayMs, closeAbortController.signal);
|
|
1674
2069
|
if (closeRequested) return;
|
|
1675
2070
|
await openAndAuthenticate(true);
|
|
1676
2071
|
return;
|
|
1677
2072
|
} catch (error) {
|
|
1678
2073
|
if (closeRequested) return;
|
|
2074
|
+
if (isAbortError$1(error)) return;
|
|
1679
2075
|
log("warn", "fitz.connection.reconnect_retry", {
|
|
1680
2076
|
attempts,
|
|
1681
2077
|
delayMs: actualDelayMs,
|
|
@@ -1689,6 +2085,7 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1689
2085
|
setState("CLOSED");
|
|
1690
2086
|
return;
|
|
1691
2087
|
}
|
|
2088
|
+
reconnectExhausted = true;
|
|
1692
2089
|
setState("DISCONNECTED");
|
|
1693
2090
|
emitLifecycleEvent("reconnect_exhausted", void 0, attempts);
|
|
1694
2091
|
};
|
|
@@ -1709,19 +2106,13 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1709
2106
|
};
|
|
1710
2107
|
const setState = (newState) => {
|
|
1711
2108
|
state = newState;
|
|
2109
|
+
notifyReadyListeners();
|
|
1712
2110
|
};
|
|
1713
2111
|
const sendSerialized = async (transport, data) => {
|
|
1714
|
-
|
|
1715
|
-
let release;
|
|
1716
|
-
writeChain = new Promise((resolve) => {
|
|
1717
|
-
release = resolve;
|
|
1718
|
-
});
|
|
1719
|
-
await prior;
|
|
1720
|
-
try {
|
|
2112
|
+
await withWriteLock(async () => {
|
|
1721
2113
|
await transport.send(data);
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
}
|
|
2114
|
+
markOutboundActivity();
|
|
2115
|
+
});
|
|
1725
2116
|
};
|
|
1726
2117
|
const emitLifecycleEvent = (event, error, attempt) => {
|
|
1727
2118
|
const payload = {
|
|
@@ -1748,6 +2139,7 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1748
2139
|
connect,
|
|
1749
2140
|
close,
|
|
1750
2141
|
request,
|
|
2142
|
+
requestDuringReconnectRestore,
|
|
1751
2143
|
send,
|
|
1752
2144
|
sendFireAndForget,
|
|
1753
2145
|
registerNotificationHandler,
|
|
@@ -1756,6 +2148,9 @@ function createConnection(transportFactory, tokenProvider, options = {}) {
|
|
|
1756
2148
|
onDisconnect,
|
|
1757
2149
|
getMultiplexer,
|
|
1758
2150
|
dispatchAsyncHandler,
|
|
2151
|
+
executeWithRetry,
|
|
2152
|
+
waitUntilReady,
|
|
2153
|
+
shouldWaitForReconnect,
|
|
1759
2154
|
getScope,
|
|
1760
2155
|
getState,
|
|
1761
2156
|
isConnected,
|
|
@@ -1793,6 +2188,7 @@ function createWebSocketTransport(url, options = {}) {
|
|
|
1793
2188
|
let receiverResolve = null;
|
|
1794
2189
|
const timeout = options.timeout ?? 3e4;
|
|
1795
2190
|
const maxFrameSize = options.maxFrameSize ?? 65535;
|
|
2191
|
+
const receiveTimeoutEnabled = options.receiveTimeout ?? true;
|
|
1796
2192
|
const enqueueMessage = (data) => {
|
|
1797
2193
|
if (receiverResolve) {
|
|
1798
2194
|
receiverResolve(data);
|
|
@@ -1866,6 +2262,56 @@ function createWebSocketTransport(url, options = {}) {
|
|
|
1866
2262
|
}
|
|
1867
2263
|
});
|
|
1868
2264
|
};
|
|
2265
|
+
const sendHeartbeat = async (heartbeatOptions) => {
|
|
2266
|
+
if (!connected || !ws) throw new TransportError("WebSocket is not connected");
|
|
2267
|
+
const activeWs = ws;
|
|
2268
|
+
if (typeof activeWs.ping !== "function" || typeof activeWs.once !== "function" || typeof activeWs.removeListener !== "function") throw new TransportError("WebSocket heartbeat is not supported");
|
|
2269
|
+
const socket = activeWs;
|
|
2270
|
+
return new Promise((resolve, reject) => {
|
|
2271
|
+
let settled = false;
|
|
2272
|
+
let timeoutId = null;
|
|
2273
|
+
const cleanup = () => {
|
|
2274
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2275
|
+
socket.removeListener("pong", onPong);
|
|
2276
|
+
socket.removeListener("close", onClose);
|
|
2277
|
+
socket.removeListener("error", onError);
|
|
2278
|
+
};
|
|
2279
|
+
const settle = (callback) => {
|
|
2280
|
+
if (settled) return;
|
|
2281
|
+
settled = true;
|
|
2282
|
+
cleanup();
|
|
2283
|
+
callback();
|
|
2284
|
+
};
|
|
2285
|
+
const onPong = () => {
|
|
2286
|
+
settle(resolve);
|
|
2287
|
+
};
|
|
2288
|
+
const onClose = () => {
|
|
2289
|
+
settle(() => reject(new TransportError("WebSocket closed during heartbeat")));
|
|
2290
|
+
};
|
|
2291
|
+
const onError = (...args) => {
|
|
2292
|
+
const event = args[0];
|
|
2293
|
+
const message = event instanceof Error ? event.message : event?.message || "unknown error";
|
|
2294
|
+
settle(() => reject(new TransportError(`WebSocket heartbeat failed: ${message}`)));
|
|
2295
|
+
};
|
|
2296
|
+
timeoutId = setTimeout(() => {
|
|
2297
|
+
settle(() => reject(new TimeoutError(`WebSocket heartbeat timeout after ${heartbeatOptions.timeoutMs}ms`)));
|
|
2298
|
+
}, heartbeatOptions.timeoutMs);
|
|
2299
|
+
socket.once("pong", onPong);
|
|
2300
|
+
socket.once("close", onClose);
|
|
2301
|
+
socket.once("error", onError);
|
|
2302
|
+
try {
|
|
2303
|
+
socket.ping(new Uint8Array(), void 0, (err) => {
|
|
2304
|
+
if (err) settle(() => reject(new TransportError(`WebSocket ping failed: ${err.message}`)));
|
|
2305
|
+
});
|
|
2306
|
+
} catch (err) {
|
|
2307
|
+
settle(() => reject(new TransportError(`WebSocket heartbeat error: ${err instanceof Error ? err.message : String(err)}`)));
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
};
|
|
2311
|
+
const supportsHeartbeat = () => {
|
|
2312
|
+
return typeof ws?.ping === "function" && typeof ws?.once === "function" && typeof ws?.removeListener === "function";
|
|
2313
|
+
};
|
|
2314
|
+
const enableKeepAlive = () => void 0;
|
|
1869
2315
|
const receive = async () => {
|
|
1870
2316
|
if (receiveQueue.length > 0) {
|
|
1871
2317
|
const message = receiveQueue.shift();
|
|
@@ -1874,12 +2320,12 @@ function createWebSocketTransport(url, options = {}) {
|
|
|
1874
2320
|
}
|
|
1875
2321
|
if (!connected) throw new TransportError("Connection closed");
|
|
1876
2322
|
return new Promise((resolve, reject) => {
|
|
1877
|
-
const timeoutId = setTimeout(() => {
|
|
2323
|
+
const timeoutId = receiveTimeoutEnabled ? setTimeout(() => {
|
|
1878
2324
|
receiverResolve = null;
|
|
1879
2325
|
reject(new TimeoutError(`WebSocket receive timeout after ${timeout}ms`));
|
|
1880
|
-
}, timeout);
|
|
2326
|
+
}, timeout) : null;
|
|
1881
2327
|
receiverResolve = (data) => {
|
|
1882
|
-
clearTimeout(timeoutId);
|
|
2328
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1883
2329
|
receiverResolve = null;
|
|
1884
2330
|
if (data === null) {
|
|
1885
2331
|
reject(new TransportError("Connection closed"));
|
|
@@ -1914,6 +2360,9 @@ function createWebSocketTransport(url, options = {}) {
|
|
|
1914
2360
|
connect,
|
|
1915
2361
|
send,
|
|
1916
2362
|
receive,
|
|
2363
|
+
sendHeartbeat,
|
|
2364
|
+
supportsHeartbeat,
|
|
2365
|
+
enableKeepAlive,
|
|
1917
2366
|
close,
|
|
1918
2367
|
getUrl,
|
|
1919
2368
|
isConnected
|
|
@@ -1939,6 +2388,7 @@ function createTcpTransport(url, options = {}) {
|
|
|
1939
2388
|
let receiverResolve = null;
|
|
1940
2389
|
const timeout = options.timeout ?? 3e4;
|
|
1941
2390
|
const maxFrameSize = options.maxFrameSize ?? 65535;
|
|
2391
|
+
const receiveTimeoutEnabled = options.receiveTimeout ?? true;
|
|
1942
2392
|
let lengthBuffer = new Uint8Array(4);
|
|
1943
2393
|
let lengthOffset = 0;
|
|
1944
2394
|
let currentMessageLength = null;
|
|
@@ -2013,7 +2463,7 @@ function createTcpTransport(url, options = {}) {
|
|
|
2013
2463
|
clearTimeout(connectTimeout);
|
|
2014
2464
|
connected = true;
|
|
2015
2465
|
activeSocket.setNoDelay(true);
|
|
2016
|
-
activeSocket.setTimeout(timeout);
|
|
2466
|
+
activeSocket.setTimeout(receiveTimeoutEnabled ? timeout : 0);
|
|
2017
2467
|
resolve();
|
|
2018
2468
|
});
|
|
2019
2469
|
activeSocket.on("data", (chunk) => {
|
|
@@ -2031,8 +2481,10 @@ function createTcpTransport(url, options = {}) {
|
|
|
2031
2481
|
if (receiverResolve) receiverResolve(new Uint8Array(0));
|
|
2032
2482
|
});
|
|
2033
2483
|
activeSocket.on("timeout", () => {
|
|
2034
|
-
|
|
2035
|
-
|
|
2484
|
+
if (receiveTimeoutEnabled) {
|
|
2485
|
+
activeSocket.destroy();
|
|
2486
|
+
connected = false;
|
|
2487
|
+
}
|
|
2036
2488
|
});
|
|
2037
2489
|
} catch (err) {
|
|
2038
2490
|
reject(new TransportError(`Failed to create TCP socket: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -2057,6 +2509,10 @@ function createTcpTransport(url, options = {}) {
|
|
|
2057
2509
|
});
|
|
2058
2510
|
});
|
|
2059
2511
|
};
|
|
2512
|
+
const enableKeepAlive = (intervalMs) => {
|
|
2513
|
+
socket?.setKeepAlive(true, intervalMs);
|
|
2514
|
+
};
|
|
2515
|
+
const supportsHeartbeat = () => false;
|
|
2060
2516
|
const receive = async () => {
|
|
2061
2517
|
if (receiveQueue.length > 0) {
|
|
2062
2518
|
const message = receiveQueue.shift();
|
|
@@ -2064,12 +2520,12 @@ function createTcpTransport(url, options = {}) {
|
|
|
2064
2520
|
return message;
|
|
2065
2521
|
}
|
|
2066
2522
|
return new Promise((resolve, reject) => {
|
|
2067
|
-
const timeoutId = setTimeout(() => {
|
|
2523
|
+
const timeoutId = receiveTimeoutEnabled ? setTimeout(() => {
|
|
2068
2524
|
receiverResolve = null;
|
|
2069
2525
|
reject(new TimeoutError(`TCP receive timeout after ${timeout}ms`));
|
|
2070
|
-
}, timeout);
|
|
2526
|
+
}, timeout) : null;
|
|
2071
2527
|
receiverResolve = (data) => {
|
|
2072
|
-
clearTimeout(timeoutId);
|
|
2528
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2073
2529
|
receiverResolve = null;
|
|
2074
2530
|
if (data.length === 0) reject(new TransportError("Connection closed"));
|
|
2075
2531
|
else resolve(data);
|
|
@@ -2101,6 +2557,8 @@ function createTcpTransport(url, options = {}) {
|
|
|
2101
2557
|
connect,
|
|
2102
2558
|
send,
|
|
2103
2559
|
receive,
|
|
2560
|
+
supportsHeartbeat,
|
|
2561
|
+
enableKeepAlive,
|
|
2104
2562
|
close,
|
|
2105
2563
|
getUrl,
|
|
2106
2564
|
isConnected
|
|
@@ -2139,11 +2597,23 @@ function createTransport(url, transportType = "auto", options = {}) {
|
|
|
2139
2597
|
//#region src/domains/base.ts
|
|
2140
2598
|
function createDomainClient(connection) {
|
|
2141
2599
|
const requestFrame = async (messageType, payload, signal) => connection.request(messageType, payload, signal);
|
|
2600
|
+
const requestReconnectFrame = async (messageType, payload, signal) => {
|
|
2601
|
+
const resilientConnection = connection;
|
|
2602
|
+
if (typeof resilientConnection.requestDuringReconnectRestore === "function") return await resilientConnection.requestDuringReconnectRestore(messageType, payload, signal);
|
|
2603
|
+
return await connection.request(messageType, payload, signal);
|
|
2604
|
+
};
|
|
2142
2605
|
const sendFrame = async (messageType, payload) => connection.send(messageType, payload);
|
|
2606
|
+
const runWithRetry = async (operation, task) => {
|
|
2607
|
+
const resilientConnection = connection;
|
|
2608
|
+
if (typeof resilientConnection.executeWithRetry === "function") return resilientConnection.executeWithRetry(operation, task);
|
|
2609
|
+
return task();
|
|
2610
|
+
};
|
|
2143
2611
|
return {
|
|
2144
2612
|
connection,
|
|
2145
2613
|
requestFrame,
|
|
2146
|
-
|
|
2614
|
+
requestReconnectFrame,
|
|
2615
|
+
sendFrame,
|
|
2616
|
+
runWithRetry
|
|
2147
2617
|
};
|
|
2148
2618
|
}
|
|
2149
2619
|
//#endregion
|
|
@@ -2321,8 +2791,11 @@ function createAsyncIterableIterator(iterator) {
|
|
|
2321
2791
|
//#region src/domains/kv/transaction.ts
|
|
2322
2792
|
function createKvTransaction(connection, route, txId) {
|
|
2323
2793
|
let closed = false;
|
|
2324
|
-
const
|
|
2794
|
+
const resilientConnection = connection;
|
|
2795
|
+
let unsubscribeDisconnect = () => void 0;
|
|
2796
|
+
unsubscribeDisconnect = connection.onDisconnect(() => {
|
|
2325
2797
|
closed = true;
|
|
2798
|
+
unsubscribeDisconnect();
|
|
2326
2799
|
});
|
|
2327
2800
|
const ensureOpen = () => {
|
|
2328
2801
|
if (closed) throw new KvError("Transaction already closed", "TX_CLOSED");
|
|
@@ -2337,6 +2810,10 @@ function createKvTransaction(connection, route, txId) {
|
|
|
2337
2810
|
[5]: "OperationNotAllowed"
|
|
2338
2811
|
}[status] ?? `Unknown(${status})`}`, operation, status);
|
|
2339
2812
|
};
|
|
2813
|
+
const runWithRetry = async (operation, task) => {
|
|
2814
|
+
if (typeof resilientConnection.executeWithRetry === "function") return resilientConnection.executeWithRetry(operation, task);
|
|
2815
|
+
return task();
|
|
2816
|
+
};
|
|
2340
2817
|
const put = async (key, value, signal) => {
|
|
2341
2818
|
ensureOpen();
|
|
2342
2819
|
const payload = KvCodec.encodePut(txId, route, key, value);
|
|
@@ -2351,15 +2828,22 @@ function createKvTransaction(connection, route, txId) {
|
|
|
2351
2828
|
};
|
|
2352
2829
|
const get = async (key, signal) => {
|
|
2353
2830
|
ensureOpen();
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2831
|
+
return runWithRetry({
|
|
2832
|
+
domain: "kv",
|
|
2833
|
+
operation: "get",
|
|
2834
|
+
retryClass: "replayable_read",
|
|
2835
|
+
signal
|
|
2836
|
+
}, async () => {
|
|
2837
|
+
const payload = KvCodec.encodeGet(txId, route, key);
|
|
2838
|
+
const response = await connection.request(103, payload, signal);
|
|
2839
|
+
const decoded = KvCodec.decodeGetResponse(response);
|
|
2840
|
+
checkStatus(decoded.status, "GET");
|
|
2841
|
+
if (!decoded.found || !decoded.value) return { type: "not-found" };
|
|
2842
|
+
return {
|
|
2843
|
+
type: "found",
|
|
2844
|
+
value: decoded.value
|
|
2845
|
+
};
|
|
2846
|
+
});
|
|
2363
2847
|
};
|
|
2364
2848
|
const deleteItem = async (key, signal) => {
|
|
2365
2849
|
ensureOpen();
|
|
@@ -2375,11 +2859,18 @@ function createKvTransaction(connection, route, txId) {
|
|
|
2375
2859
|
};
|
|
2376
2860
|
const scan = async (options = {}, signal) => {
|
|
2377
2861
|
ensureOpen();
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2862
|
+
return runWithRetry({
|
|
2863
|
+
domain: "kv",
|
|
2864
|
+
operation: "scan",
|
|
2865
|
+
retryClass: "replayable_read",
|
|
2866
|
+
signal
|
|
2867
|
+
}, async () => {
|
|
2868
|
+
const payload = KvCodec.encodeScan(txId, route, options);
|
|
2869
|
+
const response = await connection.request(108, payload, signal);
|
|
2870
|
+
const decoded = KvCodec.decodeScanResponse(response);
|
|
2871
|
+
checkStatus(decoded.status, "SCAN");
|
|
2872
|
+
return createAsyncIterableIterator(createSliceIterator(decoded.keys));
|
|
2873
|
+
});
|
|
2383
2874
|
};
|
|
2384
2875
|
const commit = async (signal) => {
|
|
2385
2876
|
ensureOpen();
|
|
@@ -2446,6 +2937,64 @@ const KvClient = function(connection) {
|
|
|
2446
2937
|
return createKvClient(connection);
|
|
2447
2938
|
};
|
|
2448
2939
|
//#endregion
|
|
2940
|
+
//#region src/core/wake-gate.ts
|
|
2941
|
+
function abortError$1() {
|
|
2942
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
2943
|
+
error.name = "AbortError";
|
|
2944
|
+
return error;
|
|
2945
|
+
}
|
|
2946
|
+
function createWakeGate() {
|
|
2947
|
+
let currentVersion = 0;
|
|
2948
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
2949
|
+
const cleanup = (waiter) => {
|
|
2950
|
+
waiters.delete(waiter);
|
|
2951
|
+
if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
2952
|
+
};
|
|
2953
|
+
const wake = () => {
|
|
2954
|
+
currentVersion += 1;
|
|
2955
|
+
const version = currentVersion;
|
|
2956
|
+
for (const waiter of Array.from(waiters)) {
|
|
2957
|
+
cleanup(waiter);
|
|
2958
|
+
waiter.resolve(version);
|
|
2959
|
+
}
|
|
2960
|
+
return version;
|
|
2961
|
+
};
|
|
2962
|
+
const waitAfter = async (observedVersion, options = {}) => {
|
|
2963
|
+
if (currentVersion > observedVersion) return currentVersion;
|
|
2964
|
+
if (options.signal?.aborted) throw abortError$1();
|
|
2965
|
+
return await new Promise((resolve, reject) => {
|
|
2966
|
+
const waiter = {
|
|
2967
|
+
observedVersion,
|
|
2968
|
+
resolve,
|
|
2969
|
+
reject,
|
|
2970
|
+
signal: options.signal
|
|
2971
|
+
};
|
|
2972
|
+
waiter.onAbort = () => {
|
|
2973
|
+
cleanup(waiter);
|
|
2974
|
+
reject(abortError$1());
|
|
2975
|
+
};
|
|
2976
|
+
if (options.signal) options.signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
2977
|
+
if (currentVersion > observedVersion) {
|
|
2978
|
+
cleanup(waiter);
|
|
2979
|
+
resolve(currentVersion);
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
waiters.add(waiter);
|
|
2983
|
+
});
|
|
2984
|
+
};
|
|
2985
|
+
const wait = async (options) => {
|
|
2986
|
+
return await waitAfter(currentVersion, options);
|
|
2987
|
+
};
|
|
2988
|
+
return {
|
|
2989
|
+
get version() {
|
|
2990
|
+
return currentVersion;
|
|
2991
|
+
},
|
|
2992
|
+
wake,
|
|
2993
|
+
waitAfter,
|
|
2994
|
+
wait
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
//#endregion
|
|
2449
2998
|
//#region src/domains/queue/codec.ts
|
|
2450
2999
|
/**
|
|
2451
3000
|
* Queue domain codec for encoding and decoding protocol messages.
|
|
@@ -2659,7 +3208,17 @@ const QueueCodec = {
|
|
|
2659
3208
|
//#endregion
|
|
2660
3209
|
//#region src/domains/queue/types.ts
|
|
2661
3210
|
function createQueueItem(id, token, body, route, connection) {
|
|
3211
|
+
let closed = false;
|
|
3212
|
+
let unsubscribeDisconnect = () => void 0;
|
|
3213
|
+
unsubscribeDisconnect = connection.onDisconnect(() => {
|
|
3214
|
+
closed = true;
|
|
3215
|
+
unsubscribeDisconnect();
|
|
3216
|
+
});
|
|
3217
|
+
const ensureOpen = () => {
|
|
3218
|
+
if (closed) throw new QueueError("Queue item is no longer valid after disconnect", "ITEM_CLOSED");
|
|
3219
|
+
};
|
|
2662
3220
|
const extend = async (leaseSecs, signal) => {
|
|
3221
|
+
ensureOpen();
|
|
2663
3222
|
const payload = QueueCodec.encodeExtend(route, id, token, leaseSecs);
|
|
2664
3223
|
const response = await connection.request(203, payload, signal);
|
|
2665
3224
|
const decoded = QueueCodec.decodeExtendResponse(response);
|
|
@@ -2670,6 +3229,7 @@ function createQueueItem(id, token, body, route, connection) {
|
|
|
2670
3229
|
}
|
|
2671
3230
|
};
|
|
2672
3231
|
const complete = async (signal) => {
|
|
3232
|
+
ensureOpen();
|
|
2673
3233
|
const requestPayload = QueueCodec.encodeComplete(route, id, token);
|
|
2674
3234
|
const response = await connection.request(204, requestPayload, signal);
|
|
2675
3235
|
const decoded = QueueCodec.decodeCompleteResponse(response);
|
|
@@ -2678,9 +3238,12 @@ function createQueueItem(id, token, body, route, connection) {
|
|
|
2678
3238
|
const statusName = QueueStatus[errorCode] || `Unknown(${errorCode})`;
|
|
2679
3239
|
throw new QueueError(`COMPLETE failed: ${decoded.errorMessage ?? statusName}`, statusName, errorCode);
|
|
2680
3240
|
}
|
|
3241
|
+
closed = true;
|
|
3242
|
+
unsubscribeDisconnect();
|
|
2681
3243
|
};
|
|
2682
3244
|
const testOnlyInvalidToken = () => id + 1n;
|
|
2683
3245
|
const testOnlyCompleteWithToken = async (tokenToUse, signal) => {
|
|
3246
|
+
ensureOpen();
|
|
2684
3247
|
const requestPayload = QueueCodec.encodeComplete(route, id, tokenToUse);
|
|
2685
3248
|
const response = await connection.request(204, requestPayload, signal);
|
|
2686
3249
|
const decoded = QueueCodec.decodeCompleteResponse(response);
|
|
@@ -2689,6 +3252,8 @@ function createQueueItem(id, token, body, route, connection) {
|
|
|
2689
3252
|
const statusName = QueueStatus[errorCode] || `Unknown(${errorCode})`;
|
|
2690
3253
|
throw new QueueError(`COMPLETE failed: ${decoded.errorMessage ?? statusName}`, statusName, errorCode);
|
|
2691
3254
|
}
|
|
3255
|
+
closed = true;
|
|
3256
|
+
unsubscribeDisconnect();
|
|
2692
3257
|
};
|
|
2693
3258
|
return {
|
|
2694
3259
|
body,
|
|
@@ -2726,7 +3291,7 @@ let QueueStatus = /* @__PURE__ */ function(QueueStatus) {
|
|
|
2726
3291
|
* Queue domain client.
|
|
2727
3292
|
*/
|
|
2728
3293
|
function createQueueClient(connection) {
|
|
2729
|
-
const { requestFrame } = createDomainClient(connection);
|
|
3294
|
+
const { requestFrame, requestReconnectFrame, runWithRetry } = createDomainClient(connection);
|
|
2730
3295
|
const subscriptionsByPattern = /* @__PURE__ */ new Map();
|
|
2731
3296
|
const patternsBySubId = /* @__PURE__ */ new Map();
|
|
2732
3297
|
let notificationHandlerRegistered = false;
|
|
@@ -2740,7 +3305,7 @@ function createQueueClient(connection) {
|
|
|
2740
3305
|
subscriptionsByPattern.clear();
|
|
2741
3306
|
patternsBySubId.clear();
|
|
2742
3307
|
for (const subscription of subscriptions) {
|
|
2743
|
-
const subId = await subscribeWire(subscription.pattern);
|
|
3308
|
+
const subId = await subscribeWire(subscription.pattern, requestReconnectFrame);
|
|
2744
3309
|
subscriptionsByPattern.set(subscription.pattern, {
|
|
2745
3310
|
subId,
|
|
2746
3311
|
handlers: new Map(subscription.handlers)
|
|
@@ -2750,11 +3315,17 @@ function createQueueClient(connection) {
|
|
|
2750
3315
|
});
|
|
2751
3316
|
const enqueue = async (route, body, options) => {
|
|
2752
3317
|
assertQueueRoute(route);
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3318
|
+
return runWithRetry({
|
|
3319
|
+
domain: "queue",
|
|
3320
|
+
operation: "enqueue",
|
|
3321
|
+
retryClass: "confirmed_negative_retry"
|
|
3322
|
+
}, async () => {
|
|
3323
|
+
const response = await requestFrame(200, QueueCodec.encodeEnqueue(route, body, options));
|
|
3324
|
+
const decoded = QueueCodec.decodeEnqueueResponse(response);
|
|
3325
|
+
checkStatus(decoded, "ENQUEUE");
|
|
3326
|
+
if (decoded.messageId === void 0) throw new QueueError("ENQUEUE response missing messageId", "MISSING_MESSAGE_ID");
|
|
3327
|
+
return decoded.messageId;
|
|
3328
|
+
});
|
|
2758
3329
|
};
|
|
2759
3330
|
const reserve = async (route, leaseSeconds, batchSize = 1, waitSeconds = 0) => {
|
|
2760
3331
|
assertQueueReserveRoute(route);
|
|
@@ -2762,43 +3333,55 @@ function createQueueClient(connection) {
|
|
|
2762
3333
|
let items = await reserveOnce(route, leaseSeconds, batchSize);
|
|
2763
3334
|
if (items.length > 0) return items;
|
|
2764
3335
|
const deadline = Date.now() + waitSeconds * 1e3;
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
pendingNotifications += 1;
|
|
2769
|
-
if (!waiter) return;
|
|
2770
|
-
const resolve = waiter;
|
|
2771
|
-
waiter = void 0;
|
|
2772
|
-
pendingNotifications = 0;
|
|
2773
|
-
resolve();
|
|
3336
|
+
const wakeGate = createWakeGate();
|
|
3337
|
+
const subscription = await subscribe(route, () => {
|
|
3338
|
+
wakeGate.wake();
|
|
2774
3339
|
});
|
|
2775
3340
|
try {
|
|
2776
3341
|
while (true) {
|
|
3342
|
+
const observed = wakeGate.version;
|
|
2777
3343
|
items = await reserveOnce(route, leaseSeconds, batchSize);
|
|
2778
3344
|
if (items.length > 0) return items;
|
|
2779
3345
|
const remainingMs = deadline - Date.now();
|
|
2780
3346
|
if (remainingMs <= 0) return items;
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
}
|
|
2787
|
-
const release = () => {
|
|
2788
|
-
clearTimeout(timeoutId);
|
|
2789
|
-
if (waiter === release) waiter = void 0;
|
|
2790
|
-
resolve();
|
|
2791
|
-
};
|
|
2792
|
-
const timeoutId = setTimeout(release, remainingMs);
|
|
2793
|
-
waiter = release;
|
|
3347
|
+
const waitPromise = wakeGate.waitAfter(observed);
|
|
3348
|
+
let timeoutId = null;
|
|
3349
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
3350
|
+
timeoutId = setTimeout(() => {
|
|
3351
|
+
resolve("timeout");
|
|
3352
|
+
}, remainingMs);
|
|
2794
3353
|
});
|
|
3354
|
+
if (await Promise.race([waitPromise.then(() => {
|
|
3355
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3356
|
+
return "wake";
|
|
3357
|
+
}), timeoutPromise]) === "timeout") return items;
|
|
2795
3358
|
}
|
|
2796
3359
|
} finally {
|
|
2797
|
-
await subscription.unsubscribe();
|
|
3360
|
+
await subscription.unsubscribe().catch(() => void 0);
|
|
2798
3361
|
}
|
|
2799
3362
|
};
|
|
2800
|
-
const
|
|
2801
|
-
|
|
3363
|
+
const reserveWhenAvailable = async function* (route, options) {
|
|
3364
|
+
assertQueueReserveRoute(route);
|
|
3365
|
+
const wakeGate = createWakeGate();
|
|
3366
|
+
const subscription = await subscribe(route, () => {
|
|
3367
|
+
wakeGate.wake();
|
|
3368
|
+
});
|
|
3369
|
+
try {
|
|
3370
|
+
while (true) {
|
|
3371
|
+
const observed = wakeGate.version;
|
|
3372
|
+
const items = await reserveOnce(route, options.leaseSeconds, options.batchSize ?? 1, options.signal);
|
|
3373
|
+
if (items.length > 0) {
|
|
3374
|
+
yield items;
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
await wakeGate.waitAfter(observed, { signal: options.signal });
|
|
3378
|
+
}
|
|
3379
|
+
} finally {
|
|
3380
|
+
await subscription.unsubscribe().catch(() => void 0);
|
|
3381
|
+
}
|
|
3382
|
+
};
|
|
3383
|
+
const reserveOnce = async (route, leaseSeconds, batchSize, signal) => {
|
|
3384
|
+
const response = await requestFrame(202, QueueCodec.encodeReserve(route, leaseSeconds, batchSize), signal);
|
|
2802
3385
|
const decoded = QueueCodec.decodeReserveResponse(response);
|
|
2803
3386
|
checkStatus(decoded, "RESERVE");
|
|
2804
3387
|
return (decoded.items ?? []).map((item) => createQueueItem(item.id, item.token, item.body, route, connection));
|
|
@@ -2810,8 +3393,8 @@ function createQueueClient(connection) {
|
|
|
2810
3393
|
if (existing) return addLocalSubscription(pattern, existing.subId, handler);
|
|
2811
3394
|
return addLocalSubscription(pattern, await subscribeWire(pattern), handler);
|
|
2812
3395
|
};
|
|
2813
|
-
const subscribeWire = async (pattern) => {
|
|
2814
|
-
const response = await
|
|
3396
|
+
const subscribeWire = async (pattern, request = requestFrame) => {
|
|
3397
|
+
const response = await request(207, QueueCodec.encodeSubscribe(wireWatchPattern(pattern)));
|
|
2815
3398
|
const decoded = QueueCodec.decodeSubscribeResponse(response);
|
|
2816
3399
|
checkStatus(decoded, "SUBSCRIBE");
|
|
2817
3400
|
if (decoded.subId === void 0) throw new QueueError("SUBSCRIBE response missing subId", "MISSING_SUB_ID");
|
|
@@ -2878,11 +3461,16 @@ function createQueueClient(connection) {
|
|
|
2878
3461
|
[4]: "QueueFull",
|
|
2879
3462
|
[5]: "InvalidDelay"
|
|
2880
3463
|
}[errorCode] ?? `Unknown(${errorCode})`;
|
|
2881
|
-
throw new QueueError(`${operation} failed: ${response.errorMessage ?? statusName}`, statusName, errorCode)
|
|
3464
|
+
throw attachResilienceMeta(new QueueError(`${operation} failed: ${response.errorMessage ?? statusName}`, statusName, errorCode), {
|
|
3465
|
+
boundary: "post-send",
|
|
3466
|
+
failureKind: "domain",
|
|
3467
|
+
explicitNegative: true
|
|
3468
|
+
});
|
|
2882
3469
|
};
|
|
2883
3470
|
return {
|
|
2884
3471
|
enqueue,
|
|
2885
3472
|
reserve,
|
|
3473
|
+
reserveWhenAvailable,
|
|
2886
3474
|
subscribe
|
|
2887
3475
|
};
|
|
2888
3476
|
}
|
|
@@ -3193,16 +3781,35 @@ function createRpcSubscription(route, unsubscribeFn) {
|
|
|
3193
3781
|
*/
|
|
3194
3782
|
function createRpcResponseWriter(connection, correlationId) {
|
|
3195
3783
|
let sequence = 0n;
|
|
3784
|
+
let stale = false;
|
|
3785
|
+
let unsubscribeDisconnect = () => void 0;
|
|
3786
|
+
const dispose = () => {
|
|
3787
|
+
if (stale) return;
|
|
3788
|
+
stale = true;
|
|
3789
|
+
unsubscribeDisconnect();
|
|
3790
|
+
unsubscribeDisconnect = () => void 0;
|
|
3791
|
+
};
|
|
3792
|
+
unsubscribeDisconnect = connection.onDisconnect(() => {
|
|
3793
|
+
dispose();
|
|
3794
|
+
});
|
|
3196
3795
|
const send = async (body, isEnd) => {
|
|
3796
|
+
if (stale) throw new ConnectionError("RPC response writer is no longer valid");
|
|
3197
3797
|
const payload = RpcCodec.encodeResponse(correlationId, sequence++, body, isEnd);
|
|
3198
3798
|
try {
|
|
3199
3799
|
await connection.send(303, payload);
|
|
3800
|
+
if (isEnd) dispose();
|
|
3200
3801
|
} catch (error) {
|
|
3201
|
-
if (isBenignShutdownError(error, connection))
|
|
3802
|
+
if (isBenignShutdownError(error, connection)) {
|
|
3803
|
+
dispose();
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3202
3806
|
throw error;
|
|
3203
3807
|
}
|
|
3204
3808
|
};
|
|
3205
|
-
return {
|
|
3809
|
+
return {
|
|
3810
|
+
send,
|
|
3811
|
+
dispose
|
|
3812
|
+
};
|
|
3206
3813
|
}
|
|
3207
3814
|
function isBenignShutdownError(error, connection) {
|
|
3208
3815
|
if (connection.getState() !== "AUTHENTICATED") return true;
|
|
@@ -3350,7 +3957,7 @@ const RpcClient = function(connection) {
|
|
|
3350
3957
|
return createRpcClient(connection);
|
|
3351
3958
|
};
|
|
3352
3959
|
function createRpcClient(connection) {
|
|
3353
|
-
const { requestFrame } = createDomainClient(connection);
|
|
3960
|
+
const { requestFrame, requestReconnectFrame } = createDomainClient(connection);
|
|
3354
3961
|
const pendingRpcs = /* @__PURE__ */ new Map();
|
|
3355
3962
|
const workers = /* @__PURE__ */ new Map();
|
|
3356
3963
|
let initialized = false;
|
|
@@ -3368,7 +3975,7 @@ function createRpcClient(connection) {
|
|
|
3368
3975
|
if (workers.size === 0) return;
|
|
3369
3976
|
const registeredWorkers = Array.from(workers.entries());
|
|
3370
3977
|
workers.clear();
|
|
3371
|
-
for (const [route, handler] of registeredWorkers) await
|
|
3978
|
+
for (const [route, handler] of registeredWorkers) await registerWorkerInternal(route, handler, requestReconnectFrame);
|
|
3372
3979
|
});
|
|
3373
3980
|
const call = async (route, body, options) => {
|
|
3374
3981
|
assertRpcRoute(route);
|
|
@@ -3394,13 +4001,16 @@ function createRpcClient(connection) {
|
|
|
3394
4001
|
throw error;
|
|
3395
4002
|
}
|
|
3396
4003
|
};
|
|
3397
|
-
const
|
|
3398
|
-
|
|
3399
|
-
initRpcHandler();
|
|
3400
|
-
const response = await requestFrame(300, RpcCodec.encodeSubscribeWorker(route));
|
|
4004
|
+
const registerWorkerInternal = async (route, handler, request = requestFrame) => {
|
|
4005
|
+
const response = await request(300, RpcCodec.encodeSubscribeWorker(route));
|
|
3401
4006
|
const decoded = RpcCodec.decodeSubscribeWorkerResponse(response);
|
|
3402
4007
|
if (decoded.status !== 0) throw new RpcError(`RPC SUBSCRIBE_WORKER failed: status ${decoded.status}`, "SUBSCRIBE_FAILED", decoded.status);
|
|
3403
4008
|
workers.set(route, handler);
|
|
4009
|
+
};
|
|
4010
|
+
const registerWorker = async (route, handler) => {
|
|
4011
|
+
assertRpcRoute(route);
|
|
4012
|
+
initRpcHandler();
|
|
4013
|
+
await registerWorkerInternal(route, handler);
|
|
3404
4014
|
const unsubscribeFn = async (registeredRoute) => {
|
|
3405
4015
|
await unregisterWorker(registeredRoute);
|
|
3406
4016
|
};
|
|
@@ -3469,6 +4079,8 @@ function createRpcClient(connection) {
|
|
|
3469
4079
|
try {
|
|
3470
4080
|
await writer.send(utf8Encoder.encode(`Handler error: ${message}`), true);
|
|
3471
4081
|
} catch {}
|
|
4082
|
+
} finally {
|
|
4083
|
+
writer.dispose();
|
|
3472
4084
|
}
|
|
3473
4085
|
});
|
|
3474
4086
|
};
|
|
@@ -3704,7 +4316,17 @@ function createLeaseSubscription(subId, pattern, unsubscribeFn) {
|
|
|
3704
4316
|
function createLease(token, expiresAt, route, connection) {
|
|
3705
4317
|
let currentToken = token;
|
|
3706
4318
|
let currentExpiry = expiresAt;
|
|
4319
|
+
let closed = false;
|
|
4320
|
+
let unsubscribeDisconnect = () => void 0;
|
|
4321
|
+
unsubscribeDisconnect = connection.onDisconnect(() => {
|
|
4322
|
+
closed = true;
|
|
4323
|
+
unsubscribeDisconnect();
|
|
4324
|
+
});
|
|
4325
|
+
const ensureOpen = () => {
|
|
4326
|
+
if (closed) throw new LeaseError("Lease handle is no longer valid after disconnect", "CLOSED");
|
|
4327
|
+
};
|
|
3707
4328
|
const extend = async (ttlSecs, signal) => {
|
|
4329
|
+
ensureOpen();
|
|
3708
4330
|
const requestPayload = LeaseCodec.encodeExtend(route, currentToken, ttlSecs);
|
|
3709
4331
|
const data = assertSuccess(await connection.request(401, requestPayload, signal), "EXTEND");
|
|
3710
4332
|
if (data && data.length >= 8) currentToken = new BufferReader(data).readU64BE();
|
|
@@ -3712,12 +4334,16 @@ function createLease(token, expiresAt, route, connection) {
|
|
|
3712
4334
|
return currentExpiry;
|
|
3713
4335
|
};
|
|
3714
4336
|
const release = async (signal) => {
|
|
4337
|
+
ensureOpen();
|
|
3715
4338
|
const payload = LeaseCodec.encodeRelease(route, currentToken);
|
|
3716
4339
|
assertSuccess(await connection.request(402, payload, signal), "RELEASE");
|
|
4340
|
+
closed = true;
|
|
4341
|
+
unsubscribeDisconnect();
|
|
3717
4342
|
};
|
|
3718
4343
|
const getExpiry = () => currentExpiry;
|
|
3719
4344
|
const testOnlyInvalidToken = () => currentToken + 1n;
|
|
3720
4345
|
const testOnlyExtendWithToken = async (tokenToUse, ttlSecs, signal) => {
|
|
4346
|
+
ensureOpen();
|
|
3721
4347
|
const requestPayload = LeaseCodec.encodeExtend(route, tokenToUse, ttlSecs);
|
|
3722
4348
|
const data = assertSuccess(await connection.request(401, requestPayload, signal), "EXTEND");
|
|
3723
4349
|
if (data && data.length >= 8) currentToken = new BufferReader(data).readU64BE();
|
|
@@ -3725,8 +4351,11 @@ function createLease(token, expiresAt, route, connection) {
|
|
|
3725
4351
|
return currentExpiry;
|
|
3726
4352
|
};
|
|
3727
4353
|
const testOnlyReleaseWithToken = async (tokenToUse, signal) => {
|
|
4354
|
+
ensureOpen();
|
|
3728
4355
|
const payload = LeaseCodec.encodeRelease(route, tokenToUse);
|
|
3729
4356
|
assertSuccess(await connection.request(402, payload, signal), "RELEASE");
|
|
4357
|
+
closed = true;
|
|
4358
|
+
unsubscribeDisconnect();
|
|
3730
4359
|
};
|
|
3731
4360
|
return {
|
|
3732
4361
|
extend,
|
|
@@ -3743,7 +4372,7 @@ function createLease(token, expiresAt, route, connection) {
|
|
|
3743
4372
|
* Lease domain client.
|
|
3744
4373
|
*/
|
|
3745
4374
|
function createLeaseClient(connection) {
|
|
3746
|
-
const { requestFrame } = createDomainClient(connection);
|
|
4375
|
+
const { requestFrame, requestReconnectFrame, runWithRetry } = createDomainClient(connection);
|
|
3747
4376
|
const subscriptionsByPattern = /* @__PURE__ */ new Map();
|
|
3748
4377
|
let initialized = false;
|
|
3749
4378
|
let nextHandlerId = 1;
|
|
@@ -3755,7 +4384,7 @@ function createLeaseClient(connection) {
|
|
|
3755
4384
|
}));
|
|
3756
4385
|
subscriptionsByPattern.clear();
|
|
3757
4386
|
for (const subscription of subscriptions) {
|
|
3758
|
-
const subId = await subscribeWire(subscription.pattern);
|
|
4387
|
+
const subId = await subscribeWire(subscription.pattern, requestReconnectFrame);
|
|
3759
4388
|
subscriptionsByPattern.set(subscription.pattern, {
|
|
3760
4389
|
subId,
|
|
3761
4390
|
handlers: new Map(subscription.handlers)
|
|
@@ -3772,16 +4401,22 @@ function createLeaseClient(connection) {
|
|
|
3772
4401
|
};
|
|
3773
4402
|
const query = async (route) => {
|
|
3774
4403
|
assertExactLeaseRoute(route);
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4404
|
+
return runWithRetry({
|
|
4405
|
+
domain: "lease",
|
|
4406
|
+
operation: "query",
|
|
4407
|
+
retryClass: "replayable_read"
|
|
4408
|
+
}, async () => {
|
|
4409
|
+
const response = await requestFrame(403, LeaseCodec.encodeQuery(route));
|
|
4410
|
+
const decoded = LeaseCodec.decodeQueryResponse(response);
|
|
4411
|
+
if (decoded.status !== 0) throw new LeaseError("QUERY failed", "QUERY_FAILED", decoded.status);
|
|
4412
|
+
return {
|
|
4413
|
+
isHeld: decoded.isHeld ?? false,
|
|
4414
|
+
owner: decoded.owner,
|
|
4415
|
+
token: decoded.token,
|
|
4416
|
+
ttlRemainingSecs: decoded.ttlRemainingSecs,
|
|
4417
|
+
expiresAt: decoded.expiresAt
|
|
4418
|
+
};
|
|
4419
|
+
});
|
|
3785
4420
|
};
|
|
3786
4421
|
const subscribe = async (pattern, handler) => {
|
|
3787
4422
|
assertExactLeaseRoute(pattern);
|
|
@@ -3790,8 +4425,8 @@ function createLeaseClient(connection) {
|
|
|
3790
4425
|
if (existing) return addLocalSubscription(pattern, existing.subId, handler);
|
|
3791
4426
|
return addLocalSubscription(pattern, await subscribeWire(pattern), handler);
|
|
3792
4427
|
};
|
|
3793
|
-
const subscribeWire = async (pattern) => {
|
|
3794
|
-
const response = await
|
|
4428
|
+
const subscribeWire = async (pattern, request = requestFrame) => {
|
|
4429
|
+
const response = await request(407, LeaseCodec.encodeSubscribe(pattern));
|
|
3795
4430
|
const decoded = LeaseCodec.decodeSubscribeResponse(response);
|
|
3796
4431
|
if (decoded.subId === void 0) throw new LeaseError("SUBSCRIBE failed", "SUBSCRIBE_FAILED");
|
|
3797
4432
|
return decoded.subId;
|
|
@@ -3938,7 +4573,7 @@ function createNoticeSubscription(subId, pattern, unsubscribeFn) {
|
|
|
3938
4573
|
* Notice domain client.
|
|
3939
4574
|
*/
|
|
3940
4575
|
function createNoticeClient(connection) {
|
|
3941
|
-
const { requestFrame } = createDomainClient(connection);
|
|
4576
|
+
const { requestFrame, requestReconnectFrame } = createDomainClient(connection);
|
|
3942
4577
|
const subscriptionsByPattern = /* @__PURE__ */ new Map();
|
|
3943
4578
|
const patternsBySubId = /* @__PURE__ */ new Map();
|
|
3944
4579
|
let initialized = false;
|
|
@@ -3952,7 +4587,7 @@ function createNoticeClient(connection) {
|
|
|
3952
4587
|
subscriptionsByPattern.clear();
|
|
3953
4588
|
patternsBySubId.clear();
|
|
3954
4589
|
for (const subscription of subscriptions) {
|
|
3955
|
-
const subId = await subscribeWire(subscription.pattern);
|
|
4590
|
+
const subId = await subscribeWire(subscription.pattern, requestReconnectFrame);
|
|
3956
4591
|
subscriptionsByPattern.set(subscription.pattern, {
|
|
3957
4592
|
subId,
|
|
3958
4593
|
handlers: new Map(subscription.handlers)
|
|
@@ -3978,8 +4613,8 @@ function createNoticeClient(connection) {
|
|
|
3978
4613
|
if (existing) return addLocalSubscription(pattern, existing.subId, handler);
|
|
3979
4614
|
return addLocalSubscription(pattern, await subscribeWire(pattern), handler);
|
|
3980
4615
|
};
|
|
3981
|
-
const subscribeWire = async (pattern) => {
|
|
3982
|
-
const response = await
|
|
4616
|
+
const subscribeWire = async (pattern, request = requestFrame) => {
|
|
4617
|
+
const response = await request(501, NoticeCodec.encodeSubscribe(pattern));
|
|
3983
4618
|
const decoded = NoticeCodec.decodeSubscribeResponse(response);
|
|
3984
4619
|
if (decoded.subId === void 0) throw new NoticeError("SUBSCRIBE response missing subId", "MISSING_SUB_ID");
|
|
3985
4620
|
return decoded.subId;
|
|
@@ -4421,8 +5056,10 @@ function createStreamSubscription(subId, pattern, unsubscribeFn) {
|
|
|
4421
5056
|
//#region src/domains/stream/session.ts
|
|
4422
5057
|
function createStreamSession(connection, _route, sessionId) {
|
|
4423
5058
|
let closed = false;
|
|
4424
|
-
|
|
5059
|
+
let unsubscribeDisconnect = () => void 0;
|
|
5060
|
+
unsubscribeDisconnect = connection.onDisconnect(() => {
|
|
4425
5061
|
closed = true;
|
|
5062
|
+
unsubscribeDisconnect();
|
|
4426
5063
|
});
|
|
4427
5064
|
const ensureOpen = () => {
|
|
4428
5065
|
if (closed) throw new StreamError("Stream session already closed", "SESSION_CLOSED");
|
|
@@ -4496,7 +5133,7 @@ function isAbortSignal(value) {
|
|
|
4496
5133
|
* 3. `commit()` or `rollback()` finalizes the session
|
|
4497
5134
|
*/
|
|
4498
5135
|
function createStreamClient(connection) {
|
|
4499
|
-
const { requestFrame } = createDomainClient(connection);
|
|
5136
|
+
const { requestFrame, requestReconnectFrame, runWithRetry } = createDomainClient(connection);
|
|
4500
5137
|
const subscriptionsByPattern = /* @__PURE__ */ new Map();
|
|
4501
5138
|
const patternsBySubId = /* @__PURE__ */ new Map();
|
|
4502
5139
|
let initialized = false;
|
|
@@ -4510,7 +5147,7 @@ function createStreamClient(connection) {
|
|
|
4510
5147
|
subscriptionsByPattern.clear();
|
|
4511
5148
|
patternsBySubId.clear();
|
|
4512
5149
|
for (const subscription of snapshot) {
|
|
4513
|
-
const subId = await subscribeWire(subscription.pattern);
|
|
5150
|
+
const subId = await subscribeWire(subscription.pattern, requestReconnectFrame);
|
|
4514
5151
|
subscriptionsByPattern.set(subscription.pattern, {
|
|
4515
5152
|
subId,
|
|
4516
5153
|
handlers: new Map(subscription.handlers)
|
|
@@ -4528,43 +5165,89 @@ function createStreamClient(connection) {
|
|
|
4528
5165
|
};
|
|
4529
5166
|
const readPage = async (route, startOffset, limit = 100, options) => {
|
|
4530
5167
|
assertStreamPattern(route);
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
5168
|
+
return runWithRetry({
|
|
5169
|
+
domain: "stream",
|
|
5170
|
+
operation: "read",
|
|
5171
|
+
retryClass: "replayable_read",
|
|
5172
|
+
signal: options?.signal
|
|
5173
|
+
}, async () => {
|
|
5174
|
+
const response = await requestFrame(604, StreamCodec.encodeRead(route, startOffset, limit, options), options?.signal);
|
|
5175
|
+
const decoded = StreamCodec.decodeReadResponse(response);
|
|
5176
|
+
checkStatus(decoded.status, "READ");
|
|
5177
|
+
return {
|
|
5178
|
+
items: decoded.items,
|
|
5179
|
+
cursor: decoded.cursor ?? {
|
|
5180
|
+
lastResourceOffset: startOffset,
|
|
5181
|
+
lastAreaOffset: void 0,
|
|
5182
|
+
lastRealmOffset: void 0,
|
|
5183
|
+
hasMore: false
|
|
5184
|
+
}
|
|
5185
|
+
};
|
|
5186
|
+
});
|
|
4543
5187
|
};
|
|
4544
5188
|
const read = async (route, startOffset, limit = 100, options) => {
|
|
4545
5189
|
const page = await readPage(route, startOffset, limit, options);
|
|
4546
5190
|
return StreamCodec.flattenStreamReadItems(page.items);
|
|
4547
5191
|
};
|
|
5192
|
+
const readWhenCommitted = async function* (route, options) {
|
|
5193
|
+
assertStreamPattern(route);
|
|
5194
|
+
const wakeGate = createWakeGate();
|
|
5195
|
+
const subscription = await subscribe(route, () => {
|
|
5196
|
+
wakeGate.wake();
|
|
5197
|
+
});
|
|
5198
|
+
try {
|
|
5199
|
+
let offset = options.offset;
|
|
5200
|
+
while (true) {
|
|
5201
|
+
const observed = wakeGate.version;
|
|
5202
|
+
const page = await readPage(route, offset, options.batchSize ?? 100, {
|
|
5203
|
+
maxBytes: options.maxBytes,
|
|
5204
|
+
filter: options.filter,
|
|
5205
|
+
signal: options.signal
|
|
5206
|
+
});
|
|
5207
|
+
if (page.items.length > 0) {
|
|
5208
|
+
offset = page.cursor.lastResourceOffset + 1n;
|
|
5209
|
+
const records = StreamCodec.flattenStreamReadItems(page.items);
|
|
5210
|
+
if (records.length > 0) yield records;
|
|
5211
|
+
}
|
|
5212
|
+
if (page.cursor.hasMore) continue;
|
|
5213
|
+
await wakeGate.waitAfter(observed, { signal: options.signal });
|
|
5214
|
+
}
|
|
5215
|
+
} finally {
|
|
5216
|
+
await subscription.unsubscribe().catch(() => void 0);
|
|
5217
|
+
}
|
|
5218
|
+
};
|
|
4548
5219
|
const consume = async (route, startOffset, limit = 100, options) => {
|
|
4549
5220
|
return createAsyncIterableIterator(createSliceIterator(await read(route, startOffset, limit, options)));
|
|
4550
5221
|
};
|
|
4551
5222
|
const peek = async (route) => {
|
|
4552
5223
|
assertStreamRoute(route);
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
5224
|
+
return runWithRetry({
|
|
5225
|
+
domain: "stream",
|
|
5226
|
+
operation: "last",
|
|
5227
|
+
retryClass: "replayable_read"
|
|
5228
|
+
}, async () => {
|
|
5229
|
+
const response = await requestFrame(605, StreamCodec.encodeLast(route));
|
|
5230
|
+
const decoded = StreamCodec.decodeLastResponse(response);
|
|
5231
|
+
checkStatus(decoded.status, "LAST");
|
|
5232
|
+
return decoded.record ?? null;
|
|
5233
|
+
});
|
|
4557
5234
|
};
|
|
4558
5235
|
const metadata = async (route) => {
|
|
4559
5236
|
assertStreamRoute(route);
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
5237
|
+
return runWithRetry({
|
|
5238
|
+
domain: "stream",
|
|
5239
|
+
operation: "metadata",
|
|
5240
|
+
retryClass: "replayable_read"
|
|
5241
|
+
}, async () => {
|
|
5242
|
+
const response = await requestFrame(606, StreamCodec.encodeMetadata(route));
|
|
5243
|
+
const decoded = StreamCodec.decodeMetadataResponse(response);
|
|
5244
|
+
checkStatus(decoded.status, "GET_METADATA");
|
|
5245
|
+
return decoded.metadata ?? {
|
|
5246
|
+
firstOffset: 0n,
|
|
5247
|
+
lastOffset: 0n,
|
|
5248
|
+
recordCount: 0n
|
|
5249
|
+
};
|
|
5250
|
+
});
|
|
4568
5251
|
};
|
|
4569
5252
|
const subscribe = async (pattern, handler) => {
|
|
4570
5253
|
assertStreamPattern(pattern);
|
|
@@ -4573,8 +5256,8 @@ function createStreamClient(connection) {
|
|
|
4573
5256
|
if (existing) return addLocalSubscription(pattern, existing.subId, handler);
|
|
4574
5257
|
return addLocalSubscription(pattern, await subscribeWire(pattern), handler);
|
|
4575
5258
|
};
|
|
4576
|
-
const subscribeWire = async (pattern) => {
|
|
4577
|
-
const response = await
|
|
5259
|
+
const subscribeWire = async (pattern, request = requestFrame) => {
|
|
5260
|
+
const response = await request(607, StreamCodec.encodeSubscribe(pattern));
|
|
4578
5261
|
const decoded = StreamCodec.decodeSubscribeResponse(response);
|
|
4579
5262
|
checkStatus(decoded.status, "SUBSCRIBE");
|
|
4580
5263
|
if (decoded.subId === void 0) throw new StreamError("SUBSCRIBE response missing subId", "MISSING_SESSION_ID");
|
|
@@ -4652,6 +5335,7 @@ function createStreamClient(connection) {
|
|
|
4652
5335
|
begin,
|
|
4653
5336
|
readPage,
|
|
4654
5337
|
read,
|
|
5338
|
+
readWhenCommitted,
|
|
4655
5339
|
consume,
|
|
4656
5340
|
peek,
|
|
4657
5341
|
metadata,
|
|
@@ -4829,9 +5513,10 @@ var ScheduleError$1 = class extends Error {
|
|
|
4829
5513
|
* Schedule domain client.
|
|
4830
5514
|
*/
|
|
4831
5515
|
function createScheduleClient(connection) {
|
|
4832
|
-
const { requestFrame } = createDomainClient(connection);
|
|
5516
|
+
const { requestFrame, requestReconnectFrame } = createDomainClient(connection);
|
|
4833
5517
|
const subscriptionsByPattern = /* @__PURE__ */ new Map();
|
|
4834
5518
|
const patternsBySubId = /* @__PURE__ */ new Map();
|
|
5519
|
+
const pendingNotificationsBySubId = /* @__PURE__ */ new Map();
|
|
4835
5520
|
let notifyHandlerInitialized = false;
|
|
4836
5521
|
let nextHandlerId = 1;
|
|
4837
5522
|
connection.onReconnect(async () => {
|
|
@@ -4842,8 +5527,9 @@ function createScheduleClient(connection) {
|
|
|
4842
5527
|
}));
|
|
4843
5528
|
subscriptionsByPattern.clear();
|
|
4844
5529
|
patternsBySubId.clear();
|
|
5530
|
+
pendingNotificationsBySubId.clear();
|
|
4845
5531
|
for (const subscription of subscriptions) {
|
|
4846
|
-
const subId = await subscribeWire(subscription.pattern);
|
|
5532
|
+
const subId = await subscribeWire(subscription.pattern, requestReconnectFrame);
|
|
4847
5533
|
subscriptionsByPattern.set(subscription.pattern, {
|
|
4848
5534
|
subId,
|
|
4849
5535
|
handlers: new Map(subscription.handlers)
|
|
@@ -4866,6 +5552,29 @@ function createScheduleClient(connection) {
|
|
|
4866
5552
|
const decoded = ScheduleCodec.decodeListResponse(assertSuccess(response, "LIST"));
|
|
4867
5553
|
return [decoded.entries, decoded.totalCount];
|
|
4868
5554
|
};
|
|
5555
|
+
const waitForNotifications = async function* (route, options = {}) {
|
|
5556
|
+
assertConcreteScheduleRoute(route);
|
|
5557
|
+
const wakeGate = createWakeGate();
|
|
5558
|
+
const pendingNotifications = [];
|
|
5559
|
+
const subscription = await subscribe(route, (notification) => {
|
|
5560
|
+
pendingNotifications.push(notification);
|
|
5561
|
+
wakeGate.wake();
|
|
5562
|
+
});
|
|
5563
|
+
try {
|
|
5564
|
+
while (true) {
|
|
5565
|
+
const notification = pendingNotifications.shift();
|
|
5566
|
+
if (notification) {
|
|
5567
|
+
yield notification;
|
|
5568
|
+
continue;
|
|
5569
|
+
}
|
|
5570
|
+
const observed = wakeGate.version;
|
|
5571
|
+
if (pendingNotifications.length > 0) continue;
|
|
5572
|
+
await wakeGate.waitAfter(observed, { signal: options.signal });
|
|
5573
|
+
}
|
|
5574
|
+
} finally {
|
|
5575
|
+
await subscription.unsubscribe().catch(() => void 0);
|
|
5576
|
+
}
|
|
5577
|
+
};
|
|
4869
5578
|
const subscribe = async (pattern, handler) => {
|
|
4870
5579
|
assertConcreteScheduleRoute(pattern);
|
|
4871
5580
|
initNotifyHandler();
|
|
@@ -4873,8 +5582,8 @@ function createScheduleClient(connection) {
|
|
|
4873
5582
|
if (existing) return addLocalSubscription(pattern, existing.subId, handler);
|
|
4874
5583
|
return addLocalSubscription(pattern, await subscribeWire(pattern), handler);
|
|
4875
5584
|
};
|
|
4876
|
-
const subscribeWire = async (pattern) => {
|
|
4877
|
-
const response = await
|
|
5585
|
+
const subscribeWire = async (pattern, request = requestFrame) => {
|
|
5586
|
+
const response = await request(703, ScheduleCodec.encodeSubscribe(pattern));
|
|
4878
5587
|
return ScheduleCodec.decodeSubscribeResponse(assertSuccess(response, "SUBSCRIBE")).subId;
|
|
4879
5588
|
};
|
|
4880
5589
|
const addLocalSubscription = (pattern, subId, handler) => {
|
|
@@ -4889,6 +5598,7 @@ function createScheduleClient(connection) {
|
|
|
4889
5598
|
patternsBySubId.set(subId, pattern);
|
|
4890
5599
|
}
|
|
4891
5600
|
subscription.handlers.set(handlerId, handler);
|
|
5601
|
+
flushPendingNotifications(subId);
|
|
4892
5602
|
return createScheduleSubscription(subId, pattern, async () => {
|
|
4893
5603
|
await unsubscribe(pattern, handlerId);
|
|
4894
5604
|
});
|
|
@@ -4900,6 +5610,7 @@ function createScheduleClient(connection) {
|
|
|
4900
5610
|
if (subscription.handlers.size > 0) return;
|
|
4901
5611
|
subscriptionsByPattern.delete(pattern);
|
|
4902
5612
|
patternsBySubId.delete(subscription.subId);
|
|
5613
|
+
pendingNotificationsBySubId.delete(subscription.subId);
|
|
4903
5614
|
const response = await requestFrame(704, ScheduleCodec.encodeUnsubscribe(pattern));
|
|
4904
5615
|
ScheduleCodec.decodeUnsubscribeResponse(assertSuccess(response, "UNSUBSCRIBE"));
|
|
4905
5616
|
};
|
|
@@ -4910,16 +5621,40 @@ function createScheduleClient(connection) {
|
|
|
4910
5621
|
try {
|
|
4911
5622
|
const decoded = ScheduleCodec.decodeNotification(payload);
|
|
4912
5623
|
const pattern = patternsBySubId.get(decoded.subId);
|
|
4913
|
-
if (!pattern)
|
|
5624
|
+
if (!pattern) {
|
|
5625
|
+
queuePendingNotification(decoded.subId, { payload: decoded.payload });
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
4914
5628
|
const subscription = subscriptionsByPattern.get(pattern);
|
|
4915
|
-
if (!subscription)
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
});
|
|
5629
|
+
if (!subscription) {
|
|
5630
|
+
queuePendingNotification(decoded.subId, { payload: decoded.payload });
|
|
5631
|
+
return;
|
|
5632
|
+
}
|
|
5633
|
+
dispatchNotification(subscription, { payload: decoded.payload });
|
|
4920
5634
|
} catch {}
|
|
4921
5635
|
});
|
|
4922
5636
|
};
|
|
5637
|
+
const queuePendingNotification = (subId, notification) => {
|
|
5638
|
+
const existing = pendingNotificationsBySubId.get(subId);
|
|
5639
|
+
if (existing) {
|
|
5640
|
+
existing.push(notification);
|
|
5641
|
+
return;
|
|
5642
|
+
}
|
|
5643
|
+
pendingNotificationsBySubId.set(subId, [notification]);
|
|
5644
|
+
};
|
|
5645
|
+
const flushPendingNotifications = (subId) => {
|
|
5646
|
+
const pending = pendingNotificationsBySubId.get(subId);
|
|
5647
|
+
if (!pending || pending.length === 0) return;
|
|
5648
|
+
pendingNotificationsBySubId.delete(subId);
|
|
5649
|
+
const subscription = Array.from(subscriptionsByPattern.values()).find((entry) => entry.subId === subId);
|
|
5650
|
+
if (!subscription) return;
|
|
5651
|
+
for (const notification of pending) dispatchNotification(subscription, notification);
|
|
5652
|
+
};
|
|
5653
|
+
const dispatchNotification = (subscription, notification) => {
|
|
5654
|
+
for (const handler of subscription.handlers.values()) connection.dispatchAsyncHandler(async () => {
|
|
5655
|
+
await handler(notification);
|
|
5656
|
+
});
|
|
5657
|
+
};
|
|
4923
5658
|
const assertSuccess = (payload, operation) => {
|
|
4924
5659
|
const result = parseStandardResponse(payload);
|
|
4925
5660
|
if (result.success) return result.data;
|
|
@@ -4936,7 +5671,8 @@ function createScheduleClient(connection) {
|
|
|
4936
5671
|
create,
|
|
4937
5672
|
cancel,
|
|
4938
5673
|
list,
|
|
4939
|
-
subscribe
|
|
5674
|
+
subscribe,
|
|
5675
|
+
waitForNotifications
|
|
4940
5676
|
};
|
|
4941
5677
|
}
|
|
4942
5678
|
const ScheduleClient = function(connection) {
|
|
@@ -4947,6 +5683,39 @@ function assertConcreteScheduleRoute(route) {
|
|
|
4947
5683
|
}
|
|
4948
5684
|
//#endregion
|
|
4949
5685
|
//#region src/client/client.ts
|
|
5686
|
+
const abortError = () => {
|
|
5687
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
5688
|
+
error.name = "AbortError";
|
|
5689
|
+
return error;
|
|
5690
|
+
};
|
|
5691
|
+
const throwIfAborted = (signal) => {
|
|
5692
|
+
if (signal?.aborted) throw abortError();
|
|
5693
|
+
};
|
|
5694
|
+
const waitForSharedPromise = async (promise, signal) => {
|
|
5695
|
+
if (!signal) return promise;
|
|
5696
|
+
if (signal.aborted) throw abortError();
|
|
5697
|
+
return await new Promise((resolve, reject) => {
|
|
5698
|
+
let settled = false;
|
|
5699
|
+
const cleanup = () => {
|
|
5700
|
+
signal.removeEventListener("abort", onAbort);
|
|
5701
|
+
};
|
|
5702
|
+
const settle = (callback) => {
|
|
5703
|
+
if (settled) return;
|
|
5704
|
+
settled = true;
|
|
5705
|
+
cleanup();
|
|
5706
|
+
callback();
|
|
5707
|
+
};
|
|
5708
|
+
const onAbort = () => {
|
|
5709
|
+
settle(() => reject(abortError()));
|
|
5710
|
+
};
|
|
5711
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5712
|
+
promise.then((value) => {
|
|
5713
|
+
settle(() => resolve(value));
|
|
5714
|
+
}, (error) => {
|
|
5715
|
+
settle(() => reject(error));
|
|
5716
|
+
});
|
|
5717
|
+
});
|
|
5718
|
+
};
|
|
4950
5719
|
function createClient(config) {
|
|
4951
5720
|
const observability = config.observability;
|
|
4952
5721
|
const resolvedConfig = {
|
|
@@ -4958,12 +5727,25 @@ function createClient(config) {
|
|
|
4958
5727
|
maxRequestQueueSize: 1024,
|
|
4959
5728
|
observability: config.observability ?? {},
|
|
4960
5729
|
reconnect: {
|
|
4961
|
-
enabled:
|
|
5730
|
+
enabled: true,
|
|
4962
5731
|
maxAttempts: Infinity,
|
|
4963
5732
|
backoffMs: 250,
|
|
4964
5733
|
maxBackoffMs: 5e3,
|
|
4965
5734
|
...config.reconnect
|
|
4966
5735
|
},
|
|
5736
|
+
retry: {
|
|
5737
|
+
enabled: true,
|
|
5738
|
+
maxAttempts: 3,
|
|
5739
|
+
backoffMs: 100,
|
|
5740
|
+
maxBackoffMs: 1e3,
|
|
5741
|
+
...config.retry
|
|
5742
|
+
},
|
|
5743
|
+
heartbeat: {
|
|
5744
|
+
enabled: true,
|
|
5745
|
+
intervalMs: 1e4,
|
|
5746
|
+
timeoutMs: 3e4,
|
|
5747
|
+
...config.heartbeat
|
|
5748
|
+
},
|
|
4967
5749
|
asyncHandlers: {
|
|
4968
5750
|
maxConcurrency: Infinity,
|
|
4969
5751
|
timeoutMs: 3e4,
|
|
@@ -4980,34 +5762,72 @@ function createClient(config) {
|
|
|
4980
5762
|
let noticeClient = null;
|
|
4981
5763
|
let streamClient = null;
|
|
4982
5764
|
let scheduleClient = null;
|
|
5765
|
+
let clientClosed = false;
|
|
5766
|
+
let pendingConnectPromise = null;
|
|
4983
5767
|
const resolveTokenProvider = () => {
|
|
4984
5768
|
if (resolvedConfig.tokenProvider) return resolvedConfig.tokenProvider;
|
|
4985
5769
|
return () => "";
|
|
4986
5770
|
};
|
|
4987
5771
|
const ensureConnection = () => {
|
|
5772
|
+
if (clientClosed) throw new ConnectionError("Client is closed", { state: "CLOSED" });
|
|
4988
5773
|
if (!connection) throw new ConnectionError("Not connected to Fitz server. Call connect() first.", { state: getState() });
|
|
4989
5774
|
return connection;
|
|
4990
5775
|
};
|
|
4991
|
-
const
|
|
4992
|
-
if (connection
|
|
5776
|
+
const createOwnedConnection = () => {
|
|
5777
|
+
if (connection) return connection;
|
|
4993
5778
|
connection = createConnection(() => createTransport(resolvedConfig.url, resolvedConfig.transport, {
|
|
4994
5779
|
timeout: resolvedConfig.timeout,
|
|
4995
|
-
maxFrameSize: resolvedConfig.maxFrameSize
|
|
5780
|
+
maxFrameSize: resolvedConfig.maxFrameSize,
|
|
5781
|
+
receiveTimeout: resolvedConfig.heartbeat?.enabled === false
|
|
4996
5782
|
}), resolveTokenProvider(), {
|
|
4997
5783
|
timeout: resolvedConfig.timeout,
|
|
4998
5784
|
authSettleDelayMs: resolvedConfig.authSettleDelayMs,
|
|
4999
5785
|
maxInFlightRequests: resolvedConfig.maxInFlightRequests,
|
|
5000
5786
|
maxRequestQueueSize: resolvedConfig.maxRequestQueueSize,
|
|
5001
5787
|
reconnect: resolvedConfig.reconnect,
|
|
5788
|
+
retry: resolvedConfig.retry,
|
|
5789
|
+
heartbeat: resolvedConfig.heartbeat,
|
|
5002
5790
|
observability,
|
|
5003
5791
|
asyncHandlers: resolvedConfig.asyncHandlers
|
|
5004
5792
|
});
|
|
5005
|
-
|
|
5793
|
+
return connection;
|
|
5794
|
+
};
|
|
5795
|
+
const connect = async (options = {}) => {
|
|
5796
|
+
if (clientClosed) throw new ConnectionError("Client is closed", { state: "CLOSED" });
|
|
5797
|
+
throwIfAborted(options.signal);
|
|
5798
|
+
const activeConnection = createOwnedConnection();
|
|
5799
|
+
if (activeConnection.isConnected()) return;
|
|
5800
|
+
if (pendingConnectPromise) {
|
|
5801
|
+
await waitForSharedPromise(pendingConnectPromise, options.signal);
|
|
5802
|
+
return;
|
|
5803
|
+
}
|
|
5804
|
+
const state = activeConnection.getState();
|
|
5805
|
+
const trackedConnectPromise = (state === "CONNECTING" || state === "CONNECTED" || state === "AUTHENTICATING" || state === "RECONNECTING" || state === "DISCONNECTED" && typeof activeConnection.shouldWaitForReconnect === "function" && activeConnection.shouldWaitForReconnect() ? typeof activeConnection.waitUntilReady === "function" ? activeConnection.waitUntilReady(void 0, resolvedConfig.timeout) : activeConnection.connect() : activeConnection.connect(options)).finally(() => {
|
|
5806
|
+
if (pendingConnectPromise === trackedConnectPromise) pendingConnectPromise = null;
|
|
5807
|
+
});
|
|
5808
|
+
pendingConnectPromise = trackedConnectPromise;
|
|
5809
|
+
await waitForSharedPromise(trackedConnectPromise, options.signal);
|
|
5006
5810
|
};
|
|
5007
5811
|
const close = async () => {
|
|
5812
|
+
if (clientClosed && !connection) {
|
|
5813
|
+
kvClient = null;
|
|
5814
|
+
queueClient = null;
|
|
5815
|
+
rpcClient = null;
|
|
5816
|
+
leaseClient = null;
|
|
5817
|
+
noticeClient = null;
|
|
5818
|
+
streamClient = null;
|
|
5819
|
+
scheduleClient = null;
|
|
5820
|
+
return;
|
|
5821
|
+
}
|
|
5822
|
+
clientClosed = true;
|
|
5823
|
+
pendingConnectPromise = null;
|
|
5008
5824
|
if (connection) {
|
|
5009
|
-
|
|
5010
|
-
|
|
5825
|
+
const activeConnection = connection;
|
|
5826
|
+
try {
|
|
5827
|
+
await activeConnection.close();
|
|
5828
|
+
} finally {
|
|
5829
|
+
if (connection === activeConnection) connection = null;
|
|
5830
|
+
}
|
|
5011
5831
|
}
|
|
5012
5832
|
kvClient = null;
|
|
5013
5833
|
queueClient = null;
|
|
@@ -5018,7 +5838,7 @@ function createClient(config) {
|
|
|
5018
5838
|
scheduleClient = null;
|
|
5019
5839
|
};
|
|
5020
5840
|
const isConnected = () => {
|
|
5021
|
-
return connection?.isConnected() ?? false;
|
|
5841
|
+
return !clientClosed && (connection?.isConnected() ?? false);
|
|
5022
5842
|
};
|
|
5023
5843
|
const kv = () => {
|
|
5024
5844
|
const activeConnection = ensureConnection();
|
|
@@ -5059,7 +5879,10 @@ function createClient(config) {
|
|
|
5059
5879
|
return ensureConnection().getUrl();
|
|
5060
5880
|
};
|
|
5061
5881
|
const getState = () => {
|
|
5062
|
-
|
|
5882
|
+
if (clientClosed) return "CLOSED";
|
|
5883
|
+
if (!connection) return "DISCONNECTED";
|
|
5884
|
+
const state = connection.getState();
|
|
5885
|
+
return state === "CLOSED" ? "DISCONNECTED" : state;
|
|
5063
5886
|
};
|
|
5064
5887
|
return {
|
|
5065
5888
|
config: resolvedConfig,
|
|
@@ -5219,6 +6042,6 @@ function isAbortError(error) {
|
|
|
5219
6042
|
return error instanceof Error && error.name === "AbortError";
|
|
5220
6043
|
}
|
|
5221
6044
|
//#endregion
|
|
5222
|
-
export { AuthenticationError, Client, CodecError, ConnectionError, ConnectionState, ErrCodeKvBackendError, ErrCodeKvIsolationConflict, ErrCodeLeaseHeld, ErrCodeQueueFull, ErrCodeRpcBackpressure, ErrCodeRpcCorrelationNotFound, ErrCodeRpcRouteNotRegistered, ErrCodeRpcTimeout, ErrCodeRpcUnauthorized, ErrCodeRpcWorkerNotFound, ErrKvConflictingWrite, ErrKvKeyNotFound, ErrKvLeaseExpired, ErrKvOperationNotAllowed, ErrKvTransactionAborted, ErrLeaseHeld, ErrLeaseInvalidToken, ErrLeaseNotFound, ErrNoticeGeneral, ErrQueueFull, ErrQueueInvalidDelay, ErrQueueInvalidToken, ErrQueueMessageNotFound, ErrQueueNotFound, ErrRpcHandlerError, ErrRpcHandlerNotFound, ErrRpcInvalidRequest, ErrRpcTimeout, ErrScheduleInvalidCron, ErrScheduleInvalidDelay, ErrScheduleInvalidTimestamp, ErrScheduleNotFound, ErrScheduleTaskNotFound, ErrStreamExpectedOffsetMismatch, ErrStreamFull, ErrStreamInvalidOffset, ErrStreamNotFound, ErrStreamOffsetOutOfRange, ErrStreamSessionClosed, ErrStreamSessionNotFound, FitzError, KvClient, KvError, LeaseClient, LeaseError, NoticeClient, NoticeError, ProtocolError, QueueClient, QueueError, RequestQueueFullError, RpcClient, RpcError, ScheduleClient, ScheduleError, StreamClient, StreamError, TimeoutError, TransportError, createClient, createTaskGroup, isRetryable };
|
|
6045
|
+
export { AuthenticationError, Client, CodecError, ConnectionError, ConnectionState, ErrCodeKvBackendError, ErrCodeKvIsolationConflict, ErrCodeLeaseHeld, ErrCodeQueueFull, ErrCodeRpcBackpressure, ErrCodeRpcCorrelationNotFound, ErrCodeRpcRouteNotRegistered, ErrCodeRpcTimeout, ErrCodeRpcUnauthorized, ErrCodeRpcWorkerNotFound, ErrKvConflictingWrite, ErrKvKeyNotFound, ErrKvLeaseExpired, ErrKvOperationNotAllowed, ErrKvTransactionAborted, ErrLeaseHeld, ErrLeaseInvalidToken, ErrLeaseNotFound, ErrNoticeGeneral, ErrQueueFull, ErrQueueInvalidDelay, ErrQueueInvalidToken, ErrQueueMessageNotFound, ErrQueueNotFound, ErrRpcHandlerError, ErrRpcHandlerNotFound, ErrRpcInvalidRequest, ErrRpcTimeout, ErrScheduleInvalidCron, ErrScheduleInvalidDelay, ErrScheduleInvalidTimestamp, ErrScheduleNotFound, ErrScheduleTaskNotFound, ErrStreamExpectedOffsetMismatch, ErrStreamFull, ErrStreamInvalidOffset, ErrStreamNotFound, ErrStreamOffsetOutOfRange, ErrStreamSessionClosed, ErrStreamSessionNotFound, FitzError, KvClient, KvError, LeaseClient, LeaseError, NoticeClient, NoticeError, ProtocolError, QueueClient, QueueError, RequestQueueFullError, RpcClient, RpcError, ScheduleClient, ScheduleError, StreamClient, StreamError, TimeoutError, TransportError, createClient, createTaskGroup, createWakeGate, isRetryable };
|
|
5223
6046
|
|
|
5224
6047
|
//# sourceMappingURL=index.mjs.map
|