@ferricstore/ferricstore 0.1.4 → 0.1.6

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 (91) hide show
  1. package/README.md +56 -1
  2. package/dist/index.cjs +1320 -45
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +282 -9
  5. package/dist/index.d.ts +282 -9
  6. package/dist/index.js +1317 -45
  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 +25 -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/classes/Workflow.html +3 -2
  27. package/docs/api/classes/WorkflowClient.html +2 -2
  28. package/docs/api/classes/WorkflowContext.html +2 -2
  29. package/docs/api/classes/WorkflowFlowCommands.html +2 -2
  30. package/docs/api/classes/WorkflowWorker.html +2 -2
  31. package/docs/api/functions/isReconnectableClosedConnectionError.html +1 -1
  32. package/docs/api/hierarchy.html +1 -1
  33. package/docs/api/index.html +21 -7
  34. package/docs/api/interfaces/AutoBatchOptions.html +2 -2
  35. package/docs/api/interfaces/CancelOptions.html +2 -2
  36. package/docs/api/interfaces/ClaimDueOptions.html +2 -2
  37. package/docs/api/interfaces/ClaimedItem.html +2 -3
  38. package/docs/api/interfaces/CommandExecutor.html +4 -2
  39. package/docs/api/interfaces/CompleteOptions.html +2 -2
  40. package/docs/api/interfaces/CompleteOutcome.html +1 -1
  41. package/docs/api/interfaces/CreateManyOptions.html +2 -2
  42. package/docs/api/interfaces/CreateOptions.html +2 -2
  43. package/docs/api/interfaces/ExecutePipelineOptions.html +2 -2
  44. package/docs/api/interfaces/FailOptions.html +2 -2
  45. package/docs/api/interfaces/FailOutcome.html +1 -1
  46. package/docs/api/interfaces/FencedItem.html +2 -2
  47. package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +2 -2
  48. package/docs/api/interfaces/FerricStoreClientOptions.html +2 -2
  49. package/docs/api/interfaces/FetchOrComputeResult.html +2 -2
  50. package/docs/api/interfaces/FlowPolicyOptions.html +7 -0
  51. package/docs/api/interfaces/FlowRecord.html +2 -2
  52. package/docs/api/interfaces/FlowStatePolicy.html +3 -0
  53. package/docs/api/interfaces/InvocationCreateOptions.html +4 -0
  54. package/docs/api/interfaces/KeyInfo.html +2 -2
  55. package/docs/api/interfaces/MutateOptions.html +2 -2
  56. package/docs/api/interfaces/NamedValueMutation.html +1 -1
  57. package/docs/api/interfaces/NativeAdapterOptions.html +8 -2
  58. package/docs/api/interfaces/ProtocolCommand.html +5 -0
  59. package/docs/api/interfaces/RateLimitResult.html +2 -2
  60. package/docs/api/interfaces/ReadOptions.html +2 -2
  61. package/docs/api/interfaces/ReclaimOptions.html +2 -2
  62. package/docs/api/interfaces/ReconnectOptions.html +2 -2
  63. package/docs/api/interfaces/RequestContext.html +4 -0
  64. package/docs/api/interfaces/RequestContextOptions.html +2 -0
  65. package/docs/api/interfaces/RetryOptions.html +2 -2
  66. package/docs/api/interfaces/RetryOutcome.html +1 -1
  67. package/docs/api/interfaces/RoutingEndpoint.html +5 -0
  68. package/docs/api/interfaces/RoutingRoute.html +7 -0
  69. package/docs/api/interfaces/SearchOptions.html +12 -0
  70. package/docs/api/interfaces/StateOptions.html +3 -2
  71. package/docs/api/interfaces/StateRegistration.html +3 -2
  72. package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
  73. package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
  74. package/docs/api/interfaces/TransitionOptions.html +2 -2
  75. package/docs/api/interfaces/TransitionOutcome.html +1 -1
  76. package/docs/api/interfaces/WorkflowOptions.html +2 -2
  77. package/docs/api/interfaces/WorkflowWorkerResult.html +2 -2
  78. package/docs/api/modules.html +1 -1
  79. package/docs/api/types/CommandArgument.html +1 -1
  80. package/docs/api/types/EndpointPolicy.html +1 -0
  81. package/docs/api/types/FlowStateMode.html +1 -0
  82. package/docs/api/types/FlowStatePolicyLike.html +1 -0
  83. package/docs/api/types/ManagementPairs.html +1 -0
  84. package/docs/api/types/QueueBatchHandler.html +1 -1
  85. package/docs/api/types/QueueHandler.html +1 -1
  86. package/docs/api/types/QueueJob.html +1 -0
  87. package/docs/api/types/SearchStateMeta.html +1 -0
  88. package/docs/api/types/WorkflowHandler.html +1 -1
  89. package/docs/api/variables/COMMAND_OPCODES.html +1 -0
  90. package/docs/python-parity.md +4 -1
  91. 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,20 +565,73 @@ 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) {
387
574
  if (args.length === 0) {
388
575
  throw new FerricStoreError("command requires at least one argument");
389
576
  }
577
+ const commandArgs = args.slice(1);
578
+ const payload = {
579
+ args: commandArgs,
580
+ command: asText(args[0]).toUpperCase()
581
+ };
582
+ if (commandArgs.length >= 2 && commandTokenIs(commandArgs[commandArgs.length - 2], "REQUEST_CONTEXT")) {
583
+ const requestContext = normalizeRequestContext(commandArgs[commandArgs.length - 1]);
584
+ payload.args = commandArgs.slice(0, -2);
585
+ if (requestContext != null) {
586
+ payload.request_context = requestContext;
587
+ }
588
+ }
390
589
  return {
391
590
  opcode: OPCODES.commandExec,
392
- payload: {
393
- args: args.slice(1),
394
- command: asText(args[0]).toUpperCase()
395
- }
591
+ payload
396
592
  };
397
593
  }
