@ferricstore/ferricstore 0.1.3 → 0.1.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/dist/index.cjs +132 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +130 -3
- package/dist/index.js.map +1 -1
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/FerricStoreClient.html +2 -2
- package/docs/api/classes/FerricStoreError.html +1 -1
- package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
- package/docs/api/classes/FlowNotFoundError.html +1 -1
- package/docs/api/classes/FlowWrongStateError.html +1 -1
- package/docs/api/classes/InvalidCommandError.html +1 -1
- package/docs/api/classes/LockHeldError.html +1 -1
- package/docs/api/classes/LockNotOwnedError.html +1 -1
- package/docs/api/classes/NativeAdapter.html +2 -2
- package/docs/api/classes/OverloadedError.html +1 -1
- package/docs/api/classes/ReconnectingExecutor.html +5 -0
- package/docs/api/classes/StaleLeaseError.html +1 -1
- package/docs/api/functions/isReconnectableClosedConnectionError.html +1 -0
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/interfaces/AutoBatchOptions.html +2 -2
- package/docs/api/interfaces/CancelOptions.html +2 -2
- package/docs/api/interfaces/ClaimDueOptions.html +2 -2
- package/docs/api/interfaces/CommandExecutor.html +1 -1
- package/docs/api/interfaces/CompleteOptions.html +2 -2
- package/docs/api/interfaces/CompleteOutcome.html +1 -1
- package/docs/api/interfaces/CreateManyOptions.html +2 -2
- package/docs/api/interfaces/CreateOptions.html +2 -2
- package/docs/api/interfaces/FailOptions.html +2 -2
- package/docs/api/interfaces/FailOutcome.html +1 -1
- package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +6 -0
- package/docs/api/interfaces/FerricStoreClientOptions.html +2 -2
- package/docs/api/interfaces/MutateOptions.html +2 -2
- package/docs/api/interfaces/NamedValueMutation.html +1 -1
- package/docs/api/interfaces/NativeAdapterOptions.html +6 -2
- package/docs/api/interfaces/ReadOptions.html +2 -2
- package/docs/api/interfaces/ReclaimOptions.html +2 -2
- package/docs/api/interfaces/ReconnectOptions.html +2 -0
- package/docs/api/interfaces/RetryOptions.html +2 -2
- package/docs/api/interfaces/RetryOutcome.html +1 -1
- package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
- package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
- package/docs/api/interfaces/TransitionOptions.html +2 -2
- package/docs/api/interfaces/TransitionOutcome.html +1 -1
- package/docs/api/modules.html +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
QueueClient: () => QueueClient,
|
|
56
56
|
QueueWorker: () => QueueWorker,
|
|
57
57
|
RawCodec: () => RawCodec,
|
|
58
|
+
ReconnectingExecutor: () => ReconnectingExecutor,
|
|
58
59
|
SetStore: () => SetStore,
|
|
59
60
|
SortedSetStore: () => SortedSetStore,
|
|
60
61
|
StaleLeaseError: () => StaleLeaseError,
|
|
@@ -69,6 +70,7 @@ __export(index_exports, {
|
|
|
69
70
|
classifyServerError: () => classifyServerError,
|
|
70
71
|
complete: () => complete,
|
|
71
72
|
fail: () => fail,
|
|
73
|
+
isReconnectableClosedConnectionError: () => isReconnectableClosedConnectionError,
|
|
72
74
|
mapException: () => mapException,
|
|
73
75
|
retry: () => retry,
|
|
74
76
|
transition: () => transition
|
|
@@ -1470,15 +1472,21 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1470
1472
|
maxChunkBytes;
|
|
1471
1473
|
requestId = 0n;
|
|
1472
1474
|
protocolLanes;
|
|
1475
|
+
heartbeatIntervalMs;
|
|
1476
|
+
heartbeatInFlight = false;
|
|
1477
|
+
heartbeatTimer;
|
|
1478
|
+
lastActivityMs = Date.now();
|
|
1473
1479
|
timeoutMs;
|
|
1474
|
-
constructor(socket, timeoutMs, protocolLanes, maxChunkBytes) {
|
|
1480
|
+
constructor(socket, timeoutMs, protocolLanes, maxChunkBytes, heartbeatIntervalMs) {
|
|
1475
1481
|
this.socket = socket;
|
|
1476
1482
|
this.maxChunkBytes = Math.max(1, Math.trunc(maxChunkBytes));
|
|
1477
1483
|
this.timeoutMs = timeoutMs;
|
|
1478
1484
|
this.protocolLanes = Math.max(1, Math.trunc(protocolLanes));
|
|
1485
|
+
this.heartbeatIntervalMs = normalizeHeartbeatInterval(heartbeatIntervalMs);
|
|
1479
1486
|
this.socket.on("data", (chunk) => this.onData(chunk));
|
|
1480
1487
|
this.socket.on("error", (error) => this.failAll(error));
|
|
1481
1488
|
this.socket.on("close", () => this.failAll(new FerricStoreError("FerricStore connection closed")));
|
|
1489
|
+
this.startHeartbeat();
|
|
1482
1490
|
}
|
|
1483
1491
|
static async fromUrl(url, options = {}) {
|
|
1484
1492
|
const parsed = parseFerricUrl(url);
|
|
@@ -1487,7 +1495,8 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1487
1495
|
socket,
|
|
1488
1496
|
options.timeoutMs ?? 3e4,
|
|
1489
1497
|
options.protocolLanes ?? 8,
|
|
1490
|
-
options.maxChunkBytes ?? 64 * 1024 * 1024
|
|
1498
|
+
options.maxChunkBytes ?? 64 * 1024 * 1024,
|
|
1499
|
+
options.heartbeatIntervalMs
|
|
1491
1500
|
);
|
|
1492
1501
|
await adapter.startup(options.clientName);
|
|
1493
1502
|
const password = options.password ?? parsed.password;
|
|
@@ -1511,6 +1520,7 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1511
1520
|
return;
|
|
1512
1521
|
}
|
|
1513
1522
|
this.closed = true;
|
|
1523
|
+
this.stopHeartbeat();
|
|
1514
1524
|
this.socket.end();
|
|
1515
1525
|
}
|
|
1516
1526
|
async startup(clientName) {
|
|
@@ -1532,6 +1542,7 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1532
1542
|
if (this.closed) {
|
|
1533
1543
|
throw new FerricStoreError("FerricStore connection is closed");
|
|
1534
1544
|
}
|
|
1545
|
+
this.lastActivityMs = Date.now();
|
|
1535
1546
|
const requestId = this.nextRequestId();
|
|
1536
1547
|
const frame = encodeRequest(this.assignLane(command), requestId);
|
|
1537
1548
|
return await new Promise((resolve, reject) => {
|
|
@@ -1545,10 +1556,12 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1545
1556
|
opcode: command.opcode,
|
|
1546
1557
|
reject: (reason) => {
|
|
1547
1558
|
clearTimeout(timer);
|
|
1559
|
+
this.lastActivityMs = Date.now();
|
|
1548
1560
|
reject(reason instanceof Error ? reason : classifyServerError(String(reason), reason));
|
|
1549
1561
|
},
|
|
1550
1562
|
resolve: (value) => {
|
|
1551
1563
|
clearTimeout(timer);
|
|
1564
|
+
this.lastActivityMs = Date.now();
|
|
1552
1565
|
resolve(value);
|
|
1553
1566
|
}
|
|
1554
1567
|
});
|
|
@@ -1557,11 +1570,43 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1557
1570
|
this.pending.delete(requestId);
|
|
1558
1571
|
this.cleanupChunksForRequest(requestId);
|
|
1559
1572
|
clearTimeout(timer);
|
|
1573
|
+
this.lastActivityMs = Date.now();
|
|
1560
1574
|
reject(error);
|
|
1561
1575
|
}
|
|
1562
1576
|
});
|
|
1563
1577
|
});
|
|
1564
1578
|
}
|
|
1579
|
+
startHeartbeat() {
|
|
1580
|
+
if (this.heartbeatIntervalMs == null) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1584
|
+
void this.sendHeartbeat();
|
|
1585
|
+
}, this.heartbeatIntervalMs);
|
|
1586
|
+
this.heartbeatTimer.unref?.();
|
|
1587
|
+
}
|
|
1588
|
+
stopHeartbeat() {
|
|
1589
|
+
if (this.heartbeatTimer == null) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
clearInterval(this.heartbeatTimer);
|
|
1593
|
+
this.heartbeatTimer = void 0;
|
|
1594
|
+
}
|
|
1595
|
+
async sendHeartbeat() {
|
|
1596
|
+
if (this.closed || this.heartbeatInFlight || this.pending.size > 0 || this.heartbeatIntervalMs == null) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if (Date.now() - this.lastActivityMs < this.heartbeatIntervalMs) {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
this.heartbeatInFlight = true;
|
|
1603
|
+
try {
|
|
1604
|
+
await this.request(buildProtocolCommand(["PING"]));
|
|
1605
|
+
} catch {
|
|
1606
|
+
} finally {
|
|
1607
|
+
this.heartbeatInFlight = false;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1565
1610
|
onData(chunk) {
|
|
1566
1611
|
try {
|
|
1567
1612
|
this.buffer = this.buffer.byteLength === 0 ? chunk : import_node_buffer2.Buffer.concat([this.buffer, chunk]);
|
|
@@ -1620,6 +1665,7 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1620
1665
|
return;
|
|
1621
1666
|
}
|
|
1622
1667
|
this.closed = true;
|
|
1668
|
+
this.stopHeartbeat();
|
|
1623
1669
|
const error = reason instanceof Error ? reason : classifyServerError(String(reason), reason);
|
|
1624
1670
|
for (const pending of this.pending.values()) {
|
|
1625
1671
|
pending.reject(error);
|
|
@@ -1652,6 +1698,74 @@ var NativeAdapter = class _NativeAdapter {
|
|
|
1652
1698
|
return { ...command, laneId: this.dataLane };
|
|
1653
1699
|
}
|
|
1654
1700
|
};
|
|
1701
|
+
var ReconnectingExecutor = class {
|
|
1702
|
+
constructor(createExecutor, options = {}) {
|
|
1703
|
+
this.createExecutor = createExecutor;
|
|
1704
|
+
this.maxRetries = Math.max(0, Math.trunc(options.maxRetries ?? 1));
|
|
1705
|
+
this.executorPromise = createExecutor();
|
|
1706
|
+
}
|
|
1707
|
+
createExecutor;
|
|
1708
|
+
closed = false;
|
|
1709
|
+
maxRetries;
|
|
1710
|
+
executorPromise;
|
|
1711
|
+
reconnectPromise;
|
|
1712
|
+
async executeCommand(...args) {
|
|
1713
|
+
return await this.withReconnect((executor) => executor.executeCommand(...args));
|
|
1714
|
+
}
|
|
1715
|
+
async executePipeline(commands, options = {}) {
|
|
1716
|
+
return await this.withReconnect(async (executor) => {
|
|
1717
|
+
if (executor.executePipeline != null) {
|
|
1718
|
+
return await executor.executePipeline(commands, options);
|
|
1719
|
+
}
|
|
1720
|
+
return await Promise.all(commands.map((command) => executor.executeCommand(...command)));
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
async close() {
|
|
1724
|
+
if (this.closed) {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
this.closed = true;
|
|
1728
|
+
const executor = await this.executorPromise.catch(() => void 0);
|
|
1729
|
+
await Promise.resolve(executor?.close?.());
|
|
1730
|
+
}
|
|
1731
|
+
async withReconnect(operation) {
|
|
1732
|
+
let executor = await this.executorPromise;
|
|
1733
|
+
for (let attempt = 0; ; attempt++) {
|
|
1734
|
+
try {
|
|
1735
|
+
return await operation(executor);
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
if (this.closed || attempt >= this.maxRetries || !isReconnectableClosedConnectionError(error)) {
|
|
1738
|
+
throw error;
|
|
1739
|
+
}
|
|
1740
|
+
executor = await this.reconnect(executor);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async reconnect(staleExecutor) {
|
|
1745
|
+
if (this.closed) {
|
|
1746
|
+
throw new FerricStoreError("FerricStore client is closed");
|
|
1747
|
+
}
|
|
1748
|
+
if (this.reconnectPromise != null) {
|
|
1749
|
+
return await this.reconnectPromise;
|
|
1750
|
+
}
|
|
1751
|
+
const currentExecutor = await this.executorPromise.catch(() => void 0);
|
|
1752
|
+
if (currentExecutor != null && currentExecutor !== staleExecutor) {
|
|
1753
|
+
return currentExecutor;
|
|
1754
|
+
}
|
|
1755
|
+
this.reconnectPromise = (async () => {
|
|
1756
|
+
await Promise.resolve(staleExecutor.close?.()).catch(() => void 0);
|
|
1757
|
+
const nextExecutor = await this.createExecutor();
|
|
1758
|
+
this.executorPromise = Promise.resolve(nextExecutor);
|
|
1759
|
+
return nextExecutor;
|
|
1760
|
+
})().finally(() => {
|
|
1761
|
+
this.reconnectPromise = void 0;
|
|
1762
|
+
});
|
|
1763
|
+
return await this.reconnectPromise;
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
function isReconnectableClosedConnectionError(error) {
|
|
1767
|
+
return error instanceof Error && error.message === "FerricStore connection is closed";
|
|
1768
|
+
}
|
|
1655
1769
|
function parseFerricUrl(value) {
|
|
1656
1770
|
const url = new URL(value);
|
|
1657
1771
|
if (url.protocol !== "ferric:" && url.protocol !== "ferrics:") {
|
|
@@ -1675,6 +1789,7 @@ async function connect(parsed, options) {
|
|
|
1675
1789
|
}, timeoutMs);
|
|
1676
1790
|
timer.unref?.();
|
|
1677
1791
|
socket.setNoDelay(true);
|
|
1792
|
+
socket.setKeepAlive(options.keepAlive ?? true, normalizeKeepAliveInitialDelay(options.keepAliveInitialDelayMs));
|
|
1678
1793
|
socket.once("connect", () => {
|
|
1679
1794
|
clearTimeout(timer);
|
|
1680
1795
|
resolve(socket);
|
|
@@ -1685,6 +1800,15 @@ async function connect(parsed, options) {
|
|
|
1685
1800
|
});
|
|
1686
1801
|
});
|
|
1687
1802
|
}
|
|
1803
|
+
function normalizeHeartbeatInterval(value) {
|
|
1804
|
+
if (value != null && value <= 0) {
|
|
1805
|
+
return void 0;
|
|
1806
|
+
}
|
|
1807
|
+
return Math.trunc(value ?? 6e4);
|
|
1808
|
+
}
|
|
1809
|
+
function normalizeKeepAliveInitialDelay(value) {
|
|
1810
|
+
return Math.max(0, Math.trunc(value ?? 3e4));
|
|
1811
|
+
}
|
|
1688
1812
|
|
|
1689
1813
|
// src/codecs.ts
|
|
1690
1814
|
var RawCodec = class {
|
|
@@ -3075,7 +3199,10 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
3075
3199
|
this.zset = new SortedSetStore(this);
|
|
3076
3200
|
}
|
|
3077
3201
|
static async fromUrl(url, options = {}) {
|
|
3078
|
-
|
|
3202
|
+
const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
|
|
3203
|
+
const createExecutor = async () => await NativeAdapter.fromUrl(url, options.nativeOptions);
|
|
3204
|
+
const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
|
|
3205
|
+
return new _FerricStoreClient(executor, options);
|
|
3079
3206
|
}
|
|
3080
3207
|
async command(...args) {
|
|
3081
3208
|
return await this.executor.executeCommand(...args);
|
|
@@ -5198,6 +5325,7 @@ function valueRefToString(value) {
|
|
|
5198
5325
|
QueueClient,
|
|
5199
5326
|
QueueWorker,
|
|
5200
5327
|
RawCodec,
|
|
5328
|
+
ReconnectingExecutor,
|
|
5201
5329
|
SetStore,
|
|
5202
5330
|
SortedSetStore,
|
|
5203
5331
|
StaleLeaseError,
|
|
@@ -5212,6 +5340,7 @@ function valueRefToString(value) {
|
|
|
5212
5340
|
classifyServerError,
|
|
5213
5341
|
complete,
|
|
5214
5342
|
fail,
|
|
5343
|
+
isReconnectableClosedConnectionError,
|
|
5215
5344
|
mapException,
|
|
5216
5345
|
retry,
|
|
5217
5346
|
transition
|