@ferricstore/ferricstore 0.5.1 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +44 -5
  2. package/dist/index.cjs +2162 -518
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +183 -34
  5. package/dist/index.d.ts +183 -34
  6. package/dist/index.js +2134 -490
  7. package/dist/index.js.map +1 -1
  8. package/docs/api/assets/hierarchy.js +1 -1
  9. package/docs/api/assets/navigation.js +1 -1
  10. package/docs/api/assets/search.js +1 -1
  11. package/docs/api/classes/ConnectionClosedError.html +1 -1
  12. package/docs/api/classes/FerricStoreClient.html +3 -3
  13. package/docs/api/classes/FerricStoreError.html +1 -1
  14. package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
  15. package/docs/api/classes/FlowBatchError.html +1 -1
  16. package/docs/api/classes/FlowNotFoundError.html +1 -1
  17. package/docs/api/classes/FlowQueryError.html +1 -1
  18. package/docs/api/classes/FlowWrongStateError.html +1 -1
  19. package/docs/api/classes/InvalidCommandError.html +1 -1
  20. package/docs/api/classes/LeaseRenewalError.html +1 -1
  21. package/docs/api/classes/LockHeldError.html +1 -1
  22. package/docs/api/classes/LockNotOwnedError.html +1 -1
  23. package/docs/api/classes/NativeAdapter.html +1 -1
  24. package/docs/api/classes/OverloadedError.html +1 -1
  25. package/docs/api/classes/QueueCompletionError.html +1 -1
  26. package/docs/api/classes/RequestTimeoutError.html +1 -1
  27. package/docs/api/classes/RerouteError.html +1 -1
  28. package/docs/api/classes/StaleLeaseError.html +1 -1
  29. package/docs/api/classes/StalePolicyGenerationError.html +1 -1
  30. package/docs/api/functions/projectFlowQuery.html +1 -1
  31. package/docs/api/hierarchy.html +1 -1
  32. package/docs/api/index.html +34 -5
  33. package/docs/api/interfaces/AdminListOptions.html +2 -2
  34. package/docs/api/interfaces/ApprovalListOptions.html +2 -2
  35. package/docs/api/interfaces/ApprovalRequestOptions.html +2 -2
  36. package/docs/api/interfaces/AttributeQueryOptions.html +2 -2
  37. package/docs/api/interfaces/AutoBatchOptions.html +2 -2
  38. package/docs/api/interfaces/BudgetCommitOptions.html +2 -2
  39. package/docs/api/interfaces/BudgetReserveOptions.html +2 -2
  40. package/docs/api/interfaces/CancelOptions.html +2 -2
  41. package/docs/api/interfaces/CircuitOpenOptions.html +2 -2
  42. package/docs/api/interfaces/ClaimDueOptions.html +3 -3
  43. package/docs/api/interfaces/ClaimHydrationItem.html +2 -2
  44. package/docs/api/interfaces/CompleteJobsAndClaimJobsResult.html +6 -6
  45. package/docs/api/interfaces/CompleteManyOptions.html +2 -2
  46. package/docs/api/interfaces/CompleteOptions.html +2 -2
  47. package/docs/api/interfaces/CompleteOutcome.html +1 -1
  48. package/docs/api/interfaces/CreateManyOptions.html +5 -5
  49. package/docs/api/interfaces/CreateOptions.html +3 -3
  50. package/docs/api/interfaces/EffectCompensateOptions.html +2 -2
  51. package/docs/api/interfaces/EffectConfirmOptions.html +2 -2
  52. package/docs/api/interfaces/EffectFailOptions.html +2 -2
  53. package/docs/api/interfaces/EffectReserveOptions.html +2 -2
  54. package/docs/api/interfaces/EffectStatusOptions.html +2 -2
  55. package/docs/api/interfaces/ExtendLeaseOptions.html +2 -2
  56. package/docs/api/interfaces/FailOptions.html +2 -2
  57. package/docs/api/interfaces/FailOutcome.html +1 -1
  58. package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +4 -4
  59. package/docs/api/interfaces/FerricStoreClientOptions.html +4 -4
  60. package/docs/api/interfaces/FlowBatchCompletedItem.html +2 -2
  61. package/docs/api/interfaces/FlowEventProjectionField.html +2 -2
  62. package/docs/api/interfaces/FlowExplainCapabilities.html +5 -0
  63. package/docs/api/interfaces/FlowExplainResult.html +8 -2
  64. package/docs/api/interfaces/FlowPolicyOptions.html +5 -5
  65. package/docs/api/interfaces/FlowQueryCountResult.html +1 -1
  66. package/docs/api/interfaces/FlowQueryIndex.html +11 -2
  67. package/docs/api/interfaces/FlowQueryIndexBuild.html +10 -0
  68. package/docs/api/interfaces/FlowQueryIndexCoverage.html +5 -0
  69. package/docs/api/interfaces/FlowQueryIndexField.html +5 -0
  70. package/docs/api/interfaces/FlowQueryIndexFormat.html +2 -2
  71. package/docs/api/interfaces/FlowQueryIndexProgress.html +7 -0
  72. package/docs/api/interfaces/FlowQueryIndexRegistry.html +2 -2
  73. package/docs/api/interfaces/FlowQueryIndexRetirement.html +10 -0
  74. package/docs/api/interfaces/FlowQueryIndexServices.html +6 -0
  75. package/docs/api/interfaces/FlowQueryIndexStatistics.html +11 -0
  76. package/docs/api/interfaces/FlowQueryIndexStatus.html +2 -2
  77. package/docs/api/interfaces/FlowQueryIndexValidation.html +13 -0
  78. package/docs/api/interfaces/FlowQueryQuality.html +2 -2
  79. package/docs/api/interfaces/FlowQueryRecordsResult.html +1 -1
  80. package/docs/api/interfaces/FlowRunProjectionField.html +2 -2
  81. package/docs/api/interfaces/FlowStatePolicy.html +2 -2
  82. package/docs/api/interfaces/FlowStatsOptions.html +2 -2
  83. package/docs/api/interfaces/GovernanceLedgerOptions.html +2 -2
  84. package/docs/api/interfaces/HistoryOptions.html +2 -2
  85. package/docs/api/interfaces/InvocationCreateOptions.html +2 -2
  86. package/docs/api/interfaces/LeaseMutationOptions.html +2 -2
  87. package/docs/api/interfaces/LimitAmountOptions.html +2 -2
  88. package/docs/api/interfaces/LimitLeaseOptions.html +2 -2
  89. package/docs/api/interfaces/LimitListOptions.html +2 -2
  90. package/docs/api/interfaces/LimitReleaseOptions.html +2 -2
  91. package/docs/api/interfaces/MutateOptions.html +2 -2
  92. package/docs/api/interfaces/NamedValueMutation.html +1 -1
  93. package/docs/api/interfaces/ReadOptions.html +3 -3
  94. package/docs/api/interfaces/ReclaimOptions.html +2 -2
  95. package/docs/api/interfaces/RequestContext.html +2 -2
  96. package/docs/api/interfaces/RequestContextOptions.html +2 -2
  97. package/docs/api/interfaces/RetryOptions.html +2 -2
  98. package/docs/api/interfaces/RetryOutcome.html +1 -1
  99. package/docs/api/interfaces/RunStepsItem.html +2 -2
  100. package/docs/api/interfaces/RunStepsManyOptions.html +2 -2
  101. package/docs/api/interfaces/ScheduleFireDueOptions.html +3 -2
  102. package/docs/api/interfaces/ScheduleFireOptions.html +3 -0
  103. package/docs/api/interfaces/ScheduleListOptions.html +2 -2
  104. package/docs/api/interfaces/ScheduleOptions.html +4 -2
  105. package/docs/api/interfaces/SearchOptions.html +3 -3
  106. package/docs/api/interfaces/StartAndClaimOptions.html +3 -3
  107. package/docs/api/interfaces/StepContinueOptions.html +3 -3
  108. package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
  109. package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
  110. package/docs/api/interfaces/TransitionOptions.html +2 -2
  111. package/docs/api/interfaces/TransitionOutcome.html +1 -1
  112. package/docs/api/modules.html +1 -1
  113. package/docs/api/types/ConnectionRequestDisposition.html +1 -1
  114. package/docs/api/types/FlowProjectionField.html +1 -1
  115. package/docs/api/types/FlowProjectionShape.html +1 -1
  116. package/docs/api/types/FlowQueryIndexServiceState.html +1 -0
  117. package/docs/api/types/FlowStateMode.html +1 -1
  118. package/docs/api/types/FlowStatePolicyLike.html +1 -1
  119. package/docs/api/types/ManagementPairs.html +1 -1
  120. package/docs/api/types/ScheduleCatchupPolicy.html +2 -0
  121. package/docs/api/types/ScheduleFireDueResult.html +3 -0
  122. package/docs/api/types/ScheduleFireResult.html +2 -0
  123. package/docs/api/types/ScheduleKind.html +1 -0
  124. package/docs/api/types/ScheduleOverlapPolicy.html +1 -0
  125. package/docs/api/types/ScheduleRecord.html +12 -0
  126. package/docs/api/types/ScheduleState.html +1 -0
  127. package/docs/api/types/SearchStateMeta.html +1 -1
  128. package/docs/api/variables/FERRICSTORE_MINIMUM_SERVER_VERSION.html +1 -1
  129. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
  130. package/docs/api/variables/FlowProjection.html +1 -1
  131. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1140,10 +1140,10 @@ var NativeFlowControl = class {
1140
1140
 
1141
1141
  // src/native-heartbeat.ts
1142
1142
  var NativeHeartbeat = class {
1143
- constructor(intervalMs, send, fail2) {
1143
+ constructor(intervalMs, send, fail3) {
1144
1144
  this.intervalMs = intervalMs;
1145
1145
  this.send = send;
1146
- this.fail = fail2;
1146
+ this.fail = fail3;
1147
1147
  }
1148
1148
  intervalMs;
1149
1149
  send;
@@ -1190,9 +1190,11 @@ var FLOW_QUERY_LANGUAGE_VERSION = "FQL1";
1190
1190
  var FLOW_QUERY_MAX_BYTES = 16 * 1024;
1191
1191
  var FLOW_QUERY_MAX_PARAMETERS = 64;
1192
1192
  var FLOW_QUERY_MAX_PARAMETER_NAME_BYTES = 128;
1193
+ var FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES = 65535;
1193
1194
  var MIN_I64 = -(1n << 63n);
1194
1195
  var MAX_I64 = (1n << 63n) - 1n;
1195
1196
  var INDEX_ID = /^[A-Za-z0-9_.:-]{1,64}$/u;
1197
+ var PARAMETER_NAME = /^[A-Za-z0-9_.-]+$/u;
1196
1198
  function flowQueryArgs(query, params = {}) {
1197
1199
  validateFlowQueryText(query);
1198
1200
  const entries = flowQueryParameterEntries(params);
@@ -1246,9 +1248,34 @@ function validateFlowQueryIndexId(indexId) {
1246
1248
  }
1247
1249
  }
1248
1250
  function hasFlowExplainPrefix(query) {
1249
- const source = query.trimStart();
1250
- const prefix = source.slice(0, 7);
1251
- return prefix.toUpperCase() === "EXPLAIN" && (source.length === 7 || /[\t\n\r ]/u.test(source[7] ?? ""));
1251
+ const source = trimFlowQueryAsciiWhitespaceStart(query);
1252
+ return matchesFlowQueryAsciiKeyword(source, 0, "EXPLAIN") && (source.length === 7 || isFlowQueryAsciiWhitespaceCode(source.charCodeAt(7)));
1253
+ }
1254
+ function matchesFlowQueryAsciiKeyword(value, offset, keyword) {
1255
+ if (offset < 0 || offset + keyword.length > value.length) return false;
1256
+ for (let index = 0; index < keyword.length; index += 1) {
1257
+ let actual = value.charCodeAt(offset + index);
1258
+ if (actual >= 97 && actual <= 122) actual -= 32;
1259
+ if (actual !== keyword.charCodeAt(index)) return false;
1260
+ }
1261
+ return true;
1262
+ }
1263
+ function trimFlowQueryAsciiWhitespaceStart(value) {
1264
+ let start = 0;
1265
+ while (start < value.length && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(start))) {
1266
+ start += 1;
1267
+ }
1268
+ return start === 0 ? value : value.slice(start);
1269
+ }
1270
+ function trimFlowQueryAsciiWhitespace(value) {
1271
+ let start = 0;
1272
+ let end = value.length;
1273
+ while (start < end && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(start))) start += 1;
1274
+ while (end > start && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(end - 1))) end -= 1;
1275
+ return start === 0 && end === value.length ? value : value.slice(start, end);
1276
+ }
1277
+ function isFlowQueryAsciiWhitespaceCode(code) {
1278
+ return code === 32 || code === 9 || code === 10 || code === 13;
1252
1279
  }
