@ferricstore/ferricstore 0.1.4 → 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 +1133 -34
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +244 -3
  5. package/dist/index.d.ts +244 -3
  6. package/dist/index.js +1130 -34
  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 +4 -2
  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 -1
  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 +2 -2
  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 +8 -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 -2
  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,
@@ -56,12 +57,14 @@ __export(index_exports, {
56
57
  QueueWorker: () => QueueWorker,
57
58
  RawCodec: () => RawCodec,
58
59
  ReconnectingExecutor: () => ReconnectingExecutor,
60
+ RoutingTopology: () => RoutingTopology,
59
61
  SetStore: () => SetStore,
60
62
  SortedSetStore: () => SortedSetStore,
61
63
  StaleLeaseError: () => StaleLeaseError,
62
64
  StreamStore: () => StreamStore,
63
65
  TDigestStore: () => TDigestStore,
64
66
  TopKStore: () => TopKStore,
67
+ TopologyNativeAdapterPool: () => TopologyNativeAdapterPool,
65
68
  Workflow: () => Workflow,
66
69
  WorkflowClient: () => WorkflowClient,
67
70
  WorkflowContext: () => WorkflowContext,
@@ -189,24 +192,171 @@ var FLAG_CUSTOM_PAYLOAD = 2;
189
192
  var FLAG_COMPRESSED = 8;
190
193
  var FLAG_MORE_CHUNKS = 32;
191
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
+ };
192
335
  var OPCODES = {
193
- startup: 12,
194
- auth: 2,
195
- ping: 3,
196
- clientSetName: 4,
197
- clientInfo: 5,
198
- commandExec: 256,
199
- get: 257,
200
- set: 258,
201
- del: 259,
202
- mget: 260,
203
- mset: 261,
204
- pipeline: 14,
205
- flowCreate: 513,
206
- flowClaimDue: 515,
207
- flowComplete: 516,
208
- flowCreateMany: 527,
209
- 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"]
210
360
  };
211
361
  var COMPACT_FLOW_CLAIM_JOBS = 128;
212
362
  var COMPACT_OK_LIST = 129;
@@ -339,12 +489,46 @@ function buildProtocolCommand(args) {
339
489
  }
340
490
  const command = asText(args[0]).toUpperCase();
341
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
+ }
342
502
  if (command === "PING") {
343
503
  return { laneId: 0, opcode: OPCODES.ping, payload: commandArgs.length === 0 ? {} : { message: commandArgs[0] } };
344
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
+ }
345
523
  if (command === "CLIENT" && asText(commandArgs[0]).toUpperCase() === "SETNAME" && commandArgs.length === 2) {
346
524
  return { laneId: 0, opcode: OPCODES.clientSetName, payload: { name: commandArgs[1] } };
347
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
+ }
348
532
  if (command === "GET" && commandArgs.length >= 1) {
349
533
  return { opcode: OPCODES.get, payload: { key: commandArgs[0] } };
350
534
  }
