@ferricstore/ferricstore 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +37 -1
  2. package/dist/index.cjs +1264 -36
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +277 -6
  5. package/dist/index.d.ts +277 -6
  6. package/dist/index.js +1259 -36
  7. package/dist/index.js.map +1 -1
  8. package/docs/api/assets/hierarchy.js +1 -1
  9. package/docs/api/assets/highlight.css +6 -6
  10. package/docs/api/assets/navigation.js +1 -1
  11. package/docs/api/assets/search.js +1 -1
  12. package/docs/api/classes/FerricStoreClient.html +19 -2
  13. package/docs/api/classes/FerricStoreError.html +3 -3
  14. package/docs/api/classes/FlowAlreadyExistsError.html +3 -3
  15. package/docs/api/classes/FlowNotFoundError.html +3 -3
  16. package/docs/api/classes/FlowWrongStateError.html +3 -3
  17. package/docs/api/classes/InvalidCommandError.html +3 -3
  18. package/docs/api/classes/LockHeldError.html +3 -3
  19. package/docs/api/classes/LockNotOwnedError.html +3 -3
  20. package/docs/api/classes/NativeAdapter.html +3 -2
  21. package/docs/api/classes/OverloadedError.html +3 -3
  22. package/docs/api/classes/ReconnectingExecutor.html +7 -0
  23. package/docs/api/classes/RoutingTopology.html +9 -0
  24. package/docs/api/classes/StaleLeaseError.html +3 -3
  25. package/docs/api/classes/TopologyNativeAdapterPool.html +8 -0
  26. package/docs/api/functions/isReconnectableClosedConnectionError.html +1 -0
  27. package/docs/api/hierarchy.html +1 -1
  28. package/docs/api/index.html +16 -7
  29. package/docs/api/interfaces/AutoBatchOptions.html +2 -2
  30. package/docs/api/interfaces/CancelOptions.html +2 -2
  31. package/docs/api/interfaces/ClaimDueOptions.html +2 -2
  32. package/docs/api/interfaces/ClaimedItem.html +2 -3
  33. package/docs/api/interfaces/CommandExecutor.html +4 -2
  34. package/docs/api/interfaces/CompleteOptions.html +2 -2
  35. package/docs/api/interfaces/CompleteOutcome.html +1 -1
  36. package/docs/api/interfaces/CreateManyOptions.html +2 -2
  37. package/docs/api/interfaces/CreateOptions.html +2 -2
  38. package/docs/api/interfaces/ExecutePipelineOptions.html +2 -2
  39. package/docs/api/interfaces/FailOptions.html +2 -2
  40. package/docs/api/interfaces/FailOutcome.html +1 -1
  41. package/docs/api/interfaces/FencedItem.html +2 -2
  42. package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +6 -0
  43. package/docs/api/interfaces/FerricStoreClientOptions.html +2 -2
  44. package/docs/api/interfaces/FetchOrComputeResult.html +2 -2
  45. package/docs/api/interfaces/FlowRecord.html +2 -2
  46. package/docs/api/interfaces/KeyInfo.html +2 -2
  47. package/docs/api/interfaces/MutateOptions.html +2 -2
  48. package/docs/api/interfaces/NamedValueMutation.html +1 -1
  49. package/docs/api/interfaces/NativeAdapterOptions.html +12 -2
  50. package/docs/api/interfaces/ProtocolCommand.html +5 -0
  51. package/docs/api/interfaces/RateLimitResult.html +2 -2
  52. package/docs/api/interfaces/ReadOptions.html +2 -2
  53. package/docs/api/interfaces/ReclaimOptions.html +2 -2
  54. package/docs/api/interfaces/ReconnectOptions.html +2 -0
  55. package/docs/api/interfaces/RetryOptions.html +2 -2
  56. package/docs/api/interfaces/RetryOutcome.html +1 -1
  57. package/docs/api/interfaces/RoutingEndpoint.html +5 -0
  58. package/docs/api/interfaces/RoutingRoute.html +7 -0
  59. package/docs/api/interfaces/SearchOptions.html +12 -0
  60. package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
  61. package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
  62. package/docs/api/interfaces/TransitionOptions.html +2 -2
  63. package/docs/api/interfaces/TransitionOutcome.html +1 -1
  64. package/docs/api/modules.html +1 -1
  65. package/docs/api/types/CommandArgument.html +1 -1
  66. package/docs/api/types/EndpointPolicy.html +1 -0
  67. package/docs/api/types/ManagementPairs.html +1 -0
  68. package/docs/api/types/QueueBatchHandler.html +1 -1
  69. package/docs/api/types/QueueHandler.html +1 -1
  70. package/docs/api/types/QueueJob.html +1 -0
  71. package/docs/api/types/SearchStateMeta.html +1 -0
  72. package/docs/api/variables/COMMAND_OPCODES.html +1 -0
  73. package/docs/python-parity.md +4 -1
  74. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BitmapStore: () => BitmapStore,
34
34
  BloomFilterStore: () => BloomFilterStore,
35
+ COMMAND_OPCODES: () => COMMAND_OPCODES,
35
36
  CountMinSketchStore: () => CountMinSketchStore,
36
37
  CuckooFilterStore: () => CuckooFilterStore,
37
38
  FerricStoreClient: () => FerricStoreClient,