1253
1280
  function flowQueryParameterEntries(params) {
1254
1281
  if (typeof params !== "object" || params == null || Array.isArray(params)) {
@@ -1274,19 +1301,26 @@ function flowQueryParameterEntries(params) {
1274
1301
  function validateFlowQueryParameterName(name) {
1275
1302
  validateUnicodeScalarText(name, "FLOW.QUERY parameter name");
1276
1303
  const size = Buffer4.byteLength(name, "utf8");
1277
- if (size === 0 || size > FLOW_QUERY_MAX_PARAMETER_NAME_BYTES) {
1304
+ if (size === 0 || size > FLOW_QUERY_MAX_PARAMETER_NAME_BYTES || !PARAMETER_NAME.test(name)) {
1278
1305
  throw new TypeError(
1279
- `FLOW.QUERY parameter names must be 1..${FLOW_QUERY_MAX_PARAMETER_NAME_BYTES} bytes`
1306
+ `FLOW.QUERY parameter names must be 1..${FLOW_QUERY_MAX_PARAMETER_NAME_BYTES} ASCII letters, digits, '_', '.', or '-'`
1280
1307
  );
1281
1308
  }
1282
1309
  }
1283
1310
  function normalizeFlowQueryParameter(value, name) {
1284
1311
  if (typeof value === "string") {
1285
1312
  validateUnicodeScalarText(value, `FLOW.QUERY parameter ${JSON.stringify(name)}`);
1313
+ validateParameterValueSize(Buffer4.byteLength(value, "utf8"), name);
1314
+ return value;
1315
+ }
1316
+ if (Buffer4.isBuffer(value)) {
1317
+ validateParameterValueSize(value.byteLength, name);
1286
1318
  return value;
1287
1319
  }
1288
- if (Buffer4.isBuffer(value)) return value;
1289
- if (value instanceof Uint8Array) return Buffer4.from(value);
1320
+ if (value instanceof Uint8Array) {
1321
+ validateParameterValueSize(value.byteLength, name);
1322
+ return Buffer4.from(value);
1323
+ }
1290
1324
  if (typeof value === "boolean") return value;
1291
1325
  if (typeof value === "bigint" && value >= MIN_I64 && value <= MAX_I64) return value;
1292
1326
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -1296,6 +1330,13 @@ function normalizeFlowQueryParameter(value, name) {
1296
1330
  `FLOW.QUERY parameter ${JSON.stringify(name)} must be text, bytes, boolean, a finite float, or a signed 64-bit integer`
1297
1331
  );
1298
1332
  }
1333
+ function validateParameterValueSize(size, name) {
1334
+ if (size > FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES) {
1335
+ throw new TypeError(
1336
+ `FLOW.QUERY parameter ${JSON.stringify(name)} exceeds ${FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES} bytes`
1337
+ );
1338
+ }
1339
+ }
1299
1340
  function commandText(value, context) {
1300
1341
  if (typeof value === "string") {
1301
1342
  validateUnicodeScalarText(value, context);
@@ -3049,6 +3090,7 @@ var FLOW_ADMIN_FIELDS = {
3049
3090
  AT_MS: "at_ms",
3050
3091
  BLOCK: "block_ms",
3051
3092
  BLOCK_MS: "block_ms",
3093
+ CATCHUP_POLICY: "catchup_policy",
3052
3094
  CONSISTENT_PROJECTION: "consistent_projection",
3053
3095
  CORRELATION_ID: "correlation_id",
3054
3096
  COUNT: "count",
@@ -5482,15 +5524,15 @@ function requiredMap(value, context) {
5482
5524
  }
5483
5525
  throw incompatibleServer(`${context} is missing or invalid`);
5484
5526
  }
5485
- function requiredBoundedText(mapping, name) {
5486
- const value = strictText(field(mapping, name));
5527
+ function requiredBoundedText(mapping2, name) {
5528
+ const value = strictText(field(mapping2, name));
5487
5529
  if (value == null || value.length === 0 || Buffer20.byteLength(value) > 256) {
5488
5530
  throw incompatibleServer(`HELLO flow_query ${name} must be bounded non-empty text`);
5489
5531
  }
5490
5532
  return value;
5491
5533
  }
5492
- function boundedTextSet(mapping, name, maximum) {
5493
- const raw = field(mapping, name);
5534
+ function boundedTextSet(mapping2, name, maximum) {
5535
+ const raw = field(mapping2, name);
5494
5536
  if (!Array.isArray(raw) || raw.length < 1 || raw.length > maximum) {
5495
5537
  throw incompatibleServer(`HELLO ${name} must contain 1..${maximum} entries`);
5496
5538
  }
@@ -5730,7 +5772,7 @@ function truncate(value) {
5730
5772
  }
5731
5773
 
5732
5774
  // src/protocol-flow-query-result.ts
5733
- import { Buffer as Buffer24 } from "buffer";
5775
+ import { Buffer as Buffer24, isUtf8 as isUtf82 } from "buffer";
5734
5776
 
5735
5777
  // src/protocol-binary-detacher.ts
5736
5778
  import { Buffer as Buffer23 } from "buffer";
@@ -5767,7 +5809,10 @@ function isDecodedRecord(value) {
5767
5809
  // src/protocol-flow-query-result.ts
5768
5810
  var CONTRACT = "ferric.flow.query.result/v1";
5769
5811
  var MAX_RECORDS = 100;
5812
+ var MIN_CURSOR_BYTES = 16;
5770
5813
  var MAX_CURSOR_BYTES = 4096;
5814
+ var CURSOR_PREFIX = Buffer24.from("fqc1_");
5815
+ var MAX_SIGNED_64 = (1n << 63n) - 1n;
5771
5816
  var RECORD_FIELDS = [
5772
5817
  "id",
5773
5818
  "type",
@@ -5831,9 +5876,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5831
5876
  const usage = {};
5832
5877
  for (const field3 of USAGE_FIELDS) {
5833
5878
  const read = readU64(data, offset);
5879
+ if (BigInt(read.value) > MAX_SIGNED_64) {
5880
+ throw new FerricStoreError("compact FLOW.QUERY usage exceeds signed 64-bit range");
5881
+ }
5834
5882
  usage[field3] = read.value;
5835
5883
  offset = read.offset;
5836
5884
  }
5885
+ validateCompactUsage(usage);
5837
5886
  let result;
5838
5887
  if (kind === 0) {
5839
5888
  const page = readPage(data, offset);
@@ -5842,6 +5891,7 @@ function decodeCompactFlowQueryResult(data, budget) {
5842
5891
  const count = data.readUInt32BE(offset);
5843
5892
  offset += 4;
5844
5893
  if (count > MAX_RECORDS) throw new FerricStoreError("compact FLOW.QUERY page exceeds 100 records");
5894
+ validateCompactRecordUsage(usage, count);
5845
5895
  consumeDecodeItems(count, budget);
5846
5896
  const records = new Array(count);
5847
5897
  for (let index = 0; index < count; index += 1) {
@@ -5851,7 +5901,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5851
5901
  }
5852
5902
  result = { version: Buffer24.from(CONTRACT), records, page: page.value, quality, usage };
5853
5903
  } else if (kind === 1) {
5904
+ if (usageInteger(usage, "result_records") !== 1n) {
5905
+ throw new FerricStoreError("compact FLOW.QUERY count usage is inconsistent");
5906
+ }
5854
5907
  const count = readU64(data, offset);
5908
+ if (BigInt(count.value) > MAX_SIGNED_64) {
5909
+ throw new FerricStoreError("compact FLOW.QUERY count exceeds signed 64-bit range");
5910
+ }
5855
5911
  offset = count.offset;
5856
5912
  result = {
5857
5913
  version: Buffer24.from(CONTRACT),
@@ -5876,18 +5932,37 @@ function readPage(data, offset) {
5876
5932
  if (hasMoreCode === 0 && size === NULL_U32) {
5877
5933
  return { value: { has_more: false, cursor: null }, offset };
5878
5934
  }
5879
- if (hasMoreCode !== 1 || size === 0 || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
5935
+ if (hasMoreCode !== 1 || size < MIN_CURSOR_BYTES || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
5880
5936
  throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
5881
5937
  }
5882
5938
  requireAvailable(data, offset, size);
5939
+ const cursor = data.subarray(offset, offset + size);
5940
+ if (!cursor.subarray(0, CURSOR_PREFIX.byteLength).equals(CURSOR_PREFIX) || !isUtf82(cursor)) {
5941
+ throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
5942
+ }
5883
5943
  return {
5884
5944
  value: {
5885
5945
  has_more: true,
5886
- cursor: detachDecodedBinary(data.subarray(offset, offset + size), data.byteLength)
5946
+ cursor: detachDecodedBinary(cursor, data.byteLength)
5887
5947
  },
5888
5948
  offset: offset + size
5889
5949
  };
5890
5950
  }
5951
+ function validateCompactUsage(usage) {
5952
+ const scanned = usageInteger(usage, "scanned_entries");
5953
+ if (usageInteger(usage, "hydrated_records") > scanned || usageInteger(usage, "duplicate_entries") > scanned || usageInteger(usage, "range_pages") > scanned + usageInteger(usage, "range_seeks") || usageInteger(usage, "residual_checks") > scanned * 12n) {
5954
+ throw new FerricStoreError("compact FLOW.QUERY usage counters are inconsistent");
5955
+ }
5956
+ }
5957
+ function validateCompactRecordUsage(usage, count) {
5958
+ const resultRecords = usageInteger(usage, "result_records");
5959
+ if (resultRecords !== BigInt(count) || resultRecords > usageInteger(usage, "scanned_entries")) {
5960
+ throw new FerricStoreError("compact FLOW.QUERY record usage is inconsistent");
5961
+ }
5962
+ }
5963
+ function usageInteger(usage, field3) {
5964
+ return BigInt(usage[field3]);
5965
+ }
5891
5966
  function readRecord(data, offset, budget) {
5892
5967
  requireAvailable(data, offset, 4);
5893
5968
  const bitmap = data.readUInt32BE(offset);
@@ -6780,15 +6855,15 @@ function snapshotOwnStringArray(values, name) {
6780
6855
 
6781
6856
  // src/client-native-options.ts
6782
6857
  function snapshotNativeClientOptions(options) {
6783
- const own = { ...options };
6858
+ const own2 = { ...options };
6784
6859
  const snapshot = {
6785
- ...own,
6786
- ...typeof own.autoReconnect === "object" && own.autoReconnect != null ? { autoReconnect: Object.freeze({ ...own.autoReconnect }) } : {},
6787
- ...own.endpointPolicy == null ? {} : { endpointPolicy: snapshotEndpointPolicy(own.endpointPolicy) },
6788
- ...own.events == null ? {} : { events: snapshotOwnStringArray(own.events, "events") },
6789
- ...own.seeds == null ? {} : { seeds: snapshotOwnStringArray(own.seeds, "seeds") },
6790
- ...own.tlsOptions == null ? {} : { tlsOptions: snapshotTlsOptions(own.tlsOptions) },
6791
- ...own.trustedHosts == null ? {} : { trustedHosts: snapshotOwnStringArray(own.trustedHosts, "trustedHosts") }
6860
+ ...own2,
6861
+ ...typeof own2.autoReconnect === "object" && own2.autoReconnect != null ? { autoReconnect: Object.freeze({ ...own2.autoReconnect }) } : {},
6862
+ ...own2.endpointPolicy == null ? {} : { endpointPolicy: snapshotEndpointPolicy(own2.endpointPolicy) },
6863
+ ...own2.events == null ? {} : { events: snapshotOwnStringArray(own2.events, "events") },
6864
+ ...own2.seeds == null ? {} : { seeds: snapshotOwnStringArray(own2.seeds, "seeds") },
6865
+ ...own2.tlsOptions == null ? {} : { tlsOptions: snapshotTlsOptions(own2.tlsOptions) },
6866
+ ...own2.trustedHosts == null ? {} : { trustedHosts: snapshotOwnStringArray(own2.trustedHosts, "trustedHosts") }
6792
6867
  };
6793
6868
  return Object.freeze(snapshot);
6794
6869
  }
@@ -6829,12 +6904,12 @@ function topologyNativeOptions(options) {
6829
6904
  }
6830
6905
  function snapshotEndpointPolicy(policy) {
6831
6906
  if (typeof policy !== "object") return policy;
6832
- const own = { ...policy };
6833
- if (!Object.hasOwn(own, "allowHosts") || !Array.isArray(own.allowHosts)) {
6907
+ const own2 = { ...policy };
6908
+ if (!Object.hasOwn(own2, "allowHosts") || !Array.isArray(own2.allowHosts)) {
6834
6909
  throw new TypeError("endpointPolicy.allowHosts must be an own array of strings");
6835
6910
  }
6836
6911
  return Object.freeze({
6837
- allowHosts: snapshotOwnStringArray(own.allowHosts, "endpointPolicy.allowHosts")
6912
+ allowHosts: snapshotOwnStringArray(own2.allowHosts, "endpointPolicy.allowHosts")
6838
6913
  });
6839
6914
  }
6840
6915
  function snapshotTlsOptions(options) {
@@ -7913,6 +7988,63 @@ function nativeStartupPayload(clientName, events) {
7913
7988
  };
7914
7989
  }
7915
7990
 
7991
+ // src/flow-query-deadline.ts
7992
+ import { Buffer as Buffer31 } from "buffer";
7993
+ function withFlowQueryDeadline(command, timeoutMs, nowMs2) {
7994
+ if (timeoutMs == null) return command;
7995
+ if (command.opcode === OPCODES.pipeline) {
7996
+ return withPipelineQueryDeadlines(command, timeoutMs, nowMs2);
7997
+ }
7998
+ if (command.opcode !== OPCODES.flowQuery) return command;
7999
+ const payload = command.payload;
8000
+ if (payload instanceof Map) {
8001
+ const nextPayload = new Map(payload);
8002
+ nextPayload.set("deadline_ms", flowQueryDeadlineMs(timeoutMs, nowMs2));
8003
+ return { ...command, payload: nextPayload };
8004
+ }
8005
+ if (typeof payload !== "object" || payload == null || Array.isArray(payload) || Buffer31.isBuffer(payload) || payload instanceof Uint8Array) {
8006
+ return command;
8007
+ }
8008
+ return {
8009
+ ...command,
8010
+ payload: {
8011
+ ...payload,
8012
+ deadline_ms: flowQueryDeadlineMs(timeoutMs, nowMs2)
8013
+ }
8014
+ };
8015
+ }
8016
+ function withPipelineQueryDeadlines(command, timeoutMs, nowMs2) {
8017
+ const payload = plainRecord(command.payload);
8018
+ if (payload == null) return command;
8019
+ const rawCommands = payload.commands;
8020
+ if (!Array.isArray(rawCommands)) return command;
8021
+ let commands;
8022
+ let deadlineMs;
8023
+ for (let index = 0; index < rawCommands.length; index += 1) {
8024
+ const rawItem = rawCommands[index];
8025
+ const item = plainRecord(rawItem);
8026
+ if (item?.opcode !== OPCODES.flowQuery) continue;
8027
+ const body = plainRecord(item.body);
8028
+ if (body == null) continue;
8029
+ commands ??= rawCommands.slice();
8030
+ deadlineMs ??= flowQueryDeadlineMs(timeoutMs, nowMs2);
8031
+ commands[index] = { ...item, body: { ...body, deadline_ms: deadlineMs } };
8032
+ }
8033
+ return commands == null ? command : { ...command, payload: { ...payload, commands } };
8034
+ }
8035
+ function flowQueryDeadlineMs(timeoutMs, nowMs2) {
8036
+ return Math.min(
8037
+ Number.MAX_SAFE_INTEGER,
8038
+ (nowMs2 ?? Date.now()) + Math.max(1, Math.ceil(timeoutMs))
8039
+ );
8040
+ }
8041
+ function plainRecord(value) {
8042
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer31.isBuffer(value) || value instanceof Uint8Array) {
8043
+ return void 0;
8044
+ }
8045
+ return value;
8046
+ }
8047
+
7916
8048
  // src/native-adapter.ts
7917
8049
  var NativeAdapter = class _NativeAdapter {
7918
8050
  socket;
@@ -8118,7 +8250,11 @@ var NativeAdapter = class _NativeAdapter {
8118
8250
  let frame;
8119
8251
  try {
8120
8252
  requestId = this.requestScheduler.nextRequestId();
8121
- frame = encodeRequest(command, requestId, this.capabilities.requestFrameBytes);
8253
+ frame = encodeRequest(
8254
+ withFlowQueryDeadline(command, timeoutMs),
8255
+ requestId,
8256
+ this.capabilities.requestFrameBytes
8257
+ );
8122
8258
  } catch (error) {
8123
8259
  if (hasFlowControlCredit) this.flowControl.release(laneId);
8124
8260
  return Promise.reject(error instanceof Error ? error : new FerricStoreError(String(error), { raw: error }));
@@ -8266,7 +8402,7 @@ function executeCommandArgs(executor, args) {
8266
8402
  import "buffer";
8267
8403
 
8268
8404
  // src/command-retry-policy.ts
8269
- import { Buffer as Buffer32 } from "buffer";
8405
+ import { Buffer as Buffer33 } from "buffer";
8270
8406
  function isCasMutation(args) {
8271
8407
  const offset = commandName2(args[0]) === "COMMAND_EXEC" ? 1 : 0;
8272
8408
  const name = commandName2(args[offset]);
@@ -8280,8 +8416,8 @@ function isCasMutation(args) {
8280
8416
  }
8281
8417
  function commandName2(value) {
8282
8418
  if (typeof value === "string") return value.toUpperCase();
8283
- if (Buffer32.isBuffer(value) || value instanceof Uint8Array) {
8284
- return Buffer32.from(value).toString("utf8").toUpperCase();
8419
+ if (Buffer33.isBuffer(value) || value instanceof Uint8Array) {
8420
+ return Buffer33.from(value).toString("utf8").toUpperCase();
8285
8421
  }
8286
8422
  return void 0;
8287
8423
  }
@@ -8496,12 +8632,12 @@ function normalizeNonNegativeInteger2(value, fallback) {
8496
8632
  import "buffer";
8497
8633
 
8498
8634
  // src/topology-routing.ts
8499
- import { Buffer as Buffer35 } from "buffer";
8635
+ import { Buffer as Buffer36 } from "buffer";
8500
8636
 
8501
8637
  // src/flow-partition-route-cache.ts
8502
- import { Buffer as Buffer34 } from "buffer";
8638
+ import { Buffer as Buffer35 } from "buffer";
8503
8639
  import { createHash } from "crypto";
8504
- var AUTO_PREFIX = Buffer34.from("__flow_auto__:", "ascii");
8640
+ var AUTO_PREFIX = Buffer35.from("__flow_auto__:", "ascii");
8505
8641
  var MAX_CACHEABLE_PARTITION_BYTES = 4 * 1024;
8506
8642
  var MAX_CACHE_BYTES = 1 * 1024 * 1024;
8507
8643
  var MAX_CACHE_ENTRIES = 1024;
@@ -8511,10 +8647,10 @@ var cacheBytes = 0;
8511
8647
  var cacheHits = 0;
8512
8648
  var cacheMisses = 0;
8513
8649
  function flowLogicalPartitionRoutingKey(value) {
8514
- if (typeof value !== "string" && !Buffer34.isBuffer(value)) return void 0;
8650
+ if (typeof value !== "string" && !Buffer35.isBuffer(value)) return void 0;
8515
8651
  const autoBucket = flowAutoBucket(value);
8516
8652
  if (autoBucket != null) return `{fa:${autoBucket}}`;
8517
- const bytes2 = Buffer34.isBuffer(value) ? value : Buffer34.from(value);
8653
+ const bytes2 = Buffer35.isBuffer(value) ? value : Buffer35.from(value);
8518
8654
  if (bytes2.byteLength > MAX_CACHEABLE_PARTITION_BYTES) return hashRoute(bytes2);
8519
8655
  const cacheKey = bytes2.toString("base64");
8520
8656
  const cached = routeCache.get(cacheKey);
@@ -8588,7 +8724,7 @@ function routingKeyFromProtocolPayload(name, command) {
8588
8724
  "scope"
8589
8725
  ]) {
8590
8726
  const value = getField(command.payload, field3);
8591
- if (typeof value === "string" || Buffer35.isBuffer(value)) {
8727
+ if (typeof value === "string" || Buffer36.isBuffer(value)) {
8592
8728
  return value;
8593
8729
  }
8594
8730
  }
@@ -8636,7 +8772,7 @@ function flowRoutingData(name, args) {
8636
8772
  if (typeof partition === "string" && partition.toUpperCase() !== "AUTO" && partition.toUpperCase() !== "MIXED") {
8637
8773
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
8638
8774
  }
8639
- if (Buffer35.isBuffer(partition)) {
8775
+ if (Buffer36.isBuffer(partition)) {
8640
8776
  const text3 = partition.toString("utf8").toUpperCase();
8641
8777
  if (text3 !== "AUTO" && text3 !== "MIXED") {
8642
8778
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
@@ -8727,17 +8863,17 @@ function flowPartitionRoutingKeyFromCommand(command, claim) {
8727
8863
  return { handled: false };
8728
8864
  }
8729
8865
  function isRoutingKey(value) {
8730
- return typeof value === "string" || Buffer35.isBuffer(value);
8866
+ return typeof value === "string" || Buffer36.isBuffer(value);
8731
8867
  }
8732
8868
  function flowAutoIdRoutingKey(value) {
8733
- if (typeof value !== "string" && !Buffer35.isBuffer(value)) {
8869
+ if (typeof value !== "string" && !Buffer36.isBuffer(value)) {
8734
8870
  return void 0;
8735
8871
  }
8736
- const bucket = (Buffer35.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8872
+ const bucket = (Buffer36.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8737
8873
  return `{fa:${bucket}}`;
8738
8874
  }
8739
8875
  function flowClaimLogicalPartitionRoutingKey(value) {
8740
- if (typeof value !== "string" && !Buffer35.isBuffer(value)) return void 0;
8876
+ if (typeof value !== "string" && !Buffer36.isBuffer(value)) return void 0;
8741
8877
  const selector = commandPart(value);
8742
8878
  if (selector === "AUTO" || selector === "ANY") return void 0;
8743
8879
  if (selector === "GLOBAL") return "{f}";
@@ -8752,7 +8888,7 @@ function singleShardFlowClaimPartitionKey(values) {
8752
8888
  return keys.some((key) => key == null) ? void 0 : singleShardKey(keys);
8753
8889
  }
8754
8890
  function singleShardKey(keys) {
8755
- if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !Buffer35.isBuffer(key))) {
8891
+ if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !Buffer36.isBuffer(key))) {
8756
8892
  return void 0;
8757
8893
  }
8758
8894
  const usable = keys;
@@ -8785,7 +8921,7 @@ function routedKeyGroups(keys, routeKey) {
8785
8921
  const groups = /* @__PURE__ */ new Map();
8786
8922
  for (let index = 0; index < keys.length; index += 1) {
8787
8923
  const key = keys[index];
8788
- if (typeof key !== "string" && !Buffer35.isBuffer(key)) return void 0;
8924
+ if (typeof key !== "string" && !Buffer36.isBuffer(key)) return void 0;
8789
8925
  const route = routeKey(key);
8790
8926
  const groupKey = `${route.endpointKey}\0${route.laneId}`;
8791
8927
  const group = groups.get(groupKey);
@@ -9874,7 +10010,7 @@ var TopologyNativeAdapterPool = class _TopologyNativeAdapterPool {
9874
10010
  };
9875
10011
 
9876
10012
  // src/response-map-preservation.ts
9877
- import { Buffer as Buffer37 } from "buffer";
10013
+ import { Buffer as Buffer38 } from "buffer";
9878
10014
  function toStringKeyMapPreservingValues(value) {
9879
10015
  if (value == null) return void 0;
9880
10016
  const result = {};
@@ -9884,7 +10020,7 @@ function toStringKeyMapPreservingValues(value) {
9884
10020
  }
9885
10021
  return result;
9886
10022
  }
9887
- if (typeof value === "object" && !Array.isArray(value) && !Buffer37.isBuffer(value) && !(value instanceof Uint8Array)) {
10023
+ if (typeof value === "object" && !Array.isArray(value) && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
9888
10024
  for (const [key, item] of Object.entries(value)) {
9889
10025
  setOwnValue(result, text(key), normalizeMapStructurePreservingBytes(item));
9890
10026
  }
@@ -9893,7 +10029,7 @@ function toStringKeyMapPreservingValues(value) {
9893
10029
  return void 0;
9894
10030
  }
9895
10031
  function normalizeMapStructurePreservingBytes(value) {
9896
- if (Buffer37.isBuffer(value) || value instanceof Uint8Array) return value;
10032
+ if (Buffer38.isBuffer(value) || value instanceof Uint8Array) return value;
9897
10033
  if (value instanceof Map) {
9898
10034
  const result = {};
9899
10035
  for (const [key, item] of value.entries()) {
@@ -9915,7 +10051,7 @@ function normalizeMapStructurePreservingBytes(value) {
9915
10051
  }
9916
10052
 
9917
10053
  // src/native-kv-responses.ts
9918
- import { Buffer as Buffer38 } from "buffer";
10054
+ import { Buffer as Buffer39 } from "buffer";
9919
10055
  function rateLimitResultFromResp(value) {
9920
10056
  if (!Array.isArray(value) || value.length !== 4) {
9921
10057
  throw new TypeError("RATELIMIT.ADD returned an unexpected response");
@@ -9972,13 +10108,13 @@ function fetchOrComputeResultFromResp(value, codec) {
9972
10108
  }
9973
10109
  function decodePayload(codec, value) {
9974
10110
  if (value == null) return null;
9975
- if (Buffer38.isBuffer(value)) return codec.decode(value);
9976
- if (value instanceof Uint8Array) return codec.decode(Buffer38.from(value));
9977
- if (typeof value === "string") return codec.decode(Buffer38.from(value));
10111
+ if (Buffer39.isBuffer(value)) return codec.decode(value);
10112
+ if (value instanceof Uint8Array) return codec.decode(Buffer39.from(value));
10113
+ if (typeof value === "string") return codec.decode(Buffer39.from(value));
9978
10114
  return normalizeRefMeta(value);
9979
10115
  }
9980
10116
  function requiredResponseString(value, context) {
9981
- if (typeof value !== "string" && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
10117
+ if (typeof value !== "string" && !Buffer39.isBuffer(value) && !(value instanceof Uint8Array)) {
9982
10118
  throw new TypeError(`${context} returned an unexpected response`);
9983
10119
  }
9984
10120
  const result = text(value);
@@ -9995,7 +10131,7 @@ function requiredNonNegativeInteger(value, context) {
9995
10131
  return result;
9996
10132
  }
9997
10133
  function responseBytes(value, context) {
9998
- if (typeof value !== "string" && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
10134
+ if (typeof value !== "string" && !Buffer39.isBuffer(value) && !(value instanceof Uint8Array)) {
9999
10135
  throw new TypeError(`${context} returned an unexpected response`);
10000
10136
  }
10001
10137
  return bytes(value);
@@ -10678,7 +10814,7 @@ function groupAutoPartitionItems(items) {
10678
10814
  }
10679
10815
 
10680
10816
  // src/client-values.ts
10681
- import { Buffer as Buffer39 } from "buffer";
10817
+ import { Buffer as Buffer40 } from "buffer";
10682
10818
  async function valueMGetEntries(client, refs, options = {}) {
10683
10819
  const refCount = refs.length;
10684
10820
  if (refCount === 0) {
@@ -10713,12 +10849,12 @@ async function valueMGetEntries(client, refs, options = {}) {
10713
10849
  const item = response[index];
10714
10850
  if (item == null) {
10715
10851
  entries[index] = { found: false };
10716
- } else if (Buffer39.isBuffer(item)) {
10852
+ } else if (Buffer40.isBuffer(item)) {
10717
10853
  entries[index] = { found: true, value: client.codec.decode(item) };
10718
10854
  } else if (item instanceof Uint8Array) {
10719
- entries[index] = { found: true, value: client.codec.decode(Buffer39.from(item)) };
10855
+ entries[index] = { found: true, value: client.codec.decode(Buffer40.from(item)) };
10720
10856
  } else if (typeof item === "string") {
10721
- entries[index] = { found: true, value: client.codec.decode(Buffer39.from(item)) };
10857
+ entries[index] = { found: true, value: client.codec.decode(Buffer40.from(item)) };
10722
10858
  } else {
10723
10859
  entries[index] = { found: true, value: item };
10724
10860
  }
@@ -10730,7 +10866,7 @@ async function valueMGetEntries(client, refs, options = {}) {
10730
10866
  import "buffer";
10731
10867
 
10732
10868
  // src/auto-batch-ordering.ts
10733
- import { Buffer as Buffer40 } from "buffer";
10869
+ import { Buffer as Buffer41 } from "buffer";
10734
10870
  function autoBatchOrderingPlan(batch) {
10735
10871
  const accesses = /* @__PURE__ */ new Map();
10736
10872
  const fallbackDependencies = [];
@@ -10845,13 +10981,13 @@ function flowManyAutoBatchIds(command, name) {
10845
10981
  return fixedFlowItemIds(
10846
10982
  command,
10847
10983
  mixed ? 4 : 3,
10848
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && isFencingToken(command[itemIndex + (mixed ? 2 : 1)]) && Buffer40.isBuffer(command[itemIndex + (mixed ? 3 : 2)])
10984
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && isFencingToken(command[itemIndex + (mixed ? 2 : 1)]) && Buffer41.isBuffer(command[itemIndex + (mixed ? 3 : 2)])
10849
10985
  );
10850
10986
  }
10851
10987
  return fixedFlowItemIds(
10852
10988
  command,
10853
10989
  mixed ? 4 : 3,
10854
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer40.isBuffer(command[itemIndex + (mixed ? 2 : 1)]) && isFencingToken(command[itemIndex + (mixed ? 3 : 2)])
10990
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer41.isBuffer(command[itemIndex + (mixed ? 2 : 1)]) && isFencingToken(command[itemIndex + (mixed ? 3 : 2)])
10855
10991
  );
10856
10992
  }
10857
10993
  function createManyAutoBatchIds(command) {
@@ -10864,7 +11000,7 @@ function createManyAutoBatchIds(command) {
10864
11000
  const ids = fixedFlowItemIds(
10865
11001
  command,
10866
11002
  width,
10867
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer40.isBuffer(command[itemIndex + width - 1]),
11003
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer41.isBuffer(command[itemIndex + width - 1]),
10868
11004
  markerIndex
10869
11005
  );
10870
11006
  if (ids != null) return ids;
@@ -10882,7 +11018,7 @@ function extendedCreateManyAutoBatchIds(command, markerIndex) {
10882
11018
  let cursor = markerIndex + 2;
10883
11019
  for (let itemIndex = 0; itemIndex < itemCount; itemIndex += 1) {
10884
11020
  const id = command[cursor];
10885
- if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !Buffer40.isBuffer(command[cursor + 2])) return void 0;
11021
+ if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !Buffer41.isBuffer(command[cursor + 2])) return void 0;
10886
11022
  ids.push(id);
10887
11023
  cursor += 3;
10888
11024
  const afterValues = skipExtendedNamedItems(command, cursor, true);
@@ -10901,7 +11037,7 @@ function skipExtendedNamedItems(command, countIndex, encodedValues) {
10901
11037
  for (let index = 0; index < count; index += 1) {
10902
11038
  const name = command[firstItem + index * 2];
10903
11039
  const value = command[firstItem + index * 2 + 1];
10904
- if (!isAutoBatchResourceValue(name) || (encodedValues ? !Buffer40.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
11040
+ if (!isAutoBatchResourceValue(name) || (encodedValues ? !Buffer41.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
10905
11041
  }
10906
11042
  return firstItem + count * 2;
10907
11043
  }
@@ -10916,7 +11052,7 @@ function runStepsManyAutoBatchIds(command) {
10916
11052
  ids.push(item);
10917
11053
  continue;
10918
11054
  }
10919
- if (typeof item !== "object" || item == null || Array.isArray(item) || Buffer40.isBuffer(item)) return void 0;
11055
+ if (typeof item !== "object" || item == null || Array.isArray(item) || Buffer41.isBuffer(item)) return void 0;
10920
11056
  const id = item.id;
10921
11057
  if (!isAutoBatchResourceValue(id)) return void 0;
10922
11058
  ids.push(id);
@@ -10957,7 +11093,7 @@ function flowValuePutOwner(command, start) {
10957
11093
  }
10958
11094
  var flowValuePutOptionTokens = /* @__PURE__ */ new Set(["NAME", "NOW", "OVERRIDE", "OWNER_FLOW_ID", "PARTITION", "TTL", "TTL_MS"]);
10959
11095
  function isAutoBatchResourceValue(value) {
10960
- return typeof value === "string" || Buffer40.isBuffer(value);
11096
+ return typeof value === "string" || Buffer41.isBuffer(value);
10961
11097
  }
10962
11098
  function isFencingToken(value) {
10963
11099
  return typeof value === "number" && Number.isSafeInteger(value) || typeof value === "bigint";
@@ -10966,7 +11102,7 @@ function nonNegativeItemCount(value) {
10966
11102
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
10967
11103
  }
10968
11104
  function autoBatchResourceKey(namespace, value) {
10969
- return `${namespace}:${Buffer40.from(value).toString("base64")}`;
11105
+ return `${namespace}:${Buffer41.from(value).toString("base64")}`;
10970
11106
  }
10971
11107
  function autoBatchCommandName(command) {
10972
11108
  return commandView(command).name ?? null;
@@ -11378,10 +11514,10 @@ function claimFailure(error) {
11378
11514
  import "buffer";
11379
11515
 
11380
11516
  // src/flow-query-builder.ts
11381
- import { Buffer as Buffer43 } from "buffer";
11517
+ import { Buffer as Buffer44 } from "buffer";
11382
11518
 
11383
11519
  // src/flow-query-metadata.ts
11384
- import { Buffer as Buffer42 } from "buffer";
11520
+ import { Buffer as Buffer43 } from "buffer";
11385
11521
  var MAX_FLOW_QUERY_METADATA_KEY_BYTES = 64;
11386
11522
  function normalizeStateMeta(value, state) {
11387
11523
  if (value == null) return {};
@@ -11406,7 +11542,7 @@ function normalizeStateMeta(value, state) {
11406
11542
  function metadataEntries(value, context) {
11407
11543
  const entries = objectEntries(value ?? {}, context).map(([rawName, item]) => {
11408
11544
  const name = rawName.trim();
11409
- const size = Buffer42.byteLength(name, "utf8");
11545
+ const size = Buffer43.byteLength(name, "utf8");
11410
11546
  if (size === 0 || size > MAX_FLOW_QUERY_METADATA_KEY_BYTES || name.startsWith("__")) {
11411
11547
  throw new TypeError(`${context} key is invalid or reserved`);
11412
11548
  }
@@ -11430,7 +11566,7 @@ function objectEntries(value, context) {
11430
11566
  return keys.map((key) => [key, value[key]]);
11431
11567
  }
11432
11568
  function isPlainRecord(value) {
11433
- if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer42.isBuffer(value)) {
11569
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer43.isBuffer(value)) {
11434
11570
  return false;
11435
11571
  }
11436
11572
  const prototype = Object.getPrototypeOf(value);
@@ -11500,7 +11636,7 @@ var FlowCollectionQuery = class {
11500
11636
  const states = /* @__PURE__ */ new Set();
11501
11637
  for (const [rawState, metadata] of objectEntries(values, "stateMeta")) {
11502
11638
  const state = requiredText(rawState, "stateMeta state").trim();
11503
- const stateBytes = Buffer43.byteLength(state, "utf8");
11639
+ const stateBytes = Buffer44.byteLength(state, "utf8");
11504
11640
  if (stateBytes === 0 || stateBytes > MAX_FLOW_QUERY_STATE_BYTES) {
11505
11641
  throw new TypeError(
11506
11642
  `stateMeta state names must be 1..${MAX_FLOW_QUERY_STATE_BYTES} bytes`
@@ -11679,7 +11815,7 @@ function requiredPartition(value) {
11679
11815
  "FLOW.QUERY convenience methods require a partition key"
11680
11816
  );
11681
11817
  }
11682
- const size = Buffer43.byteLength(value, "utf8");
11818
+ const size = Buffer44.byteLength(value, "utf8");
11683
11819
  if (size === 0 || size > MAX_FLOW_QUERY_PARTITION_BYTES) {
11684
11820
  throw new TypeError(
11685
11821
  `FLOW.QUERY partition key must be 1..${MAX_FLOW_QUERY_PARTITION_BYTES} bytes`
@@ -11727,20 +11863,23 @@ function requiredText(value, context) {
11727
11863
  return value;
11728
11864
  }
11729
11865
  function queryParameter(value, context) {
11730
- if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || Buffer43.isBuffer(value)) {
11866
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || Buffer44.isBuffer(value)) {
11731
11867
  return value;
11732
11868
  }
11733
11869
  throw new TypeError(`${context} must be a scalar FLOW.QUERY parameter`);
11734
11870
  }
11735
11871
 
11736
11872
  // src/flow-query-response.ts
11737
- import { Buffer as Buffer45 } from "buffer";
11873
+ import { Buffer as Buffer48 } from "buffer";
11874
+
11875
+ // src/flow-query-diagnostic-response.ts
11876
+ import { Buffer as Buffer46 } from "buffer";
11738
11877
 
11739
11878
  // src/flow-query-response-validation.ts
11740
- import { Buffer as Buffer44 } from "buffer";
11879
+ import { Buffer as Buffer45 } from "buffer";
11741
11880
  function requiredMap2(value, context) {
11742
11881
  if (value instanceof Map) return value;
11743
- if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer44.isBuffer(value) || value instanceof Uint8Array) {
11882
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11744
11883
  throw decodeError(`${context} must be a map`, value);
11745
11884
  }
11746
11885
  return value;
@@ -11797,7 +11936,7 @@ function normalizeMetadataValue(value, context, budget, ancestors, depth) {
11797
11936
  if (!value.isWellFormed()) throw decodeError(`${context} contains invalid text`, value);
11798
11937
  return value;
11799
11938
  }
11800
- if (Buffer44.isBuffer(value) || value instanceof Uint8Array) {
11939
+ if (Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11801
11940
  const decoded = strictText2(value);
11802
11941
  if (decoded == null) throw decodeError(`${context} contains invalid UTF-8`, value);
11803
11942
  return decoded;
@@ -11850,36 +11989,36 @@ function isPlainObject6(value) {
11850
11989
  const prototype = Object.getPrototypeOf(value);
11851
11990
  return prototype === Object.prototype || prototype === null;
11852
11991
  }
11853
- function requireContract(mapping, name, expected, context) {
11854
- const actual = requiredText2(mapping, name, context);
11992
+ function requireContract(mapping2, name, expected, context) {
11993
+ const actual = requiredText2(mapping2, name, context);
11855
11994
  if (actual !== expected) {
11856
11995
  throw decodeError(
11857
11996
  `${context} has unsupported contract ${JSON.stringify(actual)}`,
11858
- mapping
11997
+ mapping2
11859
11998
  );
11860
11999
  }
11861
12000
  }
11862
- function requiredText2(mapping, name, context) {
11863
- const value = strictText2(field(mapping, name));
12001
+ function requiredText2(mapping2, name, context) {
12002
+ const value = strictText2(field(mapping2, name));
11864
12003
  if (value == null || value.length === 0) {
11865
- throw decodeError(`${context} ${name} must be non-empty text`, mapping);
12004
+ throw decodeError(`${context} ${name} must be non-empty text`, mapping2);
11866
12005
  }
11867
12006
  return value;
11868
12007
  }
11869
- function optionalText2(mapping, name, context) {
11870
- const raw = field(mapping, name);
12008
+ function optionalText2(mapping2, name, context) {
12009
+ const raw = field(mapping2, name);
11871
12010
  if (raw == null) return void 0;
11872
12011
  const value = strictText2(raw);
11873
12012
  if (value == null)
11874
- throw decodeError(`${context} ${name} must be text`, mapping);
12013
+ throw decodeError(`${context} ${name} must be text`, mapping2);
11875
12014
  return value;
11876
12015
  }
11877
- function requiredBoundedText2(mapping, name, context, maximumBytes) {
11878
- const value = requiredText2(mapping, name, context);
11879
- if (Buffer44.byteLength(value, "utf8") > maximumBytes) {
12016
+ function requiredBoundedText2(mapping2, name, context, maximumBytes) {
12017
+ const value = requiredText2(mapping2, name, context);
12018
+ if (Buffer45.byteLength(value, "utf8") > maximumBytes) {
11880
12019
  throw decodeError(
11881
12020
  `${context} ${name} exceeds ${maximumBytes} bytes`,
11882
- mapping
12021
+ mapping2
11883
12022
  );
11884
12023
  }
11885
12024
  return value;
@@ -11889,15 +12028,15 @@ function boundedText(value, context, maximumBytes) {
11889
12028
  if (decoded == null || decoded.length === 0) {
11890
12029
  throw decodeError(`${context} must be non-empty text`, value);
11891
12030
  }
11892
- if (Buffer44.byteLength(decoded, "utf8") > maximumBytes) {
12031
+ if (Buffer45.byteLength(decoded, "utf8") > maximumBytes) {
11893
12032
  throw decodeError(`${context} exceeds ${maximumBytes} bytes`, value);
11894
12033
  }
11895
12034
  return decoded;
11896
12035
  }
11897
- function requiredBoolean(mapping, name, context) {
11898
- const value = field(mapping, name);
12036
+ function requiredBoolean(mapping2, name, context) {
12037
+ const value = field(mapping2, name);
11899
12038
  if (typeof value !== "boolean") {
11900
- throw decodeError(`${context} ${name} must be boolean`, mapping);
12039
+ throw decodeError(`${context} ${name} must be boolean`, mapping2);
11901
12040
  }
11902
12041
  return value;
11903
12042
  }
@@ -11940,13 +12079,13 @@ function positiveBoundedInteger(value, maximum, context) {
11940
12079
  if (parsed === 0) throw decodeError(`${context} must be positive`, value);
11941
12080
  return parsed;
11942
12081
  }
11943
- function hasKey(mapping, name) {
11944
- if (!(mapping instanceof Map)) return Object.hasOwn(mapping, name);
11945
- if (mapping.has(name)) return true;
11946
- const binaryName = Buffer44.from(name);
11947
- for (const key of mapping.keys()) {
11948
- if (Buffer44.isBuffer(key) && key.equals(binaryName)) return true;
11949
- if (key instanceof Uint8Array && Buffer44.from(key).equals(binaryName))
12082
+ function hasKey(mapping2, name) {
12083
+ if (!(mapping2 instanceof Map)) return Object.hasOwn(mapping2, name);
12084
+ if (mapping2.has(name)) return true;
12085
+ const binaryName = Buffer45.from(name);
12086
+ for (const key of mapping2.keys()) {
12087
+ if (Buffer45.isBuffer(key) && key.equals(binaryName)) return true;
12088
+ if (key instanceof Uint8Array && Buffer45.from(key).equals(binaryName))
11950
12089
  return true;
11951
12090
  }
11952
12091
  return false;
@@ -11957,7 +12096,7 @@ function decodeError(message, raw) {
11957
12096
  function strictText2(value) {
11958
12097
  if (typeof value === "string")
11959
12098
  return value.isWellFormed() ? value : void 0;
11960
- if (Buffer44.isBuffer(value) || value instanceof Uint8Array) {
12099
+ if (Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11961
12100
  try {
11962
12101
  return new TextDecoder("utf-8", { fatal: true }).decode(value);
11963
12102
  } catch {
@@ -11990,341 +12129,1314 @@ var FlowQueryError = class extends FerricStoreError {
11990
12129
  }
11991
12130
  };
11992
12131
 
11993
- // src/flow-query-response.ts
11994
- var FLOW_QUERY_RESULT_CONTRACT = "ferric.flow.query.result/v1";
11995
- var FLOW_EXPLAIN_CONTRACT = "ferric.flow.explain/v1";
11996
- var FLOW_QUERY_INDEXES_CONTRACT = "ferric.flow.query.indexes/v1";
11997
- var USAGE_FIELDS2 = [
11998
- ["range_seeks", "rangeSeeks"],
11999
- ["range_pages", "rangePages"],
12000
- ["scanned_entries", "scannedEntries"],
12001
- ["scanned_bytes", "scannedBytes"],
12002
- ["hydrated_records", "hydratedRecords"],
12003
- ["residual_checks", "residualChecks"],
12004
- ["duplicate_entries", "duplicateEntries"],
12005
- ["result_records", "resultRecords"],
12006
- ["response_bytes", "responseBytes"],
12007
- ["memory_high_water_bytes", "memoryHighWaterBytes"],
12008
- ["wall_time_us", "wallTimeUs"]
12009
- ];
12010
- var MAX_SIGNED_64 = (1n << 63n) - 1n;
12011
- var MAX_UNSIGNED_64 = (1n << 64n) - 1n;
12012
- function decodeFlowQueryResult(value) {
12013
- const result = decodeFlowQueryResponse(value, (mapping) => freezeMap(mapping));
12014
- if (result.kind === "records") {
12015
- return Object.freeze({
12016
- kind: "records",
12017
- version: FLOW_QUERY_RESULT_CONTRACT,
12018
- records: Object.freeze(result.records),
12019
- page: result.page,
12020
- quality: result.quality,
12021
- usage: result.usage,
12022
- raw: result.raw
12132
+ // src/flow-query-diagnostic-response.ts
12133
+ var MIN_SIGNED_64 = -(1n << 63n);
12134
+ var MAX_SIGNED_642 = (1n << 63n) - 1n;
12135
+ var DIAGNOSTIC_TEXT_BYTES = 1024;
12136
+ var DIAGNOSTIC_CONTEXT_ENTRIES = 16;
12137
+ var DIAGNOSTIC_CONTEXT_LIST_ITEMS = 32;
12138
+ var DIAGNOSTIC_CONTEXT_KEY_BYTES = 128;
12139
+ var DIAGNOSTIC_CONTEXT_DEPTH = 6;
12140
+ var DIAGNOSTIC_CONTEXT_NODES = 512;
12141
+ function tryDecodeFlowQueryError(value, cause) {
12142
+ try {
12143
+ const mapping2 = requiredMap2(value, "FLOW.QUERY diagnostic");
12144
+ const context = decodeDiagnosticContext(field(mapping2, "context"));
12145
+ const position = decodePosition(field(mapping2, "position"));
12146
+ return new FlowQueryError({
12147
+ code: requiredBoundedText2(
12148
+ mapping2,
12149
+ "code",
12150
+ "FLOW.QUERY diagnostic",
12151
+ DIAGNOSTIC_TEXT_BYTES
12152
+ ),
12153
+ message: requiredBoundedText2(
12154
+ mapping2,
12155
+ "message",
12156
+ "FLOW.QUERY diagnostic",
12157
+ DIAGNOSTIC_TEXT_BYTES
12158
+ ),
12159
+ detail: optionalDiagnosticText(mapping2, "detail"),
12160
+ hint: optionalDiagnosticText(mapping2, "hint"),
12161
+ retryable: requiredBoolean(mapping2, "retryable", "FLOW.QUERY diagnostic"),
12162
+ safeToRetry: requiredBoolean(mapping2, "safe_to_retry", "FLOW.QUERY diagnostic"),
12163
+ retryAfterMs: nonNegativeInteger(
12164
+ field(mapping2, "retry_after_ms"),
12165
+ "FLOW.QUERY diagnostic retry_after_ms"
12166
+ ),
12167
+ position,
12168
+ context,
12169
+ raw: value,
12170
+ cause
12023
12171
  });
12172
+ } catch (error) {
12173
+ if (error instanceof FerricStoreError) return void 0;
12174
+ throw error;
12024
12175
  }
12025
- return Object.freeze({
12026
- kind: "count",
12027
- version: FLOW_QUERY_RESULT_CONTRACT,
12028
- count: result.count,
12029
- quality: result.quality,
12030
- usage: result.usage,
12031
- raw: result.raw
12032
- });
12033
12176
  }
12034
- function decodeFlowQueryRecords(value, decodeRecord) {
12035
- const result = decodeFlowQueryResponse(
12036
- value,
12037
- (mapping, index) => decodeRecord(mapping instanceof Map ? freezeMap(mapping) : mapping, index)
12038
- );
12039
- if (result.kind === "count") {
12040
- throw new FerricStoreError("Flow record convenience query returned a count result", {
12041
- raw: result.raw
12042
- });
12177
+ function optionalDiagnosticText(mapping2, name) {
12178
+ const value = optionalText2(mapping2, name, "FLOW.QUERY diagnostic");
12179
+ if (value != null && Buffer46.byteLength(value, "utf8") > DIAGNOSTIC_TEXT_BYTES) {
12180
+ throw decodeError(
12181
+ `FLOW.QUERY diagnostic ${name} exceeds ${DIAGNOSTIC_TEXT_BYTES} bytes`,
12182
+ mapping2
12183
+ );
12043
12184
  }
12044
- return result.records;
12185
+ return value;
12045
12186
  }
12046
- function decodeFlowQueryResponse(value, decodeRecord) {
12047
- const mapping = requiredMap2(value, "FLOW.QUERY result");
12048
- requireContract(mapping, "version", FLOW_QUERY_RESULT_CONTRACT, "FLOW.QUERY result");
12049
- const quality = decodeQuality(field(mapping, "quality"));
12050
- const usage = decodeUsage(field(mapping, "usage"));
12051
- const hasRecords = hasKey(mapping, "records");
12052
- const hasCount = hasKey(mapping, "result");
12053
- if (hasRecords === hasCount) {
12054
- throw decodeError("FLOW.QUERY result must contain exactly one records or count shape", value);
12187
+ function decodeDiagnosticContext(value) {
12188
+ if (value == null) return void 0;
12189
+ const mapping2 = requiredMap2(value, "FLOW.QUERY diagnostic context");
12190
+ const size = mapping2 instanceof Map ? mapping2.size : Object.keys(mapping2).length;
12191
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12192
+ throw decodeError("FLOW.QUERY diagnostic context contains too many entries", value);
12193
+ }
12194
+ validateDiagnosticContextValue(
12195
+ mapping2,
12196
+ DIAGNOSTIC_CONTEXT_DEPTH,
12197
+ { remaining: DIAGNOSTIC_CONTEXT_NODES }
12198
+ );
12199
+ return freezeMetadataMap(mapping2, "FLOW.QUERY diagnostic context");
12200
+ }
12201
+ function validateDiagnosticContextValue(value, depth, budget) {
12202
+ consumeDiagnosticContextNode(value, budget);
12203
+ if (value == null || typeof value === "boolean") return;
12204
+ if (typeof value === "number") {
12205
+ if (Number.isSafeInteger(value)) return;
12206
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12055
12207
  }
12056
- const raw = freezeMap(mapping);
12057
- if (hasRecords) {
12058
- const rawRecords = field(mapping, "records");
12059
- if (!Array.isArray(rawRecords) || rawRecords.length > 100) {
12060
- throw decodeError("FLOW.QUERY records must be an array of at most 100 maps", value);
12208
+ if (typeof value === "bigint") {
12209
+ if (value >= MIN_SIGNED_64 && value <= MAX_SIGNED_642) return;
12210
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12211
+ }
12212
+ const text3 = diagnosticContextText(value);
12213
+ if (text3 != null) {
12214
+ if (Buffer46.byteLength(text3, "utf8") <= DIAGNOSTIC_TEXT_BYTES) return;
12215
+ throw decodeError("FLOW.QUERY diagnostic context contains oversized text", value);
12216
+ }
12217
+ if (depth <= 0) {
12218
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its maximum depth", value);
12219
+ }
12220
+ if (Array.isArray(value)) {
12221
+ if (value.length > DIAGNOSTIC_CONTEXT_LIST_ITEMS) {
12222
+ throw decodeError("FLOW.QUERY diagnostic context list is too large", value);
12061
12223
  }
12062
- const records = new Array(rawRecords.length);
12063
- for (let index = 0; index < rawRecords.length; index += 1) {
12064
- if (!Object.hasOwn(rawRecords, index)) {
12065
- throw decodeError("FLOW.QUERY records must be a dense array", value);
12224
+ for (let index = 0; index < value.length; index += 1) {
12225
+ if (!Object.hasOwn(value, index)) {
12226
+ throw decodeError("FLOW.QUERY diagnostic context list must be dense", value);
12066
12227
  }
12067
- records[index] = decodeRecord(
12068
- requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
12069
- index
12070
- );
12228
+ validateDiagnosticContextValue(value[index], depth - 1, budget);
12071
12229
  }
12072
- if (usage.resultRecords !== records.length) {
12073
- throw decodeError("FLOW.QUERY usage result_records does not match records", value);
12230
+ return;
12231
+ }
12232
+ if (value instanceof Map || isDiagnosticContextRecord(value)) {
12233
+ const entries = value instanceof Map ? value.entries() : Object.entries(value);
12234
+ const size = value instanceof Map ? value.size : Object.keys(value).length;
12235
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12236
+ throw decodeError("FLOW.QUERY diagnostic context map is too large", value);
12074
12237
  }
12075
- return {
12076
- kind: "records",
12077
- records,
12078
- page: decodePage(field(mapping, "page")),
12079
- quality,
12080
- usage,
12081
- raw
12082
- };
12238
+ for (const [rawKey, item] of entries) {
12239
+ const key = diagnosticContextText(rawKey);
12240
+ if (key == null || key.length === 0 || Buffer46.byteLength(key, "utf8") > DIAGNOSTIC_CONTEXT_KEY_BYTES) {
12241
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid key", value);
12242
+ }
12243
+ validateDiagnosticContextValue(item, depth - 1, budget);
12244
+ }
12245
+ return;
12083
12246
  }
12084
- if (hasKey(mapping, "page")) {
12085
- throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
12247
+ throw decodeError("FLOW.QUERY diagnostic context contains an unsupported value", value);
12248
+ }
12249
+ function consumeDiagnosticContextNode(value, budget) {
12250
+ if (budget.remaining <= 0) {
12251
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its node limit", value);
12086
12252
  }
12087
- const countResult = requiredMap2(field(mapping, "result"), "FLOW.QUERY count result");
12088
- if (requiredText2(countResult, "kind", "FLOW.QUERY count result") !== "count") {
12089
- throw decodeError("FLOW.QUERY count result kind must be count", value);
12253
+ budget.remaining -= 1;
12254
+ }
12255
+ function diagnosticContextText(value) {
12256
+ if (typeof value === "string") return value.isWellFormed() ? value : void 0;
12257
+ if (!Buffer46.isBuffer(value) && !(value instanceof Uint8Array)) return void 0;
12258
+ try {
12259
+ return new TextDecoder("utf-8", { fatal: true }).decode(value);
12260
+ } catch {
12261
+ return void 0;
12090
12262
  }
12091
- const count = boundedInteger(
12092
- field(countResult, "value"),
12093
- MAX_SIGNED_64,
12094
- "FLOW.QUERY count value"
12263
+ }
12264
+ function isDiagnosticContextRecord(value) {
12265
+ if (typeof value !== "object" || value == null) return false;
12266
+ const prototype = Object.getPrototypeOf(value);
12267
+ return prototype === Object.prototype || prototype === null;
12268
+ }
12269
+ function decodePosition(value) {
12270
+ if (value == null) return void 0;
12271
+ const mapping2 = requiredMap2(value, "FLOW.QUERY diagnostic position");
12272
+ return Object.freeze({
12273
+ byte: positiveInteger(field(mapping2, "byte"), "FLOW.QUERY diagnostic position byte"),
12274
+ line: positiveInteger(field(mapping2, "line"), "FLOW.QUERY diagnostic position line"),
12275
+ column: positiveInteger(
12276
+ field(mapping2, "column"),
12277
+ "FLOW.QUERY diagnostic position column"
12278
+ )
12279
+ });
12280
+ }
12281
+
12282
+ // src/flow-query-index-contract.ts
12283
+ import { Buffer as Buffer47 } from "buffer";
12284
+ var FLOW_QUERY_BUILD_PHASES = ["pending", "snapshot", "backfill", "done"];
12285
+ var FLOW_QUERY_VALIDATION_PHASES = [
12286
+ "pending",
12287
+ "source",
12288
+ "index",
12289
+ "counter",
12290
+ "cleanup",
12291
+ "done"
12292
+ ];
12293
+ var FLOW_QUERY_RETIREMENT_PHASES = [
12294
+ "pending",
12295
+ "fence",
12296
+ "index",
12297
+ "counter",
12298
+ "reverse",
12299
+ "cleanup",
12300
+ "done"
12301
+ ];
12302
+ var IDENTIFIER = /^[A-Za-z0-9_.:-]+$/u;
12303
+ var UNQUOTED_METADATA = /^[A-Za-z0-9_-]+$/u;
12304
+ var ATTRIBUTE_SELECTOR = /^attribute\['((?:[^']|'')*)'\]$/u;
12305
+ var STATE_META_SELECTOR = /^state_meta\['((?:[^']|'')*)'\]\['((?:[^']|'')*)'\]$/u;
12306
+ var INTEGER_FIELDS = /* @__PURE__ */ new Set([
12307
+ "version",
12308
+ "priority",
12309
+ "created_at_ms",
12310
+ "updated_at_ms",
12311
+ "next_run_at_ms",
12312
+ "lease_deadline_ms",
12313
+ "attempts",
12314
+ "max_active_ms"
12315
+ ]);
12316
+ var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
12317
+ ...INTEGER_FIELDS,
12318
+ "partition_key",
12319
+ "run_id",
12320
+ "event_id",
12321
+ "type",
12322
+ "state",
12323
+ "run_state",
12324
+ "parent_flow_id",
12325
+ "root_flow_id",
12326
+ "correlation_id"
12327
+ ]);
12328
+ var RETIREMENT_PROGRESS_FIELDS = [
12329
+ "phase_counts",
12330
+ "current_phases",
12331
+ "completed_shards",
12332
+ "total_shards",
12333
+ "deleted_entries",
12334
+ "deleted_bytes",
12335
+ "rewritten_reverse_rows"
12336
+ ];
12337
+ function validateFlowQueryIndexContract(status, expectedId) {
12338
+ let previousId;
12339
+ let previousVersion;
12340
+ for (const index of status.indexes) {
12341
+ const version = BigInt(index.version);
12342
+ if (previousId != null && (index.id < previousId || index.id === previousId && previousVersion != null && version <= previousVersion)) {
12343
+ fail("indexes must be uniquely sorted by id and version", status.raw);
12344
+ }
12345
+ if (expectedId != null && index.id !== expectedId) {
12346
+ fail("filtered indexes do not match the requested id", status.raw);
12347
+ }
12348
+ previousId = index.id;
12349
+ previousVersion = version;
12350
+ validateIndex(index, status);
12351
+ }
12352
+ if (expectedId != null && status.indexes.length === 0) {
12353
+ fail("filtered indexes do not match the requested id", status.raw);
12354
+ }
12355
+ const hasUnavailableStatistics = status.indexes.some(
12356
+ (index) => index.statistics.status === "unavailable"
12095
12357
  );
12096
- if (usage.resultRecords !== 1) {
12097
- throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
12358
+ if (status.services.statisticsStore === "unavailable") {
12359
+ if (status.indexes.some((index) => index.statistics.status !== "unavailable")) {
12360
+ fail("statistics must be unavailable when the service is unavailable", status.raw);
12361
+ }
12362
+ } else if (hasUnavailableStatistics) {
12363
+ fail("statistics cannot be unavailable while the service is ready", status.raw);
12098
12364
  }
12099
- return {
12100
- kind: "count",
12101
- count,
12102
- quality,
12103
- usage,
12104
- raw
12105
- };
12106
12365
  }
12107
- function decodeFlowExplainResult(value) {
12108
- const mapping = requiredMap2(value, "FLOW.QUERY explain");
12109
- requireContract(mapping, "version", FLOW_EXPLAIN_CONTRACT, "FLOW.QUERY explain");
12110
- const queryFingerprint = requiredText2(mapping, "query_fingerprint", "FLOW.QUERY explain");
12111
- if (!/^[0-9a-f]{64}$/iu.test(queryFingerprint)) {
12112
- throw decodeError("FLOW.QUERY explain query_fingerprint is invalid", value);
12366
+ function validateIndex(index, status) {
12367
+ if (!IDENTIFIER.test(index.id) || index.workloads.some((workload) => !IDENTIFIER.test(workload))) {
12368
+ fail("index identity contains invalid characters", index.raw);
12113
12369
  }
12114
- const status = requiredText2(mapping, "status", "FLOW.QUERY explain");
12115
- if (status !== "planned" && status !== "rejected" && status !== "executed") {
12116
- throw decodeError(`FLOW.QUERY explain status ${JSON.stringify(status)} is unsupported`, value);
12370
+ const first = index.fields[0];
12371
+ if (first?.name !== "partition_key" || first.direction !== "asc" || first.encoding !== "hashed") {
12372
+ fail("index must begin with partition_key asc hashed", index.raw);
12117
12373
  }
12118
- const plan = freezeMetadataMap(
12119
- requiredMap2(field(mapping, "plan"), "FLOW.QUERY explain plan"),
12120
- "FLOW.QUERY explain plan"
12121
- );
12122
- const estimate = freezeMetadataMap(
12123
- requiredMap2(field(mapping, "estimate"), "FLOW.QUERY explain estimate"),
12124
- "FLOW.QUERY explain estimate"
12125
- );
12126
- const bounds = freezeMetadataMap(
12127
- requiredMap2(field(mapping, "bounds"), "FLOW.QUERY explain bounds"),
12128
- "FLOW.QUERY explain bounds"
12129
- );
12130
- const actualValue = field(mapping, "actual");
12131
- let actual;
12132
- if (status === "executed") {
12133
- if (actualValue == null) {
12134
- throw decodeError("FLOW.QUERY executed explain is missing actual usage", value);
12374
+ let attributeFields = 0;
12375
+ for (const indexField of index.fields) {
12376
+ if (indexField.encoding === "hashed" && indexField.direction !== "asc") {
12377
+ fail("hashed index fields must be ascending", index.raw);
12135
12378
  }
12136
- actual = decodeUsage(actualValue);
12137
- } else if (actualValue != null) {
12138
- throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
12379
+ const kind = fieldKind(indexField.name);
12380
+ if (kind == null) fail("index contains an unsupported field selector", index.raw);
12381
+ if (indexField.encoding === "ordered" && kind !== "integer") {
12382
+ fail("ordered index fields must be integers", index.raw);
12383
+ }
12384
+ if (kind === "attribute") attributeFields += 1;
12139
12385
  }
12140
- const diagnosticValue = field(mapping, "diagnostic");
12141
- let diagnostic;
12142
- if (status === "rejected") {
12143
- diagnostic = tryDecodeFlowQueryError(diagnosticValue);
12144
- if (diagnostic == null) {
12145
- throw decodeError("FLOW.QUERY rejected explain has an invalid diagnostic", value);
12386
+ if (attributeFields > 1) fail("index may contain at most one attribute field", index.raw);
12387
+ for (const prefix of index.countPrefixes) {
12388
+ if (index.fields.slice(0, prefix).some((field3) => field3.encoding !== "hashed")) {
12389
+ fail("count prefixes may cover only hashed fields", index.raw);
12146
12390
  }
12147
- } else if (diagnosticValue != null) {
12148
- throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
12149
12391
  }
12150
- return Object.freeze({
12151
- version: FLOW_EXPLAIN_CONTRACT,
12152
- queryFingerprint,
12153
- status,
12154
- plan,
12155
- estimate,
12156
- bounds,
12157
- actual,
12158
- diagnostic,
12159
- raw: freezeMap(mapping)
12160
- });
12161
- }
12162
- function tryDecodeFlowQueryError(value, cause) {
12163
- try {
12164
- const mapping = requiredMap2(value, "FLOW.QUERY diagnostic");
12165
- const contextValue = field(mapping, "context");
12166
- const context = contextValue == null ? void 0 : freezeMetadataMap(
12167
- requiredMap2(contextValue, "FLOW.QUERY diagnostic context"),
12168
- "FLOW.QUERY diagnostic context"
12392
+ if (index.coveringFields.length > 0) {
12393
+ if (index.coveringFields.some((field3) => fieldKind(field3) == null)) {
12394
+ fail("index contains an unsupported covering field selector", index.raw);
12395
+ }
12396
+ const required = /* @__PURE__ */ new Set(["run_id", "version", ...index.fields.map((field3) => field3.name)]);
12397
+ if ([...required].some((field3) => !index.coveringFields.includes(field3))) {
12398
+ fail("index covering fields omit an identity or index field", index.raw);
12399
+ }
12400
+ }
12401
+ if (index.countPrefixes.length > 0 !== (index.format.counter != null)) {
12402
+ fail("index counter format is inconsistent with count prefixes", index.raw);
12403
+ }
12404
+ validateProgress(index.build, FLOW_QUERY_BUILD_PHASES, "build");
12405
+ validateProgress(index.validation, FLOW_QUERY_VALIDATION_PHASES, "validation");
12406
+ if (index.retirement.status === "not_applicable") {
12407
+ if (RETIREMENT_PROGRESS_FIELDS.some((name) => hasKey(index.retirement.raw, name))) {
12408
+ fail("not_applicable retirement must not contain progress", index.retirement.raw);
12409
+ }
12410
+ } else {
12411
+ const retirement = index.retirement;
12412
+ if (retirement.phaseCounts == null || retirement.currentPhases == null || retirement.completedShards == null || retirement.totalShards == null) {
12413
+ fail("retirement progress is incomplete", retirement.raw);
12414
+ }
12415
+ validateProgress(
12416
+ {
12417
+ phaseCounts: retirement.phaseCounts,
12418
+ currentPhases: retirement.currentPhases,
12419
+ completedShards: retirement.completedShards,
12420
+ totalShards: retirement.totalShards,
12421
+ raw: retirement.raw
12422
+ },
12423
+ FLOW_QUERY_RETIREMENT_PHASES,
12424
+ "retirement"
12169
12425
  );
12170
- const position = decodePosition(field(mapping, "position"));
12171
- return new FlowQueryError({
12172
- code: requiredText2(mapping, "code", "FLOW.QUERY diagnostic"),
12173
- message: requiredText2(mapping, "message", "FLOW.QUERY diagnostic"),
12174
- detail: optionalText2(mapping, "detail", "FLOW.QUERY diagnostic"),
12175
- hint: optionalText2(mapping, "hint", "FLOW.QUERY diagnostic"),
12176
- retryable: requiredBoolean(mapping, "retryable", "FLOW.QUERY diagnostic"),
12177
- safeToRetry: requiredBoolean(mapping, "safe_to_retry", "FLOW.QUERY diagnostic"),
12178
- retryAfterMs: nonNegativeInteger(
12179
- field(mapping, "retry_after_ms"),
12180
- "FLOW.QUERY diagnostic retry_after_ms"
12181
- ),
12182
- position,
12183
- context,
12184
- raw: value,
12185
- cause
12186
- });
12187
- } catch (error) {
12188
- if (error instanceof FerricStoreError) return void 0;
12189
- throw error;
12190
12426
  }
12427
+ const totals = new Set(
12428
+ [
12429
+ index.coverage.totalShards,
12430
+ index.build.totalShards,
12431
+ index.validation.totalShards,
12432
+ ...index.retirement.totalShards == null ? [] : [index.retirement.totalShards]
12433
+ ].map(indexInteger)
12434
+ );
12435
+ if (totals.size !== 1) fail("index shard totals are inconsistent", index.raw);
12436
+ if (!sameIndexInteger(index.coverage.completeShards, index.build.completedShards)) {
12437
+ fail("coverage and build completion are inconsistent", index.raw);
12438
+ }
12439
+ if (index.coverage.validation !== index.validation.status) {
12440
+ fail("coverage and validation status are inconsistent", index.raw);
12441
+ }
12442
+ const queryable = index.state === "active" && sameIndexInteger(
12443
+ index.coverage.completeShards,
12444
+ index.coverage.totalShards
12445
+ ) && index.coverage.validation === "passed";
12446
+ if (index.queryable !== queryable) fail("index queryable flag is inconsistent", index.raw);
12447
+ validateLifecycle(index);
12448
+ validateValidation(index);
12449
+ validateStatistics(index, status);
12450
+ }
12451
+ function validateProgress(progress, phases, section) {
12452
+ const entries = Object.entries(progress.phaseCounts);
12453
+ if (entries.length === 0 || entries.some(
12454
+ ([phase, count]) => !phases.includes(phase) || indexInteger(count) <= 0n
12455
+ ) || entries.reduce((sum, [, count]) => sum + indexInteger(count), 0n) !== indexInteger(progress.totalShards)) {
12456
+ fail(`${section} phase_counts are invalid`, progress.raw);
12457
+ }
12458
+ const current = phases.filter((phase) => Object.hasOwn(progress.phaseCounts, phase));
12459
+ if (current.length !== progress.currentPhases.length || current.some((phase, position) => phase !== progress.currentPhases[position])) {
12460
+ fail(`${section} current_phases are inconsistent`, progress.raw);
12461
+ }
12462
+ if (!sameIndexInteger(
12463
+ progress.completedShards,
12464
+ progress.phaseCounts.done ?? 0
12465
+ )) {
12466
+ fail(`${section} completed_shards is inconsistent`, progress.raw);
12467
+ }
12468
+ }
12469
+ function validateLifecycle(index) {
12470
+ const built = sameIndexInteger(
12471
+ index.build.completedShards,
12472
+ index.build.totalShards
12473
+ );
12474
+ const validation = index.validation.status;
12475
+ const retirement = index.retirement.status;
12476
+ const valid = index.state === "building" && !built && validation === "pending" && retirement === "not_applicable" || index.state === "validating" && built && (validation === "pending" || validation === "passed") && retirement === "not_applicable" || index.state === "active" && built && validation === "passed" && retirement === "not_applicable" || index.state === "retiring" && built && validation !== "pending" && retirement !== "not_applicable" || index.state === "failed" && validation !== "pending" && retirement !== "not_applicable";
12477
+ if (!valid) fail("index lifecycle fields are inconsistent", index.raw);
12478
+ }
12479
+ function validateValidation(index) {
12480
+ const validation = index.validation;
12481
+ const valid = validation.status === "pending" && indexInteger(validation.mismatches) === 0n && validation.failureReason == null && validation.validatedAtMs == null || validation.status === "passed" && indexInteger(validation.mismatches) === 0n && validation.failureReason == null && validation.validatedAtMs != null || validation.status === "failed" && indexInteger(validation.mismatches) > 0n && Boolean(validation.failureReason) && validation.validatedAtMs != null;
12482
+ if (!valid) fail("validation status fields are inconsistent", validation.raw);
12483
+ }
12484
+ function validateStatistics(index, status) {
12485
+ const statistics = index.statistics;
12486
+ const times = [
12487
+ statistics.oldestCollectedAtMs,
12488
+ statistics.newestCollectedAtMs,
12489
+ statistics.oldestAgeMs,
12490
+ statistics.newestAgeMs
12491
+ ];
12492
+ if (indexInteger(statistics.samples) === 0n) {
12493
+ if (statistics.status !== "missing" && statistics.status !== "unavailable" || times.some((value) => value != null)) {
12494
+ fail("empty statistics fields are inconsistent", statistics.raw);
12495
+ }
12496
+ return;
12497
+ }
12498
+ if (times.some((value) => value == null)) {
12499
+ fail("sampled statistics require timestamps and ages", statistics.raw);
12500
+ }
12501
+ const [oldest, newest, oldestAge, newestAge] = times;
12502
+ if (indexInteger(oldest) > indexInteger(newest) || indexInteger(oldestAge) !== indexAge(status.observedAtMs, oldest) || indexInteger(newestAge) !== indexAge(status.observedAtMs, newest)) {
12503
+ fail("statistics timestamps or ages are inconsistent", statistics.raw);
12504
+ }
12505
+ const expected = sameIndexInteger(statistics.freshSamples, statistics.samples) ? ["fresh"] : indexInteger(statistics.freshSamples) === 0n ? indexInteger(statistics.futureSamples) > 0n ? ["stale", "future"] : ["stale"] : ["mixed"];
12506
+ if (!expected.includes(statistics.status)) {
12507
+ fail("statistics status does not match sample counters", statistics.raw);
12508
+ }
12509
+ }
12510
+ function fieldKind(name) {
12511
+ if (BUILTIN_FIELDS.has(name)) return INTEGER_FIELDS.has(name) ? "integer" : "keyword";
12512
+ const parts = name.split(".");
12513
+ if (parts.length === 2 && parts[0] === "attribute" && validUnquoted(parts[1] ?? "")) return "attribute";
12514
+ if (parts.length === 3 && parts[0] === "state_meta" && validUnquoted(parts[1] ?? "") && validUnquoted(parts[2] ?? "")) return "state_meta";
12515
+ const attribute = ATTRIBUTE_SELECTOR.exec(name);
12516
+ if (attribute != null) {
12517
+ const metadata = (attribute[1] ?? "").replaceAll("''", "'");
12518
+ if (validMetadata(metadata, true) && name === externalSelector("attribute", metadata)) return "attribute";
12519
+ }
12520
+ const stateMeta = STATE_META_SELECTOR.exec(name);
12521
+ if (stateMeta != null) {
12522
+ const state = (stateMeta[1] ?? "").replaceAll("''", "'");
12523
+ const metadata = (stateMeta[2] ?? "").replaceAll("''", "'");
12524
+ if (validMetadata(state, false) && validMetadata(metadata, true) && name === externalSelector("state_meta", state, metadata)) return "state_meta";
12525
+ }
12526
+ return void 0;
12527
+ }
12528
+ function validUnquoted(value) {
12529
+ return !value.startsWith("__") && UNQUOTED_METADATA.test(value) && Buffer47.byteLength(value, "ascii") <= 64;
12530
+ }
12531
+ function validMetadata(value, rejectReserved) {
12532
+ return value.length > 0 && Buffer47.byteLength(value, "utf8") <= 64 && (!rejectReserved || !value.startsWith("__"));
12533
+ }
12534
+ function externalSelector(root, ...segments) {
12535
+ return segments.every(validUnquoted) ? [root, ...segments].join(".") : root + segments.map((segment) => `['${segment.replaceAll("'", "''")}']`).join("");
12536
+ }
12537
+ function sameIndexInteger(left, right) {
12538
+ return indexInteger(left) === indexInteger(right);
12539
+ }
12540
+ function indexAge(observedAtMs, collectedAtMs) {
12541
+ const age = indexInteger(observedAtMs) - indexInteger(collectedAtMs);
12542
+ return age < 0n ? 0n : age;
12543
+ }
12544
+ function indexInteger(value) {
12545
+ return BigInt(value);
12546
+ }
12547
+ function fail(message, raw) {
12548
+ throw decodeError(`FLOW.QUERY.INDEXES ${message}`, raw);
12549
+ }
12550
+
12551
+ // src/flow-query-index-decode.ts
12552
+ var MAX_UNSIGNED_64 = (1n << 64n) - 1n;
12553
+ function decodeUniqueTextArray(value, context, maximum, maximumBytes) {
12554
+ if (!Array.isArray(value) || value.length > maximum) {
12555
+ throw decodeError(`${context} must be a bounded array`, value);
12556
+ }
12557
+ const result = new Array(value.length);
12558
+ const seen = /* @__PURE__ */ new Set();
12559
+ for (let index = 0; index < value.length; index += 1) {
12560
+ if (!Object.hasOwn(value, index)) {
12561
+ throw decodeError(`${context} must be dense`, value);
12562
+ }
12563
+ const text3 = boundedText(
12564
+ value[index],
12565
+ `${context} entry ${index}`,
12566
+ maximumBytes
12567
+ );
12568
+ if (seen.has(text3)) {
12569
+ throw decodeError(`${context} contains duplicates`, value);
12570
+ }
12571
+ seen.add(text3);
12572
+ result[index] = text3;
12573
+ }
12574
+ return Object.freeze(result);
12575
+ }
12576
+ function requiredChoice(mapping2, name, context, choices) {
12577
+ const value = requiredBoundedText2(mapping2, name, context, 64);
12578
+ if (!choices.includes(value)) {
12579
+ throw decodeError(`${context} ${name} is unsupported`, mapping2);
12580
+ }
12581
+ return value;
12582
+ }
12583
+ function optionalBoundedText(mapping2, name, context, maximumBytes) {
12584
+ const value = field(mapping2, name);
12585
+ if (value == null) return void 0;
12586
+ return boundedText(value, `${context} ${name}`, maximumBytes);
12191
12587
  }
12192
- function decodeFlowQueryIndexStatus(value) {
12193
- const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES");
12588
+ function optionalIndexInteger(mapping2, name, context) {
12589
+ const value = field(mapping2, name);
12590
+ return value == null ? void 0 : unsignedIndexInteger(value, `${context} ${name}`);
12591
+ }
12592
+ function indexCounter(mapping2, name, section) {
12593
+ return unsignedIndexInteger(
12594
+ field(mapping2, name),
12595
+ `FLOW.QUERY.INDEXES index ${section} ${name}`
12596
+ );
12597
+ }
12598
+ function unsignedIndexInteger(value, context) {
12599
+ return boundedInteger(value, MAX_UNSIGNED_64, context);
12600
+ }
12601
+ function positiveIndexInteger(value, context) {
12602
+ return positiveBoundedInteger(value, MAX_UNSIGNED_64, context);
12603
+ }
12604
+ function indexInteger2(value) {
12605
+ return BigInt(value);
12606
+ }
12607
+
12608
+ // src/flow-query-index-lifecycle-response.ts
12609
+ function decodeIndexCoverage(value) {
12610
+ const context = "FLOW.QUERY.INDEXES index coverage";
12611
+ const mapping2 = requiredMap2(value, context);
12612
+ const completeShards = unsignedIndexInteger(
12613
+ field(mapping2, "complete_shards"),
12614
+ `${context} complete_shards`
12615
+ );
12616
+ const totalShards = positiveIndexInteger(
12617
+ field(mapping2, "total_shards"),
12618
+ `${context} total_shards`
12619
+ );
12620
+ if (indexInteger2(completeShards) > indexInteger2(totalShards)) {
12621
+ throw decodeError(`${context} complete_shards exceeds total_shards`, value);
12622
+ }
12623
+ return Object.freeze({
12624
+ completeShards,
12625
+ totalShards,
12626
+ validation: requiredChoice(mapping2, "validation", context, [
12627
+ "pending",
12628
+ "passed",
12629
+ "failed"
12630
+ ]),
12631
+ raw: freezeMap(mapping2)
12632
+ });
12633
+ }
12634
+ function decodeIndexBuild(value) {
12635
+ const mapping2 = requiredMap2(value, "FLOW.QUERY.INDEXES index build");
12636
+ const progress = decodeIndexProgress(
12637
+ mapping2,
12638
+ "build",
12639
+ FLOW_QUERY_BUILD_PHASES
12640
+ );
12641
+ return Object.freeze({
12642
+ ...progress,
12643
+ scannedRecords: indexCounter(mapping2, "scanned_records", "build"),
12644
+ writtenEntries: indexCounter(mapping2, "written_entries", "build"),
12645
+ writtenBytes: indexCounter(mapping2, "written_bytes", "build")
12646
+ });
12647
+ }
12648
+ function decodeIndexValidation(value) {
12649
+ const mapping2 = requiredMap2(value, "FLOW.QUERY.INDEXES index validation");
12650
+ if (!hasKey(mapping2, "failure_reason") || !hasKey(mapping2, "validated_at_ms")) {
12651
+ throw decodeError(
12652
+ "FLOW.QUERY.INDEXES index validation is missing nullable fields",
12653
+ value
12654
+ );
12655
+ }
12656
+ const progress = decodeIndexProgress(
12657
+ mapping2,
12658
+ "validation",
12659
+ FLOW_QUERY_VALIDATION_PHASES
12660
+ );
12661
+ return Object.freeze({
12662
+ ...progress,
12663
+ status: requiredChoice(
12664
+ mapping2,
12665
+ "status",
12666
+ "FLOW.QUERY.INDEXES index validation",
12667
+ ["pending", "passed", "failed"]
12668
+ ),
12669
+ checkedRecords: indexCounter(mapping2, "checked_records", "validation"),
12670
+ checkedEntries: indexCounter(mapping2, "checked_entries", "validation"),
12671
+ mismatches: indexCounter(mapping2, "mismatches", "validation"),
12672
+ failureReason: optionalBoundedText(
12673
+ mapping2,
12674
+ "failure_reason",
12675
+ "FLOW.QUERY.INDEXES index validation",
12676
+ 128
12677
+ ),
12678
+ validatedAtMs: optionalIndexInteger(
12679
+ mapping2,
12680
+ "validated_at_ms",
12681
+ "FLOW.QUERY.INDEXES index validation"
12682
+ )
12683
+ });
12684
+ }
12685
+ function decodeIndexRetirement(value) {
12686
+ const context = "FLOW.QUERY.INDEXES index retirement";
12687
+ const mapping2 = requiredMap2(value, context);
12688
+ const status = requiredChoice(mapping2, "status", context, [
12689
+ "not_applicable",
12690
+ "pending",
12691
+ "complete"
12692
+ ]);
12693
+ if (status === "not_applicable") {
12694
+ return Object.freeze({ status, raw: freezeMap(mapping2) });
12695
+ }
12696
+ const completedShards = unsignedIndexInteger(
12697
+ field(mapping2, "completed_shards"),
12698
+ `${context} completed_shards`
12699
+ );
12700
+ const totalShards = positiveIndexInteger(
12701
+ field(mapping2, "total_shards"),
12702
+ `${context} total_shards`
12703
+ );
12704
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12705
+ throw decodeError(
12706
+ `${context} completed_shards exceeds total_shards`,
12707
+ mapping2
12708
+ );
12709
+ }
12710
+ return Object.freeze({
12711
+ status,
12712
+ phaseCounts: decodePhaseCounts(
12713
+ field(mapping2, "phase_counts"),
12714
+ "retirement"
12715
+ ),
12716
+ currentPhases: decodeAllowedPhases(
12717
+ field(mapping2, "current_phases"),
12718
+ "retirement",
12719
+ FLOW_QUERY_RETIREMENT_PHASES
12720
+ ),
12721
+ completedShards,
12722
+ totalShards,
12723
+ deletedEntries: indexCounter(mapping2, "deleted_entries", "retirement"),
12724
+ deletedBytes: indexCounter(mapping2, "deleted_bytes", "retirement"),
12725
+ rewrittenReverseRows: indexCounter(
12726
+ mapping2,
12727
+ "rewritten_reverse_rows",
12728
+ "retirement"
12729
+ ),
12730
+ raw: freezeMap(mapping2)
12731
+ });
12732
+ }
12733
+ function decodeIndexStatistics(value) {
12734
+ const context = "FLOW.QUERY.INDEXES index statistics";
12735
+ const mapping2 = requiredMap2(value, context);
12736
+ for (const name of [
12737
+ "oldest_collected_at_ms",
12738
+ "newest_collected_at_ms",
12739
+ "oldest_age_ms",
12740
+ "newest_age_ms"
12741
+ ]) {
12742
+ if (!hasKey(mapping2, name)) {
12743
+ throw decodeError(
12744
+ `${context} is missing required nullable fields`,
12745
+ value
12746
+ );
12747
+ }
12748
+ }
12749
+ const samples = indexCounter(mapping2, "samples", "statistics");
12750
+ const freshSamples = indexCounter(mapping2, "fresh_samples", "statistics");
12751
+ const staleSamples = indexCounter(mapping2, "stale_samples", "statistics");
12752
+ const futureSamples = indexCounter(mapping2, "future_samples", "statistics");
12753
+ if (indexInteger2(freshSamples) + indexInteger2(staleSamples) !== indexInteger2(samples) || indexInteger2(futureSamples) > indexInteger2(staleSamples)) {
12754
+ throw decodeError(`${context} counters are inconsistent`, value);
12755
+ }
12756
+ return Object.freeze({
12757
+ status: requiredChoice(mapping2, "status", context, [
12758
+ "fresh",
12759
+ "stale",
12760
+ "future",
12761
+ "mixed",
12762
+ "missing",
12763
+ "unavailable"
12764
+ ]),
12765
+ samples,
12766
+ freshSamples,
12767
+ staleSamples,
12768
+ futureSamples,
12769
+ oldestCollectedAtMs: optionalIndexInteger(
12770
+ mapping2,
12771
+ "oldest_collected_at_ms",
12772
+ context
12773
+ ),
12774
+ newestCollectedAtMs: optionalIndexInteger(
12775
+ mapping2,
12776
+ "newest_collected_at_ms",
12777
+ context
12778
+ ),
12779
+ oldestAgeMs: optionalIndexInteger(mapping2, "oldest_age_ms", context),
12780
+ newestAgeMs: optionalIndexInteger(mapping2, "newest_age_ms", context),
12781
+ raw: freezeMap(mapping2)
12782
+ });
12783
+ }
12784
+ function decodeIndexProgress(mapping2, section, allowedPhases) {
12785
+ const context = `FLOW.QUERY.INDEXES index ${section}`;
12786
+ const completedShards = unsignedIndexInteger(
12787
+ field(mapping2, "completed_shards"),
12788
+ `${context} completed_shards`
12789
+ );
12790
+ const totalShards = positiveIndexInteger(
12791
+ field(mapping2, "total_shards"),
12792
+ `${context} total_shards`
12793
+ );
12794
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12795
+ throw decodeError(
12796
+ `${context} completed_shards exceeds total_shards`,
12797
+ mapping2
12798
+ );
12799
+ }
12800
+ return Object.freeze({
12801
+ scope: requiredChoice(mapping2, "scope", context, ["catalog_build"]),
12802
+ phaseCounts: decodePhaseCounts(field(mapping2, "phase_counts"), section),
12803
+ currentPhases: decodeAllowedPhases(
12804
+ field(mapping2, "current_phases"),
12805
+ section,
12806
+ allowedPhases
12807
+ ),
12808
+ completedShards,
12809
+ totalShards,
12810
+ raw: freezeMap(mapping2)
12811
+ });
12812
+ }
12813
+ function decodePhaseCounts(value, section) {
12814
+ const context = `FLOW.QUERY.INDEXES index ${section} phase_counts`;
12815
+ const mapping2 = requiredMap2(value, context);
12816
+ const entries = mapping2 instanceof Map ? [...mapping2.entries()] : Object.entries(mapping2);
12817
+ if (entries.length > 16) {
12818
+ throw decodeError(`${context} contains too many entries`, value);
12819
+ }
12820
+ const result = /* @__PURE__ */ Object.create(null);
12821
+ for (const [rawPhase, rawCount] of entries) {
12822
+ const phase = boundedText(rawPhase, `${context} phase`, 64);
12823
+ if (Object.hasOwn(result, phase)) {
12824
+ throw decodeError(`${context} contains duplicate phases`, value);
12825
+ }
12826
+ result[phase] = unsignedIndexInteger(rawCount, `${context} ${phase}`);
12827
+ }
12828
+ return Object.freeze(result);
12829
+ }
12830
+ function decodeAllowedPhases(value, section, allowed) {
12831
+ const phases = decodeUniqueTextArray(
12832
+ value,
12833
+ `FLOW.QUERY.INDEXES index ${section} current_phases`,
12834
+ allowed.length,
12835
+ 64
12836
+ );
12837
+ if (phases.some((phase) => !allowed.includes(phase))) {
12838
+ throw decodeError(
12839
+ `FLOW.QUERY.INDEXES index ${section} current_phases are invalid`,
12840
+ value
12841
+ );
12842
+ }
12843
+ return phases;
12844
+ }
12845
+
12846
+ // src/flow-query-index-response.ts
12847
+ var FLOW_QUERY_INDEXES_CONTRACT = "ferric.flow.query.indexes/v1";
12848
+ function decodeFlowQueryIndexStatus(value, expectedId) {
12849
+ const mapping2 = requiredMap2(value, "FLOW.QUERY.INDEXES");
12194
12850
  requireContract(
12195
- mapping,
12851
+ mapping2,
12196
12852
  "contract_version",
12197
12853
  FLOW_QUERY_INDEXES_CONTRACT,
12198
12854
  "FLOW.QUERY.INDEXES"
12199
12855
  );
12200
- const registry = requiredMap2(field(mapping, "registry"), "FLOW.QUERY.INDEXES registry");
12201
- const services = freezeMetadataMap(
12202
- requiredMap2(field(mapping, "services"), "FLOW.QUERY.INDEXES services"),
12203
- "FLOW.QUERY.INDEXES services"
12856
+ const registry = requiredMap2(
12857
+ field(mapping2, "registry"),
12858
+ "FLOW.QUERY.INDEXES registry"
12204
12859
  );
12205
- const rawIndexes = field(mapping, "indexes");
12860
+ const services = decodeIndexServices(field(mapping2, "services"));
12861
+ const rawIndexes = field(mapping2, "indexes");
12206
12862
  if (!Array.isArray(rawIndexes) || rawIndexes.length > 32) {
12207
- throw decodeError("FLOW.QUERY.INDEXES indexes must contain at most 32 entries", value);
12863
+ throw decodeError(
12864
+ "FLOW.QUERY.INDEXES indexes must contain at most 32 entries",
12865
+ value
12866
+ );
12208
12867
  }
12209
12868
  const indexes = new Array(rawIndexes.length);
12210
12869
  for (let index = 0; index < rawIndexes.length; index += 1) {
12211
12870
  if (!Object.hasOwn(rawIndexes, index)) {
12212
- throw decodeError("FLOW.QUERY.INDEXES indexes must be a dense array", value);
12871
+ throw decodeError(
12872
+ "FLOW.QUERY.INDEXES indexes must be a dense array",
12873
+ value
12874
+ );
12213
12875
  }
12214
12876
  indexes[index] = decodeIndex(rawIndexes[index], index);
12215
12877
  }
12216
- return Object.freeze({
12878
+ const status = Object.freeze({
12217
12879
  contractVersion: FLOW_QUERY_INDEXES_CONTRACT,
12218
- observedAtMs: nonNegativeInteger(
12219
- field(mapping, "observed_at_ms"),
12880
+ observedAtMs: unsignedIndexInteger(
12881
+ field(mapping2, "observed_at_ms"),
12220
12882
  "FLOW.QUERY.INDEXES observed_at_ms"
12221
12883
  ),
12222
- statisticsMaxAgeMs: nonNegativeInteger(
12223
- field(mapping, "statistics_max_age_ms"),
12884
+ statisticsMaxAgeMs: unsignedIndexInteger(
12885
+ field(mapping2, "statistics_max_age_ms"),
12224
12886
  "FLOW.QUERY.INDEXES statistics_max_age_ms"
12225
12887
  ),
12226
12888
  registry: Object.freeze({
12227
- epoch: boundedInteger(
12889
+ epoch: unsignedIndexInteger(
12228
12890
  field(registry, "epoch"),
12229
- MAX_UNSIGNED_64,
12230
12891
  "FLOW.QUERY.INDEXES epoch"
12231
12892
  ),
12232
- catalogVersion: positiveBoundedInteger(
12893
+ catalogVersion: positiveIndexInteger(
12233
12894
  field(registry, "catalog_version"),
12234
- MAX_UNSIGNED_64,
12235
12895
  "FLOW.QUERY.INDEXES catalog_version"
12236
12896
  )
12237
12897
  }),
12238
12898
  services,
12239
12899
  indexes: Object.freeze(indexes),
12240
- raw: freezeMap(mapping)
12900
+ raw: freezeMap(mapping2)
12901
+ });
12902
+ validateFlowQueryIndexContract(status, expectedId);
12903
+ return status;
12904
+ }
12905
+ function decodeIndex(value, index) {
12906
+ const mapping2 = requiredMap2(value, `FLOW.QUERY.INDEXES index ${index}`);
12907
+ const fields = decodeIndexFields(field(mapping2, "fields"));
12908
+ return Object.freeze({
12909
+ id: requiredBoundedText2(mapping2, "id", "FLOW.QUERY.INDEXES index", 64),
12910
+ version: positiveIndexInteger(
12911
+ field(mapping2, "version"),
12912
+ "FLOW.QUERY.INDEXES index version"
12913
+ ),
12914
+ buildId: requiredBoundedText2(
12915
+ mapping2,
12916
+ "build_id",
12917
+ "FLOW.QUERY.INDEXES index",
12918
+ 128
12919
+ ),
12920
+ source: requiredChoice(mapping2, "source", "FLOW.QUERY.INDEXES index", [
12921
+ "runs"
12922
+ ]),
12923
+ state: requiredChoice(mapping2, "state", "FLOW.QUERY.INDEXES index", [
12924
+ "building",
12925
+ "validating",
12926
+ "active",
12927
+ "retiring",
12928
+ "failed"
12929
+ ]),
12930
+ queryable: requiredBoolean(
12931
+ mapping2,
12932
+ "queryable",
12933
+ "FLOW.QUERY.INDEXES index"
12934
+ ),
12935
+ fields,
12936
+ workloads: decodeUniqueTextArray(
12937
+ field(mapping2, "workloads"),
12938
+ "FLOW.QUERY.INDEXES index workloads",
12939
+ 16,
12940
+ 64
12941
+ ),
12942
+ countPrefixes: decodeCountPrefixes(
12943
+ field(mapping2, "count_prefixes"),
12944
+ fields.length
12945
+ ),
12946
+ coveringFields: decodeCoveringFields(mapping2),
12947
+ format: decodeIndexFormat(mapping2),
12948
+ coverage: decodeIndexCoverage(field(mapping2, "coverage")),
12949
+ build: decodeIndexBuild(field(mapping2, "build")),
12950
+ validation: decodeIndexValidation(field(mapping2, "validation")),
12951
+ retirement: decodeIndexRetirement(field(mapping2, "retirement")),
12952
+ statistics: decodeIndexStatistics(field(mapping2, "statistics")),
12953
+ raw: freezeMap(mapping2)
12954
+ });
12955
+ }
12956
+ function decodeIndexServices(value) {
12957
+ const mapping2 = requiredMap2(value, "FLOW.QUERY.INDEXES services");
12958
+ const choices = ["ready", "unavailable"];
12959
+ return Object.freeze({
12960
+ registry: requiredChoice(
12961
+ mapping2,
12962
+ "registry",
12963
+ "FLOW.QUERY.INDEXES services",
12964
+ choices
12965
+ ),
12966
+ lifecycleWorker: requiredChoice(
12967
+ mapping2,
12968
+ "lifecycle_worker",
12969
+ "FLOW.QUERY.INDEXES services",
12970
+ choices
12971
+ ),
12972
+ statisticsStore: requiredChoice(
12973
+ mapping2,
12974
+ "statistics_store",
12975
+ "FLOW.QUERY.INDEXES services",
12976
+ choices
12977
+ ),
12978
+ statisticsWorker: requiredChoice(
12979
+ mapping2,
12980
+ "statistics_worker",
12981
+ "FLOW.QUERY.INDEXES services",
12982
+ choices
12983
+ ),
12984
+ raw: freezeMap(mapping2)
12985
+ });
12986
+ }
12987
+ function decodeIndexFields(value) {
12988
+ const context = "FLOW.QUERY.INDEXES index fields";
12989
+ if (!Array.isArray(value) || value.length < 2 || value.length > 8) {
12990
+ throw decodeError(`${context} must contain 2 to 8 entries`, value);
12991
+ }
12992
+ const fields = new Array(value.length);
12993
+ const names = /* @__PURE__ */ new Set();
12994
+ for (let index = 0; index < value.length; index += 1) {
12995
+ if (!Object.hasOwn(value, index)) {
12996
+ throw decodeError(`${context} must be dense`, value);
12997
+ }
12998
+ const mapping2 = requiredMap2(
12999
+ value[index],
13000
+ `FLOW.QUERY.INDEXES index field ${index}`
13001
+ );
13002
+ const name = requiredBoundedText2(
13003
+ mapping2,
13004
+ "name",
13005
+ "FLOW.QUERY.INDEXES index field",
13006
+ 512
13007
+ );
13008
+ if (names.has(name)) {
13009
+ throw decodeError(`${context} contain duplicates`, value);
13010
+ }
13011
+ names.add(name);
13012
+ fields[index] = Object.freeze({
13013
+ name,
13014
+ direction: requiredChoice(
13015
+ mapping2,
13016
+ "direction",
13017
+ "FLOW.QUERY.INDEXES index field",
13018
+ ["asc", "desc"]
13019
+ ),
13020
+ encoding: requiredChoice(
13021
+ mapping2,
13022
+ "encoding",
13023
+ "FLOW.QUERY.INDEXES index field",
13024
+ ["hashed", "ordered"]
13025
+ ),
13026
+ raw: freezeMap(mapping2)
13027
+ });
13028
+ }
13029
+ return Object.freeze(fields);
13030
+ }
13031
+ function decodeCountPrefixes(value, fieldCount) {
13032
+ const context = "FLOW.QUERY.INDEXES index count_prefixes";
13033
+ if (!Array.isArray(value) || value.length > fieldCount) {
13034
+ throw decodeError(`${context} are invalid`, value);
13035
+ }
13036
+ const prefixes = new Array(value.length);
13037
+ let previous = 0;
13038
+ for (let index = 0; index < value.length; index += 1) {
13039
+ if (!Object.hasOwn(value, index)) {
13040
+ throw decodeError(`${context} must be dense`, value);
13041
+ }
13042
+ const prefix = positiveInteger(value[index], `${context} entry`);
13043
+ if (prefix > fieldCount || prefix <= previous) {
13044
+ throw decodeError(`${context} are invalid`, value);
13045
+ }
13046
+ prefixes[index] = prefix;
13047
+ previous = prefix;
13048
+ }
13049
+ return Object.freeze(prefixes);
13050
+ }
13051
+ function decodeCoveringFields(mapping2) {
13052
+ const raw = field(mapping2, "covering_fields");
13053
+ if (!Array.isArray(raw) || raw.length > 32) {
13054
+ throw decodeError(
13055
+ "FLOW.QUERY.INDEXES index covering_fields must contain at most 32 entries",
13056
+ raw
13057
+ );
13058
+ }
13059
+ const fields = new Array(raw.length);
13060
+ const seen = /* @__PURE__ */ new Set();
13061
+ for (let index = 0; index < raw.length; index += 1) {
13062
+ if (!Object.hasOwn(raw, index)) {
13063
+ throw decodeError(
13064
+ "FLOW.QUERY.INDEXES index covering_fields must be dense",
13065
+ raw
13066
+ );
13067
+ }
13068
+ const value = boundedText(
13069
+ raw[index],
13070
+ `FLOW.QUERY.INDEXES index covering_fields entry ${index}`,
13071
+ 512
13072
+ );
13073
+ if (seen.has(value)) {
13074
+ throw decodeError(
13075
+ "FLOW.QUERY.INDEXES index covering_fields contains duplicates",
13076
+ raw
13077
+ );
13078
+ }
13079
+ seen.add(value);
13080
+ fields[index] = value;
13081
+ }
13082
+ return Object.freeze(fields);
13083
+ }
13084
+ function decodeIndexFormat(mapping2) {
13085
+ const raw = requiredMap2(
13086
+ field(mapping2, "format"),
13087
+ "FLOW.QUERY.INDEXES index format"
13088
+ );
13089
+ if (!hasKey(raw, "counter")) {
13090
+ throw decodeError(
13091
+ "FLOW.QUERY.INDEXES index format counter is missing",
13092
+ raw
13093
+ );
13094
+ }
13095
+ const counterValue = field(raw, "counter");
13096
+ const counter = counterValue === null ? void 0 : boundedText(
13097
+ counterValue,
13098
+ "FLOW.QUERY.INDEXES index format counter",
13099
+ 128
13100
+ );
13101
+ return Object.freeze({
13102
+ queryRow: requiredBoundedText2(
13103
+ raw,
13104
+ "query_row",
13105
+ "FLOW.QUERY.INDEXES index format",
13106
+ 128
13107
+ ),
13108
+ key: requiredBoundedText2(
13109
+ raw,
13110
+ "key",
13111
+ "FLOW.QUERY.INDEXES index format",
13112
+ 128
13113
+ ),
13114
+ entry: requiredBoundedText2(
13115
+ raw,
13116
+ "entry",
13117
+ "FLOW.QUERY.INDEXES index format",
13118
+ 128
13119
+ ),
13120
+ reverse: requiredBoundedText2(
13121
+ raw,
13122
+ "reverse",
13123
+ "FLOW.QUERY.INDEXES index format",
13124
+ 128
13125
+ ),
13126
+ counter,
13127
+ raw: freezeMap(raw)
13128
+ });
13129
+ }
13130
+
13131
+ // src/flow-query-response.ts
13132
+ var FLOW_QUERY_RESULT_CONTRACT = "ferric.flow.query.result/v1";
13133
+ var FLOW_EXPLAIN_CONTRACT = "ferric.flow.explain/v1";
13134
+ var USAGE_FIELDS2 = [
13135
+ ["range_seeks", "rangeSeeks"],
13136
+ ["range_pages", "rangePages"],
13137
+ ["scanned_entries", "scannedEntries"],
13138
+ ["scanned_bytes", "scannedBytes"],
13139
+ ["hydrated_records", "hydratedRecords"],
13140
+ ["residual_checks", "residualChecks"],
13141
+ ["duplicate_entries", "duplicateEntries"],
13142
+ ["result_records", "resultRecords"],
13143
+ ["response_bytes", "responseBytes"],
13144
+ ["memory_high_water_bytes", "memoryHighWaterBytes"],
13145
+ ["wall_time_us", "wallTimeUs"]
13146
+ ];
13147
+ var MAX_SIGNED_643 = (1n << 63n) - 1n;
13148
+ var QUALITY_VALUES2 = {
13149
+ exactness: ["authoritative", "projected_exact", "exact", "not_applicable"],
13150
+ freshness: ["current", "projection_watermark", "not_applicable"],
13151
+ coverage: ["complete", "unavailable"],
13152
+ pagination: ["none", "complete", "authenticated_seek", "live_seek"]
13153
+ };
13154
+ function decodeFlowQueryResult(value) {
13155
+ const result = decodeFlowQueryResponse(value, (mapping2) => freezeMap(mapping2));
13156
+ if (result.kind === "records") {
13157
+ return Object.freeze({
13158
+ kind: "records",
13159
+ version: FLOW_QUERY_RESULT_CONTRACT,
13160
+ records: Object.freeze(result.records),
13161
+ page: result.page,
13162
+ quality: result.quality,
13163
+ usage: result.usage,
13164
+ raw: result.raw
13165
+ });
13166
+ }
13167
+ return Object.freeze({
13168
+ kind: "count",
13169
+ version: FLOW_QUERY_RESULT_CONTRACT,
13170
+ count: result.count,
13171
+ quality: result.quality,
13172
+ usage: result.usage,
13173
+ raw: result.raw
13174
+ });
13175
+ }
13176
+ function decodeFlowQueryRecords(value, decodeRecord) {
13177
+ const result = decodeFlowQueryResponse(
13178
+ value,
13179
+ (mapping2, index) => decodeRecord(mapping2 instanceof Map ? freezeMap(mapping2) : mapping2, index)
13180
+ );
13181
+ if (result.kind === "count") {
13182
+ throw new FerricStoreError("Flow record convenience query returned a count result", {
13183
+ raw: result.raw
13184
+ });
13185
+ }
13186
+ return result.records;
13187
+ }
13188
+ function decodeFlowQueryResponse(value, decodeRecord) {
13189
+ const mapping2 = requiredMap2(value, "FLOW.QUERY result");
13190
+ requireContract(mapping2, "version", FLOW_QUERY_RESULT_CONTRACT, "FLOW.QUERY result");
13191
+ const quality = decodeQuality(field(mapping2, "quality"));
13192
+ const usage = decodeUsage(field(mapping2, "usage"));
13193
+ const hasRecords = hasKey(mapping2, "records");
13194
+ const hasCount = hasKey(mapping2, "result");
13195
+ if (hasRecords === hasCount) {
13196
+ throw decodeError("FLOW.QUERY result must contain exactly one records or count shape", value);
13197
+ }
13198
+ const raw = freezeMap(mapping2);
13199
+ if (hasRecords) {
13200
+ const rawRecords = field(mapping2, "records");
13201
+ if (!Array.isArray(rawRecords) || rawRecords.length > 100) {
13202
+ throw decodeError("FLOW.QUERY records must be an array of at most 100 maps", value);
13203
+ }
13204
+ const records = new Array(rawRecords.length);
13205
+ for (let index = 0; index < rawRecords.length; index += 1) {
13206
+ if (!Object.hasOwn(rawRecords, index)) {
13207
+ throw decodeError("FLOW.QUERY records must be a dense array", value);
13208
+ }
13209
+ records[index] = decodeRecord(
13210
+ requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
13211
+ index
13212
+ );
13213
+ }
13214
+ if (usage.resultRecords !== records.length || usage.resultRecords > usage.scannedEntries) {
13215
+ throw decodeError("FLOW.QUERY usage result_records is inconsistent with records", value);
13216
+ }
13217
+ return {
13218
+ kind: "records",
13219
+ records,
13220
+ page: decodePage(field(mapping2, "page")),
13221
+ quality,
13222
+ usage,
13223
+ raw
13224
+ };
13225
+ }
13226
+ if (hasKey(mapping2, "page")) {
13227
+ throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
13228
+ }
13229
+ const countResult = requiredMap2(field(mapping2, "result"), "FLOW.QUERY count result");
13230
+ if (requiredText2(countResult, "kind", "FLOW.QUERY count result") !== "count") {
13231
+ throw decodeError("FLOW.QUERY count result kind must be count", value);
13232
+ }
13233
+ const count = boundedInteger(
13234
+ field(countResult, "value"),
13235
+ MAX_SIGNED_643,
13236
+ "FLOW.QUERY count value"
13237
+ );
13238
+ if (usage.resultRecords !== 1) {
13239
+ throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
13240
+ }
13241
+ return {
13242
+ kind: "count",
13243
+ count,
13244
+ quality,
13245
+ usage,
13246
+ raw
13247
+ };
13248
+ }
13249
+ function decodeFlowExplainResult(value) {
13250
+ const mapping2 = requiredMap2(value, "FLOW.QUERY explain");
13251
+ requireContract(mapping2, "version", FLOW_EXPLAIN_CONTRACT, "FLOW.QUERY explain");
13252
+ const queryFingerprint = requiredText2(mapping2, "query_fingerprint", "FLOW.QUERY explain");
13253
+ if (!/^[0-9a-f]{64}$/iu.test(queryFingerprint)) {
13254
+ throw decodeError("FLOW.QUERY explain query_fingerprint is invalid", value);
13255
+ }
13256
+ const status = requiredText2(mapping2, "status", "FLOW.QUERY explain");
13257
+ if (status !== "planned" && status !== "rejected" && status !== "executed") {
13258
+ throw decodeError(`FLOW.QUERY explain status ${JSON.stringify(status)} is unsupported`, value);
13259
+ }
13260
+ const plan = freezeMetadataMap(
13261
+ requiredMap2(field(mapping2, "plan"), "FLOW.QUERY explain plan"),
13262
+ "FLOW.QUERY explain plan"
13263
+ );
13264
+ const estimate = freezeMetadataMap(
13265
+ requiredMap2(field(mapping2, "estimate"), "FLOW.QUERY explain estimate"),
13266
+ "FLOW.QUERY explain estimate"
13267
+ );
13268
+ const bounds = freezeMetadataMap(
13269
+ requiredMap2(field(mapping2, "bounds"), "FLOW.QUERY explain bounds"),
13270
+ "FLOW.QUERY explain bounds"
13271
+ );
13272
+ const capabilities = decodeExplainCapabilities(mapping2);
13273
+ const extendedFields = ["stats", "quality", "pressure", "decision", "alternatives"];
13274
+ const extendedPresence = extendedFields.map((name) => hasKey(mapping2, name));
13275
+ const specialized = capabilities != null && !extendedPresence.some(Boolean);
13276
+ let stats;
13277
+ let quality;
13278
+ let pressure;
13279
+ let decision;
13280
+ let alternatives;
13281
+ if (specialized) {
13282
+ if (status !== "planned") {
13283
+ throw decodeError("FLOW.QUERY specialized explain must be planned", value);
13284
+ }
13285
+ if (hasKey(mapping2, "actual") || hasKey(mapping2, "diagnostic")) {
13286
+ throw decodeError("FLOW.QUERY specialized explain has extended status fields", value);
13287
+ }
13288
+ alternatives = Object.freeze([]);
13289
+ } else {
13290
+ if (!extendedPresence.every(Boolean) || !hasKey(mapping2, "actual") || !hasKey(mapping2, "diagnostic")) {
13291
+ throw decodeError("FLOW.QUERY explain is missing required v1 fields", value);
13292
+ }
13293
+ stats = freezeMetadataMap(
13294
+ requiredMap2(field(mapping2, "stats"), "FLOW.QUERY explain stats"),
13295
+ "FLOW.QUERY explain stats"
13296
+ );
13297
+ quality = decodeQuality(field(mapping2, "quality"));
13298
+ pressure = freezeMetadataMap(
13299
+ requiredMap2(field(mapping2, "pressure"), "FLOW.QUERY explain pressure"),
13300
+ "FLOW.QUERY explain pressure"
13301
+ );
13302
+ decision = freezeMetadataMap(
13303
+ requiredMap2(field(mapping2, "decision"), "FLOW.QUERY explain decision"),
13304
+ "FLOW.QUERY explain decision"
13305
+ );
13306
+ alternatives = decodeExplainAlternatives(field(mapping2, "alternatives"));
13307
+ }
13308
+ const actualValue = field(mapping2, "actual");
13309
+ let actual;
13310
+ if (status === "executed") {
13311
+ if (actualValue == null) {
13312
+ throw decodeError("FLOW.QUERY executed explain is missing actual usage", value);
13313
+ }
13314
+ actual = decodeUsage(actualValue);
13315
+ } else if (actualValue != null) {
13316
+ throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
13317
+ }
13318
+ const diagnosticValue = field(mapping2, "diagnostic");
13319
+ let diagnostic;
13320
+ if (status === "rejected") {
13321
+ diagnostic = tryDecodeFlowQueryError(diagnosticValue);
13322
+ if (diagnostic == null) {
13323
+ throw decodeError("FLOW.QUERY rejected explain has an invalid diagnostic", value);
13324
+ }
13325
+ } else if (diagnosticValue != null) {
13326
+ throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
13327
+ }
13328
+ return Object.freeze({
13329
+ version: FLOW_EXPLAIN_CONTRACT,
13330
+ queryFingerprint,
13331
+ status,
13332
+ plan,
13333
+ estimate,
13334
+ stats,
13335
+ quality,
13336
+ bounds,
13337
+ pressure,
13338
+ decision,
13339
+ alternatives,
13340
+ capabilities,
13341
+ actual,
13342
+ diagnostic,
13343
+ raw: freezeMap(mapping2)
12241
13344
  });
12242
13345
  }
12243
- function decodeIndex(value, index) {
12244
- const mapping = requiredMap2(value, `FLOW.QUERY.INDEXES index ${index}`);
13346
+ function decodeExplainCapabilities(mapping2) {
13347
+ if (!hasKey(mapping2, "capabilities")) return void 0;
13348
+ const value = requiredMap2(
13349
+ field(mapping2, "capabilities"),
13350
+ "FLOW.QUERY explain capabilities"
13351
+ );
12245
13352
  return Object.freeze({
12246
- id: requiredText2(mapping, "id", "FLOW.QUERY.INDEXES index"),
12247
- version: positiveBoundedInteger(
12248
- field(mapping, "version"),
12249
- MAX_UNSIGNED_64,
12250
- "FLOW.QUERY.INDEXES index version"
12251
- ),
12252
- buildId: requiredText2(mapping, "build_id", "FLOW.QUERY.INDEXES index"),
12253
- state: requiredText2(mapping, "state", "FLOW.QUERY.INDEXES index"),
12254
- queryable: requiredBoolean(mapping, "queryable", "FLOW.QUERY.INDEXES index"),
12255
- coveringFields: decodeCoveringFields(mapping),
12256
- format: decodeIndexFormat(mapping),
12257
- raw: freezeMap(mapping)
13353
+ requested: decodeExplainCapabilityList(field(value, "requested"), "requested"),
13354
+ available: decodeExplainCapabilityList(field(value, "available"), "available"),
13355
+ missing: decodeExplainCapabilityList(field(value, "missing"), "missing"),
13356
+ raw: freezeMetadataMap(value, "FLOW.QUERY explain capabilities")
12258
13357
  });
12259
13358
  }
12260
- function decodeCoveringFields(mapping) {
12261
- const raw = field(mapping, "covering_fields");
12262
- if (!Array.isArray(raw) || raw.length > 32) {
13359
+ function decodeExplainCapabilityList(value, name) {
13360
+ if (!Array.isArray(value) || value.length > 64) {
12263
13361
  throw decodeError(
12264
- "FLOW.QUERY.INDEXES index covering_fields must contain at most 32 entries",
12265
- raw
13362
+ `FLOW.QUERY explain capabilities ${name} must contain at most 64 entries`,
13363
+ value
12266
13364
  );
12267
13365
  }
12268
- const fields = new Array(raw.length);
13366
+ const result = new Array(value.length);
12269
13367
  const seen = /* @__PURE__ */ new Set();
12270
- for (let index = 0; index < raw.length; index += 1) {
12271
- if (!Object.hasOwn(raw, index)) {
12272
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields must be dense", raw);
13368
+ for (let index = 0; index < value.length; index += 1) {
13369
+ if (!Object.hasOwn(value, index)) {
13370
+ throw decodeError(`FLOW.QUERY explain capabilities ${name} must be dense`, value);
12273
13371
  }
12274
- const value = boundedText(
12275
- raw[index],
12276
- `FLOW.QUERY.INDEXES index covering_fields entry ${index}`,
12277
- 512
13372
+ const capability = boundedText(
13373
+ value[index],
13374
+ `FLOW.QUERY explain capabilities ${name} entry ${index}`,
13375
+ 128
12278
13376
  );
12279
- if (seen.has(value)) {
12280
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields contains duplicates", raw);
13377
+ if (seen.has(capability)) {
13378
+ throw decodeError(
13379
+ `FLOW.QUERY explain capabilities ${name} contains duplicates`,
13380
+ value
13381
+ );
12281
13382
  }
12282
- seen.add(value);
12283
- fields[index] = value;
13383
+ seen.add(capability);
13384
+ result[index] = capability;
12284
13385
  }
12285
- return Object.freeze(fields);
13386
+ return Object.freeze(result);
12286
13387
  }
12287
- function decodeIndexFormat(mapping) {
12288
- const raw = requiredMap2(field(mapping, "format"), "FLOW.QUERY.INDEXES index format");
12289
- if (!hasKey(raw, "counter")) {
12290
- throw decodeError("FLOW.QUERY.INDEXES index format counter is missing", raw);
13388
+ function decodeExplainAlternatives(value) {
13389
+ if (!Array.isArray(value) || value.length > 31) {
13390
+ throw decodeError("FLOW.QUERY explain alternatives must be an array of at most 31 maps", value);
12291
13391
  }
12292
- const counterValue = field(raw, "counter");
12293
- const counter = counterValue === null ? void 0 : boundedText(counterValue, "FLOW.QUERY.INDEXES index format counter", 128);
12294
- return Object.freeze({
12295
- queryRow: requiredBoundedText2(raw, "query_row", "FLOW.QUERY.INDEXES index format", 128),
12296
- key: requiredBoundedText2(raw, "key", "FLOW.QUERY.INDEXES index format", 128),
12297
- entry: requiredBoundedText2(raw, "entry", "FLOW.QUERY.INDEXES index format", 128),
12298
- reverse: requiredBoundedText2(raw, "reverse", "FLOW.QUERY.INDEXES index format", 128),
12299
- counter,
12300
- raw: freezeMap(raw)
12301
- });
13392
+ const alternatives = new Array(value.length);
13393
+ for (let index = 0; index < value.length; index += 1) {
13394
+ if (!Object.hasOwn(value, index)) {
13395
+ throw decodeError("FLOW.QUERY explain alternatives must be dense", value);
13396
+ }
13397
+ alternatives[index] = freezeMetadataMap(
13398
+ requiredMap2(value[index], `FLOW.QUERY explain alternative ${index}`),
13399
+ `FLOW.QUERY explain alternative ${index}`
13400
+ );
13401
+ }
13402
+ return Object.freeze(alternatives);
12302
13403
  }
12303
13404
  function decodeQuality(value) {
12304
- const mapping = requiredMap2(value, "FLOW.QUERY quality");
13405
+ const mapping2 = requiredMap2(value, "FLOW.QUERY quality");
12305
13406
  return Object.freeze({
12306
- exactness: requiredBoundedText2(mapping, "exactness", "FLOW.QUERY quality", 64),
12307
- freshness: requiredBoundedText2(mapping, "freshness", "FLOW.QUERY quality", 64),
12308
- coverage: requiredBoundedText2(mapping, "coverage", "FLOW.QUERY quality", 64),
12309
- pagination: requiredBoundedText2(mapping, "pagination", "FLOW.QUERY quality", 64)
13407
+ exactness: decodeQualityValue(mapping2, "exactness", QUALITY_VALUES2.exactness),
13408
+ freshness: decodeQualityValue(mapping2, "freshness", QUALITY_VALUES2.freshness),
13409
+ coverage: decodeQualityValue(mapping2, "coverage", QUALITY_VALUES2.coverage),
13410
+ pagination: decodeQualityValue(mapping2, "pagination", QUALITY_VALUES2.pagination)
12310
13411
  });
12311
13412
  }
13413
+ function decodeQualityValue(mapping2, name, allowed) {
13414
+ const value = requiredBoundedText2(mapping2, name, "FLOW.QUERY quality", 64);
13415
+ if (!allowed.includes(value)) {
13416
+ throw decodeError(`FLOW.QUERY quality ${name} is unsupported`, mapping2);
13417
+ }
13418
+ return value;
13419
+ }
12312
13420
  function decodeUsage(value) {
12313
- const mapping = requiredMap2(value, "FLOW.QUERY usage");
13421
+ const mapping2 = requiredMap2(value, "FLOW.QUERY usage");
12314
13422
  const usage = /* @__PURE__ */ Object.create(null);
12315
13423
  for (const [wireName, property] of USAGE_FIELDS2) {
12316
13424
  usage[property] = nonNegativeInteger(
12317
- field(mapping, wireName),
13425
+ field(mapping2, wireName),
12318
13426
  `FLOW.QUERY usage ${wireName}`
12319
13427
  );
12320
13428
  }
12321
- return Object.freeze(usage);
13429
+ const decoded = usage;
13430
+ if (decoded.hydratedRecords > decoded.scannedEntries || decoded.duplicateEntries > decoded.scannedEntries || decoded.rangePages > decoded.scannedEntries + decoded.rangeSeeks || decoded.residualChecks > decoded.scannedEntries * 12) {
13431
+ throw decodeError("FLOW.QUERY usage counters are inconsistent", value);
13432
+ }
13433
+ return Object.freeze(decoded);
12322
13434
  }
12323
13435
  function decodePage(value) {
12324
- const mapping = requiredMap2(value, "FLOW.QUERY page");
12325
- const hasMore = requiredBoolean(mapping, "has_more", "FLOW.QUERY page");
12326
- const cursor = optionalText2(mapping, "cursor", "FLOW.QUERY page");
12327
- if (cursor != null && (!cursor.startsWith("fqc1_") || Buffer45.byteLength(cursor) > 4096)) {
13436
+ const mapping2 = requiredMap2(value, "FLOW.QUERY page");
13437
+ const hasMore = requiredBoolean(mapping2, "has_more", "FLOW.QUERY page");
13438
+ const cursor = optionalText2(mapping2, "cursor", "FLOW.QUERY page");
13439
+ if (cursor != null && (!cursor.startsWith("fqc1_") || Buffer48.byteLength(cursor) < 16 || Buffer48.byteLength(cursor) > 4096)) {
12328
13440
  throw decodeError("FLOW.QUERY page cursor is invalid", value);
12329
13441
  }
12330
13442
  if (hasMore !== (cursor != null)) {
@@ -12332,26 +13444,14 @@ function decodePage(value) {
12332
13444
  }
12333
13445
  return Object.freeze({ hasMore, ...cursor == null ? {} : { cursor } });
12334
13446
  }
12335
- function decodePosition(value) {
12336
- if (value == null) return void 0;
12337
- const mapping = requiredMap2(value, "FLOW.QUERY diagnostic position");
12338
- return Object.freeze({
12339
- byte: positiveInteger(field(mapping, "byte"), "FLOW.QUERY diagnostic position byte"),
12340
- line: positiveInteger(field(mapping, "line"), "FLOW.QUERY diagnostic position line"),
12341
- column: positiveInteger(
12342
- field(mapping, "column"),
12343
- "FLOW.QUERY diagnostic position column"
12344
- )
12345
- });
12346
- }
12347
13447
 
12348
13448
  // src/client-core.ts
12349
13449
  import "buffer";
12350
13450
 
12351
13451
  // src/client-core-helpers.ts
12352
- import { Buffer as Buffer46 } from "buffer";
13452
+ import { Buffer as Buffer49 } from "buffer";
12353
13453
  function bgsaveResponse(response) {
12354
- if ((typeof response === "string" || Buffer46.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
13454
+ if ((typeof response === "string" || Buffer49.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
12355
13455
  return true;
12356
13456
  }
12357
13457
  return okResponse(response);
@@ -12365,7 +13465,7 @@ function fetchOrComputeCompletionToken(options) {
12365
13465
  "fetch-or-compute completion requires computeToken"
12366
13466
  );
12367
13467
  }
12368
- if (!Buffer46.isBuffer(options.computeToken)) {
13468
+ if (!Buffer49.isBuffer(options.computeToken)) {
12369
13469
  throw new TypeError("fetch-or-compute computeToken must be a Buffer");
12370
13470
  }
12371
13471
  return options.computeToken;
@@ -12451,12 +13551,12 @@ var ErrorMappingExecutor = class {
12451
13551
 
12452
13552
  // src/client-config.ts
12453
13553
  function snapshotClientOptions(options) {
12454
- const own = { ...options };
13554
+ const own2 = { ...options };
12455
13555
  const snapshot = {
12456
- ...own,
12457
- ...typeof own.autoBatch === "object" && own.autoBatch != null ? { autoBatch: Object.freeze({ ...own.autoBatch }) } : {},
12458
- ...own.backpressure == null ? {} : { backpressure: Object.freeze({ ...own.backpressure }) },
12459
- ...typeof own.reconnect === "object" && own.reconnect != null ? { reconnect: Object.freeze({ ...own.reconnect }) } : {}
13556
+ ...own2,
13557
+ ...typeof own2.autoBatch === "object" && own2.autoBatch != null ? { autoBatch: Object.freeze({ ...own2.autoBatch }) } : {},
13558
+ ...own2.backpressure == null ? {} : { backpressure: Object.freeze({ ...own2.backpressure }) },
13559
+ ...typeof own2.reconnect === "object" && own2.reconnect != null ? { reconnect: Object.freeze({ ...own2.reconnect }) } : {}
12460
13560
  };
12461
13561
  return Object.freeze(snapshot);
12462
13562
  }
@@ -12725,14 +13825,14 @@ var FerricStoreAdministrationClient = class extends FerricStoreClientBase {
12725
13825
  };
12726
13826
 
12727
13827
  // src/store-utilities.ts
12728
- import { Buffer as Buffer49 } from "buffer";
13828
+ import { Buffer as Buffer52 } from "buffer";
12729
13829
  function encode(codec, value) {
12730
13830
  return codec.encode(value);
12731
13831
  }
12732
13832
  function decode(codec, value) {
12733
13833
  if (value == null) return null;
12734
- if (Buffer49.isBuffer(value)) return codec.decode(value);
12735
- if (value instanceof Uint8Array) return codec.decode(Buffer49.from(value));
13834
+ if (Buffer52.isBuffer(value)) return codec.decode(value);
13835
+ if (value instanceof Uint8Array) return codec.decode(Buffer52.from(value));
12736
13836
  return value;
12737
13837
  }
12738
13838
  function number(value) {
@@ -14645,33 +15745,511 @@ var FerricStoreClientCore = class extends FerricStoreAdministrationClient {
14645
15745
  }
14646
15746
  };
14647
15747
 
14648
- // src/client-management.ts
14649
- var FerricStoreManagementClient = class extends FerricStoreClientCore {
14650
- async stats(type, options = {}) {
14651
- const args = ["FLOW.STATS", type];
14652
- append(args, "STATE", options.state);
14653
- append(args, "COUNT", options.count);
14654
- append(args, "PARTITION", options.partitionKey);
14655
- appendAttributes(args, options.attributes);
14656
- appendBool(args, "CONSISTENT_PROJECTION", options.consistentProjection);
14657
- return adminRecordResponse(await this.commandArgs(args), "FLOW.STATS");
15748
+ // src/schedule-request.ts
15749
+ var MAX_EXACT_INTEGER = Number.MAX_SAFE_INTEGER;
15750
+ var SCHEDULE_KINDS = /* @__PURE__ */ new Set(["one_shot", "delay", "interval", "cron"]);
15751
+ var RECURRING_KINDS = /* @__PURE__ */ new Set(["interval", "cron"]);
15752
+ var OVERLAP_POLICIES = /* @__PURE__ */ new Set([
15753
+ "allow",
15754
+ "skip",
15755
+ "queue_after_previous",
15756
+ "fail_schedule"
15757
+ ]);
15758
+ var SCHEDULE_STATES = /* @__PURE__ */ new Set([
15759
+ "active",
15760
+ "paused",
15761
+ "running",
15762
+ "completed",
15763
+ "failed",
15764
+ "cancelled",
15765
+ "all"
15766
+ ]);
15767
+ var TARGET_FIELDS = /* @__PURE__ */ new Set([
15768
+ "correlation_id",
15769
+ "id",
15770
+ "id_prefix",
15771
+ "parent_flow_id",
15772
+ "partition_key",
15773
+ "payload",
15774
+ "payload_ref",
15775
+ "priority",
15776
+ "root_flow_id",
15777
+ "run_at_ms",
15778
+ "state",
15779
+ "type",
15780
+ "value_refs",
15781
+ "values"
15782
+ ]);
15783
+ var TARGET_TEXT_FIELDS = [
15784
+ "state",
15785
+ "id",
15786
+ "id_prefix",
15787
+ "partition_key",
15788
+ "correlation_id",
15789
+ "parent_flow_id",
15790
+ "root_flow_id",
15791
+ "payload_ref"
15792
+ ];
15793
+ var INTERNAL_SCHEDULE_TYPE = "__ferricstore_schedule";
15794
+ var INTERNAL_SCHEDULE_ID_PREFIX = "__ferricstore_schedule__:";
15795
+ function validateScheduleCreate(id, options) {
15796
+ requiredText3(id, "id");
15797
+ if (!mapping(options)) throw new TypeError("schedule options must be a mapping");
15798
+ const kind = effectiveKind(options);
15799
+ validateTiming(kind, options);
15800
+ const recurring = RECURRING_KINDS.has(kind);
15801
+ validateTarget(options.target, recurring);
15802
+ nonNegativeInteger2(options.atMs, "at_ms");
15803
+ nonNegativeInteger2(options.delayMs, "delay_ms");
15804
+ nonNegativeInteger2(options.startAtMs, "start_at_ms");
15805
+ positiveInteger2(options.everyMs, "every_ms");
15806
+ positiveInteger2(options.overlapRetryMs, "overlap_retry_ms");
15807
+ positiveInteger2(options.maxFires, "max_fires");
15808
+ nonNegativeInteger2(options.endAtMs, "end_at_ms");
15809
+ nonNegativeInteger2(options.nowMs, "now_ms");
15810
+ optionalBoolean(options.overwrite, "overwrite");
15811
+ if (kind === "delay") {
15812
+ if (options.delayMs == null) throw new TypeError("delay_ms is required for delay schedules");
15813
+ if (options.nowMs != null && options.nowMs > MAX_EXACT_INTEGER - options.delayMs) {
15814
+ throw new TypeError("now_ms plus delay_ms exceeds the exact integer range");
15815
+ }
15816
+ }
15817
+ if (kind === "interval" && options.everyMs == null) {
15818
+ throw new TypeError("every_ms is required for interval schedules");
15819
+ }
15820
+ if (kind === "cron") {
15821
+ requiredText3(options.cron, "cron");
15822
+ optionalText3(options.timezone, "timezone");
15823
+ } else if (options.timezone != null) {
15824
+ throw new TypeError("timezone is only supported for cron schedules");
15825
+ }
15826
+ if (kind === "interval") {
15827
+ if (options.catchupPolicy != null && options.catchupPolicy !== "fire_once") {
15828
+ throw new TypeError("catchup_policy must be fire_once");
15829
+ }
15830
+ } else if (options.catchupPolicy != null) {
15831
+ throw new TypeError("catchup_policy is only supported for interval schedules");
15832
+ }
15833
+ if (recurring) {
15834
+ if (options.overlapPolicy != null && !OVERLAP_POLICIES.has(options.overlapPolicy)) {
15835
+ throw new TypeError(
15836
+ "overlap_policy must be allow, skip, queue_after_previous, or fail_schedule"
15837
+ );
15838
+ }
15839
+ if (options.overlapRetryMs != null && options.overlapPolicy !== "queue_after_previous") {
15840
+ throw new TypeError("overlap_retry_ms requires overlap_policy queue_after_previous");
15841
+ }
15842
+ const firstRun = knownFirstRun(kind, options);
15843
+ if (options.endAtMs != null && firstRun != null && options.endAtMs < firstRun) {
15844
+ throw new TypeError("end_at_ms must be at or after first run");
15845
+ }
15846
+ } else {
15847
+ if (options.overlapPolicy != null) {
15848
+ throw new TypeError("overlap_policy is only supported for recurring schedules");
15849
+ }
15850
+ if (options.overlapRetryMs != null) {
15851
+ throw new TypeError("overlap_retry_ms requires overlap_policy queue_after_previous");
15852
+ }
15853
+ if (options.maxFires != null) {
15854
+ throw new TypeError("max_fires is only supported for recurring schedules");
15855
+ }
15856
+ if (options.endAtMs != null) {
15857
+ throw new TypeError("end_at_ms is only supported for recurring schedules");
15858
+ }
14658
15859
  }
14659
- async countByState(type, state, options = {}) {
14660
- const stats = await this.stats(type, { ...options, state });
14661
- if (!("count" in stats)) throw new TypeError("FLOW.STATS response missing count");
14662
- return integer(stats.count);
15860
+ }
15861
+ function validateScheduleID(id) {
15862
+ requiredText3(id, "id");
15863
+ }
15864
+ function validateScheduleFire(id, options) {
15865
+ validateScheduleID(id);
15866
+ nonNegativeInteger2(options.fireAtMs, "fire_at_ms");
15867
+ nonNegativeInteger2(options.nowMs, "now_ms");
15868
+ }
15869
+ function validateScheduleStatus(id, nowMs2) {
15870
+ validateScheduleID(id);
15871
+ nonNegativeInteger2(nowMs2, "now_ms");
15872
+ }
15873
+ function validateScheduleFireDue(options) {
15874
+ if (!mapping(options)) throw new TypeError("schedule fire-due options must be a mapping");
15875
+ nonNegativeInteger2(options.nowMs, "now_ms");
15876
+ optionalText3(options.worker, "worker");
15877
+ positiveInteger2(options.leaseMs, "lease_ms");
15878
+ nonNegativeInteger2(options.blockMs, "block_ms");
15879
+ positiveInteger2(options.limit, "limit");
15880
+ const leaseMs = options.leaseMs ?? 3e4;
15881
+ if (options.nowMs != null && options.nowMs > MAX_EXACT_INTEGER - leaseMs) {
15882
+ throw new TypeError("now_ms plus lease_ms exceeds the exact integer range");
14663
15883
  }
14664
- async attributes(type, options = {}) {
14665
- const args = ["FLOW.ATTRIBUTES", type];
14666
- appendAttributeQueryOptions(args, options);
14667
- return adminRecordList(await this.commandArgs(args), "FLOW.ATTRIBUTES");
15884
+ }
15885
+ function validateScheduleList(options) {
15886
+ if (!mapping(options)) throw new TypeError("schedule list options must be a mapping");
15887
+ if (options.kind != null && !SCHEDULE_KINDS.has(options.kind)) {
15888
+ throw new TypeError("kind must be one_shot, delay, interval, or cron");
14668
15889
  }
14669
- async attributeValues(type, attribute, options = {}) {
14670
- const args = ["FLOW.ATTRIBUTE_VALUES", type, attribute];
14671
- appendAttributeQueryOptions(args, options);
14672
- return adminRecordList(await this.commandArgs(args), "FLOW.ATTRIBUTE_VALUES");
15890
+ if (options.state != null && !SCHEDULE_STATES.has(options.state)) {
15891
+ throw new TypeError("state is invalid");
15892
+ }
15893
+ optionalText3(options.timezone, "timezone");
15894
+ optionalText3(options.targetType, "target_type");
15895
+ nonNegativeInteger2(options.fromMs, "from_ms");
15896
+ nonNegativeInteger2(options.toMs, "to_ms");
15897
+ if (options.fromMs != null && options.toMs != null && options.fromMs > options.toMs) {
15898
+ throw new TypeError("from_ms must not exceed to_ms");
15899
+ }
15900
+ positiveInteger2(options.count, "count");
15901
+ optionalBoolean(options.rev, "rev");
15902
+ }
15903
+ function effectiveKind(options) {
15904
+ if (options.kind != null) {
15905
+ if (!SCHEDULE_KINDS.has(options.kind)) {
15906
+ throw new TypeError("kind must be one_shot, delay, interval, or cron");
15907
+ }
15908
+ return options.kind;
15909
+ }
15910
+ if (options.cron != null) return "cron";
15911
+ if (options.everyMs != null) return "interval";
15912
+ if (options.delayMs != null) return "delay";
15913
+ return "one_shot";
15914
+ }
15915
+ function validateTiming(kind, options) {
15916
+ if (options.atMs != null && options.startAtMs != null) {
15917
+ throw new TypeError("cannot set both at_ms and start_at_ms");
15918
+ }
15919
+ if (options.delayMs != null && kind !== "delay") {
15920
+ throw new TypeError("delay_ms is only supported for delay schedules");
15921
+ }
15922
+ if (options.everyMs != null && kind !== "interval") {
15923
+ throw new TypeError("every_ms is only supported for interval schedules");
15924
+ }
15925
+ if (options.cron != null && kind !== "cron") {
15926
+ throw new TypeError("cron is only supported for cron schedules");
15927
+ }
15928
+ if (kind === "delay" && (options.atMs != null || options.startAtMs != null)) {
15929
+ throw new TypeError("at_ms and start_at_ms are not supported for delay schedules");
14673
15930
  }
15931
+ }
15932
+ function validateTarget(value, recurring) {
15933
+ if (!mapping(value)) throw new TypeError("target must be a mapping with a non-empty type");
15934
+ const target = value;
15935
+ for (const key of Object.keys(target)) {
15936
+ if (!TARGET_FIELDS.has(key)) throw new TypeError(`target contains unknown field ${key}`);
15937
+ }
15938
+ const type = own(target, "type");
15939
+ requiredText3(type, "target type");
15940
+ if (type === INTERNAL_SCHEDULE_TYPE) {
15941
+ throw new TypeError("target type is reserved for internal use");
15942
+ }
15943
+ for (const key of TARGET_TEXT_FIELDS) optionalText3(own(target, key), `target ${key}`);
15944
+ const id = own(target, "id");
15945
+ const idPrefix = own(target, "id_prefix");
15946
+ if (id != null && idPrefix != null) {
15947
+ throw new TypeError("target cannot set both id and id_prefix");
15948
+ }
15949
+ if (recurring && id != null) {
15950
+ throw new TypeError("target id is not supported for recurring schedules; use id_prefix");
15951
+ }
15952
+ if (typeof id === "string" && id.startsWith(INTERNAL_SCHEDULE_ID_PREFIX)) {
15953
+ throw new TypeError("target id is reserved for internal use");
15954
+ }
15955
+ if (typeof idPrefix === "string" && idPrefix.startsWith(INTERNAL_SCHEDULE_ID_PREFIX)) {
15956
+ throw new TypeError("target id_prefix is reserved for internal use");
15957
+ }
15958
+ nonNegativeInteger2(own(target, "run_at_ms"), "target run_at_ms");
15959
+ const priority = own(target, "priority");
15960
+ if (priority != null && (typeof priority !== "number" || !Number.isSafeInteger(priority) || priority < 0 || priority > 2)) {
15961
+ throw new TypeError("target priority must be an integer between 0 and 2");
15962
+ }
15963
+ }
15964
+ function knownFirstRun(kind, options) {
15965
+ if (kind === "delay") {
15966
+ if (options.nowMs == null || options.delayMs == null) return void 0;
15967
+ return options.nowMs + options.delayMs;
15968
+ }
15969
+ if (kind === "cron") return options.startAtMs ?? options.atMs ?? options.nowMs;
15970
+ return options.atMs ?? options.startAtMs ?? options.nowMs;
15971
+ }
15972
+ function requiredText3(value, name) {
15973
+ if (typeof value !== "string" || value.length === 0) {
15974
+ throw new TypeError(`${name} must be a non-empty string`);
15975
+ }
15976
+ }
15977
+ function optionalText3(value, name) {
15978
+ if (value != null) requiredText3(value, name);
15979
+ }
15980
+ function nonNegativeInteger2(value, name) {
15981
+ if (value != null && (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)) {
15982
+ throw new TypeError(`${name} must be a non-negative exact integer`);
15983
+ }
15984
+ }
15985
+ function positiveInteger2(value, name) {
15986
+ if (value != null && (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)) {
15987
+ throw new TypeError(`${name} must be a positive exact integer`);
15988
+ }
15989
+ }
15990
+ function optionalBoolean(value, name) {
15991
+ if (value != null && typeof value !== "boolean") {
15992
+ throw new TypeError(`${name} must be a boolean`);
15993
+ }
15994
+ }
15995
+ function mapping(value) {
15996
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15997
+ }
15998
+ function own(mapping2, key) {
15999
+ return Object.prototype.hasOwnProperty.call(mapping2, key) ? mapping2[key] : void 0;
16000
+ }
16001
+
16002
+ // src/schedule-response.ts
16003
+ var SCHEDULE_INTEGER_FIELDS = [
16004
+ "end_at_ms",
16005
+ "last_catchup_at_ms",
16006
+ "last_fire_at_ms",
16007
+ "last_overlap_at_ms",
16008
+ "last_skipped_at_ms",
16009
+ "max_fires",
16010
+ "next_run_at_ms",
16011
+ "overlap_queued_due_at_ms"
16012
+ ];
16013
+ var REQUIRED_SCHEDULE_INTEGER_FIELDS = [
16014
+ "attempts",
16015
+ "coalesced_count",
16016
+ "fire_count",
16017
+ "last_coalesced_count",
16018
+ "skipped_count"
16019
+ ];
16020
+ var OPTIONAL_SCHEDULE_TEXT_FIELDS = [
16021
+ "cron",
16022
+ "end_reason",
16023
+ "flow_id",
16024
+ "last_overlap_reason",
16025
+ "last_overlap_target_id",
16026
+ "last_planning_error",
16027
+ "last_target_id",
16028
+ "timezone"
16029
+ ];
16030
+ var SCHEDULE_KINDS2 = /* @__PURE__ */ new Set(["one_shot", "delay", "interval", "cron"]);
16031
+ var SCHEDULE_STATES2 = /* @__PURE__ */ new Set([
16032
+ "active",
16033
+ "paused",
16034
+ "running",
16035
+ "completed",
16036
+ "failed",
16037
+ "cancelled"
16038
+ ]);
16039
+ var SCHEDULE_OVERLAP_POLICIES = /* @__PURE__ */ new Set([
16040
+ "allow",
16041
+ "skip",
16042
+ "queue_after_previous",
16043
+ "fail_schedule"
16044
+ ]);
16045
+ function scheduleRecordResponse(value, context) {
16046
+ const record = adminRecordResponse(value, context);
16047
+ if (typeof record.id !== "string" || record.id.length === 0) {
16048
+ throw new TypeError(`${context} response missing id`);
16049
+ }
16050
+ if (typeof record.kind !== "string" || !SCHEDULE_KINDS2.has(record.kind)) {
16051
+ throw new TypeError(`${context} response missing or invalid kind`);
16052
+ }
16053
+ if (typeof record.state !== "string" || !SCHEDULE_STATES2.has(record.state)) {
16054
+ throw new TypeError(`${context} response missing or invalid state`);
16055
+ }
16056
+ if (typeof record.target !== "object" || record.target == null || Array.isArray(record.target)) {
16057
+ throw new TypeError(`${context} response missing or invalid target`);
16058
+ }
16059
+ const target = record.target;
16060
+ if (typeof target.type !== "string" || target.type.length === 0) {
16061
+ throw new TypeError(`${context} response missing or invalid target type`);
16062
+ }
16063
+ if (!("catchup_policy" in record) || record.kind === "interval" && record.catchup_policy !== "fire_once" || record.kind !== "interval" && record.catchup_policy != null) {
16064
+ throw new TypeError(`${context} returned an invalid catchup_policy`);
16065
+ }
16066
+ if (typeof record.overlap_policy !== "string" || !SCHEDULE_OVERLAP_POLICIES.has(record.overlap_policy)) {
16067
+ throw new TypeError(`${context} response missing or invalid overlap_policy`);
16068
+ }
16069
+ safeNonNegativeInteger(record, "created_at_ms", context, true);
16070
+ const everyMs = requiredNullableNonNegativeInteger(record, "every_ms", context);
16071
+ const cron = requiredNullableResponseText(record, "cron", context);
16072
+ const timezone = requiredNullableResponseText(record, "timezone", context);
16073
+ const overlapRetryMs = requiredNullableNonNegativeInteger(
16074
+ record,
16075
+ "overlap_retry_ms",
16076
+ context
16077
+ );
16078
+ validateRecurrence(record, context, everyMs, cron, timezone, overlapRetryMs);
16079
+ let coalescedCount = 0;
16080
+ let lastCoalescedCount = 0;
16081
+ for (const field3 of REQUIRED_SCHEDULE_INTEGER_FIELDS) {
16082
+ const value2 = safeNonNegativeInteger(record, field3, context, true);
16083
+ if (field3 === "coalesced_count") coalescedCount = value2;
16084
+ if (field3 === "last_coalesced_count") lastCoalescedCount = value2;
16085
+ }
16086
+ for (const field3 of SCHEDULE_INTEGER_FIELDS) {
16087
+ if (record[field3] != null) safeNonNegativeInteger(record, field3, context);
16088
+ }
16089
+ for (const field3 of OPTIONAL_SCHEDULE_TEXT_FIELDS) {
16090
+ optionalResponseText(record, field3, context);
16091
+ }
16092
+ validateCatchupState(record, context, coalescedCount, lastCoalescedCount);
16093
+ return record;
16094
+ }
16095
+ function validateRecurrence(record, context, everyMs, cron, timezone, overlapRetryMs) {
16096
+ if (record.kind === "interval") {
16097
+ if (everyMs == null || everyMs <= 0) {
16098
+ throw new TypeError(`${context} interval every_ms must be positive`);
16099
+ }
16100
+ } else if (everyMs != null) {
16101
+ throw new TypeError(`${context} every_ms is only valid for interval schedules`);
16102
+ }
16103
+ if (record.kind === "cron") {
16104
+ if (cron == null) throw new TypeError(`${context} cron schedule is missing cron`);
16105
+ if (timezone == null) throw new TypeError(`${context} cron schedule is missing timezone`);
16106
+ } else if (cron != null) {
16107
+ throw new TypeError(`${context} cron is only valid for cron schedules`);
16108
+ } else if (timezone != null) {
16109
+ throw new TypeError(`${context} timezone is only valid for cron schedules`);
16110
+ }
16111
+ if (record.kind !== "interval" && record.kind !== "cron" && record.overlap_policy !== "allow") {
16112
+ throw new TypeError(`${context} overlap_policy is only valid for recurring schedules`);
16113
+ }
16114
+ if (overlapRetryMs != null) {
16115
+ if (overlapRetryMs <= 0) {
16116
+ throw new TypeError(`${context} overlap_retry_ms must be positive`);
16117
+ }
16118
+ if (record.overlap_policy !== "queue_after_previous") {
16119
+ throw new TypeError(`${context} overlap_retry_ms requires queue_after_previous`);
16120
+ }
16121
+ }
16122
+ }
16123
+ function validateCatchupState(record, context, coalescedCount, lastCoalescedCount) {
16124
+ const lastCatchupAt = record.last_catchup_at_ms;
16125
+ if (record.kind !== "interval") {
16126
+ if (coalescedCount !== 0) {
16127
+ throw new TypeError(`${context} non-interval coalesced_count must be zero`);
16128
+ }
16129
+ if (lastCoalescedCount !== 0) {
16130
+ throw new TypeError(`${context} non-interval last_coalesced_count must be zero`);
16131
+ }
16132
+ if (lastCatchupAt != null) {
16133
+ throw new TypeError(`${context} non-interval last_catchup_at_ms must be null`);
16134
+ }
16135
+ return;
16136
+ }
16137
+ if (lastCoalescedCount > coalescedCount) {
16138
+ throw new TypeError(`${context} last_coalesced_count exceeds coalesced_count`);
16139
+ }
16140
+ if (lastCoalescedCount > 0 && lastCatchupAt == null) {
16141
+ throw new TypeError(`${context} response missing last_catchup_at_ms after catch-up`);
16142
+ }
16143
+ if (lastCoalescedCount === 0 && lastCatchupAt != null) {
16144
+ throw new TypeError(`${context} last_catchup_at_ms requires a catch-up`);
16145
+ }
16146
+ }
16147
+ function optionalScheduleRecord(value, context) {
16148
+ return value == null ? null : scheduleRecordResponse(value, context);
16149
+ }
16150
+ function scheduleRecordList(value, context) {
16151
+ return arrayResponse(value).map((item) => scheduleRecordResponse(item, context));
16152
+ }
16153
+ function scheduleFireDueResponse(value) {
16154
+ const context = "FLOW.SCHEDULE.FIRE_DUE";
16155
+ const record = adminRecordResponse(value, context);
16156
+ const claimed = safeNonNegativeInteger(record, "claimed", context, true);
16157
+ const fired = safeNonNegativeInteger(record, "fired", context, true);
16158
+ const skipped = safeNonNegativeInteger(record, "skipped", context, true);
16159
+ const coalesced = safeNonNegativeInteger(record, "coalesced", context, true);
16160
+ if (!Array.isArray(record.errors)) {
16161
+ throw new TypeError(`${context} response missing errors`);
16162
+ }
16163
+ for (const error of record.errors) {
16164
+ if (!Array.isArray(error) || error.length !== 2 || typeof error[0] !== "string" || error[0].length === 0 || typeof error[1] !== "string" || error[1].length === 0) {
16165
+ throw new TypeError(`${context} returned an invalid error entry`);
16166
+ }
16167
+ }
16168
+ if (fired + skipped + record.errors.length !== claimed) {
16169
+ throw new TypeError(`${context} outcomes do not equal claimed`);
16170
+ }
16171
+ optionalResponseText(record, "claim_error", context);
16172
+ const lastTargetId = optionalResponseText(record, "last_target_id", context);
16173
+ const lastSkipReason = optionalResponseText(record, "last_skip_reason", context);
16174
+ if (fired > 0 && lastTargetId == null) {
16175
+ throw new TypeError(`${context} response missing last_target_id`);
16176
+ }
16177
+ if (fired === 0 && lastTargetId != null) {
16178
+ throw new TypeError(`${context} last_target_id requires a fired outcome`);
16179
+ }
16180
+ if (skipped > 0 && lastSkipReason == null) {
16181
+ throw new TypeError(`${context} response missing last_skip_reason`);
16182
+ }
16183
+ if (skipped === 0 && lastSkipReason != null) {
16184
+ throw new TypeError(`${context} last_skip_reason requires a skipped outcome`);
16185
+ }
16186
+ if (coalesced > 0 && fired + skipped === 0) {
16187
+ throw new TypeError(`${context} coalesced count requires a fired or skipped outcome`);
16188
+ }
16189
+ return record;
16190
+ }
16191
+ function scheduleFireResponse(value) {
16192
+ const context = "FLOW.SCHEDULE.FIRE";
16193
+ const record = adminRecordResponse(value, context);
16194
+ const schedule = scheduleRecordResponse(record.schedule, `${context} schedule`);
16195
+ const fired = safeNonNegativeInteger(record, "fired", context, true);
16196
+ const skipped = record.skipped == null ? 0 : safeNonNegativeInteger(record, "skipped", context);
16197
+ if (fired > 1 || skipped > 1 || fired + skipped !== 1) {
16198
+ throw new TypeError(`${context} outcomes must equal one`);
16199
+ }
16200
+ if (fired === 1 && (typeof record.target_id !== "string" || record.target_id.length === 0)) {
16201
+ throw new TypeError(`${context} response missing target_id`);
16202
+ }
16203
+ if (skipped === 1 && (typeof record.reason !== "string" || record.reason.length === 0)) {
16204
+ throw new TypeError(`${context} response missing reason`);
16205
+ }
16206
+ if (fired === 0 && record.target_id != null) {
16207
+ throw new TypeError(`${context} target_id requires a fired outcome`);
16208
+ }
16209
+ if (skipped === 0 && record.reason != null) {
16210
+ throw new TypeError(`${context} reason requires a skipped outcome`);
16211
+ }
16212
+ return { ...record, fired, skipped, schedule };
16213
+ }
16214
+ function optionalResponseText(record, field3, context) {
16215
+ const value = record[field3];
16216
+ if (value == null) return void 0;
16217
+ if (typeof value !== "string" || value.length === 0) {
16218
+ throw new TypeError(`${context} ${field3} must be non-empty text`);
16219
+ }
16220
+ return value;
16221
+ }
16222
+ function requiredNullableResponseText(record, field3, context) {
16223
+ if (!(field3 in record)) throw new TypeError(`${context} response missing ${field3}`);
16224
+ if (record[field3] === void 0) {
16225
+ throw new TypeError(`${context} response missing ${field3}`);
16226
+ }
16227
+ return optionalResponseText(record, field3, context) ?? null;
16228
+ }
16229
+ function requiredNullableNonNegativeInteger(record, field3, context) {
16230
+ if (!(field3 in record)) throw new TypeError(`${context} response missing ${field3}`);
16231
+ if (record[field3] === void 0) {
16232
+ throw new TypeError(`${context} response missing ${field3}`);
16233
+ }
16234
+ if (record[field3] === null) return null;
16235
+ return safeNonNegativeInteger(record, field3, context);
16236
+ }
16237
+ function safeNonNegativeInteger(record, field3, context, required = false) {
16238
+ if (!(field3 in record) || record[field3] == null) {
16239
+ if (required) throw new TypeError(`${context} response missing ${field3}`);
16240
+ return 0;
16241
+ }
16242
+ const value = record[field3];
16243
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
16244
+ throw new TypeError(`${context} ${field3} must be a safe non-negative integer`);
16245
+ }
16246
+ return value;
16247
+ }
16248
+
16249
+ // src/client-schedules.ts
16250
+ var FerricStoreScheduleClient = class extends FerricStoreClientCore {
14674
16251
  async scheduleCreate(id, options) {
16252
+ validateScheduleCreate(id, options);
14675
16253
  const args = ["FLOW.SCHEDULE.CREATE", id];
14676
16254
  append(args, "KIND", options.kind);
14677
16255
  append(args, "AT_MS", options.atMs);
@@ -14681,6 +16259,7 @@ var FerricStoreManagementClient = class extends FerricStoreClientCore {
14681
16259
  append(args, "CRON", options.cron);
14682
16260
  append(args, "TIMEZONE", options.timezone);
14683
16261
  append(args, "TARGET", options.target);
16262
+ append(args, "CATCHUP_POLICY", options.catchupPolicy);
14684
16263
  append(args, "OVERLAP_POLICY", options.overlapPolicy);
14685
16264
  append(args, "OVERLAP_RETRY_MS", options.overlapRetryMs);
14686
16265
  append(args, "MAX_FIRES", options.maxFires);
@@ -14690,34 +16269,55 @@ var FerricStoreManagementClient = class extends FerricStoreClientCore {
14690
16269
  for (const [name, value] of Object.entries(options.extraOptions ?? {})) {
14691
16270
  args.push(name.toUpperCase(), value);
14692
16271
  }
14693
- return adminRecordResponse(await this.commandArgs(args), "FLOW.SCHEDULE.CREATE");
16272
+ return scheduleRecordResponse(await this.commandArgs(args), "FLOW.SCHEDULE.CREATE");
14694
16273
  }
14695
- async scheduleGet(id, options = {}) {
14696
- const args = ["FLOW.SCHEDULE.GET", id];
14697
- append(args, "NOW", options.nowMs);
14698
- return optionalAdminRecord(await this.commandArgs(args), "FLOW.SCHEDULE.GET");
16274
+ async scheduleGet(id) {
16275
+ validateScheduleID(id);
16276
+ return optionalScheduleRecord(
16277
+ await this.commandArgs(["FLOW.SCHEDULE.GET", id]),
16278
+ "FLOW.SCHEDULE.GET"
16279
+ );
14699
16280
  }
14700
16281
  async scheduleFire(id, options = {}) {
14701
- return await this.scheduleStatus("FLOW.SCHEDULE.FIRE", id, options.nowMs);
16282
+ validateScheduleFire(id, options);
16283
+ const args = ["FLOW.SCHEDULE.FIRE", id];
16284
+ append(args, "FIRE_AT_MS", options.fireAtMs);
16285
+ append(args, "NOW", options.nowMs);
16286
+ return scheduleFireResponse(await this.commandArgs(args));
14702
16287
  }
14703
16288
  async schedulePause(id, options = {}) {
14704
- return await this.scheduleStatus("FLOW.SCHEDULE.PAUSE", id, options.nowMs);
16289
+ validateScheduleStatus(id, options.nowMs);
16290
+ return scheduleRecordResponse(
16291
+ await this.scheduleStatusResponse("FLOW.SCHEDULE.PAUSE", id, options.nowMs),
16292
+ "FLOW.SCHEDULE.PAUSE"
16293
+ );
14705
16294
  }
14706
16295
  async scheduleResume(id, options = {}) {
14707
- return await this.scheduleStatus("FLOW.SCHEDULE.RESUME", id, options.nowMs);
16296
+ validateScheduleStatus(id, options.nowMs);
16297
+ return scheduleRecordResponse(
16298
+ await this.scheduleStatusResponse("FLOW.SCHEDULE.RESUME", id, options.nowMs),
16299
+ "FLOW.SCHEDULE.RESUME"
16300
+ );
14708
16301
  }
14709
16302
  async scheduleDelete(id, options = {}) {
14710
- return await this.scheduleStatus("FLOW.SCHEDULE.DELETE", id, options.nowMs);
16303
+ validateScheduleStatus(id, options.nowMs);
16304
+ const response = await this.scheduleStatusResponse("FLOW.SCHEDULE.DELETE", id, options.nowMs);
16305
+ if (!okLike(response)) {
16306
+ throw new TypeError("FLOW.SCHEDULE.DELETE response must be OK");
16307
+ }
14711
16308
  }
14712
16309
  async scheduleFireDue(options = {}) {
16310
+ validateScheduleFireDue(options);
14713
16311
  const args = ["FLOW.SCHEDULE.FIRE_DUE"];
14714
16312
  append(args, "NOW", options.nowMs);
14715
16313
  append(args, "WORKER", options.worker);
16314
+ append(args, "LEASE_MS", options.leaseMs);
14716
16315
  append(args, "BLOCK", options.blockMs);
14717
16316
  append(args, "LIMIT", options.limit);
14718
- return adminRecordResponse(await this.commandArgs(args), "FLOW.SCHEDULE.FIRE_DUE");
16317
+ return scheduleFireDueResponse(await this.commandArgs(args));
14719
16318
  }
14720
16319
  async scheduleList(options = {}) {
16320
+ validateScheduleList(options);
14721
16321
  const args = ["FLOW.SCHEDULE.LIST"];
14722
16322
  append(args, "KIND", options.kind);
14723
16323
  append(args, "STATE", options.state);
@@ -14727,14 +16327,40 @@ var FerricStoreManagementClient = class extends FerricStoreClientCore {
14727
16327
  append(args, "TO_MS", options.toMs);
14728
16328
  append(args, "COUNT", options.count);
14729
16329
  appendBool(args, "REV", options.rev);
14730
- return adminRecordList(await this.commandArgs(args), "FLOW.SCHEDULE.LIST");
16330
+ return scheduleRecordList(await this.commandArgs(args), "FLOW.SCHEDULE.LIST");
14731
16331
  }
14732
- async scheduleStatus(command, id, now) {
16332
+ async scheduleStatusResponse(command, id, now) {
14733
16333
  const args = [command, id];
14734
16334
  append(args, "NOW", now);
14735
- const response = await this.commandArgs(args);
14736
- if (okLike(response)) return { id, status: "deleted" };
14737
- return adminRecordResponse(response, command);
16335
+ return await this.commandArgs(args);
16336
+ }
16337
+ };
16338
+
16339
+ // src/client-management.ts
16340
+ var FerricStoreManagementClient = class extends FerricStoreScheduleClient {
16341
+ async stats(type, options = {}) {
16342
+ const args = ["FLOW.STATS", type];
16343
+ append(args, "STATE", options.state);
16344
+ append(args, "COUNT", options.count);
16345
+ append(args, "PARTITION", options.partitionKey);
16346
+ appendAttributes(args, options.attributes);
16347
+ appendBool(args, "CONSISTENT_PROJECTION", options.consistentProjection);
16348
+ return adminRecordResponse(await this.commandArgs(args), "FLOW.STATS");
16349
+ }
16350
+ async countByState(type, state, options = {}) {
16351
+ const stats = await this.stats(type, { ...options, state });
16352
+ if (!("count" in stats)) throw new TypeError("FLOW.STATS response missing count");
16353
+ return integer(stats.count);
16354
+ }
16355
+ async attributes(type, options = {}) {
16356
+ const args = ["FLOW.ATTRIBUTES", type];
16357
+ appendAttributeQueryOptions(args, options);
16358
+ return adminRecordList(await this.commandArgs(args), "FLOW.ATTRIBUTES");
16359
+ }
16360
+ async attributeValues(type, attribute, options = {}) {
16361
+ const args = ["FLOW.ATTRIBUTE_VALUES", type, attribute];
16362
+ appendAttributeQueryOptions(args, options);
16363
+ return adminRecordList(await this.commandArgs(args), "FLOW.ATTRIBUTE_VALUES");
14738
16364
  }
14739
16365
  async effectReserve(id, effectKey, effectType, options) {
14740
16366
  const args = [
@@ -14967,7 +16593,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
14967
16593
  args.push(indexId);
14968
16594
  }
14969
16595
  try {
14970
- return decodeFlowQueryIndexStatus(await this.commandArgs(args));
16596
+ return decodeFlowQueryIndexStatus(await this.commandArgs(args), indexId);
14971
16597
  } catch (error) {
14972
16598
  this.throwFlowQueryError(error);
14973
16599
  }
@@ -15029,7 +16655,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
15029
16655
  if (hasFlowExplainPrefix(query)) {
15030
16656
  throw new TypeError("query already contains an EXPLAIN prefix");
15031
16657
  }
15032
- return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query.trim(), params));
16658
+ return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query, params));
15033
16659
  }
15034
16660
  throwFlowQueryError(error) {
15035
16661
  if (error instanceof FerricStoreError) {
@@ -15062,7 +16688,7 @@ function throwIfClosed(signal) {
15062
16688
  }
15063
16689
 
15064
16690
  // src/flow-policy.ts
15065
- import { Buffer as Buffer51 } from "buffer";
16691
+ import { Buffer as Buffer54 } from "buffer";
15066
16692
  var MAX_FLOW_POLICY_GENERATION = Number.MAX_SAFE_INTEGER;
15067
16693
  function assertFlowPolicyGeneration(value) {
15068
16694
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
@@ -15072,15 +16698,15 @@ function assertFlowPolicyGeneration(value) {
15072
16698
  function flowPolicySnapshotFromResp(value) {
15073
16699
  const policy = requiredRecord(value, "FLOW policy snapshot");
15074
16700
  const maxActiveMs = optionalPositiveInteger(field(policy, "max_active_ms"), "max_active_ms");
15075
- const state = optionalText3(field(policy, "state"), "state");
16701
+ const state = optionalText4(field(policy, "state"), "state");
15076
16702
  const mode = optionalMode(field(policy, "mode"), "mode");
15077
16703
  const version = optionalVersion(field(policy, "version"));
15078
- const indexedStateMeta = optionalText3(field(policy, "indexed_state_meta"), "indexed_state_meta");
16704
+ const indexedStateMeta = optionalText4(field(policy, "indexed_state_meta"), "indexed_state_meta");
15079
16705
  const governance = optionalRecord(field(policy, "governance"), "governance");
15080
16706
  const states = optionalStates(field(policy, "states"));
15081
16707
  return {
15082
- type: requiredText3(field(policy, "type"), "type"),
15083
- generation: nonNegativeInteger2(field(policy, "generation"), "generation"),
16708
+ type: requiredText4(field(policy, "type"), "type"),
16709
+ generation: nonNegativeInteger3(field(policy, "generation"), "generation"),
15084
16710
  ...state == null ? {} : { state },
15085
16711
  ...version == null ? {} : { version },
15086
16712
  ...mode == null ? {} : { mode },
@@ -15098,21 +16724,21 @@ function retrySnapshot(value, context) {
15098
16724
  const retry2 = requiredRecord(value, `FLOW policy ${context}`);
15099
16725
  const backoff = requiredRecord(field(retry2, "backoff"), `FLOW policy ${context}.backoff`);
15100
16726
  return {
15101
- maxRetries: nonNegativeInteger2(field(retry2, "max_retries"), `${context}.max_retries`),
16727
+ maxRetries: nonNegativeInteger3(field(retry2, "max_retries"), `${context}.max_retries`),
15102
16728
  backoff: {
15103
16729
  kind: backoffKind(field(backoff, "kind"), `${context}.backoff.kind`),
15104
- baseMs: nonNegativeInteger2(field(backoff, "base_ms"), `${context}.backoff.base_ms`),
15105
- maxMs: nonNegativeInteger2(field(backoff, "max_ms"), `${context}.backoff.max_ms`),
15106
- jitterPct: nonNegativeInteger2(field(backoff, "jitter_pct"), `${context}.backoff.jitter_pct`)
16730
+ baseMs: nonNegativeInteger3(field(backoff, "base_ms"), `${context}.backoff.base_ms`),
16731
+ maxMs: nonNegativeInteger3(field(backoff, "max_ms"), `${context}.backoff.max_ms`),
16732
+ jitterPct: nonNegativeInteger3(field(backoff, "jitter_pct"), `${context}.backoff.jitter_pct`)
15107
16733
  },
15108
- exhaustedTo: requiredText3(field(retry2, "exhausted_to"), `${context}.exhausted_to`)
16734
+ exhaustedTo: requiredText4(field(retry2, "exhausted_to"), `${context}.exhausted_to`)
15109
16735
  };
15110
16736
  }
15111
16737
  function retentionSnapshot(value, context) {
15112
16738
  const retention = requiredRecord(value, `FLOW policy ${context}`);
15113
16739
  return {
15114
- ttlMs: nonNegativeInteger2(field(retention, "ttl_ms"), `${context}.ttl_ms`),
15115
- historyMaxEvents: nonNegativeInteger2(
16740
+ ttlMs: nonNegativeInteger3(field(retention, "ttl_ms"), `${context}.ttl_ms`),
16741
+ historyMaxEvents: nonNegativeInteger3(
15116
16742
  field(retention, "history_max_events"),
15117
16743
  `${context}.history_max_events`
15118
16744
  )
@@ -15148,11 +16774,11 @@ function requiredRecord(value, context) {
15148
16774
  if (value instanceof Map) {
15149
16775
  const result = {};
15150
16776
  for (const [key, item] of value.entries()) {
15151
- setOwnValue(result, requiredText3(key, `${context} key`), item);
16777
+ setOwnValue(result, requiredText4(key, `${context} key`), item);
15152
16778
  }
15153
16779
  return result;
15154
16780
  }
15155
- if (typeof value === "object" && value != null && !Array.isArray(value) && !Buffer51.isBuffer(value) && !(value instanceof Uint8Array)) return value;
16781
+ if (typeof value === "object" && value != null && !Array.isArray(value) && !Buffer54.isBuffer(value) && !(value instanceof Uint8Array)) return value;
15156
16782
  throw new TypeError(`${context} returned an unexpected response`);
15157
16783
  }
15158
16784
  function optionalRecord(value, context) {
@@ -15167,21 +16793,21 @@ function stringArray(value, context) {
15167
16793
  if (!Object.hasOwn(value, index)) {
15168
16794
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15169
16795
  }
15170
- result[index] = requiredText3(value[index], `${context}[${index}]`);
16796
+ result[index] = requiredText4(value[index], `${context}[${index}]`);
15171
16797
  }
15172
16798
  return result;
15173
16799
  }
15174
- function requiredText3(value, context) {
16800
+ function requiredText4(value, context) {
15175
16801
  if (typeof value === "string") return value;
15176
- if (Buffer51.isBuffer(value) || value instanceof Uint8Array) {
15177
- return Buffer51.from(value).toString("utf8");
16802
+ if (Buffer54.isBuffer(value) || value instanceof Uint8Array) {
16803
+ return Buffer54.from(value).toString("utf8");
15178
16804
  }
15179
16805
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15180
16806
  }
15181
- function optionalText3(value, context) {
15182
- return value == null ? void 0 : requiredText3(value, context);
16807
+ function optionalText4(value, context) {
16808
+ return value == null ? void 0 : requiredText4(value, context);
15183
16809
  }
15184
- function nonNegativeInteger2(value, context) {
16810
+ function nonNegativeInteger3(value, context) {
15185
16811
  let parsed;
15186
16812
  try {
15187
16813
  parsed = integer(value);
@@ -15193,26 +16819,26 @@ function nonNegativeInteger2(value, context) {
15193
16819
  }
15194
16820
  function optionalPositiveInteger(value, context) {
15195
16821
  if (value == null) return void 0;
15196
- const parsed = nonNegativeInteger2(value, context);
16822
+ const parsed = nonNegativeInteger3(value, context);
15197
16823
  if (parsed === 0) throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15198
16824
  return parsed;
15199
16825
  }
15200
16826
  function optionalVersion(value) {
15201
16827
  if (value == null) return void 0;
15202
16828
  if (typeof value === "number" || typeof value === "bigint") {
15203
- return nonNegativeInteger2(value, "version");
16829
+ return nonNegativeInteger3(value, "version");
15204
16830
  }
15205
- return requiredText3(value, "version");
16831
+ return requiredText4(value, "version");
15206
16832
  }
15207
16833
  function backoffKind(value, context) {
15208
- const kind = requiredText3(value, context).toLowerCase();
16834
+ const kind = requiredText4(value, context).toLowerCase();
15209
16835
  if (kind === "none" || kind === "fixed" || kind === "linear" || kind === "exponential") {
15210
16836
  return kind;
15211
16837
  }
15212
16838
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15213
16839
  }
15214
16840
  function requiredMode(value, context) {
15215
- const mode = requiredText3(value, context).toLowerCase();
16841
+ const mode = requiredText4(value, context).toLowerCase();
15216
16842
  if (mode === "fifo" || mode === "parallel") return mode;
15217
16843
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15218
16844
  }
@@ -15430,7 +17056,7 @@ var FerricStoreFlowSupportClient = class extends FerricStoreFlowQueryClient {
15430
17056
  };
15431
17057
 
15432
17058
  // src/flow-many-snapshot.ts
15433
- import { Buffer as Buffer53 } from "buffer";
17059
+ import { Buffer as Buffer56 } from "buffer";
15434
17060
  var encodedOptionFields = ["error", "payload", "reason", "result"];
15435
17061
  function snapshotCreateItem(item, codec) {
15436
17062
  const snapshot = {
@@ -15470,7 +17096,7 @@ function snapshotFlowManyOptions(options, codec) {
15470
17096
  }
15471
17097
  function snapshotClaimedItem(item) {
15472
17098
  const wire = item[CLAIMED_ITEM_WIRE];
15473
- const leaseToken = Buffer53.from(wire?.leaseToken ?? item.leaseToken);
17099
+ const leaseToken = Buffer56.from(wire?.leaseToken ?? item.leaseToken);
15474
17100
  const snapshot = {
15475
17101
  ...item,
15476
17102
  fencingToken: wire?.fencingToken ?? item.fencingToken,
@@ -15487,15 +17113,15 @@ function snapshotClaimedItem(item) {
15487
17113
  function snapshotFencedItem(item) {
15488
17114
  return Object.freeze({
15489
17115
  ...item,
15490
- ...item.leaseToken == null ? {} : { leaseToken: Buffer53.from(item.leaseToken) }
17116
+ ...item.leaseToken == null ? {} : { leaseToken: Buffer56.from(item.leaseToken) }
15491
17117
  });
15492
17118
  }
15493
17119
  function snapshotClaimedItemWire(wire, leaseToken) {
15494
17120
  return Object.freeze({
15495
17121
  fencingToken: wire.fencingToken,
15496
- id: Buffer53.from(wire.id),
17122
+ id: Buffer56.from(wire.id),
15497
17123
  leaseToken,
15498
- partitionKey: wire.partitionKey == null ? wire.partitionKey : Buffer53.from(wire.partitionKey)
17124
+ partitionKey: wire.partitionKey == null ? wire.partitionKey : Buffer56.from(wire.partitionKey)
15499
17125
  });
15500
17126
  }
15501
17127
  function snapshotArray(values) {
@@ -15506,7 +17132,7 @@ function snapshotArray(values) {
15506
17132
  return Object.freeze(snapshot);
15507
17133
  }
15508
17134
  function snapshotStateMeta(stateMeta) {
15509
- return snapshotRecord(stateMeta, (value) => Buffer53.isBuffer(value) ? Buffer53.from(value) : value);
17135
+ return snapshotRecord(stateMeta, (value) => Buffer56.isBuffer(value) ? Buffer56.from(value) : value);
15510
17136
  }
15511
17137
  function snapshotCommandRecord(values) {
15512
17138
  const seen = /* @__PURE__ */ new WeakMap();
@@ -15524,7 +17150,7 @@ function snapshotRecord(values, capture) {
15524
17150
  }
15525
17151
  function snapshotCommandArgument(value, seen) {
15526
17152
  if (typeof value !== "object" || value == null) return value;
15527
- if (Buffer53.isBuffer(value) || value instanceof Uint8Array) return Buffer53.from(value);
17153
+ if (Buffer56.isBuffer(value) || value instanceof Uint8Array) return Buffer56.from(value);
15528
17154
  const objectValue = value;
15529
17155
  const existing = seen.get(objectValue);
15530
17156
  if (existing != null) return existing;
@@ -16512,7 +18138,7 @@ function nativeOptionsForBootstrap(options, signal) {
16512
18138
  }
16513
18139
 
16514
18140
  // src/flow-query-projection.ts
16515
- import { Buffer as Buffer54 } from "buffer";
18141
+ import { Buffer as Buffer57 } from "buffer";
16516
18142
  var MAX_PROJECTION_FIELDS = 32;
16517
18143
  var MAX_DYNAMIC_NAME_BYTES = 64;
16518
18144
  var FIELD_BRAND = /* @__PURE__ */ Symbol("FerricStoreFlowProjectionField");
@@ -16580,20 +18206,38 @@ function projectFlowQuery(query, shape, ...fields) {
16580
18206
  if (containsReturnKeyword(query)) {
16581
18207
  throw new TypeError("Flow query already contains a RETURN clause");
16582
18208
  }
16583
- const trimmed = query.trim();
16584
- const base = trimmed.endsWith(";") ? trimmed.slice(0, -1).trimEnd() : trimmed;
18209
+ const base = stripOptionalTerminator(query);
16585
18210
  const result = `${base} RETURN ${shape.toUpperCase()} (${selectors.join(", ")})`;
16586
- if (Buffer54.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
18211
+ if (Buffer57.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
16587
18212
  throw new TypeError(`FLOW.QUERY query exceeds ${FLOW_QUERY_MAX_BYTES} bytes`);
16588
18213
  }
16589
18214
  return result;
16590
18215
  }
18216
+ function stripOptionalTerminator(query) {
18217
+ const trimmed = trimFlowQueryAsciiWhitespace(query);
18218
+ if (!trimmed.endsWith(";")) return trimmed;
18219
+ const base = trimFlowQueryAsciiWhitespace(trimmed.slice(0, -1));
18220
+ if (base.endsWith(";")) {
18221
+ throw new TypeError("Flow query accepts at most one trailing semicolon");
18222
+ }
18223
+ return base;
18224
+ }
16591
18225
  function querySource(query) {
16592
- const match = /^\s*FROM\s+(runs|events)\b/iu.exec(query);
16593
- if (match?.[1] == null) {
18226
+ const source = trimFlowQueryAsciiWhitespaceStart(query);
18227
+ if (!matchesFlowQueryAsciiKeyword(source, 0, "FROM") || !isFlowQueryAsciiWhitespaceCode(source.charCodeAt(4))) {
16594
18228
  throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16595
18229
  }
16596
- return match[1].toLowerCase();
18230
+ let offset = 4;
18231
+ while (isFlowQueryAsciiWhitespaceCode(source.charCodeAt(offset))) offset += 1;
18232
+ for (const candidate of ["runs", "events"]) {
18233
+ if (matchesFlowQueryAsciiKeyword(source, offset, candidate.toUpperCase())) {
18234
+ const boundary = source[offset + candidate.length];
18235
+ if (boundary == null || isFlowQueryAsciiWhitespaceCode(boundary.charCodeAt(0))) {
18236
+ return candidate;
18237
+ }
18238
+ }
18239
+ }
18240
+ throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16597
18241
  }
16598
18242
  function containsReturnKeyword(query) {
16599
18243
  let quoted = false;
@@ -16624,7 +18268,7 @@ function quoteName(value, allowPrivate) {
16624
18268
  throw new TypeError("Flow query projection metadata name must be text");
16625
18269
  }
16626
18270
  validateUnicodeScalarText2(value);
16627
- const size = Buffer54.byteLength(value, "utf8");
18271
+ const size = Buffer57.byteLength(value, "utf8");
16628
18272
  if (size === 0 || size > MAX_DYNAMIC_NAME_BYTES || !allowPrivate && value.startsWith("__")) {
16629
18273
  throw new TypeError(
16630
18274
  `Flow query projection metadata names must be 1..${MAX_DYNAMIC_NAME_BYTES} UTF-8 bytes`
@@ -16658,7 +18302,7 @@ function complete(options = {}) {
16658
18302
  function retry(options = {}) {
16659
18303
  return brandOutcome({ ...options, kind: "retry" });
16660
18304
  }
16661
- function fail(options = {}) {
18305
+ function fail2(options = {}) {
16662
18306
  return brandOutcome({ ...options, kind: "fail" });
16663
18307
  }
16664
18308
  function isOutcome(value) {
@@ -16726,7 +18370,7 @@ var LeaseRenewalGuard = class {
16726
18370
  if (options.leaseRenewal === false) {
16727
18371
  return;
16728
18372
  }
16729
- const intervalMs = positiveInteger2(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
18373
+ const intervalMs = positiveInteger3(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
16730
18374
  this.timer = setLongInterval(() => this.renew(), intervalMs);
16731
18375
  this.timer.unref();
16732
18376
  }
@@ -16798,7 +18442,7 @@ function snapshotLeasedJob(job) {
16798
18442
  return snapshot;
16799
18443
  }
16800
18444
  function workerConcurrency(options) {
16801
- return positiveInteger2(options.concurrency ?? options.workers, 1);
18445
+ return positiveInteger3(options.concurrency ?? options.workers, 1);
16802
18446
  }
16803
18447
  function workerRefillStrategy(options) {
16804
18448
  return options.refillStrategy === "wave" ? "wave" : "continuous";
@@ -16841,18 +18485,18 @@ function workerErrorPayload(error, options, codec) {
16841
18485
  }
16842
18486
  function workerBatchSize(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
16843
18487
  return Math.min(
16844
- positiveInteger2(options.batchSize, 10),
16845
- positiveInteger2(flowManyBatchLimit, DEFAULT_FLOW_MANY_BATCH_LIMIT2)
18488
+ positiveInteger3(options.batchSize, 10),
18489
+ positiveInteger3(flowManyBatchLimit, DEFAULT_FLOW_MANY_BATCH_LIMIT2)
16846
18490
  );
16847
18491
  }
16848
18492
  function workerClaimLimit(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
16849
18493
  return Math.min(workerBatchSize(options, flowManyBatchLimit), workerConcurrency(options));
16850
18494
  }
16851
18495
  function workerDrainBatches(options) {
16852
- return positiveInteger2(options.claimDrainBatches, 1);
18496
+ return positiveInteger3(options.claimDrainBatches, 1);
16853
18497
  }
16854
18498
  function workerLeaseMs(options, fallback = 3e4) {
16855
- return positiveInteger2(options.leaseMs, positiveInteger2(fallback, 3e4));
18499
+ return positiveInteger3(options.leaseMs, positiveInteger3(fallback, 3e4));
16856
18500
  }
16857
18501
  function workerIdleSleepMs(options) {
16858
18502
  return nonNegativeDuration(options.idleSleepMs, 250);
@@ -16864,7 +18508,7 @@ function workerClaimBlockMs(options, useBlocking) {
16864
18508
  if (options.signal == null || !Number.isFinite(options.blockMs) || options.blockMs < 0) {
16865
18509
  return options.blockMs;
16866
18510
  }
16867
- const abortPollMs = positiveInteger2(options.abortPollMs, 1e3);
18511
+ const abortPollMs = positiveInteger3(options.abortPollMs, 1e3);
16868
18512
  return options.blockMs === 0 ? abortPollMs : Math.min(Math.trunc(options.blockMs), abortPollMs);
16869
18513
  }
16870
18514
  function workerMaxIdleSleepMs(options, idleSleepMs = workerIdleSleepMs(options)) {
@@ -16875,14 +18519,14 @@ function nextWorkerIdleSleepMs(currentIdleSleepMs, maxIdleSleepMs) {
16875
18519
  return Math.min(maxIdleSleepMs, Math.max(1, currentIdleSleepMs * 2));
16876
18520
  }
16877
18521
  async function runContinuousWorkerPool(options) {
16878
- const concurrency = positiveInteger2(options.concurrency, 1);
16879
- const maxClaimSize = positiveInteger2(options.maxClaimSize, concurrency);
18522
+ const concurrency = positiveInteger3(options.concurrency, 1);
18523
+ const maxClaimSize = positiveInteger3(options.maxClaimSize, concurrency);
16880
18524
  const initialIdleSleepMs = nonNegativeDuration(options.idleSleepMs, 250);
16881
18525
  const maxIdleSleepMs = Math.max(
16882
18526
  initialIdleSleepMs,
16883
18527
  nonNegativeDuration(options.maxIdleSleepMs, 5e3)
16884
18528
  );
16885
- const refillDelayMs = nonNegativeInteger3(options.refillDelayMs, 0);
18529
+ const refillDelayMs = nonNegativeInteger4(options.refillDelayMs, 0);
16886
18530
  const active = /* @__PURE__ */ new Set();
16887
18531
  const pending = [];
16888
18532
  let pendingIndex = 0;
@@ -16998,10 +18642,10 @@ function normalizeWorkflowWorkerConfig(options) {
16998
18642
  ...captured
16999
18643
  });
17000
18644
  }
17001
- function positiveInteger2(value, fallback) {
18645
+ function positiveInteger3(value, fallback) {
17002
18646
  return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
17003
18647
  }
17004
- function nonNegativeInteger3(value, fallback) {
18648
+ function nonNegativeInteger4(value, fallback) {
17005
18649
  return value == null || !Number.isFinite(value) ? fallback : Math.max(0, Math.trunc(value));
17006
18650
  }
17007
18651
  function nonNegativeDuration(value, fallback) {
@@ -17455,7 +19099,7 @@ async function applyQueueException(queue, options, job, error) {
17455
19099
  const policy = normalizeExceptionPolicy(options.exceptionPolicy);
17456
19100
  if (policy === "raise") throw error;
17457
19101
  const payload = workerErrorPayload(error, options, queue.client.codec);
17458
- await applyQueueOutcome(queue, job, policy === "fail" ? fail({ error: payload }) : retry({ error: payload }));
19102
+ await applyQueueOutcome(queue, job, policy === "fail" ? fail2({ error: payload }) : retry({ error: payload }));
17459
19103
  }
17460
19104
 
17461
19105
  // src/queue-pending-completions.ts
@@ -17702,7 +19346,7 @@ var QueueWorker = class {
17702
19346
  const policy = normalizeExceptionPolicy(this.options.exceptionPolicy);
17703
19347
  if (policy !== "raise") {
17704
19348
  const payload = workerErrorPayload(error, this.options, this.queue.client.codec);
17705
- outcome = policy === "fail" ? fail({ error: payload }) : retry({ error: payload });
19349
+ outcome = policy === "fail" ? fail2({ error: payload }) : retry({ error: payload });
17706
19350
  }
17707
19351
  }
17708
19352
  const stopped = await stopQueueBatchGuards(guards.map((guard) => guard.job), guards);
@@ -17885,13 +19529,13 @@ var QueueWorker = class {
17885
19529
 
17886
19530
  // src/flow-client-options.ts
17887
19531
  function snapshotFlowClientOptions(options, context) {
17888
- const own = { ...options };
17889
- if (typeof own.type !== "string" || own.type.length === 0) {
19532
+ const own2 = { ...options };
19533
+ if (typeof own2.type !== "string" || own2.type.length === 0) {
17890
19534
  throw new TypeError(`${context} type must be an own non-empty string`);
17891
19535
  }
17892
- const valueConfig = own.valueConfig;
19536
+ const valueConfig = own2.valueConfig;
17893
19537
  return Object.freeze({
17894
- ...own,
19538
+ ...own2,
17895
19539
  ...valueConfig == null ? {} : { valueConfig: Object.freeze({ ...valueConfig }) }
17896
19540
  });
17897
19541
  }
@@ -18314,19 +19958,19 @@ async function applyFail(client, ctx, outcome, returnRecord) {
18314
19958
 
18315
19959
  // src/workflow-registration.ts
18316
19960
  function createWorkflowStateRegistration(name, handler, options, defaultValueMaxBytes) {
18317
- const own = { ...options };
18318
- const claimValues = own.claimValues == null ? void 0 : snapshotOwnStringArray(own.claimValues, "claimValues");
18319
- const retryPolicy = own.retryPolicy == null ? void 0 : Object.freeze({ ...own.retryPolicy });
19961
+ const own2 = { ...options };
19962
+ const claimValues = own2.claimValues == null ? void 0 : snapshotOwnStringArray(own2.claimValues, "claimValues");
19963
+ const retryPolicy = own2.retryPolicy == null ? void 0 : Object.freeze({ ...own2.retryPolicy });
18320
19964
  return Object.freeze({
18321
- claimPayload: own.claimPayload ?? true,
18322
- claimRecord: own.claimRecord ?? true,
18323
- exceptionPolicy: normalizeExceptionPolicy(own.exceptionPolicy),
19965
+ claimPayload: own2.claimPayload ?? true,
19966
+ claimRecord: own2.claimRecord ?? true,
19967
+ exceptionPolicy: normalizeExceptionPolicy(own2.exceptionPolicy),
18324
19968
  handler,
18325
- leaseMs: own.leaseMs ?? 3e4,
18326
- ...own.mode == null ? {} : { mode: normalizeStateMode(own.mode) },
19969
+ leaseMs: own2.leaseMs ?? 3e4,
19970
+ ...own2.mode == null ? {} : { mode: normalizeStateMode(own2.mode) },
18327
19971
  name,
18328
- returnRecord: own.returnRecord ?? false,
18329
- valueMaxBytes: own.valueMaxBytes ?? defaultValueMaxBytes,
19972
+ returnRecord: own2.returnRecord ?? false,
19973
+ valueMaxBytes: own2.valueMaxBytes ?? defaultValueMaxBytes,
18330
19974
  ...claimValues == null ? {} : { claimValues },
18331
19975
  ...retryPolicy == null ? {} : { retryPolicy }
18332
19976
  });
@@ -18615,7 +20259,7 @@ var WorkflowWorker = class {
18615
20259
  await applyWorkflowOutcome(
18616
20260
  this.workflow.client,
18617
20261
  ctx,
18618
- fail({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
20262
+ fail2({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
18619
20263
  registration.returnRecord
18620
20264
  );
18621
20265
  return;
@@ -18630,8 +20274,8 @@ var WorkflowWorker = class {
18630
20274
  };
18631
20275
 
18632
20276
  // src/version.ts
18633
- var FERRICSTORE_SDK_VERSION = "0.5.1";
18634
- var FERRICSTORE_MINIMUM_SERVER_VERSION = "0.11.0";
20277
+ var FERRICSTORE_SDK_VERSION = "0.11.4";
20278
+ var FERRICSTORE_MINIMUM_SERVER_VERSION = "0.11.4";
18635
20279
  var FERRICSTORE_NATIVE_PROTOCOL_VERSION = 1;
18636
20280
  export {
18637
20281
  BitmapStore,
@@ -18689,7 +20333,7 @@ export {
18689
20333
  WorkflowWorker,
18690
20334
  classifyServerError,
18691
20335
  complete,
18692
- fail,
20336
+ fail2 as fail,
18693
20337
  isOutcome,
18694
20338
  isReconnectableClosedConnectionError,
18695
20339
  mapException,