@@ -363,7 +547,7 @@ function buildProtocolCommand(args) {
363
547
  if (command === "DEL") {
364
548
  return { opcode: OPCODES.del, payload: { keys: commandArgs } };
365
549
  }
366
- if (command.startsWith("FLOW.") && hasFlowCommandOnlyOption(commandArgs)) {
550
+ if (command.startsWith("FLOW.") && hasFlowCommandOnlyOption(command, commandArgs)) {
367
551
  return commandExec(args);
368
552
  }
369
553
  if (command === "FLOW.CREATE") {
@@ -381,6 +565,9 @@ function buildProtocolCommand(args) {
381
565
  if (command === "FLOW.COMPLETE_MANY") {
382
566
  return flowCompleteManyPayload(commandArgs) ?? commandExec(args);
383
567
  }
568
+ if (command === "FLOW.SEARCH") {
569
+ return flowSearchPayload(commandArgs) ?? commandExec(args);
570
+ }
384
571
  return commandExec(args);
385
572
  }
386
573
  function commandExec(args) {
@@ -725,13 +912,13 @@ function writeOptionalBinary(out, offset, value) {
725
912
  }
726
913
  return writeBinary(out, offset, value);
727
914
  }
728
- function hasFlowCommandOnlyOption(args) {
915
+ function hasFlowCommandOnlyOption(command, args) {
729
916
  return args.some((arg) => {
730
917
  if (typeof arg !== "string" && !import_node_buffer.Buffer.isBuffer(arg)) {
731
918
  return false;
732
919
  }
733
920
  const token = asText(arg).toUpperCase();
734
- return token === "STATE_META" || token === "INDEXED_STATE_META";
921
+ return token === "INDEXED_STATE_META" || command !== "FLOW.SEARCH" && token === "STATE_META";
735
922
  });
736
923
  }
737
924
  function flowCreatePayload(args) {
@@ -849,6 +1036,12 @@ function flowCompleteManyPayload(args) {
849
1036
  if (!auto && !mixed) payload.partition_key = partition;
850
1037
  return { opcode: OPCODES.flowCompleteMany, payload };
851
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
+ }
852
1045
  function compactFlowCreateManyPayload(partition, rawItems, mixed, auto, options) {
853
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") {
854
1047
  return void 0;
@@ -951,6 +1144,78 @@ function compactFlowCompleteManyPayload(partition, rawItems, mixed, auto, option
951
1144
  }
952
1145
  return { flags: FLAG_CUSTOM_PAYLOAD, opcode: OPCODES.flowCompleteMany, payload: out };
953
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
+ }
954
1219
  function parseFlowOptions(args, start, end, config) {
955
1220
  const payload = {};
956
1221
  const seen = /* @__PURE__ */ new Set();
@@ -1509,6 +1774,9 @@ var NativeAdapter = class _NativeAdapter {
1509
1774
  const command = buildProtocolCommand(args);
1510
1775
  return await this.request(command);
1511
1776
  }
1777
+ async executeProtocolCommand(command, laneId) {
1778
+ return await this.request(laneId == null ? command : { ...command, laneId });
1779
+ }
1512
1780
  async executePipeline(commands, options = {}) {
1513
1781
  if (commands.length === 0) {
1514
1782
  return [];
@@ -1720,6 +1988,20 @@ var ReconnectingExecutor = class {
1720
1988
  return await Promise.all(commands.map((command) => executor.executeCommand(...command)));
1721
1989
  });
1722
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
+ }
1723
2005
  async close() {
1724
2006
  if (this.closed) {
1725
2007
  return;
@@ -1810,6 +2092,653 @@ function normalizeKeepAliveInitialDelay(value) {
1810
2092
  return Math.max(0, Math.trunc(value ?? 3e4));
1811
2093
  }
1812
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
+ ]);
2741
+
1813
2742
  // src/codecs.ts
1814
2743
  var RawCodec = class {
1815
2744
  encode(value) {
@@ -2023,7 +2952,7 @@ function parseKvResponse(value) {
2023
2952
  return { value };
2024
2953
  }
2025
2954
  function autoPartitionKeyForId(id) {
2026
- return `${AUTO_PARTITION_PREFIX}${crc32(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
2955
+ return `${AUTO_PARTITION_PREFIX}${crc322(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
2027
2956
  }
2028
2957
  function expandManyResponse(value, count) {
2029
2958
  if (Array.isArray(value) && value.length === count) {
@@ -2093,7 +3022,7 @@ function coerceDiagnosticValue(value) {
2093
3022
  }
2094
3023
  return value;
2095
3024
  }
2096
- function crc32(buffer) {
3025
+ function crc322(buffer) {
2097
3026
  let crc = 4294967295;
2098
3027
  for (const byte of buffer) {
2099
3028
  crc ^= byte;
@@ -3019,14 +3948,14 @@ function claimedItemFromResp(value, codec) {
3019
3948
  const partition = tuple[1] == null ? null : bytes(tuple[1]);
3020
3949
  const leaseToken = bytes(tuple[2]);
3021
3950
  const fencingToken = integer(tuple[3]);
3022
- const attrs = tuple[5] ?? (isPlainObject(tuple[4]) ? tuple[4] : void 0);
3951
+ const attrs = tuple[5] ?? (isPlainObject3(tuple[4]) ? tuple[4] : void 0);
3023
3952
  const item = {
3024
3953
  id: text(id),
3025
3954
  partitionKey: optionalString(partition),
3026
3955
  leaseToken,
3027
3956
  fencingToken,
3028
3957
  runState: optionalString(tuple[4]),
3029
- attributes: isPlainObject(attrs) ? attrs : void 0,
3958
+ attributes: isPlainObject3(attrs) ? attrs : void 0,
3030
3959
  type: "",
3031
3960
  state: "running"
3032
3961
  };
@@ -3146,9 +4075,9 @@ function decodeValues(value, codec) {
3146
4075
  }
3147
4076
  function plainObjectField(value, key) {
3148
4077
  const item = field(value, key);
3149
- return isPlainObject(item) ? item : void 0;
4078
+ return isPlainObject3(item) ? item : void 0;
3150
4079
  }
3151
- function isPlainObject(value) {
4080
+ function isPlainObject3(value) {
3152
4081
  return typeof value === "object" && value != null && !Buffer.isBuffer(value) && !(value instanceof Uint8Array) && !Array.isArray(value);
3153
4082
  }
3154
4083
 
@@ -3200,7 +4129,18 @@ var FerricStoreClient = class _FerricStoreClient {
3200
4129
  }
3201
4130
  static async fromUrl(url, options = {}) {
3202
4131
  const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
3203
- const createExecutor = async () => await NativeAdapter.fromUrl(url, options.nativeOptions);
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);
3204
4144
  const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
3205
4145
  return new _FerricStoreClient(executor, options);
3206
4146
  }
@@ -3216,6 +4156,18 @@ var FerricStoreClient = class _FerricStoreClient {
3216
4156
  async close() {
3217
4157
  await this.executor.close?.();
3218
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
+ }
3219
4171
  async ping(message) {
3220
4172
  return await this.command("PING", ...message == null ? [] : [message]);
3221
4173
  }
@@ -3278,7 +4230,7 @@ var FerricStoreClient = class _FerricStoreClient {
3278
4230
  return response == null ? null : text(response);
3279
4231
  }
3280
4232
  async clientInfo() {
3281
- return text(await this.command("CLIENT", "INFO"));
4233
+ return infoText(await this.command("CLIENT", "INFO"));
3282
4234
  }
3283
4235
  async clientList(options = {}) {
3284
4236
  return text(await this.command("CLIENT", "LIST", ...options.type == null ? [] : ["TYPE", options.type]));
@@ -3333,7 +4285,7 @@ var FerricStoreClient = class _FerricStoreClient {
3333
4285
  return Number(await this.command("PUBSUB", "NUMPAT"));
3334
4286
  }
3335
4287
  async aclSetUser(username, rules) {
3336
- return okResponse(await this.command("ACL", "SETUSER", username, ...rules));
4288
+ return okResponse(await this.command("ACL", "SETUSER", username, ...ruleArgs(rules)));
3337
4289
  }
3338
4290
  async aclDelUser(...usernames) {
3339
4291
  return Number(await this.command("ACL", "DELUSER", ...usernames));
@@ -3344,6 +4296,9 @@ var FerricStoreClient = class _FerricStoreClient {
3344
4296
  async aclList() {
3345
4297
  return arrayResponse(await this.command("ACL", "LIST"));
3346
4298
  }
4299
+ async aclListUsers() {
4300
+ return await this.aclList();
4301
+ }
3347
4302
  async aclWhoami() {
3348
4303
  return text(await this.command("ACL", "WHOAMI"));
3349
4304
  }
@@ -3439,6 +4394,54 @@ var FerricStoreClient = class _FerricStoreClient {
3439
4394
  async ferricstoreDoctor(...args) {
3440
4395
  return await this.command("FERRICSTORE.DOCTOR", ...args);
3441
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
+ }
3442
4445
  async create(id, options) {
3443
4446
  const currentNowMs = options.nowMs ?? nowMs();
3444
4447
  const args = [
@@ -3919,6 +4922,14 @@ var FerricStoreClient = class _FerricStoreClient {
3919
4922
  async list(type, options = {}) {
3920
4923
  return await this.indexQuery("FLOW.LIST", type, options);
3921
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
+ }
3922
4933
  async terminals(type, options = {}) {
3923
4934
  return await this.indexQuery("FLOW.TERMINALS", type, options);
3924
4935
  }
@@ -4088,6 +5099,18 @@ var ErrorMappingExecutor = class {
4088
5099
  throwMapped(error);
4089
5100
  }
4090
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
+ }
4091
5114
  async close() {
4092
5115
  await this.executor.close?.();
4093
5116
  }
@@ -4123,6 +5146,18 @@ var AutoBatchExecutor = class {
4123
5146
  }
4124
5147
  return await Promise.all(commands.map((command) => this.executor.executeCommand(...command)));
4125
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
+ }
4126
5161
  async close() {
4127
5162
  this.scheduled = false;
4128
5163
  if (this.timer != null) {
@@ -4283,7 +5318,7 @@ var neverAutoBatchCommandPrefixes = /* @__PURE__ */ new Set([
4283
5318
  ]);
4284
5319
  var neverAutoBatchCommands = /* @__PURE__ */ new Set(["FLOW.CLAIM_DUE", "FLOW.RECLAIM"]);
4285
5320
  function autoBatchCommandAllowed(command, mode) {
4286
- const name = commandName(command);
5321
+ const name = commandName2(command);
4287
5322
  if (name == null || neverAutoBatchCommands.has(name)) {
4288
5323
  return false;
4289
5324
  }
@@ -4293,18 +5328,18 @@ function autoBatchCommandAllowed(command, mode) {
4293
5328
  }
4294
5329
  return mode === "all" || safeAutoBatchCommands.has(name);
4295
5330
  }
4296
- function commandName(command) {
4297
- const first = commandPart(command[0]);
5331
+ function commandName2(command) {
5332
+ const first = commandPart2(command[0]);
4298
5333
  if (first == null) {
4299
5334
  return null;
4300
5335
  }
4301
5336
  if (first === "FLOW" && command.length > 1) {
4302
- const second = commandPart(command[1]);
5337
+ const second = commandPart2(command[1]);
4303
5338
  return second == null ? first : `${first}.${second}`;
4304
5339
  }
4305
5340
  return first;
4306
5341
  }
4307
- function commandPart(value) {
5342
+ function commandPart2(value) {
4308
5343
  if (typeof value === "string") {
4309
5344
  return value.toUpperCase();
4310
5345
  }
@@ -4329,6 +5364,67 @@ function appendStateMeta(args, stateMeta) {
4329
5364
  args.push("STATE_META", name, value);
4330
5365
  }
4331
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
+ }
4332
5428
  function sharedCreateManyStateMeta(items, stateMeta) {
4333
5429
  const itemMetas = items.map((item) => item.stateMeta).filter((meta) => meta != null);
4334
5430
  if (itemMetas.length === 0) {
@@ -5302,6 +6398,7 @@ function valueRefToString(value) {
5302
6398
  0 && (module.exports = {
5303
6399
  BitmapStore,
5304
6400
  BloomFilterStore,
6401
+ COMMAND_OPCODES,
5305
6402
  CountMinSketchStore,
5306
6403
  CuckooFilterStore,
5307
6404
  FerricStoreClient,
@@ -5326,12 +6423,14 @@ function valueRefToString(value) {
5326
6423
  QueueWorker,
5327
6424
  RawCodec,
5328
6425
  ReconnectingExecutor,
6426
+ RoutingTopology,
5329
6427
  SetStore,
5330
6428
  SortedSetStore,
5331
6429
  StaleLeaseError,
5332
6430
  StreamStore,
5333
6431
  TDigestStore,
5334
6432
  TopKStore,
6433
+ TopologyNativeAdapterPool,
5335
6434
  Workflow,
5336
6435
  WorkflowClient,
5337
6436
  WorkflowContext,