@@ -55,12 +56,15 @@ __export(index_exports, {
55
56
  QueueClient: () => QueueClient,
56
57
  QueueWorker: () => QueueWorker,
57
58
  RawCodec: () => RawCodec,
59
+ ReconnectingExecutor: () => ReconnectingExecutor,
60
+ RoutingTopology: () => RoutingTopology,
58
61
  SetStore: () => SetStore,
59
62
  SortedSetStore: () => SortedSetStore,
60
63
  StaleLeaseError: () => StaleLeaseError,
61
64
  StreamStore: () => StreamStore,
62
65
  TDigestStore: () => TDigestStore,
63
66
  TopKStore: () => TopKStore,
67
+ TopologyNativeAdapterPool: () => TopologyNativeAdapterPool,
64
68
  Workflow: () => Workflow,
65
69
  WorkflowClient: () => WorkflowClient,
66
70
  WorkflowContext: () => WorkflowContext,
@@ -69,6 +73,7 @@ __export(index_exports, {
69
73
  classifyServerError: () => classifyServerError,
70
74
  complete: () => complete,
71
75
  fail: () => fail,
76
+ isReconnectableClosedConnectionError: () => isReconnectableClosedConnectionError,
72
77
  mapException: () => mapException,
73
78
  retry: () => retry,
74
79
  transition: () => transition
@@ -187,24 +192,171 @@ var FLAG_CUSTOM_PAYLOAD = 2;
187
192
  var FLAG_COMPRESSED = 8;
188
193
  var FLAG_MORE_CHUNKS = 32;
189
194
  var STATUS_OK = 0;
195
+ var COMMAND_OPCODES = {
196
+ "HELLO": 1,
197
+ "AUTH": 2,
198
+ "PING": 3,
199
+ "CLIENT.SETNAME": 4,
200
+ "CLIENT.INFO": 5,
201
+ "ROUTE": 6,
202
+ "SHARDS": 7,
203
+ "BACKPRESSURE": 8,
204
+ "QUIT": 9,
205
+ "GOAWAY": 10,
206
+ "OPTIONS": 11,
207
+ "STARTUP": 12,
208
+ "WINDOW_UPDATE": 13,
209
+ "PIPELINE": 14,
210
+ "ROUTE_BATCH": 15,
211
+ "EVENT": 16,
212
+ "SUBSCRIBE_EVENTS": 17,
213
+ "UNSUBSCRIBE_EVENTS": 18,
214
+ "COMMAND_EXEC": 256,
215
+ "GET": 257,
216
+ "SET": 258,
217
+ "DEL": 259,
218
+ "MGET": 260,
219
+ "MSET": 261,
220
+ "CAS": 262,
221
+ "LOCK": 263,
222
+ "UNLOCK": 264,
223
+ "EXTEND": 265,
224
+ "RATELIMIT.ADD": 266,
225
+ "FETCH_OR_COMPUTE": 267,
226
+ "FETCH_OR_COMPUTE_RESULT": 268,
227
+ "FETCH_OR_COMPUTE_ERROR": 269,
228
+ "HSET": 272,
229
+ "HGET": 273,
230
+ "HMGET": 274,
231
+ "HGETALL": 275,
232
+ "LPUSH": 288,
233
+ "RPUSH": 289,
234
+ "LPOP": 290,
235
+ "RPOP": 291,
236
+ "LRANGE": 292,
237
+ "SADD": 304,
238
+ "SREM": 305,
239
+ "SMEMBERS": 306,
240
+ "SISMEMBER": 307,
241
+ "ZADD": 320,
242
+ "ZREM": 321,
243
+ "ZRANGE": 322,
244
+ "ZSCORE": 323,
245
+ "FLOW.CREATE": 513,
246
+ "FLOW.GET": 514,
247
+ "FLOW.CLAIM_DUE": 515,
248
+ "FLOW.COMPLETE": 516,
249
+ "FLOW.TRANSITION": 517,
250
+ "FLOW.RETRY": 518,
251
+ "FLOW.FAIL": 519,
252
+ "FLOW.CANCEL": 520,
253
+ "FLOW.EXTEND_LEASE": 521,
254
+ "FLOW.HISTORY": 522,
255
+ "FLOW.VALUE.PUT": 523,
256
+ "FLOW.VALUE.MGET": 524,
257
+ "FLOW.SIGNAL": 525,
258
+ "FLOW.LIST": 526,
259
+ "FLOW.CREATE_MANY": 527,
260
+ "FLOW.COMPLETE_MANY": 528,
261
+ "FLOW.TRANSITION_MANY": 529,
262
+ "FLOW.RETRY_MANY": 530,
263
+ "FLOW.FAIL_MANY": 531,
264
+ "FLOW.CANCEL_MANY": 532,
265
+ "FLOW.RECLAIM": 533,
266
+ "FLOW.REWIND": 534,
267
+ "FLOW.TERMINALS": 535,
268
+ "FLOW.FAILURES": 536,
269
+ "FLOW.BY_PARENT": 537,
270
+ "FLOW.BY_ROOT": 538,
271
+ "FLOW.BY_CORRELATION": 539,
272
+ "FLOW.INFO": 540,
273
+ "FLOW.STUCK": 541,
274
+ "FLOW.POLICY.SET": 542,
275
+ "FLOW.POLICY.GET": 543,
276
+ "FLOW.SPAWN_CHILDREN": 544,
277
+ "FLOW.RETENTION_CLEANUP": 545,
278
+ "FLOW.STEP_CONTINUE": 546,
279
+ "FLOW.START_AND_CLAIM": 547,
280
+ "FLOW.RUN_STEPS_MANY": 548,
281
+ "FLOW.SCHEDULE.CREATE": 549,
282
+ "FLOW.SCHEDULE.GET": 550,
283
+ "FLOW.SCHEDULE.DELETE": 551,
284
+ "FLOW.SCHEDULE.FIRE_DUE": 552,
285
+ "FLOW.SCHEDULE.LIST": 553,
286
+ "FLOW.SCHEDULE.FIRE": 554,
287
+ "FLOW.SCHEDULE.PAUSE": 555,
288
+ "FLOW.SCHEDULE.RESUME": 556,
289
+ "FLOW.STATS": 557,
290
+ "FLOW.ATTRIBUTES": 558,
291
+ "FLOW.ATTRIBUTE_VALUES": 559,
292
+ "FLOW.SEARCH": 560,
293
+ "FLOW.EFFECT.RESERVE": 576,
294
+ "FLOW.EFFECT.CONFIRM": 577,
295
+ "FLOW.EFFECT.FAIL": 578,
296
+ "FLOW.EFFECT.COMPENSATE": 579,
297
+ "FLOW.EFFECT.GET": 580,
298
+ "FLOW.GOVERNANCE.LEDGER": 581,
299
+ "FLOW.APPROVAL.REQUEST": 582,
300
+ "FLOW.APPROVAL.APPROVE": 583,
301
+ "FLOW.APPROVAL.REJECT": 584,
302
+ "FLOW.APPROVAL.GET": 585,
303
+ "FLOW.CIRCUIT.OPEN": 586,
304
+ "FLOW.CIRCUIT.CLOSE": 587,
305
+ "FLOW.CIRCUIT.GET": 588,
306
+ "FLOW.BUDGET.RESERVE": 589,
307
+ "FLOW.BUDGET.GET": 590,
308
+ "FLOW.LIMIT.LEASE": 591,
309
+ "FLOW.LIMIT.SPEND": 592,
310
+ "FLOW.LIMIT.RELEASE": 593,
311
+ "FLOW.LIMIT.GET": 594,
312
+ "FLOW.APPROVAL.LIST": 595,
313
+ "FLOW.GOVERNANCE.OVERVIEW": 596,
314
+ "FLOW.BUDGET.LIST": 597,
315
+ "FLOW.LIMIT.LIST": 598,
316
+ "FLOW.BUDGET.COMMIT": 599,
317
+ "FLOW.BUDGET.RELEASE": 600,
318
+ "CLUSTER.HEALTH": 769,
319
+ "CLUSTER.STATS": 770,
320
+ "CLUSTER.KEYSLOT": 771,
321
+ "CLUSTER.SLOTS": 772,
322
+ "CLUSTER.STATUS": 773,
323
+ "CLUSTER.JOIN": 774,
324
+ "CLUSTER.LEAVE": 775,
325
+ "CLUSTER.FAILOVER": 776,
326
+ "CLUSTER.PROMOTE": 777,
327
+ "CLUSTER.DEMOTE": 778,
328
+ "CLUSTER.ROLE": 779,
329
+ "FERRICSTORE.KEY_INFO": 780,
330
+ "FERRICSTORE.CONFIG": 781,
331
+ "FERRICSTORE.HOTNESS": 782,
332
+ "FERRICSTORE.METRICS": 783,
333
+ "FERRICSTORE.BLOBGC": 784
334
+ };
190
335
  var OPCODES = {
191
- startup: 12,
192
- auth: 2,
193
- ping: 3,
194
- clientSetName: 4,
195
- clientInfo: 5,
196
- commandExec: 256,
197
- get: 257,
198
- set: 258,
199
- del: 259,
200
- mget: 260,
201
- mset: 261,
202
- pipeline: 14,
203
- flowCreate: 513,
204
- flowClaimDue: 515,
205
- flowComplete: 516,
206
- flowCreateMany: 527,
207
- flowCompleteMany: 528
336
+ startup: COMMAND_OPCODES.STARTUP,
337
+ auth: COMMAND_OPCODES.AUTH,
338
+ ping: COMMAND_OPCODES.PING,
339
+ clientSetName: COMMAND_OPCODES["CLIENT.SETNAME"],
340
+ clientInfo: COMMAND_OPCODES["CLIENT.INFO"],
341
+ route: COMMAND_OPCODES.ROUTE,
342
+ routeBatch: COMMAND_OPCODES.ROUTE_BATCH,
343
+ shards: COMMAND_OPCODES.SHARDS,
344
+ backpressure: COMMAND_OPCODES.BACKPRESSURE,
345
+ options: COMMAND_OPCODES.OPTIONS,
346
+ quit: COMMAND_OPCODES.QUIT,
347
+ commandExec: COMMAND_OPCODES.COMMAND_EXEC,
348
+ get: COMMAND_OPCODES.GET,
349
+ set: COMMAND_OPCODES.SET,
350
+ del: COMMAND_OPCODES.DEL,
351
+ mget: COMMAND_OPCODES.MGET,
352
+ mset: COMMAND_OPCODES.MSET,
353
+ pipeline: COMMAND_OPCODES.PIPELINE,
354
+ flowCreate: COMMAND_OPCODES["FLOW.CREATE"],
355
+ flowClaimDue: COMMAND_OPCODES["FLOW.CLAIM_DUE"],
356
+ flowComplete: COMMAND_OPCODES["FLOW.COMPLETE"],
357
+ flowCreateMany: COMMAND_OPCODES["FLOW.CREATE_MANY"],
358
+ flowCompleteMany: COMMAND_OPCODES["FLOW.COMPLETE_MANY"],
359
+ flowSearch: COMMAND_OPCODES["FLOW.SEARCH"]
208
360
  };
209
361
  var COMPACT_FLOW_CLAIM_JOBS = 128;
210
362
  var COMPACT_OK_LIST = 129;
@@ -337,12 +489,46 @@ function buildProtocolCommand(args) {
337
489
  }
338
490
  const command = asText(args[0]).toUpperCase();
339
491
  const commandArgs = args.slice(1);
492
+ if (command === "COMMAND_EXEC" && commandArgs.length >= 1) {
493
+ return commandExec(commandArgs);
494
+ }
495
+ if (command === "HELLO" || command === "STARTUP" || command === "WINDOW_UPDATE") {
496
+ return { laneId: 0, opcode: COMMAND_OPCODES[command], payload: optionMap(commandArgs) };
497
+ }
498
+ if (command === "AUTH") {
499
+ const payload = commandArgs.length === 1 ? { password: commandArgs[0], username: "default" } : { password: commandArgs[1], username: commandArgs[0] };
500
+ return { laneId: 0, opcode: OPCODES.auth, payload };
501
+ }
340
502
  if (command === "PING") {
341
503
  return { laneId: 0, opcode: OPCODES.ping, payload: commandArgs.length === 0 ? {} : { message: commandArgs[0] } };
342
504
  }
505
+ if (command === "OPTIONS") {
506
+ return { laneId: 0, opcode: OPCODES.options, payload: {} };
507
+ }
508
+ if (command === "BACKPRESSURE") {
509
+ return { laneId: 0, opcode: OPCODES.backpressure, payload: {} };
510
+ }
511
+ if (command === "QUIT") {
512
+ return { laneId: 0, opcode: OPCODES.quit, payload: {} };
513
+ }
514
+ if (command === "ROUTE" && commandArgs.length >= 1) {
515
+ return { laneId: 0, opcode: OPCODES.route, payload: { key: commandArgs[0] } };
516
+ }
517
+ if (command === "ROUTE_BATCH") {
518
+ return { laneId: 0, opcode: OPCODES.routeBatch, payload: { keys: commandArgs } };
519
+ }
520
+ if (command === "SHARDS") {
521
+ return { laneId: 0, opcode: OPCODES.shards, payload: {} };
522
+ }
343
523
  if (command === "CLIENT" && asText(commandArgs[0]).toUpperCase() === "SETNAME" && commandArgs.length === 2) {
344
524
  return { laneId: 0, opcode: OPCODES.clientSetName, payload: { name: commandArgs[1] } };
345
525
  }
526
+ if (command === "CLIENT.SETNAME" && commandArgs.length === 1) {
527
+ return { laneId: 0, opcode: OPCODES.clientSetName, payload: { name: commandArgs[0] } };
528
+ }
529
+ if (command === "CLIENT" && asText(commandArgs[0]).toUpperCase() === "INFO" && commandArgs.length === 1 || command === "CLIENT.INFO" && commandArgs.length === 0) {
530
+ return { laneId: 0, opcode: OPCODES.clientInfo, payload: {} };
531
+ }
346
532
  if (command === "GET" && commandArgs.length >= 1) {
347
533
  return { opcode: OPCODES.get, payload: { key: commandArgs[0] } };
348
534
  }
@@ -361,7 +547,7 @@ function buildProtocolCommand(args) {
361
547
  if (command === "DEL") {
362
548
  return { opcode: OPCODES.del, payload: { keys: commandArgs } };
363
549
  }
364
- if (command.startsWith("FLOW.") && hasFlowCommandOnlyOption(commandArgs)) {
550
+ if (command.startsWith("FLOW.") && hasFlowCommandOnlyOption(command, commandArgs)) {
365
551
  return commandExec(args);
366
552
  }
367
553
  if (command === "FLOW.CREATE") {
@@ -379,6 +565,9 @@ function buildProtocolCommand(args) {
379
565
  if (command === "FLOW.COMPLETE_MANY") {
380
566
  return flowCompleteManyPayload(commandArgs) ?? commandExec(args);
381
567
  }
568
+ if (command === "FLOW.SEARCH") {
569
+ return flowSearchPayload(commandArgs) ?? commandExec(args);
570
+ }
382
571
  return commandExec(args);
383
572
  }
384
573
  function commandExec(args) {
@@ -723,13 +912,13 @@ function writeOptionalBinary(out, offset, value) {
723
912
  }
724
913
  return writeBinary(out, offset, value);
725
914
  }
726
- function hasFlowCommandOnlyOption(args) {
915
+ function hasFlowCommandOnlyOption(command, args) {
727
916
  return args.some((arg) => {
728
917
  if (typeof arg !== "string" && !import_node_buffer.Buffer.isBuffer(arg)) {
729
918
  return false;
730
919
  }
731
920
  const token = asText(arg).toUpperCase();
732
- return token === "STATE_META" || token === "INDEXED_STATE_META";
921
+ return token === "INDEXED_STATE_META" || command !== "FLOW.SEARCH" && token === "STATE_META";
733
922
  });
734
923
  }
735
924
  function flowCreatePayload(args) {
@@ -847,6 +1036,12 @@ function flowCompleteManyPayload(args) {
847
1036
  if (!auto && !mixed) payload.partition_key = partition;
848
1037
  return { opcode: OPCODES.flowCompleteMany, payload };
849
1038
  }
1039
+ function flowSearchPayload(args) {
1040
+ if (args.length < 1) return void 0;
1041
+ const payload = optionMap(args.slice(1));
1042
+ normalizeFlowSearchStateMeta(payload);
1043
+ return { opcode: OPCODES.flowSearch, payload: { type: args[0], ...payload } };
1044
+ }
850
1045
  function compactFlowCreateManyPayload(partition, rawItems, mixed, auto, options) {
851
1046
  if ("priority" in options || "idempotent" in options || "retention_ttl_ms" in options || typeof options.type === "undefined" || typeof options.state === "undefined" || typeof options.now_ms !== "number") {
852
1047
  return void 0;
@@ -949,6 +1144,78 @@ function compactFlowCompleteManyPayload(partition, rawItems, mixed, auto, option
949
1144
  }
950
1145
  return { flags: FLAG_CUSTOM_PAYLOAD, opcode: OPCODES.flowCompleteMany, payload: out };
951
1146
  }
1147
+ var OPTION_FIELDS = {
1148
+ CONSISTENT_PROJECTION: "consistent_projection",
1149
+ COUNT: "count",
1150
+ FROM_MS: "from_ms",
1151
+ INCLUDE_COLD: "include_cold",
1152
+ PARTITION: "partition_key",
1153
+ REV: "rev",
1154
+ STATE: "state",
1155
+ TERMINAL_ONLY: "terminal_only",
1156
+ TO_MS: "to_ms"
1157
+ };
1158
+ var BOOL_OPTION_FIELDS = /* @__PURE__ */ new Set(["consistent_projection", "include_cold", "rev", "terminal_only"]);
1159
+ function optionMap(args) {
1160
+ const payload = {};
1161
+ for (let index = 0; index < args.length; ) {
1162
+ const token = asText(args[index]).toUpperCase();
1163
+ if (token === "ATTRIBUTE") {
1164
+ if (index + 2 >= args.length) {
1165
+ throw new FerricStoreError("ATTRIBUTE requires name and value");
1166
+ }
1167
+ const name = asText(args[index + 1]);
1168
+ const attributes = ensureObjectField(payload, "attributes");
1169
+ attributes[name] = args[index + 2];
1170
+ index += 3;
1171
+ continue;
1172
+ }
1173
+ if (token === "STATE_META") {
1174
+ if (index + 2 >= args.length) {
1175
+ throw new FerricStoreError("STATE_META requires name and value");
1176
+ }
1177
+ const name = asText(args[index + 1]);
1178
+ const stateMeta = ensureObjectField(payload, "state_meta");
1179
+ stateMeta[name] = args[index + 2];
1180
+ index += 3;
1181
+ continue;
1182
+ }
1183
+ const field2 = OPTION_FIELDS[token] ?? token.toLowerCase();
1184
+ if (index + 1 >= args.length) {
1185
+ throw new FerricStoreError(`${token} requires a value`);
1186
+ }
1187
+ const value = args[index + 1];
1188
+ payload[field2] = BOOL_OPTION_FIELDS.has(field2) ? boolArg(value) : value;
1189
+ index += 2;
1190
+ }
1191
+ return payload;
1192
+ }
1193
+ function ensureObjectField(payload, field2) {
1194
+ const value = payload[field2];
1195
+ if (isPlainObject(value)) {
1196
+ return value;
1197
+ }
1198
+ const next = {};
1199
+ payload[field2] = next;
1200
+ return next;
1201
+ }
1202
+ function normalizeFlowSearchStateMeta(payload) {
1203
+ const stateMeta = payload.state_meta;
1204
+ if (!isPlainObject(stateMeta) || Object.keys(stateMeta).length === 0) {
1205
+ return;
1206
+ }
1207
+ if (Object.values(stateMeta).every(isPlainObject)) {
1208
+ return;
1209
+ }
1210
+ const state = payload.state;
1211
+ if (state == null) {
1212
+ throw new FerricStoreError("FLOW.SEARCH STATE_META filters require STATE or nested state metadata");
1213
+ }
1214
+ payload.state_meta = { [asText(state)]: stateMeta };
1215
+ }
1216
+ function isPlainObject(value) {
1217
+ return typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer.Buffer.isBuffer(value) && !(value instanceof Uint8Array);
1218
+ }
952
1219
  function parseFlowOptions(args, start, end, config) {
953
1220
  const payload = {};
954
1221
  const seen = /* @__PURE__ */ new Set();
@@ -1470,15 +1737,21 @@ var NativeAdapter = class _NativeAdapter {
1470
1737
  maxChunkBytes;
1471
1738
  requestId = 0n;
1472
1739
  protocolLanes;
1740
+ heartbeatIntervalMs;
1741
+ heartbeatInFlight = false;
1742
+ heartbeatTimer;
1743
+ lastActivityMs = Date.now();
1473
1744
  timeoutMs;
1474
- constructor(socket, timeoutMs, protocolLanes, maxChunkBytes) {
1745
+ constructor(socket, timeoutMs, protocolLanes, maxChunkBytes, heartbeatIntervalMs) {
1475
1746
  this.socket = socket;
1476
1747
  this.maxChunkBytes = Math.max(1, Math.trunc(maxChunkBytes));
1477
1748
  this.timeoutMs = timeoutMs;
1478
1749
  this.protocolLanes = Math.max(1, Math.trunc(protocolLanes));
1750
+ this.heartbeatIntervalMs = normalizeHeartbeatInterval(heartbeatIntervalMs);
1479
1751
  this.socket.on("data", (chunk) => this.onData(chunk));
1480
1752
  this.socket.on("error", (error) => this.failAll(error));
1481
1753
  this.socket.on("close", () => this.failAll(new FerricStoreError("FerricStore connection closed")));
1754
+ this.startHeartbeat();
1482
1755
  }
1483
1756
  static async fromUrl(url, options = {}) {
1484
1757
  const parsed = parseFerricUrl(url);
@@ -1487,7 +1760,8 @@ var NativeAdapter = class _NativeAdapter {
1487
1760
  socket,
1488
1761
  options.timeoutMs ?? 3e4,
1489
1762
  options.protocolLanes ?? 8,
1490
- options.maxChunkBytes ?? 64 * 1024 * 1024
1763
+ options.maxChunkBytes ?? 64 * 1024 * 1024,
1764
+ options.heartbeatIntervalMs
1491
1765
  );
1492
1766
  await adapter.startup(options.clientName);
1493
1767
  const password = options.password ?? parsed.password;
@@ -1500,6 +1774,9 @@ var NativeAdapter = class _NativeAdapter {
1500
1774
  const command = buildProtocolCommand(args);
1501
1775
  return await this.request(command);
1502
1776
  }
1777
+ async executeProtocolCommand(command, laneId) {
1778
+ return await this.request(laneId == null ? command : { ...command, laneId });
1779
+ }
1503
1780
  async executePipeline(commands, options = {}) {
1504
1781
  if (commands.length === 0) {
1505
1782
  return [];
@@ -1511,6 +1788,7 @@ var NativeAdapter = class _NativeAdapter {
1511
1788
  return;
1512
1789
  }
1513
1790
  this.closed = true;
1791
+ this.stopHeartbeat();
1514
1792
  this.socket.end();
1515
1793
  }
1516
1794
  async startup(clientName) {
@@ -1532,6 +1810,7 @@ var NativeAdapter = class _NativeAdapter {
1532
1810
  if (this.closed) {
1533
1811
  throw new FerricStoreError("FerricStore connection is closed");
1534
1812
  }
1813
+ this.lastActivityMs = Date.now();
1535
1814
  const requestId = this.nextRequestId();
1536
1815
  const frame = encodeRequest(this.assignLane(command), requestId);
1537
1816
  return await new Promise((resolve, reject) => {
@@ -1545,10 +1824,12 @@ var NativeAdapter = class _NativeAdapter {
1545
1824
  opcode: command.opcode,
1546
1825
  reject: (reason) => {
1547
1826
  clearTimeout(timer);
1827
+ this.lastActivityMs = Date.now();
1548
1828
  reject(reason instanceof Error ? reason : classifyServerError(String(reason), reason));
1549
1829
  },
1550
1830
  resolve: (value) => {
1551
1831
  clearTimeout(timer);
1832
+ this.lastActivityMs = Date.now();
1552
1833
  resolve(value);
1553
1834
  }
1554
1835
  });
@@ -1557,11 +1838,43 @@ var NativeAdapter = class _NativeAdapter {
1557
1838
  this.pending.delete(requestId);
1558
1839
  this.cleanupChunksForRequest(requestId);
1559
1840
  clearTimeout(timer);
1841
+ this.lastActivityMs = Date.now();
1560
1842
  reject(error);
1561
1843
  }
1562
1844
  });
1563
1845
  });
1564
1846
  }
1847
+ startHeartbeat() {
1848
+ if (this.heartbeatIntervalMs == null) {
1849
+ return;
1850
+ }
1851
+ this.heartbeatTimer = setInterval(() => {
1852
+ void this.sendHeartbeat();
1853
+ }, this.heartbeatIntervalMs);
1854
+ this.heartbeatTimer.unref?.();
1855
+ }
1856
+ stopHeartbeat() {
1857
+ if (this.heartbeatTimer == null) {
1858
+ return;
1859
+ }
1860
+ clearInterval(this.heartbeatTimer);
1861
+ this.heartbeatTimer = void 0;
1862
+ }
1863
+ async sendHeartbeat() {
1864
+ if (this.closed || this.heartbeatInFlight || this.pending.size > 0 || this.heartbeatIntervalMs == null) {
1865
+ return;
1866
+ }
1867
+ if (Date.now() - this.lastActivityMs < this.heartbeatIntervalMs) {
1868
+ return;
1869
+ }
1870
+ this.heartbeatInFlight = true;
1871
+ try {
1872
+ await this.request(buildProtocolCommand(["PING"]));
1873
+ } catch {
1874
+ } finally {
1875
+ this.heartbeatInFlight = false;
1876
+ }
1877
+ }
1565
1878
  onData(chunk) {
1566
1879
  try {
1567
1880
  this.buffer = this.buffer.byteLength === 0 ? chunk : import_node_buffer2.Buffer.concat([this.buffer, chunk]);
@@ -1620,6 +1933,7 @@ var NativeAdapter = class _NativeAdapter {
1620
1933
  return;
1621
1934
  }
1622
1935
  this.closed = true;
1936
+ this.stopHeartbeat();
1623
1937
  const error = reason instanceof Error ? reason : classifyServerError(String(reason), reason);
1624
1938
  for (const pending of this.pending.values()) {
1625
1939
  pending.reject(error);
@@ -1652,6 +1966,88 @@ var NativeAdapter = class _NativeAdapter {
1652
1966
  return { ...command, laneId: this.dataLane };
1653
1967
  }
1654
1968
  };
1969
+ var ReconnectingExecutor = class {
1970
+ constructor(createExecutor, options = {}) {
1971
+ this.createExecutor = createExecutor;
1972
+ this.maxRetries = Math.max(0, Math.trunc(options.maxRetries ?? 1));
1973
+ this.executorPromise = createExecutor();
1974
+ }
1975
+ createExecutor;
1976
+ closed = false;
1977
+ maxRetries;
1978
+ executorPromise;
1979
+ reconnectPromise;
1980
+ async executeCommand(...args) {
1981
+ return await this.withReconnect((executor) => executor.executeCommand(...args));
1982
+ }
1983
+ async executePipeline(commands, options = {}) {
1984
+ return await this.withReconnect(async (executor) => {
1985
+ if (executor.executePipeline != null) {
1986
+ return await executor.executePipeline(commands, options);
1987
+ }
1988
+ return await Promise.all(commands.map((command) => executor.executeCommand(...command)));
1989
+ });
1990
+ }
1991
+ async refreshTopology() {
1992
+ const executor = await this.executorPromise;
1993
+ if (executor.refreshTopology == null) {
1994
+ throw new FerricStoreError("topology refresh requires a topology-aware native executor");
1995
+ }
1996
+ return await executor.refreshTopology();
1997
+ }
1998
+ async route(key) {
1999
+ const executor = await this.executorPromise;
2000
+ if (executor.route == null) {
2001
+ throw new FerricStoreError("route lookup requires a topology-aware native executor");
2002
+ }
2003
+ return await executor.route(key);
2004
+ }
2005
+ async close() {
2006
+ if (this.closed) {
2007
+ return;
2008
+ }
2009
+ this.closed = true;
2010
+ const executor = await this.executorPromise.catch(() => void 0);
2011
+ await Promise.resolve(executor?.close?.());
2012
+ }
2013
+ async withReconnect(operation) {
2014
+ let executor = await this.executorPromise;
2015
+ for (let attempt = 0; ; attempt++) {
2016
+ try {
2017
+ return await operation(executor);
2018
+ } catch (error) {
2019
+ if (this.closed || attempt >= this.maxRetries || !isReconnectableClosedConnectionError(error)) {
2020
+ throw error;
2021
+ }
2022
+ executor = await this.reconnect(executor);
2023
+ }
2024
+ }
2025
+ }
2026
+ async reconnect(staleExecutor) {
2027
+ if (this.closed) {
2028
+ throw new FerricStoreError("FerricStore client is closed");
2029
+ }
2030
+ if (this.reconnectPromise != null) {
2031
+ return await this.reconnectPromise;
2032
+ }
2033
+ const currentExecutor = await this.executorPromise.catch(() => void 0);
2034
+ if (currentExecutor != null && currentExecutor !== staleExecutor) {
2035
+ return currentExecutor;
2036
+ }
2037
+ this.reconnectPromise = (async () => {
2038
+ await Promise.resolve(staleExecutor.close?.()).catch(() => void 0);
2039
+ const nextExecutor = await this.createExecutor();
2040
+ this.executorPromise = Promise.resolve(nextExecutor);
2041
+ return nextExecutor;
2042
+ })().finally(() => {
2043
+ this.reconnectPromise = void 0;
2044
+ });
2045
+ return await this.reconnectPromise;
2046
+ }
2047
+ };
2048
+ function isReconnectableClosedConnectionError(error) {
2049
+ return error instanceof Error && error.message === "FerricStore connection is closed";
2050
+ }
1655
2051
  function parseFerricUrl(value) {
1656
2052
  const url = new URL(value);
1657
2053
  if (url.protocol !== "ferric:" && url.protocol !== "ferrics:") {
@@ -1675,6 +2071,7 @@ async function connect(parsed, options) {
1675
2071
  }, timeoutMs);
1676
2072
  timer.unref?.();
1677
2073
  socket.setNoDelay(true);
2074
+ socket.setKeepAlive(options.keepAlive ?? true, normalizeKeepAliveInitialDelay(options.keepAliveInitialDelayMs));
1678
2075
  socket.once("connect", () => {
1679
2076
  clearTimeout(timer);
1680
2077
  resolve(socket);
@@ -1685,6 +2082,662 @@ async function connect(parsed, options) {
1685
2082
  });
1686
2083
  });
1687
2084
  }
2085
+ function normalizeHeartbeatInterval(value) {
2086
+ if (value != null && value <= 0) {
2087
+ return void 0;
2088
+ }
2089
+ return Math.trunc(value ?? 6e4);
2090
+ }
2091
+ function normalizeKeepAliveInitialDelay(value) {
2092
+ return Math.max(0, Math.trunc(value ?? 3e4));
2093
+ }
2094
+
2095
+ // src/topology.ts
2096
+ var import_node_buffer3 = require("buffer");
2097
+ var ROUTE_SLOT_COUNT = 1024;
2098
+ var ROUTE_SLOT_MASK = ROUTE_SLOT_COUNT - 1;
2099
+ var RoutingTopology = class _RoutingTopology {
2100
+ routeEpoch;
2101
+ shardCount;
2102
+ slots;
2103
+ endpoints;
2104
+ constructor(routeEpoch, shardCount, slots, endpoints) {
2105
+ this.routeEpoch = routeEpoch;
2106
+ this.shardCount = shardCount;
2107
+ this.slots = slots;
2108
+ this.endpoints = endpoints;
2109
+ }
2110
+ static empty() {
2111
+ return new _RoutingTopology(0, 0, Array.from({ length: ROUTE_SLOT_COUNT }), /* @__PURE__ */ new Map());
2112
+ }
2113
+ static build(payload) {
2114
+ const ranges = getField(payload, "ranges");
2115
+ if (!Array.isArray(ranges)) {
2116
+ throw new FerricStoreError("invalid SHARDS topology payload", { raw: payload });
2117
+ }
2118
+ const slots = Array.from({ length: ROUTE_SLOT_COUNT });
2119
+ const endpoints = /* @__PURE__ */ new Map();
2120
+ for (const item of ranges) {
2121
+ if (textOrUndefined(getField(item, "hint")) === "leader_unknown") {
2122
+ throw new FerricStoreError("SHARDS range has no leader", { raw: item });
2123
+ }
2124
+ const first = numberOrUndefined(getField(item, "first_slot"));
2125
+ const last = numberOrUndefined(getField(item, "last_slot"));
2126
+ const shard = numberOrUndefined(getField(item, "shard"));
2127
+ const laneId = numberOrUndefined(getField(item, "lane_id"));
2128
+ const endpoint = endpointFromRange(item);
2129
+ if (first == null || last == null || shard == null || laneId == null || first < 0 || last < first || last >= ROUTE_SLOT_COUNT) {
2130
+ throw new FerricStoreError("invalid SHARDS range", { raw: item });
2131
+ }
2132
+ const endpointKey = endpointKeyFor(endpoint);
2133
+ const route = {
2134
+ endpoint,
2135
+ endpointKey,
2136
+ laneId,
2137
+ leaderNode: endpoint.node,
2138
+ shard
2139
+ };
2140
+ for (let slot = first; slot <= last; slot += 1) {
2141
+ slots[slot] = route;
2142
+ }
2143
+ endpoints.set(endpointKey, endpoint);
2144
+ }
2145
+ return new _RoutingTopology(
2146
+ numberOrUndefined(getField(payload, "route_epoch")) ?? 0,
2147
+ numberOrUndefined(getField(payload, "shard_count")) ?? 0,
2148
+ slots,
2149
+ endpoints
2150
+ );
2151
+ }
2152
+ static slotForKey(key) {
2153
+ const textKey = import_node_buffer3.Buffer.isBuffer(key) ? key.toString("utf8") : key;
2154
+ let hashInput;
2155
+ if (textKey.startsWith("f:{")) {
2156
+ hashInput = flowHashTag(textKey.slice(3), textKey);
2157
+ } else if (textKey.startsWith("X:f:{")) {
2158
+ hashInput = flowHashTag(textKey.slice(5), textKey);
2159
+ } else {
2160
+ hashInput = hashTagOrKey(textKey);
2161
+ }
2162
+ return crc32(import_node_buffer3.Buffer.from(hashInput)) & ROUTE_SLOT_MASK;
2163
+ }
2164
+ routeKey(key) {
2165
+ const slot = _RoutingTopology.slotForKey(key);
2166
+ const route = this.slots[slot];
2167
+ if (route == null) {
2168
+ throw new FerricStoreError(`no route for slot ${slot}`);
2169
+ }
2170
+ return { ...route, slot };
2171
+ }
2172
+ };
2173
+ var TopologyNativeAdapterPool = class _TopologyNativeAdapterPool {
2174
+ adapterOptions;
2175
+ adapters = /* @__PURE__ */ new Map();
2176
+ endpointPolicy;
2177
+ endpointValidator;
2178
+ seedEndpointKeys;
2179
+ seedUrls;
2180
+ tls;
2181
+ trustedHosts;
2182
+ warmConnections;
2183
+ topologyValue = RoutingTopology.empty();
2184
+ constructor(urls, options = {}) {
2185
+ if (urls.length === 0) {
2186
+ throw new FerricStoreError("TopologyNativeAdapterPool requires at least one seed URL");
2187
+ }
2188
+ this.seedUrls = [...urls];
2189
+ this.endpointPolicy = options.endpointPolicy ?? "seed_hosts";
2190
+ this.endpointValidator = options.endpointValidator;
2191
+ this.warmConnections = options.warmConnections ?? false;
2192
+ this.tls = options.tlsOptions != null || urls.some((url) => parseFerricUrl2(url).tls);
2193
+ this.adapterOptions = nativeOnlyOptions(withSeedAuthDefaults(urls, options));
2194
+ this.seedEndpointKeys = new Set(urls.map((url) => endpointKeyFor(endpointFromUrl(url))));
2195
+ this.trustedHosts = normalizedHostSet(options.trustedHosts ?? []);
2196
+ }
2197
+ static async fromUrls(urls, options = {}) {
2198
+ const pool = new _TopologyNativeAdapterPool(urls, options);
2199
+ await pool.refreshTopology();
2200
+ return pool;
2201
+ }
2202
+ get topology() {
2203
+ return this.topologyValue;
2204
+ }
2205
+ async refreshTopology() {
2206
+ let lastError;
2207
+ for (const url of this.refreshCandidateUrls()) {
2208
+ try {
2209
+ const adapter = await this.adapterForUrl(url);
2210
+ const topology = RoutingTopology.build(await adapter.executeCommand("SHARDS"));
2211
+ this.topologyValue = topology;
2212
+ if (this.warmConnections) {
2213
+ await Promise.allSettled([...topology.endpoints.values()].map(async (endpoint) => await this.adapterForEndpoint(endpoint)));
2214
+ }
2215
+ return topology;
2216
+ } catch (error) {
2217
+ lastError = error;
2218
+ }
2219
+ }
2220
+ throw new FerricStoreError("no FerricStore topology endpoint reachable", { raw: lastError });
2221
+ }
2222
+ route(key) {
2223
+ const route = this.topologyValue.routeKey(key);
2224
+ this.validateEndpoint(route.endpoint);
2225
+ return route;
2226
+ }
2227
+ async executeCommand(...args) {
2228
+ const routed = this.routeData(args);
2229
+ if (routed == null) {
2230
+ return await (await this.controlAdapter()).executeCommand(...args);
2231
+ }
2232
+ const adapter = await this.adapterForEndpoint(routed.route.endpoint);
2233
+ try {
2234
+ return await adapter.executeProtocolCommand(routed.command, routed.route.laneId);
2235
+ } catch (error) {
2236
+ if (isRetryableRouteError(error)) {
2237
+ await this.refreshTopology().catch(() => void 0);
2238
+ }
2239
+ throw error;
2240
+ }
2241
+ }
2242
+ async executePipeline(commands, options = {}) {
2243
+ if (commands.length === 0) {
2244
+ return [];
2245
+ }
2246
+ const route = this.singleRouteForCommands(commands);
2247
+ if (route == null) {
2248
+ return await (await this.controlAdapter()).executePipeline(commands, options);
2249
+ }
2250
+ const adapter = await this.adapterForEndpoint(route.endpoint);
2251
+ try {
2252
+ const response = await adapter.executeProtocolCommand(pipelineCommand(commands), route.laneId);
2253
+ return unwrapPipelineResponse(response, options);
2254
+ } catch (error) {
2255
+ if (isRetryableRouteError(error)) {
2256
+ await this.refreshTopology().catch(() => void 0);
2257
+ }
2258
+ throw error;
2259
+ }
2260
+ }
2261
+ async close() {
2262
+ await Promise.allSettled([...this.adapters.values()].map(async (adapter) => await adapter.close()));
2263
+ this.adapters.clear();
2264
+ }
2265
+ routeData(args) {
2266
+ if (args.length === 0) {
2267
+ return void 0;
2268
+ }
2269
+ let command;
2270
+ try {
2271
+ command = buildProtocolCommand(args);
2272
+ } catch {
2273
+ return void 0;
2274
+ }
2275
+ const key = routingKey(args, command);
2276
+ if (key == null) {
2277
+ return void 0;
2278
+ }
2279
+ return { command, route: this.route(key) };
2280
+ }
2281
+ singleRouteForCommands(commands) {
2282
+ let route;
2283
+ for (const command of commands) {
2284
+ const routed = this.routeData(command);
2285
+ if (routed == null) {
2286
+ return void 0;
2287
+ }
2288
+ const current = routed.route;
2289
+ if (route == null) {
2290
+ route = current;
2291
+ } else if (route.endpointKey !== current.endpointKey || route.laneId !== current.laneId) {
2292
+ return void 0;
2293
+ }
2294
+ }
2295
+ return route;
2296
+ }
2297
+ async controlAdapter() {
2298
+ for (const url of this.refreshCandidateUrls()) {
2299
+ try {
2300
+ return await this.adapterForUrl(url);
2301
+ } catch {
2302
+ }
2303
+ }
2304
+ return await this.adapterForUrl(this.seedUrls[0] ?? "ferric://127.0.0.1:6388");
2305
+ }
2306
+ async adapterForUrl(url) {
2307
+ const endpoint = endpointFromUrl(url);
2308
+ const key = endpointKeyFor(endpoint);
2309
+ const existing = this.adapters.get(key);
2310
+ if (existing != null) {
2311
+ return existing;
2312
+ }
2313
+ const adapter = await NativeAdapter.fromUrl(url, this.adapterOptions);
2314
+ this.adapters.set(key, adapter);
2315
+ return adapter;
2316
+ }
2317
+ async adapterForEndpoint(endpoint) {
2318
+ this.validateEndpoint(endpoint);
2319
+ const key = endpointKeyFor(endpoint);
2320
+ const existing = this.adapters.get(key);
2321
+ if (existing != null) {
2322
+ return existing;
2323
+ }
2324
+ const adapter = await NativeAdapter.fromUrl(urlFromEndpoint(endpoint, this.tls), this.adapterOptions);
2325
+ this.adapters.set(key, adapter);
2326
+ return adapter;
2327
+ }
2328
+ validateEndpoint(endpoint) {
2329
+ let allowed;
2330
+ if (this.endpointPolicy === "any" || this.endpointPolicy === "none") {
2331
+ allowed = true;
2332
+ } else if (this.endpointPolicy === "seed_hosts") {
2333
+ allowed = this.seedEndpointKeys.has(endpointKeyFor(endpoint)) || this.trustedHosts.has(endpoint.host.toLowerCase());
2334
+ } else if ("allowHosts" in this.endpointPolicy) {
2335
+ allowed = normalizedHostSet(this.endpointPolicy.allowHosts).has(endpoint.host.toLowerCase());
2336
+ } else {
2337
+ throw new FerricStoreError("invalid endpoint policy", { raw: this.endpointPolicy });
2338
+ }
2339
+ if (!allowed) {
2340
+ throw new FerricStoreError("unsafe learned endpoint", { raw: endpoint });
2341
+ }
2342
+ if (this.endpointValidator != null && !this.endpointValidator(endpoint)) {
2343
+ throw new FerricStoreError("unsafe learned endpoint", { raw: endpoint });
2344
+ }
2345
+ }
2346
+ refreshCandidateUrls() {
2347
+ const urls = [
2348
+ ...this.seedUrls,
2349
+ ...[...this.topologyValue.endpoints.values()].map((endpoint) => urlFromEndpoint(endpoint, this.tls))
2350
+ ];
2351
+ return [...new Set(urls)];
2352
+ }
2353
+ };
2354
+ function routingKey(args, command) {
2355
+ const name = commandName(args);
2356
+ if (command.opcode < 256 || name === "CLUSTER.KEYSLOT" || name === "SHARDS" || name === "ROUTE") {
2357
+ return void 0;
2358
+ }
2359
+ const keyFromArgs = routingKeyFromArgs(name, args);
2360
+ if (keyFromArgs != null) {
2361
+ return keyFromArgs;
2362
+ }
2363
+ if (!isPlainObject2(command.payload)) {
2364
+ return void 0;
2365
+ }
2366
+ for (const field2 of ["key", "partition_key", "id", "owner_flow_id", "parent_id", "root_id", "correlation_id", "scope"]) {
2367
+ const value = getField(command.payload, field2);
2368
+ if (typeof value === "string" || import_node_buffer3.Buffer.isBuffer(value)) {
2369
+ return value;
2370
+ }
2371
+ }
2372
+ const keys = getField(command.payload, "keys");
2373
+ if (Array.isArray(keys)) {
2374
+ return singleShardKey(keys);
2375
+ }
2376
+ const pairsValue = getField(command.payload, "pairs");
2377
+ if (Array.isArray(pairsValue)) {
2378
+ return singleShardKey(
2379
+ pairsValue.filter((pair) => Array.isArray(pair) && pair.length > 0).map((pair) => pair[0])
2380
+ );
2381
+ }
2382
+ return void 0;
2383
+ }
2384
+ function routingKeyFromArgs(name, args) {
2385
+ if (name == null) {
2386
+ return void 0;
2387
+ }
2388
+ if (name === "MGET" || name === "DEL") {
2389
+ return singleShardKey(args.slice(1));
2390
+ }
2391
+ if (name === "MSET") {
2392
+ const keys = [];
2393
+ for (let index = 1; index < args.length; index += 2) {
2394
+ keys.push(args[index]);
2395
+ }
2396
+ return singleShardKey(keys);
2397
+ }
2398
+ if (name === "BITOP") {
2399
+ return bitopRoutingKey(args);
2400
+ }
2401
+ if (name === "RENAME" || name === "RENAMENX") {
2402
+ return singleShardKey(args.slice(1, 3));
2403
+ }
2404
+ if (name === "XREAD" || name === "XREADGROUP") {
2405
+ return streamReadRoutingKey(args);
2406
+ }
2407
+ if (name.startsWith("FLOW.")) {
2408
+ return flowRoutingKey(name, args);
2409
+ }
2410
+ if (firstKeyCommands.has(name)) {
2411
+ const key = args[1];
2412
+ return typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key) ? key : void 0;
2413
+ }
2414
+ return void 0;
2415
+ }
2416
+ function flowRoutingKey(name, args) {
2417
+ const flowArgs = args.slice(1);
2418
+ if (flowArgs.length === 0) {
2419
+ return void 0;
2420
+ }
2421
+ if (name === "FLOW.CLAIM_DUE" || name === "FLOW.RECLAIM") {
2422
+ return flowPartitionRoutingKey(flowArgs, 1);
2423
+ }
2424
+ if (name === "FLOW.CREATE_MANY" || name === "FLOW.COMPLETE_MANY" || name === "FLOW.TRANSITION_MANY" || name === "FLOW.RETRY_MANY" || name === "FLOW.FAIL_MANY" || name === "FLOW.CANCEL_MANY") {
2425
+ const partition2 = flowArgs[0];
2426
+ if (typeof partition2 === "string" && partition2.toUpperCase() !== "AUTO" && partition2.toUpperCase() !== "MIXED") {
2427
+ return partition2;
2428
+ }
2429
+ if (import_node_buffer3.Buffer.isBuffer(partition2)) {
2430
+ const text2 = partition2.toString("utf8").toUpperCase();
2431
+ if (text2 !== "AUTO" && text2 !== "MIXED") {
2432
+ return partition2;
2433
+ }
2434
+ }
2435
+ return void 0;
2436
+ }
2437
+ const partition = flowPartitionRoutingKey(flowArgs, 1);
2438
+ if (partition != null) {
2439
+ return partition;
2440
+ }
2441
+ if (typeScopedFlowCommands.has(name)) {
2442
+ return void 0;
2443
+ }
2444
+ const id = flowArgs[0];
2445
+ return typeof id === "string" || import_node_buffer3.Buffer.isBuffer(id) ? id : void 0;
2446
+ }
2447
+ function bitopRoutingKey(args) {
2448
+ if (args.length < 4) {
2449
+ return void 0;
2450
+ }
2451
+ return singleShardKey(args.slice(2));
2452
+ }
2453
+ function streamReadRoutingKey(args) {
2454
+ const streamsIndex = args.findIndex((arg) => commandPart(arg) === "STREAMS");
2455
+ if (streamsIndex < 0) {
2456
+ return void 0;
2457
+ }
2458
+ const streamArgs = args.slice(streamsIndex + 1);
2459
+ if (streamArgs.length === 0 || streamArgs.length % 2 !== 0) {
2460
+ return void 0;
2461
+ }
2462
+ return singleShardKey(streamArgs.slice(0, streamArgs.length / 2));
2463
+ }
2464
+ function flowPartitionRoutingKey(args, start) {
2465
+ for (let index = start; index < args.length; index += 1) {
2466
+ const token = commandPart(args[index]);
2467
+ if (token === "PARTITION") {
2468
+ const key = args[index + 1];
2469
+ return typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key) ? key : void 0;
2470
+ }
2471
+ if (token === "PARTITIONS") {
2472
+ const count = Number(args[index + 1]);
2473
+ if (!Number.isInteger(count) || count < 0) {
2474
+ return void 0;
2475
+ }
2476
+ return singleShardKey(args.slice(index + 2, index + 2 + count));
2477
+ }
2478
+ }
2479
+ return void 0;
2480
+ }
2481
+ function singleShardKey(keys) {
2482
+ const usable = keys.filter((key) => typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key));
2483
+ if (usable.length === 0) {
2484
+ return void 0;
2485
+ }
2486
+ const first = usable[0];
2487
+ if (first == null) {
2488
+ return void 0;
2489
+ }
2490
+ const firstSlot = RoutingTopology.slotForKey(first);
2491
+ return usable.every((key) => RoutingTopology.slotForKey(key) === firstSlot) ? first : void 0;
2492
+ }
2493
+ function commandName(args) {
2494
+ const first = commandPart(args[0]);
2495
+ if (first == null) {
2496
+ return void 0;
2497
+ }
2498
+ if (first === "FLOW" && args.length > 1) {
2499
+ const second = commandPart(args[1]);
2500
+ return second == null ? first : `${first}.${second}`;
2501
+ }
2502
+ if (first === "CLIENT" && args.length > 1) {
2503
+ const second = commandPart(args[1]);
2504
+ return second == null ? first : `${first}.${second}`;
2505
+ }
2506
+ if (first === "CLUSTER" && args.length > 1) {
2507
+ const second = commandPart(args[1]);
2508
+ return second == null ? first : `${first}.${second}`;
2509
+ }
2510
+ return first;
2511
+ }
2512
+ function commandPart(value) {
2513
+ if (typeof value === "string") {
2514
+ return value.toUpperCase();
2515
+ }
2516
+ if (import_node_buffer3.Buffer.isBuffer(value)) {
2517
+ return value.toString("utf8").toUpperCase();
2518
+ }
2519
+ return void 0;
2520
+ }
2521
+ function isPlainObject2(value) {
2522
+ return typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer3.Buffer.isBuffer(value);
2523
+ }
2524
+ function getField(source, ...keys) {
2525
+ if (source instanceof Map) {
2526
+ for (const key of keys) {
2527
+ if (source.has(key)) return source.get(key);
2528
+ const bufferKey = import_node_buffer3.Buffer.from(key);
2529
+ for (const [itemKey, value] of source.entries()) {
2530
+ if (import_node_buffer3.Buffer.isBuffer(itemKey) && itemKey.equals(bufferKey)) {
2531
+ return value;
2532
+ }
2533
+ }
2534
+ }
2535
+ return void 0;
2536
+ }
2537
+ if (isPlainObject2(source)) {
2538
+ for (const key of keys) {
2539
+ if (key in source) return source[key];
2540
+ }
2541
+ }
2542
+ return void 0;
2543
+ }
2544
+ function textOrUndefined(value) {
2545
+ if (typeof value === "string") {
2546
+ return value;
2547
+ }
2548
+ if (import_node_buffer3.Buffer.isBuffer(value)) {
2549
+ return value.toString("utf8");
2550
+ }
2551
+ return void 0;
2552
+ }
2553
+ function numberOrUndefined(value) {
2554
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
2555
+ }
2556
+ function endpointFromRange(item) {
2557
+ const endpointValue = getField(item, "endpoint");
2558
+ const raw = isPlainObject2(endpointValue) || endpointValue instanceof Map ? endpointValue : item;
2559
+ const host = textOrUndefined(getField(raw, "host", "native_host"));
2560
+ const nativePort = numberOrUndefined(getField(raw, "native_port"));
2561
+ if (host == null || nativePort == null) {
2562
+ throw new FerricStoreError("invalid SHARDS endpoint", { raw: item });
2563
+ }
2564
+ const nativeTlsPort = numberOrUndefined(getField(raw, "native_tls_port"));
2565
+ return {
2566
+ host,
2567
+ nativePort,
2568
+ node: textOrUndefined(getField(raw, "node", "leader_node", "owner_node")) ?? host,
2569
+ ...nativeTlsPort == null ? {} : { nativeTlsPort }
2570
+ };
2571
+ }
2572
+ function endpointFromUrl(url) {
2573
+ const parsed = parseFerricUrl2(url);
2574
+ return {
2575
+ host: parsed.host,
2576
+ nativePort: parsed.port,
2577
+ node: parsed.host,
2578
+ ...parsed.tls ? { nativeTlsPort: parsed.port } : {}
2579
+ };
2580
+ }
2581
+ function parseFerricUrl2(value) {
2582
+ const url = new URL(value);
2583
+ if (url.protocol !== "ferric:" && url.protocol !== "ferrics:") {
2584
+ throw new FerricStoreError(`unsupported FerricStore URL scheme: ${url.protocol}`);
2585
+ }
2586
+ return {
2587
+ host: url.hostname || "127.0.0.1",
2588
+ ...url.password === "" ? {} : { password: decodeURIComponent(url.password) },
2589
+ port: Number(url.port || (url.protocol === "ferrics:" ? 6389 : 6388)),
2590
+ tls: url.protocol === "ferrics:",
2591
+ ...url.username === "" ? {} : { username: decodeURIComponent(url.username) }
2592
+ };
2593
+ }
2594
+ function endpointKeyFor(endpoint) {
2595
+ return `${endpoint.host.toLowerCase()}:${endpoint.nativePort}`;
2596
+ }
2597
+ function urlFromEndpoint(endpoint, useTls) {
2598
+ const port = useTls && endpoint.nativeTlsPort != null ? endpoint.nativeTlsPort : endpoint.nativePort;
2599
+ const host = endpoint.host.includes(":") && !endpoint.host.startsWith("[") ? `[${endpoint.host}]` : endpoint.host;
2600
+ return `${useTls ? "ferrics" : "ferric"}://${host}:${port}`;
2601
+ }
2602
+ function nativeOnlyOptions(options) {
2603
+ const nativeOptions = { ...options };
2604
+ delete nativeOptions.endpointPolicy;
2605
+ delete nativeOptions.endpointValidator;
2606
+ delete nativeOptions.haRouting;
2607
+ delete nativeOptions.seeds;
2608
+ delete nativeOptions.trustedHosts;
2609
+ delete nativeOptions.warmConnections;
2610
+ return nativeOptions;
2611
+ }
2612
+ function withSeedAuthDefaults(urls, options) {
2613
+ const next = { ...options };
2614
+ for (const url of urls) {
2615
+ const parsed = parseFerricUrl2(url);
2616
+ if (next.username == null && parsed.username != null) {
2617
+ next.username = parsed.username;
2618
+ }
2619
+ if (next.password == null && parsed.password != null) {
2620
+ next.password = parsed.password;
2621
+ }
2622
+ if (next.username != null && next.password != null) {
2623
+ break;
2624
+ }
2625
+ }
2626
+ return next;
2627
+ }
2628
+ function isRetryableRouteError(error) {
2629
+ if (!(error instanceof Error)) {
2630
+ return false;
2631
+ }
2632
+ const message = error.message.toLowerCase();
2633
+ return message.includes("connection closed") || message.includes("connection is closed") || message.includes("shard not available") || message.includes("leader") || message.includes("route");
2634
+ }
2635
+ function normalizedHostSet(hosts) {
2636
+ return new Set(hosts.filter((host) => host != null && host !== "").map((host) => host.toLowerCase()));
2637
+ }
2638
+ function hashTagOrKey(key) {
2639
+ const start = key.indexOf("{");
2640
+ if (start < 0) {
2641
+ return key;
2642
+ }
2643
+ const end = key.indexOf("}", start + 1);
2644
+ return end > start + 1 ? key.slice(start + 1, end) : key;
2645
+ }
2646
+ function flowHashTag(rest, fallbackKey) {
2647
+ const end = rest.indexOf("}");
2648
+ return end > 0 ? rest.slice(0, end) : hashTagOrKey(fallbackKey);
2649
+ }
2650
+ var crc32Table = new Uint32Array(256);
2651
+ for (let index = 0; index < 256; index += 1) {
2652
+ let value = index;
2653
+ for (let bit = 0; bit < 8; bit += 1) {
2654
+ value = (value & 1) !== 0 ? 3988292384 ^ value >>> 1 : value >>> 1;
2655
+ }
2656
+ crc32Table[index] = value >>> 0;
2657
+ }
2658
+ function crc32(buffer) {
2659
+ let value = 4294967295;
2660
+ for (const byte of buffer) {
2661
+ value = value >>> 8 ^ (crc32Table[(value ^ byte) & 255] ?? 0);
2662
+ }
2663
+ return (value ^ 4294967295) >>> 0;
2664
+ }
2665
+ var firstKeyCommands = /* @__PURE__ */ new Set([
2666
+ "BITCOUNT",
2667
+ "BITFIELD",
2668
+ "BITPOS",
2669
+ "CAS",
2670
+ "EXISTS",
2671
+ "EXPIRE",
2672
+ "EXPIREAT",
2673
+ "FETCH_OR_COMPUTE",
2674
+ "FETCH_OR_COMPUTE_ERROR",
2675
+ "FETCH_OR_COMPUTE_RESULT",
2676
+ "FERRICSTORE.KEY_INFO",
2677
+ "GET",
2678
+ "GETBIT",
2679
+ "GETDEL",
2680
+ "GETEX",
2681
+ "HDEL",
2682
+ "HEXISTS",
2683
+ "HGET",
2684
+ "HGETALL",
2685
+ "HINCRBY",
2686
+ "HKEYS",
2687
+ "HLEN",
2688
+ "HMGET",
2689
+ "HMSET",
2690
+ "HSET",
2691
+ "HVALS",
2692
+ "LOCK",
2693
+ "LPOP",
2694
+ "LPUSH",
2695
+ "LRANGE",
2696
+ "LREM",
2697
+ "RATELIMIT.ADD",
2698
+ "RPOP",
2699
+ "RPUSH",
2700
+ "SADD",
2701
+ "SCARD",
2702
+ "SISMEMBER",
2703
+ "SMEMBERS",
2704
+ "SREM",
2705
+ "SET",
2706
+ "SETBIT",
2707
+ "STRLEN",
2708
+ "TTL",
2709
+ "TYPE",
2710
+ "UNLINK",
2711
+ "UNLOCK",
2712
+ "XADD",
2713
+ "XLEN",
2714
+ "XRANGE",
2715
+ "ZADD",
2716
+ "ZCARD",
2717
+ "ZRANGE",
2718
+ "ZREM",
2719
+ "ZSCORE"
2720
+ ]);
2721
+ var typeScopedFlowCommands = /* @__PURE__ */ new Set([
2722
+ "FLOW.APPROVAL.LIST",
2723
+ "FLOW.ATTRIBUTE_VALUES",
2724
+ "FLOW.ATTRIBUTES",
2725
+ "FLOW.BUDGET.LIST",
2726
+ "FLOW.FAILURES",
2727
+ "FLOW.GOVERNANCE.OVERVIEW",
2728
+ "FLOW.INFO",
2729
+ "FLOW.LIMIT.LIST",
2730
+ "FLOW.LIST",
2731
+ "FLOW.POLICY.GET",
2732
+ "FLOW.POLICY.SET",
2733
+ "FLOW.RETENTION_CLEANUP",
2734
+ "FLOW.SCHEDULE.FIRE_DUE",
2735
+ "FLOW.SCHEDULE.LIST",
2736
+ "FLOW.SEARCH",
2737
+ "FLOW.STATS",
2738
+ "FLOW.STUCK",
2739
+ "FLOW.TERMINALS"
2740
+ ]);
1688
2741
 
1689
2742
  // src/codecs.ts
1690
2743
  var RawCodec = class {
@@ -1899,7 +2952,7 @@ function parseKvResponse(value) {
1899
2952
  return { value };
1900
2953
  }
1901
2954
  function autoPartitionKeyForId(id) {
1902
- return `${AUTO_PARTITION_PREFIX}${crc32(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
2955
+ return `${AUTO_PARTITION_PREFIX}${crc322(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
1903
2956
  }
1904
2957
  function expandManyResponse(value, count) {
1905
2958
  if (Array.isArray(value) && value.length === count) {
@@ -1969,7 +3022,7 @@ function coerceDiagnosticValue(value) {
1969
3022
  }
1970
3023
  return value;
1971
3024
  }
1972
- function crc32(buffer) {
3025
+ function crc322(buffer) {
1973
3026
  let crc = 4294967295;
1974
3027
  for (const byte of buffer) {
1975
3028
  crc ^= byte;
@@ -2895,14 +3948,14 @@ function claimedItemFromResp(value, codec) {
2895
3948
  const partition = tuple[1] == null ? null : bytes(tuple[1]);
2896
3949
  const leaseToken = bytes(tuple[2]);
2897
3950
  const fencingToken = integer(tuple[3]);
2898
- const attrs = tuple[5] ?? (isPlainObject(tuple[4]) ? tuple[4] : void 0);
3951
+ const attrs = tuple[5] ?? (isPlainObject3(tuple[4]) ? tuple[4] : void 0);
2899
3952
  const item = {
2900
3953
  id: text(id),
2901
3954
  partitionKey: optionalString(partition),
2902
3955
  leaseToken,
2903
3956
  fencingToken,
2904
3957
  runState: optionalString(tuple[4]),
2905
- attributes: isPlainObject(attrs) ? attrs : void 0,
3958
+ attributes: isPlainObject3(attrs) ? attrs : void 0,
2906
3959
  type: "",
2907
3960
  state: "running"
2908
3961
  };
@@ -3022,9 +4075,9 @@ function decodeValues(value, codec) {
3022
4075
  }
3023
4076
  function plainObjectField(value, key) {
3024
4077
  const item = field(value, key);
3025
- return isPlainObject(item) ? item : void 0;
4078
+ return isPlainObject3(item) ? item : void 0;
3026
4079
  }
3027
- function isPlainObject(value) {
4080
+ function isPlainObject3(value) {
3028
4081
  return typeof value === "object" && value != null && !Buffer.isBuffer(value) && !(value instanceof Uint8Array) && !Array.isArray(value);
3029
4082
  }
3030
4083
 
@@ -3075,7 +4128,21 @@ var FerricStoreClient = class _FerricStoreClient {
3075
4128
  this.zset = new SortedSetStore(this);
3076
4129
  }
3077
4130
  static async fromUrl(url, options = {}) {
3078
- return new _FerricStoreClient(await NativeAdapter.fromUrl(url, options.nativeOptions), options);
4131
+ const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
4132
+ const nativeOptions = options.nativeOptions ?? {};
4133
+ const seeds = nativeOptions.seeds ?? [];
4134
+ const createExecutor = async () => nativeOptions.haRouting === true || seeds.length > 0 ? await TopologyNativeAdapterPool.fromUrls([url, ...seeds], nativeOptions) : await NativeAdapter.fromUrl(url, nativeOptions);
4135
+ const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
4136
+ return new _FerricStoreClient(executor, options);
4137
+ }
4138
+ static async fromUrls(urls, options = {}) {
4139
+ if (urls.length === 0) {
4140
+ throw new Error("FerricStoreClient.fromUrls requires at least one URL");
4141
+ }
4142
+ const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
4143
+ const createExecutor = async () => await TopologyNativeAdapterPool.fromUrls(urls, options.nativeOptions);
4144
+ const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
4145
+ return new _FerricStoreClient(executor, options);
3079
4146
  }
3080
4147
  async command(...args) {
3081
4148
  return await this.executor.executeCommand(...args);
@@ -3089,6 +4156,18 @@ var FerricStoreClient = class _FerricStoreClient {
3089
4156
  async close() {
3090
4157
  await this.executor.close?.();
3091
4158
  }
4159
+ async refreshTopology() {
4160
+ if (this.executor.refreshTopology == null) {
4161
+ throw new Error("topology refresh requires a topology-aware native executor");
4162
+ }
4163
+ return await this.executor.refreshTopology();
4164
+ }
4165
+ async route(key) {
4166
+ if (this.executor.route == null) {
4167
+ throw new Error("route lookup requires a topology-aware native executor");
4168
+ }
4169
+ return await this.executor.route(key);
4170
+ }
3092
4171
  async ping(message) {
3093
4172
  return await this.command("PING", ...message == null ? [] : [message]);
3094
4173
  }
@@ -3151,7 +4230,7 @@ var FerricStoreClient = class _FerricStoreClient {
3151
4230
  return response == null ? null : text(response);
3152
4231
  }
3153
4232
  async clientInfo() {
3154
- return text(await this.command("CLIENT", "INFO"));
4233
+ return infoText(await this.command("CLIENT", "INFO"));
3155
4234
  }
3156
4235
  async clientList(options = {}) {
3157
4236
  return text(await this.command("CLIENT", "LIST", ...options.type == null ? [] : ["TYPE", options.type]));
@@ -3206,7 +4285,7 @@ var FerricStoreClient = class _FerricStoreClient {
3206
4285
  return Number(await this.command("PUBSUB", "NUMPAT"));
3207
4286
  }
3208
4287
  async aclSetUser(username, rules) {
3209
- return okResponse(await this.command("ACL", "SETUSER", username, ...rules));
4288
+ return okResponse(await this.command("ACL", "SETUSER", username, ...ruleArgs(rules)));
3210
4289
  }
3211
4290
  async aclDelUser(...usernames) {
3212
4291
  return Number(await this.command("ACL", "DELUSER", ...usernames));
@@ -3217,6 +4296,9 @@ var FerricStoreClient = class _FerricStoreClient {
3217
4296
  async aclList() {
3218
4297
  return arrayResponse(await this.command("ACL", "LIST"));
3219
4298
  }
4299
+ async aclListUsers() {
4300
+ return await this.aclList();
4301
+ }
3220
4302
  async aclWhoami() {
3221
4303
  return text(await this.command("ACL", "WHOAMI"));
3222
4304
  }
@@ -3312,6 +4394,54 @@ var FerricStoreClient = class _FerricStoreClient {
3312
4394
  async ferricstoreDoctor(...args) {
3313
4395
  return await this.command("FERRICSTORE.DOCTOR", ...args);
3314
4396
  }
4397
+ async capabilities() {
4398
+ return normalizeAdminResponse(await this.command("FERRICSTORE.CAPABILITIES"));
4399
+ }
4400
+ async ensureNamespace(prefix, attrs = {}) {
4401
+ return normalizeAdminResponse(
4402
+ await this.command("FERRICSTORE.NAMESPACE", "ENSURE", prefix, ...managementPairArgs(attrs))
4403
+ );
4404
+ }
4405
+ async getNamespace(prefix) {
4406
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "GET", prefix));
4407
+ }
4408
+ async listNamespaces() {
4409
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "LIST"));
4410
+ }
4411
+ async deleteNamespace(prefix) {
4412
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "DELETE", prefix));
4413
+ }
4414
+ async setQuota(namespace, quotaSpec = {}) {
4415
+ return normalizeAdminResponse(
4416
+ await this.command("FERRICSTORE.QUOTA", "SET", namespace, ...managementPairArgs(quotaSpec))
4417
+ );
4418
+ }
4419
+ async getQuota(namespace) {
4420
+ return normalizeAdminResponse(await this.command("FERRICSTORE.QUOTA", "GET", namespace));
4421
+ }
4422
+ async quotaUsage(namespace) {
4423
+ return normalizeAdminResponse(await this.command("FERRICSTORE.QUOTA", "USAGE", namespace));
4424
+ }
4425
+ async clusterInfo() {
4426
+ return normalizeAdminResponse(await this.command("FERRICSTORE.TELEMETRY", "CLUSTER_INFO"));
4427
+ }
4428
+ async namespaceUsage(prefix) {
4429
+ return normalizeAdminResponse(
4430
+ await this.command("FERRICSTORE.TELEMETRY", "NAMESPACE_USAGE", prefix)
4431
+ );
4432
+ }
4433
+ async flowQuery(attrs = {}) {
4434
+ const response = normalizeAdminResponse(
4435
+ await this.command("FERRICSTORE.TELEMETRY", "FLOW_QUERY", ...managementPairArgs(attrs))
4436
+ );
4437
+ return Array.isArray(response) ? [...response] : [];
4438
+ }
4439
+ async flowHistory(id, attrs = {}) {
4440
+ const response = normalizeAdminResponse(
4441
+ await this.command("FERRICSTORE.TELEMETRY", "FLOW_HISTORY", id, ...managementPairArgs(attrs))
4442
+ );
4443
+ return Array.isArray(response) ? [...response] : [];
4444
+ }
3315
4445
  async create(id, options) {
3316
4446
  const currentNowMs = options.nowMs ?? nowMs();
3317
4447
  const args = [
@@ -3792,6 +4922,14 @@ var FerricStoreClient = class _FerricStoreClient {
3792
4922
  async list(type, options = {}) {
3793
4923
  return await this.indexQuery("FLOW.LIST", type, options);
3794
4924
  }
4925
+ async search(type, options = {}) {
4926
+ const args = ["FLOW.SEARCH", type];
4927
+ appendReadOptions(args, options);
4928
+ appendAttributes(args, options.attributes);
4929
+ appendSearchStateMeta(args, options.state, options.stateMeta);
4930
+ const response = await this.command(...args);
4931
+ return Array.isArray(response) ? this.records(response) : [];
4932
+ }
3795
4933
  async terminals(type, options = {}) {
3796
4934
  return await this.indexQuery("FLOW.TERMINALS", type, options);
3797
4935
  }
@@ -3961,6 +5099,18 @@ var ErrorMappingExecutor = class {
3961
5099
  throwMapped(error);
3962
5100
  }
3963
5101
  }
5102
+ async refreshTopology() {
5103
+ if (this.executor.refreshTopology == null) {
5104
+ throw new Error("topology refresh requires a topology-aware native executor");
5105
+ }
5106
+ return await this.executor.refreshTopology();
5107
+ }
5108
+ async route(key) {
5109
+ if (this.executor.route == null) {
5110
+ throw new Error("route lookup requires a topology-aware native executor");
5111
+ }
5112
+ return await this.executor.route(key);
5113
+ }
3964
5114
  async close() {
3965
5115
  await this.executor.close?.();
3966
5116
  }
@@ -3996,6 +5146,18 @@ var AutoBatchExecutor = class {
3996
5146
  }
3997
5147
  return await Promise.all(commands.map((command) => this.executor.executeCommand(...command)));
3998
5148
  }
5149
+ async refreshTopology() {
5150
+ if (this.executor.refreshTopology == null) {
5151
+ throw new Error("topology refresh requires a topology-aware native executor");
5152
+ }
5153
+ return await this.executor.refreshTopology();
5154
+ }
5155
+ async route(key) {
5156
+ if (this.executor.route == null) {
5157
+ throw new Error("route lookup requires a topology-aware native executor");
5158
+ }
5159
+ return await this.executor.route(key);
5160
+ }
3999
5161
  async close() {
4000
5162
  this.scheduled = false;
4001
5163
  if (this.timer != null) {
@@ -4156,7 +5318,7 @@ var neverAutoBatchCommandPrefixes = /* @__PURE__ */ new Set([
4156
5318
  ]);
4157
5319
  var neverAutoBatchCommands = /* @__PURE__ */ new Set(["FLOW.CLAIM_DUE", "FLOW.RECLAIM"]);
4158
5320
  function autoBatchCommandAllowed(command, mode) {
4159
- const name = commandName(command);
5321
+ const name = commandName2(command);
4160
5322
  if (name == null || neverAutoBatchCommands.has(name)) {
4161
5323
  return false;
4162
5324
  }
@@ -4166,18 +5328,18 @@ function autoBatchCommandAllowed(command, mode) {
4166
5328
  }
4167
5329
  return mode === "all" || safeAutoBatchCommands.has(name);
4168
5330
  }
4169
- function commandName(command) {
4170
- const first = commandPart(command[0]);
5331
+ function commandName2(command) {
5332
+ const first = commandPart2(command[0]);
4171
5333
  if (first == null) {
4172
5334
  return null;
4173
5335
  }
4174
5336
  if (first === "FLOW" && command.length > 1) {
4175
- const second = commandPart(command[1]);
5337
+ const second = commandPart2(command[1]);
4176
5338
  return second == null ? first : `${first}.${second}`;
4177
5339
  }
4178
5340
  return first;
4179
5341
  }
4180
- function commandPart(value) {
5342
+ function commandPart2(value) {
4181
5343
  if (typeof value === "string") {
4182
5344
  return value.toUpperCase();
4183
5345
  }
@@ -4202,6 +5364,67 @@ function appendStateMeta(args, stateMeta) {
4202
5364
  args.push("STATE_META", name, value);
4203
5365
  }
4204
5366
  }
5367
+ function appendAttributes(args, attributes) {
5368
+ for (const [name, value] of Object.entries(attributes ?? {})) {
5369
+ args.push("ATTRIBUTE", name, value);
5370
+ }
5371
+ }
5372
+ function appendSearchStateMeta(args, state, stateMeta) {
5373
+ const entries = Object.entries(stateMeta ?? {});
5374
+ if (entries.length === 0) {
5375
+ return;
5376
+ }
5377
+ if (entries.every(([, value]) => isPlainObject4(value))) {
5378
+ for (const [metaState, values] of entries) {
5379
+ args.push("STATE_META", metaState, values);
5380
+ }
5381
+ return;
5382
+ }
5383
+ if (state == null) {
5384
+ throw new Error("search stateMeta filters require state or nested state metadata");
5385
+ }
5386
+ args.push("STATE_META", state, stateMeta);
5387
+ }
5388
+ function normalizeAdminResponse(value) {
5389
+ return normalizeRefMeta(value);
5390
+ }
5391
+ function infoText(value) {
5392
+ if (typeof value === "string" || Buffer.isBuffer(value)) {
5393
+ return text(value);
5394
+ }
5395
+ const normalized = normalizeRefMeta(value);
5396
+ if (isPlainObject4(normalized)) {
5397
+ return Object.entries(normalized).map(([key, item]) => `${key}=${diagnosticValueText(item)}`).join(" ");
5398
+ }
5399
+ return diagnosticValueText(normalized);
5400
+ }
5401
+ function diagnosticValueText(value) {
5402
+ if (value == null) {
5403
+ return "";
5404
+ }
5405
+ if (Buffer.isBuffer(value)) {
5406
+ return value.toString("utf8");
5407
+ }
5408
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
5409
+ return String(value);
5410
+ }
5411
+ return JSON.stringify(value);
5412
+ }
5413
+ function managementPairArgs(pairs2) {
5414
+ const args = [];
5415
+ for (const [key, value] of Object.entries(pairs2)) {
5416
+ if (value != null) {
5417
+ args.push(key.toUpperCase(), value);
5418
+ }
5419
+ }
5420
+ return args;
5421
+ }
5422
+ function ruleArgs(rules) {
5423
+ return typeof rules === "string" ? [rules] : [...rules];
5424
+ }
5425
+ function isPlainObject4(value) {
5426
+ return typeof value === "object" && value != null && !Array.isArray(value) && !Buffer.isBuffer(value);
5427
+ }
4205
5428
  function sharedCreateManyStateMeta(items, stateMeta) {
4206
5429
  const itemMetas = items.map((item) => item.stateMeta).filter((meta) => meta != null);
4207
5430
  if (itemMetas.length === 0) {
@@ -5175,6 +6398,7 @@ function valueRefToString(value) {
5175
6398
  0 && (module.exports = {
5176
6399
  BitmapStore,
5177
6400
  BloomFilterStore,
6401
+ COMMAND_OPCODES,
5178
6402
  CountMinSketchStore,
5179
6403
  CuckooFilterStore,
5180
6404
  FerricStoreClient,
@@ -5198,12 +6422,15 @@ function valueRefToString(value) {
5198
6422
  QueueClient,
5199
6423
  QueueWorker,
5200
6424
  RawCodec,
6425
+ ReconnectingExecutor,
6426
+ RoutingTopology,
5201
6427
  SetStore,
5202
6428
  SortedSetStore,
5203
6429
  StaleLeaseError,
5204
6430
  StreamStore,
5205
6431
  TDigestStore,
5206
6432
  TopKStore,
6433
+ TopologyNativeAdapterPool,
5207
6434
  Workflow,
5208
6435
  WorkflowClient,
5209
6436
  WorkflowContext,
@@ -5212,6 +6439,7 @@ function valueRefToString(value) {
5212
6439
  classifyServerError,
5213
6440
  complete,
5214
6441
  fail,
6442
+ isReconnectableClosedConnectionError,
5215
6443
  mapException,
5216
6444
  retry,
5217
6445
  transition