594
+ function normalizeRequestContext(context) {
595
+ if (!isPlainObject(context)) {
596
+ return void 0;
597
+ }
598
+ const out = {};
599
+ const subject = optionalText(context.subject);
600
+ const tenant = optionalText(context.tenant);
601
+ const scopes = normalizeRequestContextScopes(context.scopes);
602
+ if (subject != null) {
603
+ out.subject = subject;
604
+ }
605
+ if (tenant != null) {
606
+ out.tenant = tenant;
607
+ }
608
+ if (scopes.length > 0) {
609
+ out.scopes = scopes;
610
+ }
611
+ return Object.keys(out).length > 0 ? out : void 0;
612
+ }
613
+ function normalizeRequestContextScopes(scopes) {
614
+ const values = typeof scopes === "string" || import_node_buffer.Buffer.isBuffer(scopes) || scopes instanceof Uint8Array ? asText(scopes).split(/\s+/u) : Array.isArray(scopes) ? scopes.map((scope) => optionalText(scope)).filter((scope) => scope != null) : [];
615
+ return [...new Set(values.filter((scope) => scope.length > 0))];
616
+ }
617
+ function optionalText(value) {
618
+ if (value == null || value === "") {
619
+ return void 0;
620
+ }
621
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || import_node_buffer.Buffer.isBuffer(value) || value instanceof Uint8Array) {
622
+ return asText(value);
623
+ }
624
+ return void 0;
625
+ }
626
+ function commandTokenIs(value, expected) {
627
+ if (typeof value === "string") {
628
+ return value.toUpperCase() === expected;
629
+ }
630
+ if (import_node_buffer.Buffer.isBuffer(value) || value instanceof Uint8Array) {
631
+ return asText(value).toUpperCase() === expected;
632
+ }
633
+ return false;
634
+ }
398
635
  function pipelineCommand(commands) {
399
636
  const compact = compactPipelinePayload(commands);
400
637
  if (compact != null) {
@@ -725,13 +962,13 @@ function writeOptionalBinary(out, offset, value) {
725
962
  }
726
963
  return writeBinary(out, offset, value);
727
964
  }
728
- function hasFlowCommandOnlyOption(args) {
965
+ function hasFlowCommandOnlyOption(command, args) {
729
966
  return args.some((arg) => {
730
967
  if (typeof arg !== "string" && !import_node_buffer.Buffer.isBuffer(arg)) {
731
968
  return false;
732
969
  }
733
970
  const token = asText(arg).toUpperCase();
734
- return token === "STATE_META" || token === "INDEXED_STATE_META";
971
+ return token === "INDEXED_STATE_META" || command !== "FLOW.SEARCH" && token === "STATE_META";
735
972
  });
736
973
  }
737
974
  function flowCreatePayload(args) {
@@ -849,6 +1086,12 @@ function flowCompleteManyPayload(args) {
849
1086
  if (!auto && !mixed) payload.partition_key = partition;
850
1087
  return { opcode: OPCODES.flowCompleteMany, payload };
851
1088
  }
1089
+ function flowSearchPayload(args) {
1090
+ if (args.length < 1) return void 0;
1091
+ const payload = optionMap(args.slice(1));
1092
+ normalizeFlowSearchStateMeta(payload);
1093
+ return { opcode: OPCODES.flowSearch, payload: { type: args[0], ...payload } };
1094
+ }
852
1095
  function compactFlowCreateManyPayload(partition, rawItems, mixed, auto, options) {
853
1096
  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
1097
  return void 0;
@@ -951,6 +1194,78 @@ function compactFlowCompleteManyPayload(partition, rawItems, mixed, auto, option
951
1194
  }
952
1195
  return { flags: FLAG_CUSTOM_PAYLOAD, opcode: OPCODES.flowCompleteMany, payload: out };
953
1196
  }
1197
+ var OPTION_FIELDS = {
1198
+ CONSISTENT_PROJECTION: "consistent_projection",
1199
+ COUNT: "count",
1200
+ FROM_MS: "from_ms",
1201
+ INCLUDE_COLD: "include_cold",
1202
+ PARTITION: "partition_key",
1203
+ REV: "rev",
1204
+ STATE: "state",
1205
+ TERMINAL_ONLY: "terminal_only",
1206
+ TO_MS: "to_ms"
1207
+ };
1208
+ var BOOL_OPTION_FIELDS = /* @__PURE__ */ new Set(["consistent_projection", "include_cold", "rev", "terminal_only"]);
1209
+ function optionMap(args) {
1210
+ const payload = {};
1211
+ for (let index = 0; index < args.length; ) {
1212
+ const token = asText(args[index]).toUpperCase();
1213
+ if (token === "ATTRIBUTE") {
1214
+ if (index + 2 >= args.length) {
1215
+ throw new FerricStoreError("ATTRIBUTE requires name and value");
1216
+ }
1217
+ const name = asText(args[index + 1]);
1218
+ const attributes = ensureObjectField(payload, "attributes");
1219
+ attributes[name] = args[index + 2];
1220
+ index += 3;
1221
+ continue;
1222
+ }
1223
+ if (token === "STATE_META") {
1224
+ if (index + 2 >= args.length) {
1225
+ throw new FerricStoreError("STATE_META requires name and value");
1226
+ }
1227
+ const name = asText(args[index + 1]);
1228
+ const stateMeta = ensureObjectField(payload, "state_meta");
1229
+ stateMeta[name] = args[index + 2];
1230
+ index += 3;
1231
+ continue;
1232
+ }
1233
+ const field2 = OPTION_FIELDS[token] ?? token.toLowerCase();
1234
+ if (index + 1 >= args.length) {
1235
+ throw new FerricStoreError(`${token} requires a value`);
1236
+ }
1237
+ const value = args[index + 1];
1238
+ payload[field2] = BOOL_OPTION_FIELDS.has(field2) ? boolArg(value) : value;
1239
+ index += 2;
1240
+ }
1241
+ return payload;
1242
+ }
1243
+ function ensureObjectField(payload, field2) {
1244
+ const value = payload[field2];
1245
+ if (isPlainObject(value)) {
1246
+ return value;
1247
+ }
1248
+ const next = {};
1249
+ payload[field2] = next;
1250
+ return next;
1251
+ }
1252
+ function normalizeFlowSearchStateMeta(payload) {
1253
+ const stateMeta = payload.state_meta;
1254
+ if (!isPlainObject(stateMeta) || Object.keys(stateMeta).length === 0) {
1255
+ return;
1256
+ }
1257
+ if (Object.values(stateMeta).every(isPlainObject)) {
1258
+ return;
1259
+ }
1260
+ const state = payload.state;
1261
+ if (state == null) {
1262
+ throw new FerricStoreError("FLOW.SEARCH STATE_META filters require STATE or nested state metadata");
1263
+ }
1264
+ payload.state_meta = { [asText(state)]: stateMeta };
1265
+ }
1266
+ function isPlainObject(value) {
1267
+ return typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer.Buffer.isBuffer(value) && !(value instanceof Uint8Array);
1268
+ }
954
1269
  function parseFlowOptions(args, start, end, config) {
955
1270
  const payload = {};
956
1271
  const seen = /* @__PURE__ */ new Set();
@@ -1509,6 +1824,9 @@ var NativeAdapter = class _NativeAdapter {
1509
1824
  const command = buildProtocolCommand(args);
1510
1825
  return await this.request(command);
1511
1826
  }
1827
+ async executeProtocolCommand(command, laneId) {
1828
+ return await this.request(laneId == null ? command : { ...command, laneId });
1829
+ }
1512
1830
  async executePipeline(commands, options = {}) {
1513
1831
  if (commands.length === 0) {
1514
1832
  return [];
@@ -1720,6 +2038,20 @@ var ReconnectingExecutor = class {
1720
2038
  return await Promise.all(commands.map((command) => executor.executeCommand(...command)));
1721
2039
  });
1722
2040
  }
2041
+ async refreshTopology() {
2042
+ const executor = await this.executorPromise;
2043
+ if (executor.refreshTopology == null) {
2044
+ throw new FerricStoreError("topology refresh requires a topology-aware native executor");
2045
+ }
2046
+ return await executor.refreshTopology();
2047
+ }
2048
+ async route(key) {
2049
+ const executor = await this.executorPromise;
2050
+ if (executor.route == null) {
2051
+ throw new FerricStoreError("route lookup requires a topology-aware native executor");
2052
+ }
2053
+ return await executor.route(key);
2054
+ }
1723
2055
  async close() {
1724
2056
  if (this.closed) {
1725
2057
  return;
@@ -1810,6 +2142,653 @@ function normalizeKeepAliveInitialDelay(value) {
1810
2142
  return Math.max(0, Math.trunc(value ?? 3e4));
1811
2143
  }
1812
2144
 
2145
+ // src/topology.ts
2146
+ var import_node_buffer3 = require("buffer");
2147
+ var ROUTE_SLOT_COUNT = 1024;
2148
+ var ROUTE_SLOT_MASK = ROUTE_SLOT_COUNT - 1;
2149
+ var RoutingTopology = class _RoutingTopology {
2150
+ routeEpoch;
2151
+ shardCount;
2152
+ slots;
2153
+ endpoints;
2154
+ constructor(routeEpoch, shardCount, slots, endpoints) {
2155
+ this.routeEpoch = routeEpoch;
2156
+ this.shardCount = shardCount;
2157
+ this.slots = slots;
2158
+ this.endpoints = endpoints;
2159
+ }
2160
+ static empty() {
2161
+ return new _RoutingTopology(0, 0, Array.from({ length: ROUTE_SLOT_COUNT }), /* @__PURE__ */ new Map());
2162
+ }
2163
+ static build(payload) {
2164
+ const ranges = getField(payload, "ranges");
2165
+ if (!Array.isArray(ranges)) {
2166
+ throw new FerricStoreError("invalid SHARDS topology payload", { raw: payload });
2167
+ }
2168
+ const slots = Array.from({ length: ROUTE_SLOT_COUNT });
2169
+ const endpoints = /* @__PURE__ */ new Map();
2170
+ for (const item of ranges) {
2171
+ if (textOrUndefined(getField(item, "hint")) === "leader_unknown") {
2172
+ throw new FerricStoreError("SHARDS range has no leader", { raw: item });
2173
+ }
2174
+ const first = numberOrUndefined(getField(item, "first_slot"));
2175
+ const last = numberOrUndefined(getField(item, "last_slot"));
2176
+ const shard = numberOrUndefined(getField(item, "shard"));
2177
+ const laneId = numberOrUndefined(getField(item, "lane_id"));
2178
+ const endpoint = endpointFromRange(item);
2179
+ if (first == null || last == null || shard == null || laneId == null || first < 0 || last < first || last >= ROUTE_SLOT_COUNT) {
2180
+ throw new FerricStoreError("invalid SHARDS range", { raw: item });
2181
+ }
2182
+ const endpointKey = endpointKeyFor(endpoint);
2183
+ const route = {
2184
+ endpoint,
2185
+ endpointKey,
2186
+ laneId,
2187
+ leaderNode: endpoint.node,
2188
+ shard
2189
+ };
2190
+ for (let slot = first; slot <= last; slot += 1) {
2191
+ slots[slot] = route;
2192
+ }
2193
+ endpoints.set(endpointKey, endpoint);
2194
+ }
2195
+ return new _RoutingTopology(
2196
+ numberOrUndefined(getField(payload, "route_epoch")) ?? 0,
2197
+ numberOrUndefined(getField(payload, "shard_count")) ?? 0,
2198
+ slots,
2199
+ endpoints
2200
+ );
2201
+ }
2202
+ static slotForKey(key) {
2203
+ const textKey = import_node_buffer3.Buffer.isBuffer(key) ? key.toString("utf8") : key;
2204
+ let hashInput;
2205
+ if (textKey.startsWith("f:{")) {
2206
+ hashInput = flowHashTag(textKey.slice(3), textKey);
2207
+ } else if (textKey.startsWith("X:f:{")) {
2208
+ hashInput = flowHashTag(textKey.slice(5), textKey);
2209
+ } else {
2210
+ hashInput = hashTagOrKey(textKey);
2211
+ }
2212
+ return crc32(import_node_buffer3.Buffer.from(hashInput)) & ROUTE_SLOT_MASK;
2213
+ }
2214
+ routeKey(key) {
2215
+ const slot = _RoutingTopology.slotForKey(key);
2216
+ const route = this.slots[slot];
2217
+ if (route == null) {
2218
+ throw new FerricStoreError(`no route for slot ${slot}`);
2219
+ }
2220
+ return { ...route, slot };
2221
+ }
2222
+ };
2223
+ var TopologyNativeAdapterPool = class _TopologyNativeAdapterPool {
2224
+ adapterOptions;
2225
+ adapters = /* @__PURE__ */ new Map();
2226
+ endpointPolicy;
2227
+ endpointValidator;
2228
+ seedEndpointKeys;
2229
+ seedUrls;
2230
+ tls;
2231
+ trustedHosts;
2232
+ warmConnections;
2233
+ topologyValue = RoutingTopology.empty();
2234
+ constructor(urls, options = {}) {
2235
+ if (urls.length === 0) {
2236
+ throw new FerricStoreError("TopologyNativeAdapterPool requires at least one seed URL");
2237
+ }
2238
+ this.seedUrls = [...urls];
2239
+ this.endpointPolicy = options.endpointPolicy ?? "seed_hosts";
2240
+ this.endpointValidator = options.endpointValidator;
2241
+ this.warmConnections = options.warmConnections ?? false;
2242
+ this.tls = options.tlsOptions != null || urls.some((url) => parseFerricUrl2(url).tls);
2243
+ this.adapterOptions = nativeOnlyOptions(withSeedAuthDefaults(urls, options));
2244
+ this.seedEndpointKeys = new Set(urls.map((url) => endpointKeyFor(endpointFromUrl(url))));
2245
+ this.trustedHosts = normalizedHostSet(options.trustedHosts ?? []);
2246
+ }
2247
+ static async fromUrls(urls, options = {}) {
2248
+ const pool = new _TopologyNativeAdapterPool(urls, options);
2249
+ await pool.refreshTopology();
2250
+ return pool;
2251
+ }
2252
+ get topology() {
2253
+ return this.topologyValue;
2254
+ }
2255
+ async refreshTopology() {
2256
+ let lastError;
2257
+ for (const url of this.refreshCandidateUrls()) {
2258
+ try {
2259
+ const adapter = await this.adapterForUrl(url);
2260
+ const topology = RoutingTopology.build(await adapter.executeCommand("SHARDS"));
2261
+ this.topologyValue = topology;
2262
+ if (this.warmConnections) {
2263
+ await Promise.allSettled([...topology.endpoints.values()].map(async (endpoint) => await this.adapterForEndpoint(endpoint)));
2264
+ }
2265
+ return topology;
2266
+ } catch (error) {
2267
+ lastError = error;
2268
+ }
2269
+ }
2270
+ throw new FerricStoreError("no FerricStore topology endpoint reachable", { raw: lastError });
2271
+ }
2272
+ route(key) {
2273
+ const route = this.topologyValue.routeKey(key);
2274
+ this.validateEndpoint(route.endpoint);
2275
+ return route;
2276
+ }
2277
+ async executeCommand(...args) {
2278
+ const routed = this.routeData(args);
2279
+ if (routed == null) {
2280
+ return await (await this.controlAdapter()).executeCommand(...args);
2281
+ }
2282
+ const adapter = await this.adapterForEndpoint(routed.route.endpoint);
2283
+ try {
2284
+ return await adapter.executeProtocolCommand(routed.command, routed.route.laneId);
2285
+ } catch (error) {
2286
+ if (isRetryableRouteError(error)) {
2287
+ await this.refreshTopology().catch(() => void 0);
2288
+ }
2289
+ throw error;
2290
+ }
2291
+ }
2292
+ async executePipeline(commands, options = {}) {
2293
+ if (commands.length === 0) {
2294
+ return [];
2295
+ }
2296
+ const route = this.singleRouteForCommands(commands);
2297
+ if (route == null) {
2298
+ return await (await this.controlAdapter()).executePipeline(commands, options);
2299
+ }
2300
+ const adapter = await this.adapterForEndpoint(route.endpoint);
2301
+ try {
2302
+ const response = await adapter.executeProtocolCommand(pipelineCommand(commands), route.laneId);
2303
+ return unwrapPipelineResponse(response, options);
2304
+ } catch (error) {
2305
+ if (isRetryableRouteError(error)) {
2306
+ await this.refreshTopology().catch(() => void 0);
2307
+ }
2308
+ throw error;
2309
+ }
2310
+ }
2311
+ async close() {
2312
+ await Promise.allSettled([...this.adapters.values()].map(async (adapter) => await adapter.close()));
2313
+ this.adapters.clear();
2314
+ }
2315
+ routeData(args) {
2316
+ if (args.length === 0) {
2317
+ return void 0;
2318
+ }
2319
+ let command;
2320
+ try {
2321
+ command = buildProtocolCommand(args);
2322
+ } catch {
2323
+ return void 0;
2324
+ }
2325
+ const key = routingKey(args, command);
2326
+ if (key == null) {
2327
+ return void 0;
2328
+ }
2329
+ return { command, route: this.route(key) };
2330
+ }
2331
+ singleRouteForCommands(commands) {
2332
+ let route;
2333
+ for (const command of commands) {
2334
+ const routed = this.routeData(command);
2335
+ if (routed == null) {
2336
+ return void 0;
2337
+ }
2338
+ const current = routed.route;
2339
+ if (route == null) {
2340
+ route = current;
2341
+ } else if (route.endpointKey !== current.endpointKey || route.laneId !== current.laneId) {
2342
+ return void 0;
2343
+ }
2344
+ }
2345
+ return route;
2346
+ }
2347
+ async controlAdapter() {
2348
+ for (const url of this.refreshCandidateUrls()) {
2349
+ try {
2350
+ return await this.adapterForUrl(url);
2351
+ } catch {
2352
+ }
2353
+ }
2354
+ return await this.adapterForUrl(this.seedUrls[0] ?? "ferric://127.0.0.1:6388");
2355
+ }
2356
+ async adapterForUrl(url) {
2357
+ const endpoint = endpointFromUrl(url);
2358
+ const key = endpointKeyFor(endpoint);
2359
+ const existing = this.adapters.get(key);
2360
+ if (existing != null) {
2361
+ return existing;
2362
+ }
2363
+ const adapter = await NativeAdapter.fromUrl(url, this.adapterOptions);
2364
+ this.adapters.set(key, adapter);
2365
+ return adapter;
2366
+ }
2367
+ async adapterForEndpoint(endpoint) {
2368
+ this.validateEndpoint(endpoint);
2369
+ const key = endpointKeyFor(endpoint);
2370
+ const existing = this.adapters.get(key);
2371
+ if (existing != null) {
2372
+ return existing;
2373
+ }
2374
+ const adapter = await NativeAdapter.fromUrl(urlFromEndpoint(endpoint, this.tls), this.adapterOptions);
2375
+ this.adapters.set(key, adapter);
2376
+ return adapter;
2377
+ }
2378
+ validateEndpoint(endpoint) {
2379
+ let allowed;
2380
+ if (this.endpointPolicy === "any" || this.endpointPolicy === "none") {
2381
+ allowed = true;
2382
+ } else if (this.endpointPolicy === "seed_hosts") {
2383
+ allowed = this.seedEndpointKeys.has(endpointKeyFor(endpoint)) || this.trustedHosts.has(endpoint.host.toLowerCase());
2384
+ } else if ("allowHosts" in this.endpointPolicy) {
2385
+ allowed = normalizedHostSet(this.endpointPolicy.allowHosts).has(endpoint.host.toLowerCase());
2386
+ } else {
2387
+ throw new FerricStoreError("invalid endpoint policy", { raw: this.endpointPolicy });
2388
+ }
2389
+ if (!allowed) {
2390
+ throw new FerricStoreError("unsafe learned endpoint", { raw: endpoint });
2391
+ }
2392
+ if (this.endpointValidator != null && !this.endpointValidator(endpoint)) {
2393
+ throw new FerricStoreError("unsafe learned endpoint", { raw: endpoint });
2394
+ }
2395
+ }
2396
+ refreshCandidateUrls() {
2397
+ const urls = [
2398
+ ...this.seedUrls,
2399
+ ...[...this.topologyValue.endpoints.values()].map((endpoint) => urlFromEndpoint(endpoint, this.tls))
2400
+ ];
2401
+ return [...new Set(urls)];
2402
+ }
2403
+ };
2404
+ function routingKey(args, command) {
2405
+ const name = commandName(args);
2406
+ if (command.opcode < 256 || name === "CLUSTER.KEYSLOT" || name === "SHARDS" || name === "ROUTE") {
2407
+ return void 0;
2408
+ }
2409
+ const keyFromArgs = routingKeyFromArgs(name, args);
2410
+ if (keyFromArgs != null) {
2411
+ return keyFromArgs;
2412
+ }
2413
+ if (!isPlainObject2(command.payload)) {
2414
+ return void 0;
2415
+ }
2416
+ for (const field2 of ["key", "partition_key", "id", "owner_flow_id", "parent_id", "root_id", "correlation_id", "scope"]) {
2417
+ const value = getField(command.payload, field2);
2418
+ if (typeof value === "string" || import_node_buffer3.Buffer.isBuffer(value)) {
2419
+ return value;
2420
+ }
2421
+ }
2422
+ const keys = getField(command.payload, "keys");
2423
+ if (Array.isArray(keys)) {
2424
+ return singleShardKey(keys);
2425
+ }
2426
+ const pairsValue = getField(command.payload, "pairs");
2427
+ if (Array.isArray(pairsValue)) {
2428
+ return singleShardKey(
2429
+ pairsValue.filter((pair) => Array.isArray(pair) && pair.length > 0).map((pair) => pair[0])
2430
+ );
2431
+ }
2432
+ return void 0;
2433
+ }
2434
+ function routingKeyFromArgs(name, args) {
2435
+ if (name == null) {
2436
+ return void 0;
2437
+ }
2438
+ if (name === "MGET" || name === "DEL") {
2439
+ return singleShardKey(args.slice(1));
2440
+ }
2441
+ if (name === "MSET") {
2442
+ const keys = [];
2443
+ for (let index = 1; index < args.length; index += 2) {
2444
+ keys.push(args[index]);
2445
+ }
2446
+ return singleShardKey(keys);
2447
+ }
2448
+ if (name === "BITOP") {
2449
+ return bitopRoutingKey(args);
2450
+ }
2451
+ if (name === "RENAME" || name === "RENAMENX") {
2452
+ return singleShardKey(args.slice(1, 3));
2453
+ }
2454
+ if (name === "XREAD" || name === "XREADGROUP") {
2455
+ return streamReadRoutingKey(args);
2456
+ }
2457
+ if (name.startsWith("FLOW.")) {
2458
+ return flowRoutingKey(name, args);
2459
+ }
2460
+ if (firstKeyCommands.has(name)) {
2461
+ const key = args[1];
2462
+ return typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key) ? key : void 0;
2463
+ }
2464
+ return void 0;
2465
+ }
2466
+ function flowRoutingKey(name, args) {
2467
+ const flowArgs = args.slice(1);
2468
+ if (flowArgs.length === 0) {
2469
+ return void 0;
2470
+ }
2471
+ if (name === "FLOW.CLAIM_DUE" || name === "FLOW.RECLAIM") {
2472
+ return flowPartitionRoutingKey(flowArgs, 1);
2473
+ }
2474
+ 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") {
2475
+ const partition2 = flowArgs[0];
2476
+ if (typeof partition2 === "string" && partition2.toUpperCase() !== "AUTO" && partition2.toUpperCase() !== "MIXED") {
2477
+ return partition2;
2478
+ }
2479
+ if (import_node_buffer3.Buffer.isBuffer(partition2)) {
2480
+ const text2 = partition2.toString("utf8").toUpperCase();
2481
+ if (text2 !== "AUTO" && text2 !== "MIXED") {
2482
+ return partition2;
2483
+ }
2484
+ }
2485
+ return void 0;
2486
+ }
2487
+ const partition = flowPartitionRoutingKey(flowArgs, 1);
2488
+ if (partition != null) {
2489
+ return partition;
2490
+ }
2491
+ if (typeScopedFlowCommands.has(name)) {
2492
+ return void 0;
2493
+ }
2494
+ const id = flowArgs[0];
2495
+ return typeof id === "string" || import_node_buffer3.Buffer.isBuffer(id) ? id : void 0;
2496
+ }
2497
+ function bitopRoutingKey(args) {
2498
+ if (args.length < 4) {
2499
+ return void 0;
2500
+ }
2501
+ return singleShardKey(args.slice(2));
2502
+ }
2503
+ function streamReadRoutingKey(args) {
2504
+ const streamsIndex = args.findIndex((arg) => commandPart(arg) === "STREAMS");
2505
+ if (streamsIndex < 0) {
2506
+ return void 0;
2507
+ }
2508
+ const streamArgs = args.slice(streamsIndex + 1);
2509
+ if (streamArgs.length === 0 || streamArgs.length % 2 !== 0) {
2510
+ return void 0;
2511
+ }
2512
+ return singleShardKey(streamArgs.slice(0, streamArgs.length / 2));
2513
+ }
2514
+ function flowPartitionRoutingKey(args, start) {
2515
+ for (let index = start; index < args.length; index += 1) {
2516
+ const token = commandPart(args[index]);
2517
+ if (token === "PARTITION") {
2518
+ const key = args[index + 1];
2519
+ return typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key) ? key : void 0;
2520
+ }
2521
+ if (token === "PARTITIONS") {
2522
+ const count = Number(args[index + 1]);
2523
+ if (!Number.isInteger(count) || count < 0) {
2524
+ return void 0;
2525
+ }
2526
+ return singleShardKey(args.slice(index + 2, index + 2 + count));
2527
+ }
2528
+ }
2529
+ return void 0;
2530
+ }
2531
+ function singleShardKey(keys) {
2532
+ const usable = keys.filter((key) => typeof key === "string" || import_node_buffer3.Buffer.isBuffer(key));
2533
+ if (usable.length === 0) {
2534
+ return void 0;
2535
+ }
2536
+ const first = usable[0];
2537
+ if (first == null) {
2538
+ return void 0;
2539
+ }
2540
+ const firstSlot = RoutingTopology.slotForKey(first);
2541
+ return usable.every((key) => RoutingTopology.slotForKey(key) === firstSlot) ? first : void 0;
2542
+ }
2543
+ function commandName(args) {
2544
+ const first = commandPart(args[0]);
2545
+ if (first == null) {
2546
+ return void 0;
2547
+ }
2548
+ if (first === "FLOW" && args.length > 1) {
2549
+ const second = commandPart(args[1]);
2550
+ return second == null ? first : `${first}.${second}`;
2551
+ }
2552
+ if (first === "CLIENT" && args.length > 1) {
2553
+ const second = commandPart(args[1]);
2554
+ return second == null ? first : `${first}.${second}`;
2555
+ }
2556
+ if (first === "CLUSTER" && args.length > 1) {
2557
+ const second = commandPart(args[1]);
2558
+ return second == null ? first : `${first}.${second}`;
2559
+ }
2560
+ return first;
2561
+ }
2562
+ function commandPart(value) {
2563
+ if (typeof value === "string") {
2564
+ return value.toUpperCase();
2565
+ }
2566
+ if (import_node_buffer3.Buffer.isBuffer(value)) {
2567
+ return value.toString("utf8").toUpperCase();
2568
+ }
2569
+ return void 0;
2570
+ }
2571
+ function isPlainObject2(value) {
2572
+ return typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer3.Buffer.isBuffer(value);
2573
+ }
2574
+ function getField(source, ...keys) {
2575
+ if (source instanceof Map) {
2576
+ for (const key of keys) {
2577
+ if (source.has(key)) return source.get(key);
2578
+ const bufferKey = import_node_buffer3.Buffer.from(key);
2579
+ for (const [itemKey, value] of source.entries()) {
2580
+ if (import_node_buffer3.Buffer.isBuffer(itemKey) && itemKey.equals(bufferKey)) {
2581
+ return value;
2582
+ }
2583
+ }
2584
+ }
2585
+ return void 0;
2586
+ }
2587
+ if (isPlainObject2(source)) {
2588
+ for (const key of keys) {
2589
+ if (key in source) return source[key];
2590
+ }
2591
+ }
2592
+ return void 0;
2593
+ }
2594
+ function textOrUndefined(value) {
2595
+ if (typeof value === "string") {
2596
+ return value;
2597
+ }
2598
+ if (import_node_buffer3.Buffer.isBuffer(value)) {
2599
+ return value.toString("utf8");
2600
+ }
2601
+ return void 0;
2602
+ }
2603
+ function numberOrUndefined(value) {
2604
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
2605
+ }
2606
+ function endpointFromRange(item) {
2607
+ const endpointValue = getField(item, "endpoint");
2608
+ const raw = isPlainObject2(endpointValue) || endpointValue instanceof Map ? endpointValue : item;
2609
+ const host = textOrUndefined(getField(raw, "host", "native_host"));
2610
+ const nativePort = numberOrUndefined(getField(raw, "native_port"));
2611
+ if (host == null || nativePort == null) {
2612
+ throw new FerricStoreError("invalid SHARDS endpoint", { raw: item });
2613
+ }
2614
+ const nativeTlsPort = numberOrUndefined(getField(raw, "native_tls_port"));
2615
+ return {
2616
+ host,
2617
+ nativePort,
2618
+ node: textOrUndefined(getField(raw, "node", "leader_node", "owner_node")) ?? host,
2619
+ ...nativeTlsPort == null ? {} : { nativeTlsPort }
2620
+ };
2621
+ }
2622
+ function endpointFromUrl(url) {
2623
+ const parsed = parseFerricUrl2(url);
2624
+ return {
2625
+ host: parsed.host,
2626
+ nativePort: parsed.port,
2627
+ node: parsed.host,
2628
+ ...parsed.tls ? { nativeTlsPort: parsed.port } : {}
2629
+ };
2630
+ }
2631
+ function parseFerricUrl2(value) {
2632
+ const url = new URL(value);
2633
+ if (url.protocol !== "ferric:" && url.protocol !== "ferrics:") {
2634
+ throw new FerricStoreError(`unsupported FerricStore URL scheme: ${url.protocol}`);
2635
+ }
2636
+ return {
2637
+ host: url.hostname || "127.0.0.1",
2638
+ ...url.password === "" ? {} : { password: decodeURIComponent(url.password) },
2639
+ port: Number(url.port || (url.protocol === "ferrics:" ? 6389 : 6388)),
2640
+ tls: url.protocol === "ferrics:",
2641
+ ...url.username === "" ? {} : { username: decodeURIComponent(url.username) }
2642
+ };
2643
+ }
2644
+ function endpointKeyFor(endpoint) {
2645
+ return `${endpoint.host.toLowerCase()}:${endpoint.nativePort}`;
2646
+ }
2647
+ function urlFromEndpoint(endpoint, useTls) {
2648
+ const port = useTls && endpoint.nativeTlsPort != null ? endpoint.nativeTlsPort : endpoint.nativePort;
2649
+ const host = endpoint.host.includes(":") && !endpoint.host.startsWith("[") ? `[${endpoint.host}]` : endpoint.host;
2650
+ return `${useTls ? "ferrics" : "ferric"}://${host}:${port}`;
2651
+ }
2652
+ function nativeOnlyOptions(options) {
2653
+ const nativeOptions = { ...options };
2654
+ delete nativeOptions.endpointPolicy;
2655
+ delete nativeOptions.endpointValidator;
2656
+ delete nativeOptions.haRouting;
2657
+ delete nativeOptions.seeds;
2658
+ delete nativeOptions.trustedHosts;
2659
+ delete nativeOptions.warmConnections;
2660
+ return nativeOptions;
2661
+ }
2662
+ function withSeedAuthDefaults(urls, options) {
2663
+ const next = { ...options };
2664
+ for (const url of urls) {
2665
+ const parsed = parseFerricUrl2(url);
2666
+ if (next.username == null && parsed.username != null) {
2667
+ next.username = parsed.username;
2668
+ }
2669
+ if (next.password == null && parsed.password != null) {
2670
+ next.password = parsed.password;
2671
+ }
2672
+ if (next.username != null && next.password != null) {
2673
+ break;
2674
+ }
2675
+ }
2676
+ return next;
2677
+ }
2678
+ function isRetryableRouteError(error) {
2679
+ if (!(error instanceof Error)) {
2680
+ return false;
2681
+ }
2682
+ const message = error.message.toLowerCase();
2683
+ return message.includes("connection closed") || message.includes("connection is closed") || message.includes("shard not available") || message.includes("leader") || message.includes("route");
2684
+ }
2685
+ function normalizedHostSet(hosts) {
2686
+ return new Set(hosts.filter((host) => host != null && host !== "").map((host) => host.toLowerCase()));
2687
+ }
2688
+ function hashTagOrKey(key) {
2689
+ const start = key.indexOf("{");
2690
+ if (start < 0) {
2691
+ return key;
2692
+ }
2693
+ const end = key.indexOf("}", start + 1);
2694
+ return end > start + 1 ? key.slice(start + 1, end) : key;
2695
+ }
2696
+ function flowHashTag(rest, fallbackKey) {
2697
+ const end = rest.indexOf("}");
2698
+ return end > 0 ? rest.slice(0, end) : hashTagOrKey(fallbackKey);
2699
+ }
2700
+ var crc32Table = new Uint32Array(256);
2701
+ for (let index = 0; index < 256; index += 1) {
2702
+ let value = index;
2703
+ for (let bit = 0; bit < 8; bit += 1) {
2704
+ value = (value & 1) !== 0 ? 3988292384 ^ value >>> 1 : value >>> 1;
2705
+ }
2706
+ crc32Table[index] = value >>> 0;
2707
+ }
2708
+ function crc32(buffer) {
2709
+ let value = 4294967295;
2710
+ for (const byte of buffer) {
2711
+ value = value >>> 8 ^ (crc32Table[(value ^ byte) & 255] ?? 0);
2712
+ }
2713
+ return (value ^ 4294967295) >>> 0;
2714
+ }
2715
+ var firstKeyCommands = /* @__PURE__ */ new Set([
2716
+ "BITCOUNT",
2717
+ "BITFIELD",
2718
+ "BITPOS",
2719
+ "CAS",
2720
+ "EXISTS",
2721
+ "EXPIRE",
2722
+ "EXPIREAT",
2723
+ "FETCH_OR_COMPUTE",
2724
+ "FETCH_OR_COMPUTE_ERROR",
2725
+ "FETCH_OR_COMPUTE_RESULT",
2726
+ "FERRICSTORE.KEY_INFO",
2727
+ "GET",
2728
+ "GETBIT",
2729
+ "GETDEL",
2730
+ "GETEX",
2731
+ "HDEL",
2732
+ "HEXISTS",
2733
+ "HGET",
2734
+ "HGETALL",
2735
+ "HINCRBY",
2736
+ "HKEYS",
2737
+ "HLEN",
2738
+ "HMGET",
2739
+ "HMSET",
2740
+ "HSET",
2741
+ "HVALS",
2742
+ "LOCK",
2743
+ "LPOP",
2744
+ "LPUSH",
2745
+ "LRANGE",
2746
+ "LREM",
2747
+ "RATELIMIT.ADD",
2748
+ "RPOP",
2749
+ "RPUSH",
2750
+ "SADD",
2751
+ "SCARD",
2752
+ "SISMEMBER",
2753
+ "SMEMBERS",
2754
+ "SREM",
2755
+ "SET",
2756
+ "SETBIT",
2757
+ "STRLEN",
2758
+ "TTL",
2759
+ "TYPE",
2760
+ "UNLINK",
2761
+ "UNLOCK",
2762
+ "XADD",
2763
+ "XLEN",
2764
+ "XRANGE",
2765
+ "ZADD",
2766
+ "ZCARD",
2767
+ "ZRANGE",
2768
+ "ZREM",
2769
+ "ZSCORE"
2770
+ ]);
2771
+ var typeScopedFlowCommands = /* @__PURE__ */ new Set([
2772
+ "FLOW.APPROVAL.LIST",
2773
+ "FLOW.ATTRIBUTE_VALUES",
2774
+ "FLOW.ATTRIBUTES",
2775
+ "FLOW.BUDGET.LIST",
2776
+ "FLOW.FAILURES",
2777
+ "FLOW.GOVERNANCE.OVERVIEW",
2778
+ "FLOW.INFO",
2779
+ "FLOW.LIMIT.LIST",
2780
+ "FLOW.LIST",
2781
+ "FLOW.POLICY.GET",
2782
+ "FLOW.POLICY.SET",
2783
+ "FLOW.RETENTION_CLEANUP",
2784
+ "FLOW.SCHEDULE.FIRE_DUE",
2785
+ "FLOW.SCHEDULE.LIST",
2786
+ "FLOW.SEARCH",
2787
+ "FLOW.STATS",
2788
+ "FLOW.STUCK",
2789
+ "FLOW.TERMINALS"
2790
+ ]);
2791
+
1813
2792
  // src/codecs.ts
1814
2793
  var RawCodec = class {
1815
2794
  encode(value) {
@@ -2023,7 +3002,7 @@ function parseKvResponse(value) {
2023
3002
  return { value };
2024
3003
  }
2025
3004
  function autoPartitionKeyForId(id) {
2026
- return `${AUTO_PARTITION_PREFIX}${crc32(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
3005
+ return `${AUTO_PARTITION_PREFIX}${crc322(Buffer.from(id)) % AUTO_PARTITION_BUCKETS}`;
2027
3006
  }
2028
3007
  function expandManyResponse(value, count) {
2029
3008
  if (Array.isArray(value) && value.length === count) {
@@ -2093,7 +3072,7 @@ function coerceDiagnosticValue(value) {
2093
3072
  }
2094
3073
  return value;
2095
3074
  }
2096
- function crc32(buffer) {
3075
+ function crc322(buffer) {
2097
3076
  let crc = 4294967295;
2098
3077
  for (const byte of buffer) {
2099
3078
  crc ^= byte;
@@ -3019,14 +3998,14 @@ function claimedItemFromResp(value, codec) {
3019
3998
  const partition = tuple[1] == null ? null : bytes(tuple[1]);
3020
3999
  const leaseToken = bytes(tuple[2]);
3021
4000
  const fencingToken = integer(tuple[3]);
3022
- const attrs = tuple[5] ?? (isPlainObject(tuple[4]) ? tuple[4] : void 0);
4001
+ const attrs = tuple[5] ?? (isPlainObject3(tuple[4]) ? tuple[4] : void 0);
3023
4002
  const item = {
3024
4003
  id: text(id),
3025
4004
  partitionKey: optionalString(partition),
3026
4005
  leaseToken,
3027
4006
  fencingToken,
3028
4007
  runState: optionalString(tuple[4]),
3029
- attributes: isPlainObject(attrs) ? attrs : void 0,
4008
+ attributes: isPlainObject3(attrs) ? attrs : void 0,
3030
4009
  type: "",
3031
4010
  state: "running"
3032
4011
  };
@@ -3146,9 +4125,9 @@ function decodeValues(value, codec) {
3146
4125
  }
3147
4126
  function plainObjectField(value, key) {
3148
4127
  const item = field(value, key);
3149
- return isPlainObject(item) ? item : void 0;
4128
+ return isPlainObject3(item) ? item : void 0;
3150
4129
  }
3151
- function isPlainObject(value) {
4130
+ function isPlainObject3(value) {
3152
4131
  return typeof value === "object" && value != null && !Buffer.isBuffer(value) && !(value instanceof Uint8Array) && !Array.isArray(value);
3153
4132
  }
3154
4133
 
@@ -3200,7 +4179,18 @@ var FerricStoreClient = class _FerricStoreClient {
3200
4179
  }
3201
4180
  static async fromUrl(url, options = {}) {
3202
4181
  const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
3203
- const createExecutor = async () => await NativeAdapter.fromUrl(url, options.nativeOptions);
4182
+ const nativeOptions = options.nativeOptions ?? {};
4183
+ const seeds = nativeOptions.seeds ?? [];
4184
+ const createExecutor = async () => nativeOptions.haRouting === true || seeds.length > 0 ? await TopologyNativeAdapterPool.fromUrls([url, ...seeds], nativeOptions) : await NativeAdapter.fromUrl(url, nativeOptions);
4185
+ const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
4186
+ return new _FerricStoreClient(executor, options);
4187
+ }
4188
+ static async fromUrls(urls, options = {}) {
4189
+ if (urls.length === 0) {
4190
+ throw new Error("FerricStoreClient.fromUrls requires at least one URL");
4191
+ }
4192
+ const reconnectOptions = options.reconnect ?? options.nativeOptions?.autoReconnect ?? true;
4193
+ const createExecutor = async () => await TopologyNativeAdapterPool.fromUrls(urls, options.nativeOptions);
3204
4194
  const executor = reconnectOptions === false ? await createExecutor() : new ReconnectingExecutor(createExecutor, reconnectOptions === true ? {} : reconnectOptions);
3205
4195
  return new _FerricStoreClient(executor, options);
3206
4196
  }
@@ -3216,6 +4206,18 @@ var FerricStoreClient = class _FerricStoreClient {
3216
4206
  async close() {
3217
4207
  await this.executor.close?.();
3218
4208
  }
4209
+ async refreshTopology() {
4210
+ if (this.executor.refreshTopology == null) {
4211
+ throw new Error("topology refresh requires a topology-aware native executor");
4212
+ }
4213
+ return await this.executor.refreshTopology();
4214
+ }
4215
+ async route(key) {
4216
+ if (this.executor.route == null) {
4217
+ throw new Error("route lookup requires a topology-aware native executor");
4218
+ }
4219
+ return await this.executor.route(key);
4220
+ }
3219
4221
  async ping(message) {
3220
4222
  return await this.command("PING", ...message == null ? [] : [message]);
3221
4223
  }
@@ -3278,7 +4280,7 @@ var FerricStoreClient = class _FerricStoreClient {
3278
4280
  return response == null ? null : text(response);
3279
4281
  }
3280
4282
  async clientInfo() {
3281
- return text(await this.command("CLIENT", "INFO"));
4283
+ return infoText(await this.command("CLIENT", "INFO"));
3282
4284
  }
3283
4285
  async clientList(options = {}) {
3284
4286
  return text(await this.command("CLIENT", "LIST", ...options.type == null ? [] : ["TYPE", options.type]));
@@ -3333,7 +4335,7 @@ var FerricStoreClient = class _FerricStoreClient {
3333
4335
  return Number(await this.command("PUBSUB", "NUMPAT"));
3334
4336
  }
3335
4337
  async aclSetUser(username, rules) {
3336
- return okResponse(await this.command("ACL", "SETUSER", username, ...rules));
4338
+ return okResponse(await this.command("ACL", "SETUSER", username, ...ruleArgs(rules)));
3337
4339
  }
3338
4340
  async aclDelUser(...usernames) {
3339
4341
  return Number(await this.command("ACL", "DELUSER", ...usernames));
@@ -3344,6 +4346,9 @@ var FerricStoreClient = class _FerricStoreClient {
3344
4346
  async aclList() {
3345
4347
  return arrayResponse(await this.command("ACL", "LIST"));
3346
4348
  }
4349
+ async aclListUsers() {
4350
+ return await this.aclList();
4351
+ }
3347
4352
  async aclWhoami() {
3348
4353
  return text(await this.command("ACL", "WHOAMI"));
3349
4354
  }
@@ -3439,6 +4444,107 @@ var FerricStoreClient = class _FerricStoreClient {
3439
4444
  async ferricstoreDoctor(...args) {
3440
4445
  return await this.command("FERRICSTORE.DOCTOR", ...args);
3441
4446
  }
4447
+ async capabilities() {
4448
+ return normalizeAdminResponse(await this.command("FERRICSTORE.CAPABILITIES"));
4449
+ }
4450
+ async ensureNamespace(prefix, attrs = {}) {
4451
+ return normalizeAdminResponse(
4452
+ await this.command("FERRICSTORE.NAMESPACE", "ENSURE", prefix, ...managementPairArgs(attrs))
4453
+ );
4454
+ }
4455
+ async getNamespace(prefix) {
4456
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "GET", prefix));
4457
+ }
4458
+ async listNamespaces() {
4459
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "LIST"));
4460
+ }
4461
+ async deleteNamespace(prefix) {
4462
+ return normalizeAdminResponse(await this.command("FERRICSTORE.NAMESPACE", "DELETE", prefix));
4463
+ }
4464
+ async setQuota(namespace, quotaSpec = {}) {
4465
+ return normalizeAdminResponse(
4466
+ await this.command("FERRICSTORE.QUOTA", "SET", namespace, ...managementPairArgs(quotaSpec))
4467
+ );
4468
+ }
4469
+ async getQuota(namespace) {
4470
+ return normalizeAdminResponse(await this.command("FERRICSTORE.QUOTA", "GET", namespace));
4471
+ }
4472
+ async quotaUsage(namespace) {
4473
+ return normalizeAdminResponse(await this.command("FERRICSTORE.QUOTA", "USAGE", namespace));
4474
+ }
4475
+ async clusterInfo() {
4476
+ return normalizeAdminResponse(await this.command("FERRICSTORE.TELEMETRY", "CLUSTER_INFO"));
4477
+ }
4478
+ async namespaceUsage(prefix) {
4479
+ return normalizeAdminResponse(
4480
+ await this.command("FERRICSTORE.TELEMETRY", "NAMESPACE_USAGE", prefix)
4481
+ );
4482
+ }
4483
+ async flowQuery(attrs = {}) {
4484
+ const response = normalizeAdminResponse(
4485
+ await this.command("FERRICSTORE.TELEMETRY", "FLOW_QUERY", ...managementPairArgs(attrs))
4486
+ );
4487
+ return Array.isArray(response) ? [...response] : [];
4488
+ }
4489
+ async flowHistory(id, attrs = {}) {
4490
+ const response = normalizeAdminResponse(
4491
+ await this.command("FERRICSTORE.TELEMETRY", "FLOW_HISTORY", id, ...managementPairArgs(attrs))
4492
+ );
4493
+ return Array.isArray(response) ? [...response] : [];
4494
+ }
4495
+ async invocationDefinitionPut(definition, options = {}) {
4496
+ return normalizeAdminResponse(
4497
+ await this.command(
4498
+ ...commandWithRequestContext(
4499
+ "INVOCATION.DEFINITION.PUT",
4500
+ [jsonArg(definition)],
4501
+ options.requestContext
4502
+ )
4503
+ )
4504
+ );
4505
+ }
4506
+ async invocationDefinitionGet(name, options = {}) {
4507
+ return normalizeAdminResponse(
4508
+ await this.command(
4509
+ ...commandWithRequestContext("INVOCATION.DEFINITION.GET", [name], options.requestContext)
4510
+ )
4511
+ );
4512
+ }
4513
+ async invocationDefinitionList(options = {}) {
4514
+ const response = normalizeAdminResponse(
4515
+ await this.command(
4516
+ ...commandWithRequestContext("INVOCATION.DEFINITION.LIST", [], options.requestContext)
4517
+ )
4518
+ );
4519
+ return Array.isArray(response) ? [...response] : [];
4520
+ }
4521
+ async invocationCreate(name, attrs, options = {}) {
4522
+ const envelope = { attrs };
4523
+ if (options.context != null) {
4524
+ envelope.context = options.context;
4525
+ }
4526
+ if (options.idempotencyKey != null) {
4527
+ envelope.idempotency_key = options.idempotencyKey;
4528
+ }
4529
+ return normalizeAdminResponse(
4530
+ await this.command(
4531
+ ...commandWithRequestContext("INVOCATION.CREATE", [name, jsonArg(envelope)], options.requestContext)
4532
+ )
4533
+ );
4534
+ }
4535
+ async invocationGet(id, options = {}) {
4536
+ return normalizeAdminResponse(
4537
+ await this.command(...commandWithRequestContext("INVOCATION.GET", [id], options.requestContext))
4538
+ );
4539
+ }
4540
+ async invocationPartitionList(name, options = {}) {
4541
+ const args = [name];
4542
+ append(args, "SCOPE", options.scope);
4543
+ const response = normalizeAdminResponse(
4544
+ await this.command(...commandWithRequestContext("INVOCATION.PARTITION.LIST", args, options.requestContext))
4545
+ );
4546
+ return Array.isArray(response) ? [...response] : [];
4547
+ }
3442
4548
  async create(id, options) {
3443
4549
  const currentNowMs = options.nowMs ?? nowMs();
3444
4550
  const args = [
@@ -3471,7 +4577,6 @@ var FerricStoreClient = class _FerricStoreClient {
3471
4577
  async enqueue(id, options) {
3472
4578
  return await this.create(id, {
3473
4579
  ...options,
3474
- priority: options.priority ?? 0,
3475
4580
  state: options.state ?? "queued"
3476
4581
  });
3477
4582
  }
@@ -3483,7 +4588,6 @@ var FerricStoreClient = class _FerricStoreClient {
3483
4588
  return await this.createMany(options.partitionKey, items, {
3484
4589
  ...options,
3485
4590
  independent: options.independent ?? true,
3486
- priority: options.priority ?? 0,
3487
4591
  state: options.state ?? "queued"
3488
4592
  });
3489
4593
  }
@@ -3498,7 +4602,6 @@ var FerricStoreClient = class _FerricStoreClient {
3498
4602
  const response = await this.createMany(bucket, groupItems, {
3499
4603
  ...options,
3500
4604
  independent: options.independent ?? true,
3501
- priority: options.priority ?? 0,
3502
4605
  state: options.state ?? "queued"
3503
4606
  });
3504
4607
  const expanded = expandManyResponse(response, indexedItems.length);
@@ -3651,8 +4754,7 @@ var FerricStoreClient = class _FerricStoreClient {
3651
4754
  ...options,
3652
4755
  includeState: options.includeState ?? false,
3653
4756
  jobOnly: true,
3654
- limit: options.limit ?? 100,
3655
- priority: options.priority ?? 0
4757
+ limit: options.limit ?? 100
3656
4758
  });
3657
4759
  }
3658
4760
  async reclaim(type, options) {
@@ -3919,6 +5021,14 @@ var FerricStoreClient = class _FerricStoreClient {
3919
5021
  async list(type, options = {}) {
3920
5022
  return await this.indexQuery("FLOW.LIST", type, options);
3921
5023
  }
5024
+ async search(type, options = {}) {
5025
+ const args = ["FLOW.SEARCH", type];
5026
+ appendReadOptions(args, options);
5027
+ appendAttributes(args, options.attributes);
5028
+ appendSearchStateMeta(args, options.state, options.stateMeta);
5029
+ const response = await this.command(...args);
5030
+ return Array.isArray(response) ? this.records(response) : [];
5031
+ }
3922
5032
  async terminals(type, options = {}) {
3923
5033
  return await this.indexQuery("FLOW.TERMINALS", type, options);
3924
5034
  }
@@ -3995,9 +5105,14 @@ var FerricStoreClient = class _FerricStoreClient {
3995
5105
  const args = ["FLOW.POLICY.SET", type];
3996
5106
  append(args, "INDEXED_STATE_META", options.indexedStateMeta);
3997
5107
  append(args, "STATE", options.state);
5108
+ appendPolicyMode(args, options.state, options.mode);
3998
5109
  if (options.retry != null) {
3999
5110
  appendRetryPolicy(args, options.retry);
4000
5111
  }
5112
+ for (const [state, policy] of Object.entries(options.states ?? {})) {
5113
+ args.push("STATE", state);
5114
+ appendFlowStatePolicy(args, policy);
5115
+ }
4001
5116
  append(args, "RETENTION_TTL_MS", options.retentionTtlMs);
4002
5117
  return await this.command(...args);
4003
5118
  }
@@ -4088,6 +5203,18 @@ var ErrorMappingExecutor = class {
4088
5203
  throwMapped(error);
4089
5204
  }
4090
5205
  }
5206
+ async refreshTopology() {
5207
+ if (this.executor.refreshTopology == null) {
5208
+ throw new Error("topology refresh requires a topology-aware native executor");
5209
+ }
5210
+ return await this.executor.refreshTopology();
5211
+ }
5212
+ async route(key) {
5213
+ if (this.executor.route == null) {
5214
+ throw new Error("route lookup requires a topology-aware native executor");
5215
+ }
5216
+ return await this.executor.route(key);
5217
+ }
4091
5218
  async close() {
4092
5219
  await this.executor.close?.();
4093
5220
  }
@@ -4123,6 +5250,18 @@ var AutoBatchExecutor = class {
4123
5250
  }
4124
5251
  return await Promise.all(commands.map((command) => this.executor.executeCommand(...command)));
4125
5252
  }
5253
+ async refreshTopology() {
5254
+ if (this.executor.refreshTopology == null) {
5255
+ throw new Error("topology refresh requires a topology-aware native executor");
5256
+ }
5257
+ return await this.executor.refreshTopology();
5258
+ }
5259
+ async route(key) {
5260
+ if (this.executor.route == null) {
5261
+ throw new Error("route lookup requires a topology-aware native executor");
5262
+ }
5263
+ return await this.executor.route(key);
5264
+ }
4126
5265
  async close() {
4127
5266
  this.scheduled = false;
4128
5267
  if (this.timer != null) {
@@ -4283,7 +5422,7 @@ var neverAutoBatchCommandPrefixes = /* @__PURE__ */ new Set([
4283
5422
  ]);
4284
5423
  var neverAutoBatchCommands = /* @__PURE__ */ new Set(["FLOW.CLAIM_DUE", "FLOW.RECLAIM"]);
4285
5424
  function autoBatchCommandAllowed(command, mode) {
4286
- const name = commandName(command);
5425
+ const name = commandName2(command);
4287
5426
  if (name == null || neverAutoBatchCommands.has(name)) {
4288
5427
  return false;
4289
5428
  }
@@ -4293,18 +5432,18 @@ function autoBatchCommandAllowed(command, mode) {
4293
5432
  }
4294
5433
  return mode === "all" || safeAutoBatchCommands.has(name);
4295
5434
  }
4296
- function commandName(command) {
4297
- const first = commandPart(command[0]);
5435
+ function commandName2(command) {
5436
+ const first = commandPart2(command[0]);
4298
5437
  if (first == null) {
4299
5438
  return null;
4300
5439
  }
4301
5440
  if (first === "FLOW" && command.length > 1) {
4302
- const second = commandPart(command[1]);
5441
+ const second = commandPart2(command[1]);
4303
5442
  return second == null ? first : `${first}.${second}`;
4304
5443
  }
4305
5444
  return first;
4306
5445
  }
4307
- function commandPart(value) {
5446
+ function commandPart2(value) {
4308
5447
  if (typeof value === "string") {
4309
5448
  return value.toUpperCase();
4310
5449
  }
@@ -4329,6 +5468,90 @@ function appendStateMeta(args, stateMeta) {
4329
5468
  args.push("STATE_META", name, value);
4330
5469
  }
4331
5470
  }
5471
+ function appendAttributes(args, attributes) {
5472
+ for (const [name, value] of Object.entries(attributes ?? {})) {
5473
+ args.push("ATTRIBUTE", name, value);
5474
+ }
5475
+ }
5476
+ function appendSearchStateMeta(args, state, stateMeta) {
5477
+ const entries = Object.entries(stateMeta ?? {});
5478
+ if (entries.length === 0) {
5479
+ return;
5480
+ }
5481
+ if (entries.every(([, value]) => isPlainObject4(value))) {
5482
+ for (const [metaState, values] of entries) {
5483
+ args.push("STATE_META", metaState, values);
5484
+ }
5485
+ return;
5486
+ }
5487
+ if (state == null) {
5488
+ throw new Error("search stateMeta filters require state or nested state metadata");
5489
+ }
5490
+ args.push("STATE_META", state, stateMeta);
5491
+ }
5492
+ function normalizeAdminResponse(value) {
5493
+ return normalizeRefMeta(value);
5494
+ }
5495
+ function jsonArg(value) {
5496
+ return typeof value === "string" ? value : JSON.stringify(value);
5497
+ }
5498
+ function commandWithRequestContext(command, args, requestContext) {
5499
+ const commandArgs = [command, ...args];
5500
+ if (requestContext != null) {
5501
+ commandArgs.push("REQUEST_CONTEXT", requestContext);
5502
+ }
5503
+ return commandArgs;
5504
+ }
5505
+ function infoText(value) {
5506
+ if (typeof value === "string" || Buffer.isBuffer(value)) {
5507
+ return text(value);
5508
+ }
5509
+ const normalized = normalizeRefMeta(value);
5510
+ if (isPlainObject4(normalized)) {
5511
+ return Object.entries(normalized).map(([key, item]) => `${key}=${diagnosticValueText(item)}`).join(" ");
5512
+ }
5513
+ return diagnosticValueText(normalized);
5514
+ }
5515
+ function diagnosticValueText(value) {
5516
+ if (value == null) {
5517
+ return "";
5518
+ }
5519
+ if (Buffer.isBuffer(value)) {
5520
+ return value.toString("utf8");
5521
+ }
5522
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
5523
+ return String(value);
5524
+ }
5525
+ return JSON.stringify(value);
5526
+ }
5527
+ function managementPairArgs(pairs2) {
5528
+ const args = [];
5529
+ for (const [key, value] of Object.entries(pairs2)) {
5530
+ if (value != null) {
5531
+ args.push(key.toUpperCase(), value);
5532
+ }
5533
+ }
5534
+ return args;
5535
+ }
5536
+ function appendFlowStatePolicy(args, policy) {
5537
+ if (isFlowStatePolicy(policy)) {
5538
+ appendPolicyMode(args, "state", policy.mode);
5539
+ if (policy.retry != null) {
5540
+ appendRetryPolicy(args, policy.retry);
5541
+ }
5542
+ return;
5543
+ }
5544
+ appendRetryPolicy(args, policy);
5545
+ }
5546
+ function isFlowStatePolicy(policy) {
5547
+ return typeof policy === "object" && policy != null && ("mode" in policy || "retry" in policy);
5548
+ }
5549
+ function ruleArgs(rules) {
5550
+ return typeof rules === "string" ? [rules] : [...rules];
5551
+ }
5552
+ function isPlainObject4(value) {
5553
+ return typeof value === "object" && value != null && !Array.isArray(value) && !Buffer.isBuffer(value);
5554
+ }
4332
5555
  function sharedCreateManyStateMeta(items, stateMeta) {
4333
5556
  const itemMetas = items.map((item) => item.stateMeta).filter((meta) => meta != null);
4334
5557
  if (itemMetas.length === 0) {
@@ -4415,6 +5638,24 @@ function appendRetryPolicy(args, policy) {
4415
5638
  append(args, "JITTER_PCT", policy.jitterPct);
4416
5639
  append(args, "EXHAUSTED_TO", policy.exhaustedTo);
4417
5640
  }
5641
+ function appendPolicyMode(args, state, mode) {
5642
+ if (mode == null) {
5643
+ return;
5644
+ }
5645
+ if (state == null) {
5646
+ throw new Error("policy mode requires state");
5647
+ }
5648
+ switch (mode) {
5649
+ case "fifo":
5650
+ args.push("MODE", "FIFO");
5651
+ return;
5652
+ case "parallel":
5653
+ args.push("MODE", "PARALLEL");
5654
+ return;
5655
+ default:
5656
+ throw new Error("policy mode must be 'fifo' or 'parallel'");
5657
+ }
5658
+ }
4418
5659
  function appendPayloadRead(args, payload, maxBytes) {
4419
5660
  if (payload === false) {
4420
5661
  args.push("NOPAYLOAD");
@@ -4879,6 +6120,7 @@ var Workflow = class {
4879
6120
  exceptionPolicy: normalizeExceptionPolicy(options.exceptionPolicy),
4880
6121
  handler,
4881
6122
  leaseMs: options.leaseMs ?? 3e4,
6123
+ ...options.mode == null ? {} : { mode: normalizeStateMode(options.mode) },
4882
6124
  name,
4883
6125
  returnRecord: options.returnRecord ?? false,
4884
6126
  valueMaxBytes: options.valueMaxBytes ?? this.valueConfig.valueMaxBytes,
@@ -4913,6 +6155,21 @@ var Workflow = class {
4913
6155
  worker(options = {}) {
4914
6156
  return new WorkflowWorker(this, options);
4915
6157
  }
6158
+ async installPolicy(options = {}) {
6159
+ const states = {};
6160
+ for (const registration of this.states.values()) {
6161
+ if (registration.mode != null || registration.retryPolicy != null) {
6162
+ states[registration.name] = {
6163
+ ...registration.mode == null ? {} : { mode: registration.mode },
6164
+ ...registration.retryPolicy == null ? {} : { retry: registration.retryPolicy }
6165
+ };
6166
+ }
6167
+ }
6168
+ return await this.client.installPolicy(this.type, {
6169
+ ...options,
6170
+ states
6171
+ });
6172
+ }
4916
6173
  stateNames() {
4917
6174
  return [...this.states.keys()];
4918
6175
  }
@@ -5173,13 +6430,15 @@ var WorkflowWorker = class {
5173
6430
  }
5174
6431
  async applyJob(job, registration) {
5175
6432
  const ctx = new WorkflowContext(this.workflow, job, registration.name);
6433
+ let outcome;
5176
6434
  try {
5177
6435
  const value = await registration.handler(ctx);
5178
- const outcome = isOutcome(value) ? value : complete({ result: value });
5179
- await applyOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
6436
+ outcome = isOutcome(value) ? value : complete({ result: value });
5180
6437
  } catch (error) {
5181
6438
  await this.applyHandlerError(ctx, registration, error);
6439
+ return;
5182
6440
  }
6441
+ await applyOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
5183
6442
  }
5184
6443
  async applyHandlerError(ctx, registration, error) {
5185
6444
  const policy = this.options.exceptionPolicy ?? registration.exceptionPolicy;
@@ -5210,6 +6469,7 @@ async function applyOutcome(client, ctx, outcome, returnRecord) {
5210
6469
  }
5211
6470
  }
5212
6471
  async function applyTransition(client, ctx, outcome, returnRecord) {
6472
+ validateTransitionPolicy(ctx, outcome);
5213
6473
  await client.transition(ctx.id, {
5214
6474
  dropValues: outcome.dropValues,
5215
6475
  fencingToken: ctx.fencingToken,
@@ -5227,6 +6487,12 @@ async function applyTransition(client, ctx, outcome, returnRecord) {
5227
6487
  values: outcome.values
5228
6488
  });
5229
6489
  }
6490
+ function validateTransitionPolicy(ctx, outcome) {
6491
+ const target = ctx.workflow.stateRegistration(outcome.toState);
6492
+ if (target?.mode === "fifo" && outcome.priority != null) {
6493
+ throw new Error("priority is not supported for fifo state");
6494
+ }
6495
+ }
5230
6496
  async function applyComplete(client, ctx, outcome, returnRecord) {
5231
6497
  await client.complete(ctx.id, {
5232
6498
  dropValues: outcome.dropValues,
@@ -5285,6 +6551,12 @@ function errorToPayload(error) {
5285
6551
  }
5286
6552
  return error;
5287
6553
  }
6554
+ function normalizeStateMode(mode) {
6555
+ if (mode === "fifo" || mode === "parallel") {
6556
+ return mode;
6557
+ }
6558
+ throw new Error("state mode must be 'fifo' or 'parallel'");
6559
+ }
5288
6560
  function valueRefToString(value) {
5289
6561
  if (typeof value === "string") {
5290
6562
  return value;
@@ -5302,6 +6574,7 @@ function valueRefToString(value) {
5302
6574
  0 && (module.exports = {
5303
6575
  BitmapStore,
5304
6576
  BloomFilterStore,
6577
+ COMMAND_OPCODES,
5305
6578
  CountMinSketchStore,
5306
6579
  CuckooFilterStore,
5307
6580
  FerricStoreClient,
@@ -5326,12 +6599,14 @@ function valueRefToString(value) {
5326
6599
  QueueWorker,
5327
6600
  RawCodec,
5328
6601
  ReconnectingExecutor,
6602
+ RoutingTopology,
5329
6603
  SetStore,
5330
6604
  SortedSetStore,
5331
6605
  StaleLeaseError,
5332
6606
  StreamStore,
5333
6607
  TDigestStore,
5334
6608
  TopKStore,
6609
+ TopologyNativeAdapterPool,
5335
6610
  Workflow,
5336
6611
  WorkflowClient,
5337
6612
  WorkflowContext,