@ferricstore/ferricstore 0.5.0 → 0.5.2

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 (63) hide show
  1. package/README.md +7 -6
  2. package/dist/index.cjs +1458 -341
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +95 -10
  5. package/dist/index.d.ts +95 -10
  6. package/dist/index.js +1459 -342
  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/FerricStoreError.html +1 -1
  13. package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
  14. package/docs/api/classes/FlowNotFoundError.html +1 -1
  15. package/docs/api/classes/FlowWrongStateError.html +1 -1
  16. package/docs/api/classes/InvalidCommandError.html +1 -1
  17. package/docs/api/classes/LeaseRenewalError.html +1 -1
  18. package/docs/api/classes/LockHeldError.html +1 -1
  19. package/docs/api/classes/LockNotOwnedError.html +1 -1
  20. package/docs/api/classes/NativeAdapter.html +1 -1
  21. package/docs/api/classes/OverloadedError.html +1 -1
  22. package/docs/api/classes/QueueCompletionError.html +1 -1
  23. package/docs/api/classes/RequestTimeoutError.html +1 -1
  24. package/docs/api/classes/RerouteError.html +1 -1
  25. package/docs/api/classes/StaleLeaseError.html +1 -1
  26. package/docs/api/classes/StalePolicyGenerationError.html +1 -1
  27. package/docs/api/functions/projectFlowQuery.html +1 -1
  28. package/docs/api/hierarchy.html +1 -1
  29. package/docs/api/index.html +7 -6
  30. package/docs/api/interfaces/CompleteOutcome.html +1 -1
  31. package/docs/api/interfaces/FailOutcome.html +1 -1
  32. package/docs/api/interfaces/FlowEventProjectionField.html +2 -2
  33. package/docs/api/interfaces/FlowExplainCapabilities.html +5 -0
  34. package/docs/api/interfaces/FlowExplainResult.html +8 -2
  35. package/docs/api/interfaces/FlowQueryCountResult.html +1 -1
  36. package/docs/api/interfaces/FlowQueryIndex.html +11 -2
  37. package/docs/api/interfaces/FlowQueryIndexBuild.html +10 -0
  38. package/docs/api/interfaces/FlowQueryIndexCoverage.html +5 -0
  39. package/docs/api/interfaces/FlowQueryIndexField.html +5 -0
  40. package/docs/api/interfaces/FlowQueryIndexFormat.html +2 -2
  41. package/docs/api/interfaces/FlowQueryIndexProgress.html +7 -0
  42. package/docs/api/interfaces/FlowQueryIndexRegistry.html +2 -2
  43. package/docs/api/interfaces/FlowQueryIndexRetirement.html +10 -0
  44. package/docs/api/interfaces/FlowQueryIndexServices.html +6 -0
  45. package/docs/api/interfaces/FlowQueryIndexStatistics.html +11 -0
  46. package/docs/api/interfaces/FlowQueryIndexStatus.html +2 -2
  47. package/docs/api/interfaces/FlowQueryIndexValidation.html +13 -0
  48. package/docs/api/interfaces/FlowQueryQuality.html +2 -2
  49. package/docs/api/interfaces/FlowQueryRecordsResult.html +1 -1
  50. package/docs/api/interfaces/FlowRunProjectionField.html +2 -2
  51. package/docs/api/interfaces/NamedValueMutation.html +1 -1
  52. package/docs/api/interfaces/RetryOutcome.html +1 -1
  53. package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
  54. package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
  55. package/docs/api/interfaces/TransitionOutcome.html +1 -1
  56. package/docs/api/modules.html +1 -1
  57. package/docs/api/types/ConnectionRequestDisposition.html +1 -1
  58. package/docs/api/types/FlowProjectionField.html +1 -1
  59. package/docs/api/types/FlowProjectionShape.html +1 -1
  60. package/docs/api/types/FlowQueryIndexServiceState.html +1 -0
  61. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
  62. package/docs/api/variables/FlowProjection.html +1 -1
  63. package/package.json +1 -1
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);
@@ -5730,7 +5771,7 @@ function truncate(value) {
5730
5771
  }
5731
5772
 
5732
5773
  // src/protocol-flow-query-result.ts
5733
- import { Buffer as Buffer24 } from "buffer";
5774
+ import { Buffer as Buffer24, isUtf8 as isUtf82 } from "buffer";
5734
5775
 
5735
5776
  // src/protocol-binary-detacher.ts
5736
5777
  import { Buffer as Buffer23 } from "buffer";
@@ -5767,7 +5808,10 @@ function isDecodedRecord(value) {
5767
5808
  // src/protocol-flow-query-result.ts
5768
5809
  var CONTRACT = "ferric.flow.query.result/v1";
5769
5810
  var MAX_RECORDS = 100;
5811
+ var MIN_CURSOR_BYTES = 16;
5770
5812
  var MAX_CURSOR_BYTES = 4096;
5813
+ var CURSOR_PREFIX = Buffer24.from("fqc1_");
5814
+ var MAX_SIGNED_64 = (1n << 63n) - 1n;
5771
5815
  var RECORD_FIELDS = [
5772
5816
  "id",
5773
5817
  "type",
@@ -5831,9 +5875,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5831
5875
  const usage = {};
5832
5876
  for (const field3 of USAGE_FIELDS) {
5833
5877
  const read = readU64(data, offset);
5878
+ if (BigInt(read.value) > MAX_SIGNED_64) {
5879
+ throw new FerricStoreError("compact FLOW.QUERY usage exceeds signed 64-bit range");
5880
+ }
5834
5881
  usage[field3] = read.value;
5835
5882
  offset = read.offset;
5836
5883
  }
5884
+ validateCompactUsage(usage);
5837
5885
  let result;
5838
5886
  if (kind === 0) {
5839
5887
  const page = readPage(data, offset);
@@ -5842,6 +5890,7 @@ function decodeCompactFlowQueryResult(data, budget) {
5842
5890
  const count = data.readUInt32BE(offset);
5843
5891
  offset += 4;
5844
5892
  if (count > MAX_RECORDS) throw new FerricStoreError("compact FLOW.QUERY page exceeds 100 records");
5893
+ validateCompactRecordUsage(usage, count);
5845
5894
  consumeDecodeItems(count, budget);
5846
5895
  const records = new Array(count);
5847
5896
  for (let index = 0; index < count; index += 1) {
@@ -5851,7 +5900,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5851
5900
  }
5852
5901
  result = { version: Buffer24.from(CONTRACT), records, page: page.value, quality, usage };
5853
5902
  } else if (kind === 1) {
5903
+ if (usageInteger(usage, "result_records") !== 1n) {
5904
+ throw new FerricStoreError("compact FLOW.QUERY count usage is inconsistent");
5905
+ }
5854
5906
  const count = readU64(data, offset);
5907
+ if (BigInt(count.value) > MAX_SIGNED_64) {
5908
+ throw new FerricStoreError("compact FLOW.QUERY count exceeds signed 64-bit range");
5909
+ }
5855
5910
  offset = count.offset;
5856
5911
  result = {
5857
5912
  version: Buffer24.from(CONTRACT),
@@ -5876,18 +5931,37 @@ function readPage(data, offset) {
5876
5931
  if (hasMoreCode === 0 && size === NULL_U32) {
5877
5932
  return { value: { has_more: false, cursor: null }, offset };
5878
5933
  }
5879
- if (hasMoreCode !== 1 || size === 0 || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
5934
+ if (hasMoreCode !== 1 || size < MIN_CURSOR_BYTES || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
5880
5935
  throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
5881
5936
  }
5882
5937
  requireAvailable(data, offset, size);
5938
+ const cursor = data.subarray(offset, offset + size);
5939
+ if (!cursor.subarray(0, CURSOR_PREFIX.byteLength).equals(CURSOR_PREFIX) || !isUtf82(cursor)) {
5940
+ throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
5941
+ }
5883
5942
  return {
5884
5943
  value: {
5885
5944
  has_more: true,
5886
- cursor: detachDecodedBinary(data.subarray(offset, offset + size), data.byteLength)
5945
+ cursor: detachDecodedBinary(cursor, data.byteLength)
5887
5946
  },
5888
5947
  offset: offset + size
5889
5948
  };
5890
5949
  }
5950
+ function validateCompactUsage(usage) {
5951
+ const scanned = usageInteger(usage, "scanned_entries");
5952
+ 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) {
5953
+ throw new FerricStoreError("compact FLOW.QUERY usage counters are inconsistent");
5954
+ }
5955
+ }
5956
+ function validateCompactRecordUsage(usage, count) {
5957
+ const resultRecords = usageInteger(usage, "result_records");
5958
+ if (resultRecords !== BigInt(count) || resultRecords > usageInteger(usage, "scanned_entries")) {
5959
+ throw new FerricStoreError("compact FLOW.QUERY record usage is inconsistent");
5960
+ }
5961
+ }
5962
+ function usageInteger(usage, field3) {
5963
+ return BigInt(usage[field3]);
5964
+ }
5891
5965
  function readRecord(data, offset, budget) {
5892
5966
  requireAvailable(data, offset, 4);
5893
5967
  const bitmap = data.readUInt32BE(offset);
@@ -7913,6 +7987,63 @@ function nativeStartupPayload(clientName, events) {
7913
7987
  };
7914
7988
  }
7915
7989
 
7990
+ // src/flow-query-deadline.ts
7991
+ import { Buffer as Buffer31 } from "buffer";
7992
+ function withFlowQueryDeadline(command, timeoutMs, nowMs2) {
7993
+ if (timeoutMs == null) return command;
7994
+ if (command.opcode === OPCODES.pipeline) {
7995
+ return withPipelineQueryDeadlines(command, timeoutMs, nowMs2);
7996
+ }
7997
+ if (command.opcode !== OPCODES.flowQuery) return command;
7998
+ const payload = command.payload;
7999
+ if (payload instanceof Map) {
8000
+ const nextPayload = new Map(payload);
8001
+ nextPayload.set("deadline_ms", flowQueryDeadlineMs(timeoutMs, nowMs2));
8002
+ return { ...command, payload: nextPayload };
8003
+ }
8004
+ if (typeof payload !== "object" || payload == null || Array.isArray(payload) || Buffer31.isBuffer(payload) || payload instanceof Uint8Array) {
8005
+ return command;
8006
+ }
8007
+ return {
8008
+ ...command,
8009
+ payload: {
8010
+ ...payload,
8011
+ deadline_ms: flowQueryDeadlineMs(timeoutMs, nowMs2)
8012
+ }
8013
+ };
8014
+ }
8015
+ function withPipelineQueryDeadlines(command, timeoutMs, nowMs2) {
8016
+ const payload = plainRecord(command.payload);
8017
+ if (payload == null) return command;
8018
+ const rawCommands = payload.commands;
8019
+ if (!Array.isArray(rawCommands)) return command;
8020
+ let commands;
8021
+ let deadlineMs;
8022
+ for (let index = 0; index < rawCommands.length; index += 1) {
8023
+ const rawItem = rawCommands[index];
8024
+ const item = plainRecord(rawItem);
8025
+ if (item?.opcode !== OPCODES.flowQuery) continue;
8026
+ const body = plainRecord(item.body);
8027
+ if (body == null) continue;
8028
+ commands ??= rawCommands.slice();
8029
+ deadlineMs ??= flowQueryDeadlineMs(timeoutMs, nowMs2);
8030
+ commands[index] = { ...item, body: { ...body, deadline_ms: deadlineMs } };
8031
+ }
8032
+ return commands == null ? command : { ...command, payload: { ...payload, commands } };
8033
+ }
8034
+ function flowQueryDeadlineMs(timeoutMs, nowMs2) {
8035
+ return Math.min(
8036
+ Number.MAX_SAFE_INTEGER,
8037
+ (nowMs2 ?? Date.now()) + Math.max(1, Math.ceil(timeoutMs))
8038
+ );
8039
+ }
8040
+ function plainRecord(value) {
8041
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer31.isBuffer(value) || value instanceof Uint8Array) {
8042
+ return void 0;
8043
+ }
8044
+ return value;
8045
+ }
8046
+
7916
8047
  // src/native-adapter.ts
7917
8048
  var NativeAdapter = class _NativeAdapter {
7918
8049
  socket;
@@ -8118,7 +8249,11 @@ var NativeAdapter = class _NativeAdapter {
8118
8249
  let frame;
8119
8250
  try {
8120
8251
  requestId = this.requestScheduler.nextRequestId();
8121
- frame = encodeRequest(command, requestId, this.capabilities.requestFrameBytes);
8252
+ frame = encodeRequest(
8253
+ withFlowQueryDeadline(command, timeoutMs),
8254
+ requestId,
8255
+ this.capabilities.requestFrameBytes
8256
+ );
8122
8257
  } catch (error) {
8123
8258
  if (hasFlowControlCredit) this.flowControl.release(laneId);
8124
8259
  return Promise.reject(error instanceof Error ? error : new FerricStoreError(String(error), { raw: error }));
@@ -8266,7 +8401,7 @@ function executeCommandArgs(executor, args) {
8266
8401
  import "buffer";
8267
8402
 
8268
8403
  // src/command-retry-policy.ts
8269
- import { Buffer as Buffer32 } from "buffer";
8404
+ import { Buffer as Buffer33 } from "buffer";
8270
8405
  function isCasMutation(args) {
8271
8406
  const offset = commandName2(args[0]) === "COMMAND_EXEC" ? 1 : 0;
8272
8407
  const name = commandName2(args[offset]);
@@ -8280,8 +8415,8 @@ function isCasMutation(args) {
8280
8415
  }
8281
8416
  function commandName2(value) {
8282
8417
  if (typeof value === "string") return value.toUpperCase();
8283
- if (Buffer32.isBuffer(value) || value instanceof Uint8Array) {
8284
- return Buffer32.from(value).toString("utf8").toUpperCase();
8418
+ if (Buffer33.isBuffer(value) || value instanceof Uint8Array) {
8419
+ return Buffer33.from(value).toString("utf8").toUpperCase();
8285
8420
  }
8286
8421
  return void 0;
8287
8422
  }
@@ -8496,12 +8631,12 @@ function normalizeNonNegativeInteger2(value, fallback) {
8496
8631
  import "buffer";
8497
8632
 
8498
8633
  // src/topology-routing.ts
8499
- import { Buffer as Buffer35 } from "buffer";
8634
+ import { Buffer as Buffer36 } from "buffer";
8500
8635
 
8501
8636
  // src/flow-partition-route-cache.ts
8502
- import { Buffer as Buffer34 } from "buffer";
8637
+ import { Buffer as Buffer35 } from "buffer";
8503
8638
  import { createHash } from "crypto";
8504
- var AUTO_PREFIX = Buffer34.from("__flow_auto__:", "ascii");
8639
+ var AUTO_PREFIX = Buffer35.from("__flow_auto__:", "ascii");
8505
8640
  var MAX_CACHEABLE_PARTITION_BYTES = 4 * 1024;
8506
8641
  var MAX_CACHE_BYTES = 1 * 1024 * 1024;
8507
8642
  var MAX_CACHE_ENTRIES = 1024;
@@ -8511,10 +8646,10 @@ var cacheBytes = 0;
8511
8646
  var cacheHits = 0;
8512
8647
  var cacheMisses = 0;
8513
8648
  function flowLogicalPartitionRoutingKey(value) {
8514
- if (typeof value !== "string" && !Buffer34.isBuffer(value)) return void 0;
8649
+ if (typeof value !== "string" && !Buffer35.isBuffer(value)) return void 0;
8515
8650
  const autoBucket = flowAutoBucket(value);
8516
8651
  if (autoBucket != null) return `{fa:${autoBucket}}`;
8517
- const bytes2 = Buffer34.isBuffer(value) ? value : Buffer34.from(value);
8652
+ const bytes2 = Buffer35.isBuffer(value) ? value : Buffer35.from(value);
8518
8653
  if (bytes2.byteLength > MAX_CACHEABLE_PARTITION_BYTES) return hashRoute(bytes2);
8519
8654
  const cacheKey = bytes2.toString("base64");
8520
8655
  const cached = routeCache.get(cacheKey);
@@ -8588,7 +8723,7 @@ function routingKeyFromProtocolPayload(name, command) {
8588
8723
  "scope"
8589
8724
  ]) {
8590
8725
  const value = getField(command.payload, field3);
8591
- if (typeof value === "string" || Buffer35.isBuffer(value)) {
8726
+ if (typeof value === "string" || Buffer36.isBuffer(value)) {
8592
8727
  return value;
8593
8728
  }
8594
8729
  }
@@ -8636,7 +8771,7 @@ function flowRoutingData(name, args) {
8636
8771
  if (typeof partition === "string" && partition.toUpperCase() !== "AUTO" && partition.toUpperCase() !== "MIXED") {
8637
8772
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
8638
8773
  }
8639
- if (Buffer35.isBuffer(partition)) {
8774
+ if (Buffer36.isBuffer(partition)) {
8640
8775
  const text3 = partition.toString("utf8").toUpperCase();
8641
8776
  if (text3 !== "AUTO" && text3 !== "MIXED") {
8642
8777
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
@@ -8727,17 +8862,17 @@ function flowPartitionRoutingKeyFromCommand(command, claim) {
8727
8862
  return { handled: false };
8728
8863
  }
8729
8864
  function isRoutingKey(value) {
8730
- return typeof value === "string" || Buffer35.isBuffer(value);
8865
+ return typeof value === "string" || Buffer36.isBuffer(value);
8731
8866
  }
8732
8867
  function flowAutoIdRoutingKey(value) {
8733
- if (typeof value !== "string" && !Buffer35.isBuffer(value)) {
8868
+ if (typeof value !== "string" && !Buffer36.isBuffer(value)) {
8734
8869
  return void 0;
8735
8870
  }
8736
- const bucket = (Buffer35.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8871
+ const bucket = (Buffer36.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8737
8872
  return `{fa:${bucket}}`;
8738
8873
  }
8739
8874
  function flowClaimLogicalPartitionRoutingKey(value) {
8740
- if (typeof value !== "string" && !Buffer35.isBuffer(value)) return void 0;
8875
+ if (typeof value !== "string" && !Buffer36.isBuffer(value)) return void 0;
8741
8876
  const selector = commandPart(value);
8742
8877
  if (selector === "AUTO" || selector === "ANY") return void 0;
8743
8878
  if (selector === "GLOBAL") return "{f}";
@@ -8752,7 +8887,7 @@ function singleShardFlowClaimPartitionKey(values) {
8752
8887
  return keys.some((key) => key == null) ? void 0 : singleShardKey(keys);
8753
8888
  }
8754
8889
  function singleShardKey(keys) {
8755
- if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !Buffer35.isBuffer(key))) {
8890
+ if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !Buffer36.isBuffer(key))) {
8756
8891
  return void 0;
8757
8892
  }
8758
8893
  const usable = keys;
@@ -8785,7 +8920,7 @@ function routedKeyGroups(keys, routeKey) {
8785
8920
  const groups = /* @__PURE__ */ new Map();
8786
8921
  for (let index = 0; index < keys.length; index += 1) {
8787
8922
  const key = keys[index];
8788
- if (typeof key !== "string" && !Buffer35.isBuffer(key)) return void 0;
8923
+ if (typeof key !== "string" && !Buffer36.isBuffer(key)) return void 0;
8789
8924
  const route = routeKey(key);
8790
8925
  const groupKey = `${route.endpointKey}\0${route.laneId}`;
8791
8926
  const group = groups.get(groupKey);
@@ -9874,7 +10009,7 @@ var TopologyNativeAdapterPool = class _TopologyNativeAdapterPool {
9874
10009
  };
9875
10010
 
9876
10011
  // src/response-map-preservation.ts
9877
- import { Buffer as Buffer37 } from "buffer";
10012
+ import { Buffer as Buffer38 } from "buffer";
9878
10013
  function toStringKeyMapPreservingValues(value) {
9879
10014
  if (value == null) return void 0;
9880
10015
  const result = {};
@@ -9884,7 +10019,7 @@ function toStringKeyMapPreservingValues(value) {
9884
10019
  }
9885
10020
  return result;
9886
10021
  }
9887
- if (typeof value === "object" && !Array.isArray(value) && !Buffer37.isBuffer(value) && !(value instanceof Uint8Array)) {
10022
+ if (typeof value === "object" && !Array.isArray(value) && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
9888
10023
  for (const [key, item] of Object.entries(value)) {
9889
10024
  setOwnValue(result, text(key), normalizeMapStructurePreservingBytes(item));
9890
10025
  }
@@ -9893,7 +10028,7 @@ function toStringKeyMapPreservingValues(value) {
9893
10028
  return void 0;
9894
10029
  }
9895
10030
  function normalizeMapStructurePreservingBytes(value) {
9896
- if (Buffer37.isBuffer(value) || value instanceof Uint8Array) return value;
10031
+ if (Buffer38.isBuffer(value) || value instanceof Uint8Array) return value;
9897
10032
  if (value instanceof Map) {
9898
10033
  const result = {};
9899
10034
  for (const [key, item] of value.entries()) {
@@ -9915,7 +10050,7 @@ function normalizeMapStructurePreservingBytes(value) {
9915
10050
  }
9916
10051
 
9917
10052
  // src/native-kv-responses.ts
9918
- import { Buffer as Buffer38 } from "buffer";
10053
+ import { Buffer as Buffer39 } from "buffer";
9919
10054
  function rateLimitResultFromResp(value) {
9920
10055
  if (!Array.isArray(value) || value.length !== 4) {
9921
10056
  throw new TypeError("RATELIMIT.ADD returned an unexpected response");
@@ -9972,13 +10107,13 @@ function fetchOrComputeResultFromResp(value, codec) {
9972
10107
  }
9973
10108
  function decodePayload(codec, value) {
9974
10109
  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));
10110
+ if (Buffer39.isBuffer(value)) return codec.decode(value);
10111
+ if (value instanceof Uint8Array) return codec.decode(Buffer39.from(value));
10112
+ if (typeof value === "string") return codec.decode(Buffer39.from(value));
9978
10113
  return normalizeRefMeta(value);
9979
10114
  }
9980
10115
  function requiredResponseString(value, context) {
9981
- if (typeof value !== "string" && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
10116
+ if (typeof value !== "string" && !Buffer39.isBuffer(value) && !(value instanceof Uint8Array)) {
9982
10117
  throw new TypeError(`${context} returned an unexpected response`);
9983
10118
  }
9984
10119
  const result = text(value);
@@ -9995,7 +10130,7 @@ function requiredNonNegativeInteger(value, context) {
9995
10130
  return result;
9996
10131
  }
9997
10132
  function responseBytes(value, context) {
9998
- if (typeof value !== "string" && !Buffer38.isBuffer(value) && !(value instanceof Uint8Array)) {
10133
+ if (typeof value !== "string" && !Buffer39.isBuffer(value) && !(value instanceof Uint8Array)) {
9999
10134
  throw new TypeError(`${context} returned an unexpected response`);
10000
10135
  }
10001
10136
  return bytes(value);
@@ -10678,7 +10813,7 @@ function groupAutoPartitionItems(items) {
10678
10813
  }
10679
10814
 
10680
10815
  // src/client-values.ts
10681
- import { Buffer as Buffer39 } from "buffer";
10816
+ import { Buffer as Buffer40 } from "buffer";
10682
10817
  async function valueMGetEntries(client, refs, options = {}) {
10683
10818
  const refCount = refs.length;
10684
10819
  if (refCount === 0) {
@@ -10713,12 +10848,12 @@ async function valueMGetEntries(client, refs, options = {}) {
10713
10848
  const item = response[index];
10714
10849
  if (item == null) {
10715
10850
  entries[index] = { found: false };
10716
- } else if (Buffer39.isBuffer(item)) {
10851
+ } else if (Buffer40.isBuffer(item)) {
10717
10852
  entries[index] = { found: true, value: client.codec.decode(item) };
10718
10853
  } else if (item instanceof Uint8Array) {
10719
- entries[index] = { found: true, value: client.codec.decode(Buffer39.from(item)) };
10854
+ entries[index] = { found: true, value: client.codec.decode(Buffer40.from(item)) };
10720
10855
  } else if (typeof item === "string") {
10721
- entries[index] = { found: true, value: client.codec.decode(Buffer39.from(item)) };
10856
+ entries[index] = { found: true, value: client.codec.decode(Buffer40.from(item)) };
10722
10857
  } else {
10723
10858
  entries[index] = { found: true, value: item };
10724
10859
  }
@@ -10730,7 +10865,7 @@ async function valueMGetEntries(client, refs, options = {}) {
10730
10865
  import "buffer";
10731
10866
 
10732
10867
  // src/auto-batch-ordering.ts
10733
- import { Buffer as Buffer40 } from "buffer";
10868
+ import { Buffer as Buffer41 } from "buffer";
10734
10869
  function autoBatchOrderingPlan(batch) {
10735
10870
  const accesses = /* @__PURE__ */ new Map();
10736
10871
  const fallbackDependencies = [];
@@ -10845,13 +10980,13 @@ function flowManyAutoBatchIds(command, name) {
10845
10980
  return fixedFlowItemIds(
10846
10981
  command,
10847
10982
  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)])
10983
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && isFencingToken(command[itemIndex + (mixed ? 2 : 1)]) && Buffer41.isBuffer(command[itemIndex + (mixed ? 3 : 2)])
10849
10984
  );
10850
10985
  }
10851
10986
  return fixedFlowItemIds(
10852
10987
  command,
10853
10988
  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)])
10989
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer41.isBuffer(command[itemIndex + (mixed ? 2 : 1)]) && isFencingToken(command[itemIndex + (mixed ? 3 : 2)])
10855
10990
  );
10856
10991
  }
10857
10992
  function createManyAutoBatchIds(command) {
@@ -10864,7 +10999,7 @@ function createManyAutoBatchIds(command) {
10864
10999
  const ids = fixedFlowItemIds(
10865
11000
  command,
10866
11001
  width,
10867
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer40.isBuffer(command[itemIndex + width - 1]),
11002
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && Buffer41.isBuffer(command[itemIndex + width - 1]),
10868
11003
  markerIndex
10869
11004
  );
10870
11005
  if (ids != null) return ids;
@@ -10882,7 +11017,7 @@ function extendedCreateManyAutoBatchIds(command, markerIndex) {
10882
11017
  let cursor = markerIndex + 2;
10883
11018
  for (let itemIndex = 0; itemIndex < itemCount; itemIndex += 1) {
10884
11019
  const id = command[cursor];
10885
- if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !Buffer40.isBuffer(command[cursor + 2])) return void 0;
11020
+ if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !Buffer41.isBuffer(command[cursor + 2])) return void 0;
10886
11021
  ids.push(id);
10887
11022
  cursor += 3;
10888
11023
  const afterValues = skipExtendedNamedItems(command, cursor, true);
@@ -10901,7 +11036,7 @@ function skipExtendedNamedItems(command, countIndex, encodedValues) {
10901
11036
  for (let index = 0; index < count; index += 1) {
10902
11037
  const name = command[firstItem + index * 2];
10903
11038
  const value = command[firstItem + index * 2 + 1];
10904
- if (!isAutoBatchResourceValue(name) || (encodedValues ? !Buffer40.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
11039
+ if (!isAutoBatchResourceValue(name) || (encodedValues ? !Buffer41.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
10905
11040
  }
10906
11041
  return firstItem + count * 2;
10907
11042
  }
@@ -10916,7 +11051,7 @@ function runStepsManyAutoBatchIds(command) {
10916
11051
  ids.push(item);
10917
11052
  continue;
10918
11053
  }
10919
- if (typeof item !== "object" || item == null || Array.isArray(item) || Buffer40.isBuffer(item)) return void 0;
11054
+ if (typeof item !== "object" || item == null || Array.isArray(item) || Buffer41.isBuffer(item)) return void 0;
10920
11055
  const id = item.id;
10921
11056
  if (!isAutoBatchResourceValue(id)) return void 0;
10922
11057
  ids.push(id);
@@ -10957,7 +11092,7 @@ function flowValuePutOwner(command, start) {
10957
11092
  }
10958
11093
  var flowValuePutOptionTokens = /* @__PURE__ */ new Set(["NAME", "NOW", "OVERRIDE", "OWNER_FLOW_ID", "PARTITION", "TTL", "TTL_MS"]);
10959
11094
  function isAutoBatchResourceValue(value) {
10960
- return typeof value === "string" || Buffer40.isBuffer(value);
11095
+ return typeof value === "string" || Buffer41.isBuffer(value);
10961
11096
  }
10962
11097
  function isFencingToken(value) {
10963
11098
  return typeof value === "number" && Number.isSafeInteger(value) || typeof value === "bigint";
@@ -10966,7 +11101,7 @@ function nonNegativeItemCount(value) {
10966
11101
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
10967
11102
  }
10968
11103
  function autoBatchResourceKey(namespace, value) {
10969
- return `${namespace}:${Buffer40.from(value).toString("base64")}`;
11104
+ return `${namespace}:${Buffer41.from(value).toString("base64")}`;
10970
11105
  }
10971
11106
  function autoBatchCommandName(command) {
10972
11107
  return commandView(command).name ?? null;
@@ -11378,10 +11513,10 @@ function claimFailure(error) {
11378
11513
  import "buffer";
11379
11514
 
11380
11515
  // src/flow-query-builder.ts
11381
- import { Buffer as Buffer43 } from "buffer";
11516
+ import { Buffer as Buffer44 } from "buffer";
11382
11517
 
11383
11518
  // src/flow-query-metadata.ts
11384
- import { Buffer as Buffer42 } from "buffer";
11519
+ import { Buffer as Buffer43 } from "buffer";
11385
11520
  var MAX_FLOW_QUERY_METADATA_KEY_BYTES = 64;
11386
11521
  function normalizeStateMeta(value, state) {
11387
11522
  if (value == null) return {};
@@ -11406,7 +11541,7 @@ function normalizeStateMeta(value, state) {
11406
11541
  function metadataEntries(value, context) {
11407
11542
  const entries = objectEntries(value ?? {}, context).map(([rawName, item]) => {
11408
11543
  const name = rawName.trim();
11409
- const size = Buffer42.byteLength(name, "utf8");
11544
+ const size = Buffer43.byteLength(name, "utf8");
11410
11545
  if (size === 0 || size > MAX_FLOW_QUERY_METADATA_KEY_BYTES || name.startsWith("__")) {
11411
11546
  throw new TypeError(`${context} key is invalid or reserved`);
11412
11547
  }
@@ -11430,7 +11565,7 @@ function objectEntries(value, context) {
11430
11565
  return keys.map((key) => [key, value[key]]);
11431
11566
  }
11432
11567
  function isPlainRecord(value) {
11433
- if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer42.isBuffer(value)) {
11568
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer43.isBuffer(value)) {
11434
11569
  return false;
11435
11570
  }
11436
11571
  const prototype = Object.getPrototypeOf(value);
@@ -11500,7 +11635,7 @@ var FlowCollectionQuery = class {
11500
11635
  const states = /* @__PURE__ */ new Set();
11501
11636
  for (const [rawState, metadata] of objectEntries(values, "stateMeta")) {
11502
11637
  const state = requiredText(rawState, "stateMeta state").trim();
11503
- const stateBytes = Buffer43.byteLength(state, "utf8");
11638
+ const stateBytes = Buffer44.byteLength(state, "utf8");
11504
11639
  if (stateBytes === 0 || stateBytes > MAX_FLOW_QUERY_STATE_BYTES) {
11505
11640
  throw new TypeError(
11506
11641
  `stateMeta state names must be 1..${MAX_FLOW_QUERY_STATE_BYTES} bytes`
@@ -11679,7 +11814,7 @@ function requiredPartition(value) {
11679
11814
  "FLOW.QUERY convenience methods require a partition key"
11680
11815
  );
11681
11816
  }
11682
- const size = Buffer43.byteLength(value, "utf8");
11817
+ const size = Buffer44.byteLength(value, "utf8");
11683
11818
  if (size === 0 || size > MAX_FLOW_QUERY_PARTITION_BYTES) {
11684
11819
  throw new TypeError(
11685
11820
  `FLOW.QUERY partition key must be 1..${MAX_FLOW_QUERY_PARTITION_BYTES} bytes`
@@ -11727,20 +11862,23 @@ function requiredText(value, context) {
11727
11862
  return value;
11728
11863
  }
11729
11864
  function queryParameter(value, context) {
11730
- if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || Buffer43.isBuffer(value)) {
11865
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || Buffer44.isBuffer(value)) {
11731
11866
  return value;
11732
11867
  }
11733
11868
  throw new TypeError(`${context} must be a scalar FLOW.QUERY parameter`);
11734
11869
  }
11735
11870
 
11736
11871
  // src/flow-query-response.ts
11737
- import { Buffer as Buffer45 } from "buffer";
11872
+ import { Buffer as Buffer48 } from "buffer";
11873
+
11874
+ // src/flow-query-diagnostic-response.ts
11875
+ import { Buffer as Buffer46 } from "buffer";
11738
11876
 
11739
11877
  // src/flow-query-response-validation.ts
11740
- import { Buffer as Buffer44 } from "buffer";
11878
+ import { Buffer as Buffer45 } from "buffer";
11741
11879
  function requiredMap2(value, context) {
11742
11880
  if (value instanceof Map) return value;
11743
- if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer44.isBuffer(value) || value instanceof Uint8Array) {
11881
+ if (typeof value !== "object" || value == null || Array.isArray(value) || Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11744
11882
  throw decodeError(`${context} must be a map`, value);
11745
11883
  }
11746
11884
  return value;
@@ -11797,7 +11935,7 @@ function normalizeMetadataValue(value, context, budget, ancestors, depth) {
11797
11935
  if (!value.isWellFormed()) throw decodeError(`${context} contains invalid text`, value);
11798
11936
  return value;
11799
11937
  }
11800
- if (Buffer44.isBuffer(value) || value instanceof Uint8Array) {
11938
+ if (Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11801
11939
  const decoded = strictText2(value);
11802
11940
  if (decoded == null) throw decodeError(`${context} contains invalid UTF-8`, value);
11803
11941
  return decoded;
@@ -11876,7 +12014,7 @@ function optionalText2(mapping, name, context) {
11876
12014
  }
11877
12015
  function requiredBoundedText2(mapping, name, context, maximumBytes) {
11878
12016
  const value = requiredText2(mapping, name, context);
11879
- if (Buffer44.byteLength(value, "utf8") > maximumBytes) {
12017
+ if (Buffer45.byteLength(value, "utf8") > maximumBytes) {
11880
12018
  throw decodeError(
11881
12019
  `${context} ${name} exceeds ${maximumBytes} bytes`,
11882
12020
  mapping
@@ -11889,7 +12027,7 @@ function boundedText(value, context, maximumBytes) {
11889
12027
  if (decoded == null || decoded.length === 0) {
11890
12028
  throw decodeError(`${context} must be non-empty text`, value);
11891
12029
  }
11892
- if (Buffer44.byteLength(decoded, "utf8") > maximumBytes) {
12030
+ if (Buffer45.byteLength(decoded, "utf8") > maximumBytes) {
11893
12031
  throw decodeError(`${context} exceeds ${maximumBytes} bytes`, value);
11894
12032
  }
11895
12033
  return decoded;
@@ -11943,10 +12081,10 @@ function positiveBoundedInteger(value, maximum, context) {
11943
12081
  function hasKey(mapping, name) {
11944
12082
  if (!(mapping instanceof Map)) return Object.hasOwn(mapping, name);
11945
12083
  if (mapping.has(name)) return true;
11946
- const binaryName = Buffer44.from(name);
12084
+ const binaryName = Buffer45.from(name);
11947
12085
  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))
12086
+ if (Buffer45.isBuffer(key) && key.equals(binaryName)) return true;
12087
+ if (key instanceof Uint8Array && Buffer45.from(key).equals(binaryName))
11950
12088
  return true;
11951
12089
  }
11952
12090
  return false;
@@ -11957,7 +12095,7 @@ function decodeError(message, raw) {
11957
12095
  function strictText2(value) {
11958
12096
  if (typeof value === "string")
11959
12097
  return value.isWellFormed() ? value : void 0;
11960
- if (Buffer44.isBuffer(value) || value instanceof Uint8Array) {
12098
+ if (Buffer45.isBuffer(value) || value instanceof Uint8Array) {
11961
12099
  try {
11962
12100
  return new TextDecoder("utf-8", { fatal: true }).decode(value);
11963
12101
  } catch {
@@ -11990,189 +12128,35 @@ var FlowQueryError = class extends FerricStoreError {
11990
12128
  }
11991
12129
  };
11992
12130
 
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
12023
- });
12024
- }
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
- }
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
- });
12043
- }
12044
- return result.records;
12045
- }
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);
12055
- }
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);
12061
- }
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);
12066
- }
12067
- records[index] = decodeRecord(
12068
- requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
12069
- index
12070
- );
12071
- }
12072
- if (usage.resultRecords !== records.length) {
12073
- throw decodeError("FLOW.QUERY usage result_records does not match records", value);
12074
- }
12075
- return {
12076
- kind: "records",
12077
- records,
12078
- page: decodePage(field(mapping, "page")),
12079
- quality,
12080
- usage,
12081
- raw
12082
- };
12083
- }
12084
- if (hasKey(mapping, "page")) {
12085
- throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
12086
- }
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);
12090
- }
12091
- const count = boundedInteger(
12092
- field(countResult, "value"),
12093
- MAX_SIGNED_64,
12094
- "FLOW.QUERY count value"
12095
- );
12096
- if (usage.resultRecords !== 1) {
12097
- throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
12098
- }
12099
- return {
12100
- kind: "count",
12101
- count,
12102
- quality,
12103
- usage,
12104
- raw
12105
- };
12106
- }
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);
12113
- }
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);
12117
- }
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);
12135
- }
12136
- actual = decodeUsage(actualValue);
12137
- } else if (actualValue != null) {
12138
- throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
12139
- }
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);
12146
- }
12147
- } else if (diagnosticValue != null) {
12148
- throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
12149
- }
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
- }
12131
+ // src/flow-query-diagnostic-response.ts
12132
+ var MIN_SIGNED_64 = -(1n << 63n);
12133
+ var MAX_SIGNED_642 = (1n << 63n) - 1n;
12134
+ var DIAGNOSTIC_TEXT_BYTES = 1024;
12135
+ var DIAGNOSTIC_CONTEXT_ENTRIES = 16;
12136
+ var DIAGNOSTIC_CONTEXT_LIST_ITEMS = 32;
12137
+ var DIAGNOSTIC_CONTEXT_KEY_BYTES = 128;
12138
+ var DIAGNOSTIC_CONTEXT_DEPTH = 6;
12139
+ var DIAGNOSTIC_CONTEXT_NODES = 512;
12162
12140
  function tryDecodeFlowQueryError(value, cause) {
12163
12141
  try {
12164
12142
  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"
12169
- );
12143
+ const context = decodeDiagnosticContext(field(mapping, "context"));
12170
12144
  const position = decodePosition(field(mapping, "position"));
12171
12145
  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"),
12146
+ code: requiredBoundedText2(
12147
+ mapping,
12148
+ "code",
12149
+ "FLOW.QUERY diagnostic",
12150
+ DIAGNOSTIC_TEXT_BYTES
12151
+ ),
12152
+ message: requiredBoundedText2(
12153
+ mapping,
12154
+ "message",
12155
+ "FLOW.QUERY diagnostic",
12156
+ DIAGNOSTIC_TEXT_BYTES
12157
+ ),
12158
+ detail: optionalDiagnosticText(mapping, "detail"),
12159
+ hint: optionalDiagnosticText(mapping, "hint"),
12176
12160
  retryable: requiredBoolean(mapping, "retryable", "FLOW.QUERY diagnostic"),
12177
12161
  safeToRetry: requiredBoolean(mapping, "safe_to_retry", "FLOW.QUERY diagnostic"),
12178
12162
  retryAfterMs: nonNegativeInteger(
@@ -12189,49 +12173,724 @@ function tryDecodeFlowQueryError(value, cause) {
12189
12173
  throw error;
12190
12174
  }
12191
12175
  }
12192
- function decodeFlowQueryIndexStatus(value) {
12193
- const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES");
12194
- requireContract(
12176
+ function optionalDiagnosticText(mapping, name) {
12177
+ const value = optionalText2(mapping, name, "FLOW.QUERY diagnostic");
12178
+ if (value != null && Buffer46.byteLength(value, "utf8") > DIAGNOSTIC_TEXT_BYTES) {
12179
+ throw decodeError(
12180
+ `FLOW.QUERY diagnostic ${name} exceeds ${DIAGNOSTIC_TEXT_BYTES} bytes`,
12181
+ mapping
12182
+ );
12183
+ }
12184
+ return value;
12185
+ }
12186
+ function decodeDiagnosticContext(value) {
12187
+ if (value == null) return void 0;
12188
+ const mapping = requiredMap2(value, "FLOW.QUERY diagnostic context");
12189
+ const size = mapping instanceof Map ? mapping.size : Object.keys(mapping).length;
12190
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12191
+ throw decodeError("FLOW.QUERY diagnostic context contains too many entries", value);
12192
+ }
12193
+ validateDiagnosticContextValue(
12195
12194
  mapping,
12196
- "contract_version",
12197
- FLOW_QUERY_INDEXES_CONTRACT,
12198
- "FLOW.QUERY.INDEXES"
12199
- );
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"
12195
+ DIAGNOSTIC_CONTEXT_DEPTH,
12196
+ { remaining: DIAGNOSTIC_CONTEXT_NODES }
12204
12197
  );
12205
- const rawIndexes = field(mapping, "indexes");
12206
- if (!Array.isArray(rawIndexes) || rawIndexes.length > 32) {
12207
- throw decodeError("FLOW.QUERY.INDEXES indexes must contain at most 32 entries", value);
12198
+ return freezeMetadataMap(mapping, "FLOW.QUERY diagnostic context");
12199
+ }
12200
+ function validateDiagnosticContextValue(value, depth, budget) {
12201
+ consumeDiagnosticContextNode(value, budget);
12202
+ if (value == null || typeof value === "boolean") return;
12203
+ if (typeof value === "number") {
12204
+ if (Number.isSafeInteger(value)) return;
12205
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12208
12206
  }
12209
- const indexes = new Array(rawIndexes.length);
12210
- for (let index = 0; index < rawIndexes.length; index += 1) {
12211
- if (!Object.hasOwn(rawIndexes, index)) {
12212
- throw decodeError("FLOW.QUERY.INDEXES indexes must be a dense array", value);
12213
- }
12214
- indexes[index] = decodeIndex(rawIndexes[index], index);
12207
+ if (typeof value === "bigint") {
12208
+ if (value >= MIN_SIGNED_64 && value <= MAX_SIGNED_642) return;
12209
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12215
12210
  }
12216
- return Object.freeze({
12217
- contractVersion: FLOW_QUERY_INDEXES_CONTRACT,
12218
- observedAtMs: nonNegativeInteger(
12219
- field(mapping, "observed_at_ms"),
12220
- "FLOW.QUERY.INDEXES observed_at_ms"
12211
+ const text3 = diagnosticContextText(value);
12212
+ if (text3 != null) {
12213
+ if (Buffer46.byteLength(text3, "utf8") <= DIAGNOSTIC_TEXT_BYTES) return;
12214
+ throw decodeError("FLOW.QUERY diagnostic context contains oversized text", value);
12215
+ }
12216
+ if (depth <= 0) {
12217
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its maximum depth", value);
12218
+ }
12219
+ if (Array.isArray(value)) {
12220
+ if (value.length > DIAGNOSTIC_CONTEXT_LIST_ITEMS) {
12221
+ throw decodeError("FLOW.QUERY diagnostic context list is too large", value);
12222
+ }
12223
+ for (let index = 0; index < value.length; index += 1) {
12224
+ if (!Object.hasOwn(value, index)) {
12225
+ throw decodeError("FLOW.QUERY diagnostic context list must be dense", value);
12226
+ }
12227
+ validateDiagnosticContextValue(value[index], depth - 1, budget);
12228
+ }
12229
+ return;
12230
+ }
12231
+ if (value instanceof Map || isDiagnosticContextRecord(value)) {
12232
+ const entries = value instanceof Map ? value.entries() : Object.entries(value);
12233
+ const size = value instanceof Map ? value.size : Object.keys(value).length;
12234
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12235
+ throw decodeError("FLOW.QUERY diagnostic context map is too large", value);
12236
+ }
12237
+ for (const [rawKey, item] of entries) {
12238
+ const key = diagnosticContextText(rawKey);
12239
+ if (key == null || key.length === 0 || Buffer46.byteLength(key, "utf8") > DIAGNOSTIC_CONTEXT_KEY_BYTES) {
12240
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid key", value);
12241
+ }
12242
+ validateDiagnosticContextValue(item, depth - 1, budget);
12243
+ }
12244
+ return;
12245
+ }
12246
+ throw decodeError("FLOW.QUERY diagnostic context contains an unsupported value", value);
12247
+ }
12248
+ function consumeDiagnosticContextNode(value, budget) {
12249
+ if (budget.remaining <= 0) {
12250
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its node limit", value);
12251
+ }
12252
+ budget.remaining -= 1;
12253
+ }
12254
+ function diagnosticContextText(value) {
12255
+ if (typeof value === "string") return value.isWellFormed() ? value : void 0;
12256
+ if (!Buffer46.isBuffer(value) && !(value instanceof Uint8Array)) return void 0;
12257
+ try {
12258
+ return new TextDecoder("utf-8", { fatal: true }).decode(value);
12259
+ } catch {
12260
+ return void 0;
12261
+ }
12262
+ }
12263
+ function isDiagnosticContextRecord(value) {
12264
+ if (typeof value !== "object" || value == null) return false;
12265
+ const prototype = Object.getPrototypeOf(value);
12266
+ return prototype === Object.prototype || prototype === null;
12267
+ }
12268
+ function decodePosition(value) {
12269
+ if (value == null) return void 0;
12270
+ const mapping = requiredMap2(value, "FLOW.QUERY diagnostic position");
12271
+ return Object.freeze({
12272
+ byte: positiveInteger(field(mapping, "byte"), "FLOW.QUERY diagnostic position byte"),
12273
+ line: positiveInteger(field(mapping, "line"), "FLOW.QUERY diagnostic position line"),
12274
+ column: positiveInteger(
12275
+ field(mapping, "column"),
12276
+ "FLOW.QUERY diagnostic position column"
12277
+ )
12278
+ });
12279
+ }
12280
+
12281
+ // src/flow-query-index-contract.ts
12282
+ import { Buffer as Buffer47 } from "buffer";
12283
+ var FLOW_QUERY_BUILD_PHASES = ["pending", "snapshot", "backfill", "done"];
12284
+ var FLOW_QUERY_VALIDATION_PHASES = [
12285
+ "pending",
12286
+ "source",
12287
+ "index",
12288
+ "counter",
12289
+ "cleanup",
12290
+ "done"
12291
+ ];
12292
+ var FLOW_QUERY_RETIREMENT_PHASES = [
12293
+ "pending",
12294
+ "fence",
12295
+ "index",
12296
+ "counter",
12297
+ "reverse",
12298
+ "cleanup",
12299
+ "done"
12300
+ ];
12301
+ var IDENTIFIER = /^[A-Za-z0-9_.:-]+$/u;
12302
+ var UNQUOTED_METADATA = /^[A-Za-z0-9_-]+$/u;
12303
+ var ATTRIBUTE_SELECTOR = /^attribute\['((?:[^']|'')*)'\]$/u;
12304
+ var STATE_META_SELECTOR = /^state_meta\['((?:[^']|'')*)'\]\['((?:[^']|'')*)'\]$/u;
12305
+ var INTEGER_FIELDS = /* @__PURE__ */ new Set([
12306
+ "version",
12307
+ "priority",
12308
+ "created_at_ms",
12309
+ "updated_at_ms",
12310
+ "next_run_at_ms",
12311
+ "lease_deadline_ms",
12312
+ "attempts",
12313
+ "max_active_ms"
12314
+ ]);
12315
+ var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
12316
+ ...INTEGER_FIELDS,
12317
+ "partition_key",
12318
+ "run_id",
12319
+ "event_id",
12320
+ "type",
12321
+ "state",
12322
+ "run_state",
12323
+ "parent_flow_id",
12324
+ "root_flow_id",
12325
+ "correlation_id"
12326
+ ]);
12327
+ var RETIREMENT_PROGRESS_FIELDS = [
12328
+ "phase_counts",
12329
+ "current_phases",
12330
+ "completed_shards",
12331
+ "total_shards",
12332
+ "deleted_entries",
12333
+ "deleted_bytes",
12334
+ "rewritten_reverse_rows"
12335
+ ];
12336
+ function validateFlowQueryIndexContract(status, expectedId) {
12337
+ let previousId;
12338
+ let previousVersion;
12339
+ for (const index of status.indexes) {
12340
+ const version = BigInt(index.version);
12341
+ if (previousId != null && (index.id < previousId || index.id === previousId && previousVersion != null && version <= previousVersion)) {
12342
+ fail("indexes must be uniquely sorted by id and version", status.raw);
12343
+ }
12344
+ if (expectedId != null && index.id !== expectedId) {
12345
+ fail("filtered indexes do not match the requested id", status.raw);
12346
+ }
12347
+ previousId = index.id;
12348
+ previousVersion = version;
12349
+ validateIndex(index, status);
12350
+ }
12351
+ if (expectedId != null && status.indexes.length === 0) {
12352
+ fail("filtered indexes do not match the requested id", status.raw);
12353
+ }
12354
+ const hasUnavailableStatistics = status.indexes.some(
12355
+ (index) => index.statistics.status === "unavailable"
12356
+ );
12357
+ if (status.services.statisticsStore === "unavailable") {
12358
+ if (status.indexes.some((index) => index.statistics.status !== "unavailable")) {
12359
+ fail("statistics must be unavailable when the service is unavailable", status.raw);
12360
+ }
12361
+ } else if (hasUnavailableStatistics) {
12362
+ fail("statistics cannot be unavailable while the service is ready", status.raw);
12363
+ }
12364
+ }
12365
+ function validateIndex(index, status) {
12366
+ if (!IDENTIFIER.test(index.id) || index.workloads.some((workload) => !IDENTIFIER.test(workload))) {
12367
+ fail("index identity contains invalid characters", index.raw);
12368
+ }
12369
+ const first = index.fields[0];
12370
+ if (first?.name !== "partition_key" || first.direction !== "asc" || first.encoding !== "hashed") {
12371
+ fail("index must begin with partition_key asc hashed", index.raw);
12372
+ }
12373
+ let attributeFields = 0;
12374
+ for (const indexField of index.fields) {
12375
+ if (indexField.encoding === "hashed" && indexField.direction !== "asc") {
12376
+ fail("hashed index fields must be ascending", index.raw);
12377
+ }
12378
+ const kind = fieldKind(indexField.name);
12379
+ if (kind == null) fail("index contains an unsupported field selector", index.raw);
12380
+ if (indexField.encoding === "ordered" && kind !== "integer") {
12381
+ fail("ordered index fields must be integers", index.raw);
12382
+ }
12383
+ if (kind === "attribute") attributeFields += 1;
12384
+ }
12385
+ if (attributeFields > 1) fail("index may contain at most one attribute field", index.raw);
12386
+ for (const prefix of index.countPrefixes) {
12387
+ if (index.fields.slice(0, prefix).some((field3) => field3.encoding !== "hashed")) {
12388
+ fail("count prefixes may cover only hashed fields", index.raw);
12389
+ }
12390
+ }
12391
+ if (index.coveringFields.length > 0) {
12392
+ if (index.coveringFields.some((field3) => fieldKind(field3) == null)) {
12393
+ fail("index contains an unsupported covering field selector", index.raw);
12394
+ }
12395
+ const required = /* @__PURE__ */ new Set(["run_id", "version", ...index.fields.map((field3) => field3.name)]);
12396
+ if ([...required].some((field3) => !index.coveringFields.includes(field3))) {
12397
+ fail("index covering fields omit an identity or index field", index.raw);
12398
+ }
12399
+ }
12400
+ if (index.countPrefixes.length > 0 !== (index.format.counter != null)) {
12401
+ fail("index counter format is inconsistent with count prefixes", index.raw);
12402
+ }
12403
+ validateProgress(index.build, FLOW_QUERY_BUILD_PHASES, "build");
12404
+ validateProgress(index.validation, FLOW_QUERY_VALIDATION_PHASES, "validation");
12405
+ if (index.retirement.status === "not_applicable") {
12406
+ if (RETIREMENT_PROGRESS_FIELDS.some((name) => hasKey(index.retirement.raw, name))) {
12407
+ fail("not_applicable retirement must not contain progress", index.retirement.raw);
12408
+ }
12409
+ } else {
12410
+ const retirement = index.retirement;
12411
+ if (retirement.phaseCounts == null || retirement.currentPhases == null || retirement.completedShards == null || retirement.totalShards == null) {
12412
+ fail("retirement progress is incomplete", retirement.raw);
12413
+ }
12414
+ validateProgress(
12415
+ {
12416
+ phaseCounts: retirement.phaseCounts,
12417
+ currentPhases: retirement.currentPhases,
12418
+ completedShards: retirement.completedShards,
12419
+ totalShards: retirement.totalShards,
12420
+ raw: retirement.raw
12421
+ },
12422
+ FLOW_QUERY_RETIREMENT_PHASES,
12423
+ "retirement"
12424
+ );
12425
+ }
12426
+ const totals = new Set(
12427
+ [
12428
+ index.coverage.totalShards,
12429
+ index.build.totalShards,
12430
+ index.validation.totalShards,
12431
+ ...index.retirement.totalShards == null ? [] : [index.retirement.totalShards]
12432
+ ].map(indexInteger)
12433
+ );
12434
+ if (totals.size !== 1) fail("index shard totals are inconsistent", index.raw);
12435
+ if (!sameIndexInteger(index.coverage.completeShards, index.build.completedShards)) {
12436
+ fail("coverage and build completion are inconsistent", index.raw);
12437
+ }
12438
+ if (index.coverage.validation !== index.validation.status) {
12439
+ fail("coverage and validation status are inconsistent", index.raw);
12440
+ }
12441
+ const queryable = index.state === "active" && sameIndexInteger(
12442
+ index.coverage.completeShards,
12443
+ index.coverage.totalShards
12444
+ ) && index.coverage.validation === "passed";
12445
+ if (index.queryable !== queryable) fail("index queryable flag is inconsistent", index.raw);
12446
+ validateLifecycle(index);
12447
+ validateValidation(index);
12448
+ validateStatistics(index, status);
12449
+ }
12450
+ function validateProgress(progress, phases, section) {
12451
+ const entries = Object.entries(progress.phaseCounts);
12452
+ if (entries.length === 0 || entries.some(
12453
+ ([phase, count]) => !phases.includes(phase) || indexInteger(count) <= 0n
12454
+ ) || entries.reduce((sum, [, count]) => sum + indexInteger(count), 0n) !== indexInteger(progress.totalShards)) {
12455
+ fail(`${section} phase_counts are invalid`, progress.raw);
12456
+ }
12457
+ const current = phases.filter((phase) => Object.hasOwn(progress.phaseCounts, phase));
12458
+ if (current.length !== progress.currentPhases.length || current.some((phase, position) => phase !== progress.currentPhases[position])) {
12459
+ fail(`${section} current_phases are inconsistent`, progress.raw);
12460
+ }
12461
+ if (!sameIndexInteger(
12462
+ progress.completedShards,
12463
+ progress.phaseCounts.done ?? 0
12464
+ )) {
12465
+ fail(`${section} completed_shards is inconsistent`, progress.raw);
12466
+ }
12467
+ }
12468
+ function validateLifecycle(index) {
12469
+ const built = sameIndexInteger(
12470
+ index.build.completedShards,
12471
+ index.build.totalShards
12472
+ );
12473
+ const validation = index.validation.status;
12474
+ const retirement = index.retirement.status;
12475
+ 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";
12476
+ if (!valid) fail("index lifecycle fields are inconsistent", index.raw);
12477
+ }
12478
+ function validateValidation(index) {
12479
+ const validation = index.validation;
12480
+ 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;
12481
+ if (!valid) fail("validation status fields are inconsistent", validation.raw);
12482
+ }
12483
+ function validateStatistics(index, status) {
12484
+ const statistics = index.statistics;
12485
+ const times = [
12486
+ statistics.oldestCollectedAtMs,
12487
+ statistics.newestCollectedAtMs,
12488
+ statistics.oldestAgeMs,
12489
+ statistics.newestAgeMs
12490
+ ];
12491
+ if (indexInteger(statistics.samples) === 0n) {
12492
+ if (statistics.status !== "missing" && statistics.status !== "unavailable" || times.some((value) => value != null)) {
12493
+ fail("empty statistics fields are inconsistent", statistics.raw);
12494
+ }
12495
+ return;
12496
+ }
12497
+ if (times.some((value) => value == null)) {
12498
+ fail("sampled statistics require timestamps and ages", statistics.raw);
12499
+ }
12500
+ const [oldest, newest, oldestAge, newestAge] = times;
12501
+ if (indexInteger(oldest) > indexInteger(newest) || indexInteger(oldestAge) !== indexAge(status.observedAtMs, oldest) || indexInteger(newestAge) !== indexAge(status.observedAtMs, newest)) {
12502
+ fail("statistics timestamps or ages are inconsistent", statistics.raw);
12503
+ }
12504
+ const expected = sameIndexInteger(statistics.freshSamples, statistics.samples) ? ["fresh"] : indexInteger(statistics.freshSamples) === 0n ? indexInteger(statistics.futureSamples) > 0n ? ["stale", "future"] : ["stale"] : ["mixed"];
12505
+ if (!expected.includes(statistics.status)) {
12506
+ fail("statistics status does not match sample counters", statistics.raw);
12507
+ }
12508
+ }
12509
+ function fieldKind(name) {
12510
+ if (BUILTIN_FIELDS.has(name)) return INTEGER_FIELDS.has(name) ? "integer" : "keyword";
12511
+ const parts = name.split(".");
12512
+ if (parts.length === 2 && parts[0] === "attribute" && validUnquoted(parts[1] ?? "")) return "attribute";
12513
+ if (parts.length === 3 && parts[0] === "state_meta" && validUnquoted(parts[1] ?? "") && validUnquoted(parts[2] ?? "")) return "state_meta";
12514
+ const attribute = ATTRIBUTE_SELECTOR.exec(name);
12515
+ if (attribute != null) {
12516
+ const metadata = (attribute[1] ?? "").replaceAll("''", "'");
12517
+ if (validMetadata(metadata, true) && name === externalSelector("attribute", metadata)) return "attribute";
12518
+ }
12519
+ const stateMeta = STATE_META_SELECTOR.exec(name);
12520
+ if (stateMeta != null) {
12521
+ const state = (stateMeta[1] ?? "").replaceAll("''", "'");
12522
+ const metadata = (stateMeta[2] ?? "").replaceAll("''", "'");
12523
+ if (validMetadata(state, false) && validMetadata(metadata, true) && name === externalSelector("state_meta", state, metadata)) return "state_meta";
12524
+ }
12525
+ return void 0;
12526
+ }
12527
+ function validUnquoted(value) {
12528
+ return !value.startsWith("__") && UNQUOTED_METADATA.test(value) && Buffer47.byteLength(value, "ascii") <= 64;
12529
+ }
12530
+ function validMetadata(value, rejectReserved) {
12531
+ return value.length > 0 && Buffer47.byteLength(value, "utf8") <= 64 && (!rejectReserved || !value.startsWith("__"));
12532
+ }
12533
+ function externalSelector(root, ...segments) {
12534
+ return segments.every(validUnquoted) ? [root, ...segments].join(".") : root + segments.map((segment) => `['${segment.replaceAll("'", "''")}']`).join("");
12535
+ }
12536
+ function sameIndexInteger(left, right) {
12537
+ return indexInteger(left) === indexInteger(right);
12538
+ }
12539
+ function indexAge(observedAtMs, collectedAtMs) {
12540
+ const age = indexInteger(observedAtMs) - indexInteger(collectedAtMs);
12541
+ return age < 0n ? 0n : age;
12542
+ }
12543
+ function indexInteger(value) {
12544
+ return BigInt(value);
12545
+ }
12546
+ function fail(message, raw) {
12547
+ throw decodeError(`FLOW.QUERY.INDEXES ${message}`, raw);
12548
+ }
12549
+
12550
+ // src/flow-query-index-decode.ts
12551
+ var MAX_UNSIGNED_64 = (1n << 64n) - 1n;
12552
+ function decodeUniqueTextArray(value, context, maximum, maximumBytes) {
12553
+ if (!Array.isArray(value) || value.length > maximum) {
12554
+ throw decodeError(`${context} must be a bounded array`, value);
12555
+ }
12556
+ const result = new Array(value.length);
12557
+ const seen = /* @__PURE__ */ new Set();
12558
+ for (let index = 0; index < value.length; index += 1) {
12559
+ if (!Object.hasOwn(value, index)) {
12560
+ throw decodeError(`${context} must be dense`, value);
12561
+ }
12562
+ const text3 = boundedText(
12563
+ value[index],
12564
+ `${context} entry ${index}`,
12565
+ maximumBytes
12566
+ );
12567
+ if (seen.has(text3)) {
12568
+ throw decodeError(`${context} contains duplicates`, value);
12569
+ }
12570
+ seen.add(text3);
12571
+ result[index] = text3;
12572
+ }
12573
+ return Object.freeze(result);
12574
+ }
12575
+ function requiredChoice(mapping, name, context, choices) {
12576
+ const value = requiredBoundedText2(mapping, name, context, 64);
12577
+ if (!choices.includes(value)) {
12578
+ throw decodeError(`${context} ${name} is unsupported`, mapping);
12579
+ }
12580
+ return value;
12581
+ }
12582
+ function optionalBoundedText(mapping, name, context, maximumBytes) {
12583
+ const value = field(mapping, name);
12584
+ if (value == null) return void 0;
12585
+ return boundedText(value, `${context} ${name}`, maximumBytes);
12586
+ }
12587
+ function optionalIndexInteger(mapping, name, context) {
12588
+ const value = field(mapping, name);
12589
+ return value == null ? void 0 : unsignedIndexInteger(value, `${context} ${name}`);
12590
+ }
12591
+ function indexCounter(mapping, name, section) {
12592
+ return unsignedIndexInteger(
12593
+ field(mapping, name),
12594
+ `FLOW.QUERY.INDEXES index ${section} ${name}`
12595
+ );
12596
+ }
12597
+ function unsignedIndexInteger(value, context) {
12598
+ return boundedInteger(value, MAX_UNSIGNED_64, context);
12599
+ }
12600
+ function positiveIndexInteger(value, context) {
12601
+ return positiveBoundedInteger(value, MAX_UNSIGNED_64, context);
12602
+ }
12603
+ function indexInteger2(value) {
12604
+ return BigInt(value);
12605
+ }
12606
+
12607
+ // src/flow-query-index-lifecycle-response.ts
12608
+ function decodeIndexCoverage(value) {
12609
+ const context = "FLOW.QUERY.INDEXES index coverage";
12610
+ const mapping = requiredMap2(value, context);
12611
+ const completeShards = unsignedIndexInteger(
12612
+ field(mapping, "complete_shards"),
12613
+ `${context} complete_shards`
12614
+ );
12615
+ const totalShards = positiveIndexInteger(
12616
+ field(mapping, "total_shards"),
12617
+ `${context} total_shards`
12618
+ );
12619
+ if (indexInteger2(completeShards) > indexInteger2(totalShards)) {
12620
+ throw decodeError(`${context} complete_shards exceeds total_shards`, value);
12621
+ }
12622
+ return Object.freeze({
12623
+ completeShards,
12624
+ totalShards,
12625
+ validation: requiredChoice(mapping, "validation", context, [
12626
+ "pending",
12627
+ "passed",
12628
+ "failed"
12629
+ ]),
12630
+ raw: freezeMap(mapping)
12631
+ });
12632
+ }
12633
+ function decodeIndexBuild(value) {
12634
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES index build");
12635
+ const progress = decodeIndexProgress(
12636
+ mapping,
12637
+ "build",
12638
+ FLOW_QUERY_BUILD_PHASES
12639
+ );
12640
+ return Object.freeze({
12641
+ ...progress,
12642
+ scannedRecords: indexCounter(mapping, "scanned_records", "build"),
12643
+ writtenEntries: indexCounter(mapping, "written_entries", "build"),
12644
+ writtenBytes: indexCounter(mapping, "written_bytes", "build")
12645
+ });
12646
+ }
12647
+ function decodeIndexValidation(value) {
12648
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES index validation");
12649
+ if (!hasKey(mapping, "failure_reason") || !hasKey(mapping, "validated_at_ms")) {
12650
+ throw decodeError(
12651
+ "FLOW.QUERY.INDEXES index validation is missing nullable fields",
12652
+ value
12653
+ );
12654
+ }
12655
+ const progress = decodeIndexProgress(
12656
+ mapping,
12657
+ "validation",
12658
+ FLOW_QUERY_VALIDATION_PHASES
12659
+ );
12660
+ return Object.freeze({
12661
+ ...progress,
12662
+ status: requiredChoice(
12663
+ mapping,
12664
+ "status",
12665
+ "FLOW.QUERY.INDEXES index validation",
12666
+ ["pending", "passed", "failed"]
12667
+ ),
12668
+ checkedRecords: indexCounter(mapping, "checked_records", "validation"),
12669
+ checkedEntries: indexCounter(mapping, "checked_entries", "validation"),
12670
+ mismatches: indexCounter(mapping, "mismatches", "validation"),
12671
+ failureReason: optionalBoundedText(
12672
+ mapping,
12673
+ "failure_reason",
12674
+ "FLOW.QUERY.INDEXES index validation",
12675
+ 128
12676
+ ),
12677
+ validatedAtMs: optionalIndexInteger(
12678
+ mapping,
12679
+ "validated_at_ms",
12680
+ "FLOW.QUERY.INDEXES index validation"
12681
+ )
12682
+ });
12683
+ }
12684
+ function decodeIndexRetirement(value) {
12685
+ const context = "FLOW.QUERY.INDEXES index retirement";
12686
+ const mapping = requiredMap2(value, context);
12687
+ const status = requiredChoice(mapping, "status", context, [
12688
+ "not_applicable",
12689
+ "pending",
12690
+ "complete"
12691
+ ]);
12692
+ if (status === "not_applicable") {
12693
+ return Object.freeze({ status, raw: freezeMap(mapping) });
12694
+ }
12695
+ const completedShards = unsignedIndexInteger(
12696
+ field(mapping, "completed_shards"),
12697
+ `${context} completed_shards`
12698
+ );
12699
+ const totalShards = positiveIndexInteger(
12700
+ field(mapping, "total_shards"),
12701
+ `${context} total_shards`
12702
+ );
12703
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12704
+ throw decodeError(
12705
+ `${context} completed_shards exceeds total_shards`,
12706
+ mapping
12707
+ );
12708
+ }
12709
+ return Object.freeze({
12710
+ status,
12711
+ phaseCounts: decodePhaseCounts(
12712
+ field(mapping, "phase_counts"),
12713
+ "retirement"
12714
+ ),
12715
+ currentPhases: decodeAllowedPhases(
12716
+ field(mapping, "current_phases"),
12717
+ "retirement",
12718
+ FLOW_QUERY_RETIREMENT_PHASES
12719
+ ),
12720
+ completedShards,
12721
+ totalShards,
12722
+ deletedEntries: indexCounter(mapping, "deleted_entries", "retirement"),
12723
+ deletedBytes: indexCounter(mapping, "deleted_bytes", "retirement"),
12724
+ rewrittenReverseRows: indexCounter(
12725
+ mapping,
12726
+ "rewritten_reverse_rows",
12727
+ "retirement"
12728
+ ),
12729
+ raw: freezeMap(mapping)
12730
+ });
12731
+ }
12732
+ function decodeIndexStatistics(value) {
12733
+ const context = "FLOW.QUERY.INDEXES index statistics";
12734
+ const mapping = requiredMap2(value, context);
12735
+ for (const name of [
12736
+ "oldest_collected_at_ms",
12737
+ "newest_collected_at_ms",
12738
+ "oldest_age_ms",
12739
+ "newest_age_ms"
12740
+ ]) {
12741
+ if (!hasKey(mapping, name)) {
12742
+ throw decodeError(
12743
+ `${context} is missing required nullable fields`,
12744
+ value
12745
+ );
12746
+ }
12747
+ }
12748
+ const samples = indexCounter(mapping, "samples", "statistics");
12749
+ const freshSamples = indexCounter(mapping, "fresh_samples", "statistics");
12750
+ const staleSamples = indexCounter(mapping, "stale_samples", "statistics");
12751
+ const futureSamples = indexCounter(mapping, "future_samples", "statistics");
12752
+ if (indexInteger2(freshSamples) + indexInteger2(staleSamples) !== indexInteger2(samples) || indexInteger2(futureSamples) > indexInteger2(staleSamples)) {
12753
+ throw decodeError(`${context} counters are inconsistent`, value);
12754
+ }
12755
+ return Object.freeze({
12756
+ status: requiredChoice(mapping, "status", context, [
12757
+ "fresh",
12758
+ "stale",
12759
+ "future",
12760
+ "mixed",
12761
+ "missing",
12762
+ "unavailable"
12763
+ ]),
12764
+ samples,
12765
+ freshSamples,
12766
+ staleSamples,
12767
+ futureSamples,
12768
+ oldestCollectedAtMs: optionalIndexInteger(
12769
+ mapping,
12770
+ "oldest_collected_at_ms",
12771
+ context
12772
+ ),
12773
+ newestCollectedAtMs: optionalIndexInteger(
12774
+ mapping,
12775
+ "newest_collected_at_ms",
12776
+ context
12777
+ ),
12778
+ oldestAgeMs: optionalIndexInteger(mapping, "oldest_age_ms", context),
12779
+ newestAgeMs: optionalIndexInteger(mapping, "newest_age_ms", context),
12780
+ raw: freezeMap(mapping)
12781
+ });
12782
+ }
12783
+ function decodeIndexProgress(mapping, section, allowedPhases) {
12784
+ const context = `FLOW.QUERY.INDEXES index ${section}`;
12785
+ const completedShards = unsignedIndexInteger(
12786
+ field(mapping, "completed_shards"),
12787
+ `${context} completed_shards`
12788
+ );
12789
+ const totalShards = positiveIndexInteger(
12790
+ field(mapping, "total_shards"),
12791
+ `${context} total_shards`
12792
+ );
12793
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12794
+ throw decodeError(
12795
+ `${context} completed_shards exceeds total_shards`,
12796
+ mapping
12797
+ );
12798
+ }
12799
+ return Object.freeze({
12800
+ scope: requiredChoice(mapping, "scope", context, ["catalog_build"]),
12801
+ phaseCounts: decodePhaseCounts(field(mapping, "phase_counts"), section),
12802
+ currentPhases: decodeAllowedPhases(
12803
+ field(mapping, "current_phases"),
12804
+ section,
12805
+ allowedPhases
12806
+ ),
12807
+ completedShards,
12808
+ totalShards,
12809
+ raw: freezeMap(mapping)
12810
+ });
12811
+ }
12812
+ function decodePhaseCounts(value, section) {
12813
+ const context = `FLOW.QUERY.INDEXES index ${section} phase_counts`;
12814
+ const mapping = requiredMap2(value, context);
12815
+ const entries = mapping instanceof Map ? [...mapping.entries()] : Object.entries(mapping);
12816
+ if (entries.length > 16) {
12817
+ throw decodeError(`${context} contains too many entries`, value);
12818
+ }
12819
+ const result = /* @__PURE__ */ Object.create(null);
12820
+ for (const [rawPhase, rawCount] of entries) {
12821
+ const phase = boundedText(rawPhase, `${context} phase`, 64);
12822
+ if (Object.hasOwn(result, phase)) {
12823
+ throw decodeError(`${context} contains duplicate phases`, value);
12824
+ }
12825
+ result[phase] = unsignedIndexInteger(rawCount, `${context} ${phase}`);
12826
+ }
12827
+ return Object.freeze(result);
12828
+ }
12829
+ function decodeAllowedPhases(value, section, allowed) {
12830
+ const phases = decodeUniqueTextArray(
12831
+ value,
12832
+ `FLOW.QUERY.INDEXES index ${section} current_phases`,
12833
+ allowed.length,
12834
+ 64
12835
+ );
12836
+ if (phases.some((phase) => !allowed.includes(phase))) {
12837
+ throw decodeError(
12838
+ `FLOW.QUERY.INDEXES index ${section} current_phases are invalid`,
12839
+ value
12840
+ );
12841
+ }
12842
+ return phases;
12843
+ }
12844
+
12845
+ // src/flow-query-index-response.ts
12846
+ var FLOW_QUERY_INDEXES_CONTRACT = "ferric.flow.query.indexes/v1";
12847
+ function decodeFlowQueryIndexStatus(value, expectedId) {
12848
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES");
12849
+ requireContract(
12850
+ mapping,
12851
+ "contract_version",
12852
+ FLOW_QUERY_INDEXES_CONTRACT,
12853
+ "FLOW.QUERY.INDEXES"
12854
+ );
12855
+ const registry = requiredMap2(
12856
+ field(mapping, "registry"),
12857
+ "FLOW.QUERY.INDEXES registry"
12858
+ );
12859
+ const services = decodeIndexServices(field(mapping, "services"));
12860
+ const rawIndexes = field(mapping, "indexes");
12861
+ if (!Array.isArray(rawIndexes) || rawIndexes.length > 32) {
12862
+ throw decodeError(
12863
+ "FLOW.QUERY.INDEXES indexes must contain at most 32 entries",
12864
+ value
12865
+ );
12866
+ }
12867
+ const indexes = new Array(rawIndexes.length);
12868
+ for (let index = 0; index < rawIndexes.length; index += 1) {
12869
+ if (!Object.hasOwn(rawIndexes, index)) {
12870
+ throw decodeError(
12871
+ "FLOW.QUERY.INDEXES indexes must be a dense array",
12872
+ value
12873
+ );
12874
+ }
12875
+ indexes[index] = decodeIndex(rawIndexes[index], index);
12876
+ }
12877
+ const status = Object.freeze({
12878
+ contractVersion: FLOW_QUERY_INDEXES_CONTRACT,
12879
+ observedAtMs: unsignedIndexInteger(
12880
+ field(mapping, "observed_at_ms"),
12881
+ "FLOW.QUERY.INDEXES observed_at_ms"
12221
12882
  ),
12222
- statisticsMaxAgeMs: nonNegativeInteger(
12883
+ statisticsMaxAgeMs: unsignedIndexInteger(
12223
12884
  field(mapping, "statistics_max_age_ms"),
12224
12885
  "FLOW.QUERY.INDEXES statistics_max_age_ms"
12225
12886
  ),
12226
12887
  registry: Object.freeze({
12227
- epoch: boundedInteger(
12888
+ epoch: unsignedIndexInteger(
12228
12889
  field(registry, "epoch"),
12229
- MAX_UNSIGNED_64,
12230
12890
  "FLOW.QUERY.INDEXES epoch"
12231
12891
  ),
12232
- catalogVersion: positiveBoundedInteger(
12892
+ catalogVersion: positiveIndexInteger(
12233
12893
  field(registry, "catalog_version"),
12234
- MAX_UNSIGNED_64,
12235
12894
  "FLOW.QUERY.INDEXES catalog_version"
12236
12895
  )
12237
12896
  }),
@@ -12239,24 +12898,155 @@ function decodeFlowQueryIndexStatus(value) {
12239
12898
  indexes: Object.freeze(indexes),
12240
12899
  raw: freezeMap(mapping)
12241
12900
  });
12901
+ validateFlowQueryIndexContract(status, expectedId);
12902
+ return status;
12242
12903
  }
12243
12904
  function decodeIndex(value, index) {
12244
12905
  const mapping = requiredMap2(value, `FLOW.QUERY.INDEXES index ${index}`);
12906
+ const fields = decodeIndexFields(field(mapping, "fields"));
12245
12907
  return Object.freeze({
12246
- id: requiredText2(mapping, "id", "FLOW.QUERY.INDEXES index"),
12247
- version: positiveBoundedInteger(
12908
+ id: requiredBoundedText2(mapping, "id", "FLOW.QUERY.INDEXES index", 64),
12909
+ version: positiveIndexInteger(
12248
12910
  field(mapping, "version"),
12249
- MAX_UNSIGNED_64,
12250
12911
  "FLOW.QUERY.INDEXES index version"
12251
12912
  ),
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"),
12913
+ buildId: requiredBoundedText2(
12914
+ mapping,
12915
+ "build_id",
12916
+ "FLOW.QUERY.INDEXES index",
12917
+ 128
12918
+ ),
12919
+ source: requiredChoice(mapping, "source", "FLOW.QUERY.INDEXES index", [
12920
+ "runs"
12921
+ ]),
12922
+ state: requiredChoice(mapping, "state", "FLOW.QUERY.INDEXES index", [
12923
+ "building",
12924
+ "validating",
12925
+ "active",
12926
+ "retiring",
12927
+ "failed"
12928
+ ]),
12929
+ queryable: requiredBoolean(
12930
+ mapping,
12931
+ "queryable",
12932
+ "FLOW.QUERY.INDEXES index"
12933
+ ),
12934
+ fields,
12935
+ workloads: decodeUniqueTextArray(
12936
+ field(mapping, "workloads"),
12937
+ "FLOW.QUERY.INDEXES index workloads",
12938
+ 16,
12939
+ 64
12940
+ ),
12941
+ countPrefixes: decodeCountPrefixes(
12942
+ field(mapping, "count_prefixes"),
12943
+ fields.length
12944
+ ),
12255
12945
  coveringFields: decodeCoveringFields(mapping),
12256
12946
  format: decodeIndexFormat(mapping),
12947
+ coverage: decodeIndexCoverage(field(mapping, "coverage")),
12948
+ build: decodeIndexBuild(field(mapping, "build")),
12949
+ validation: decodeIndexValidation(field(mapping, "validation")),
12950
+ retirement: decodeIndexRetirement(field(mapping, "retirement")),
12951
+ statistics: decodeIndexStatistics(field(mapping, "statistics")),
12952
+ raw: freezeMap(mapping)
12953
+ });
12954
+ }
12955
+ function decodeIndexServices(value) {
12956
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES services");
12957
+ const choices = ["ready", "unavailable"];
12958
+ return Object.freeze({
12959
+ registry: requiredChoice(
12960
+ mapping,
12961
+ "registry",
12962
+ "FLOW.QUERY.INDEXES services",
12963
+ choices
12964
+ ),
12965
+ lifecycleWorker: requiredChoice(
12966
+ mapping,
12967
+ "lifecycle_worker",
12968
+ "FLOW.QUERY.INDEXES services",
12969
+ choices
12970
+ ),
12971
+ statisticsStore: requiredChoice(
12972
+ mapping,
12973
+ "statistics_store",
12974
+ "FLOW.QUERY.INDEXES services",
12975
+ choices
12976
+ ),
12977
+ statisticsWorker: requiredChoice(
12978
+ mapping,
12979
+ "statistics_worker",
12980
+ "FLOW.QUERY.INDEXES services",
12981
+ choices
12982
+ ),
12257
12983
  raw: freezeMap(mapping)
12258
12984
  });
12259
12985
  }
12986
+ function decodeIndexFields(value) {
12987
+ const context = "FLOW.QUERY.INDEXES index fields";
12988
+ if (!Array.isArray(value) || value.length < 2 || value.length > 8) {
12989
+ throw decodeError(`${context} must contain 2 to 8 entries`, value);
12990
+ }
12991
+ const fields = new Array(value.length);
12992
+ const names = /* @__PURE__ */ new Set();
12993
+ for (let index = 0; index < value.length; index += 1) {
12994
+ if (!Object.hasOwn(value, index)) {
12995
+ throw decodeError(`${context} must be dense`, value);
12996
+ }
12997
+ const mapping = requiredMap2(
12998
+ value[index],
12999
+ `FLOW.QUERY.INDEXES index field ${index}`
13000
+ );
13001
+ const name = requiredBoundedText2(
13002
+ mapping,
13003
+ "name",
13004
+ "FLOW.QUERY.INDEXES index field",
13005
+ 512
13006
+ );
13007
+ if (names.has(name)) {
13008
+ throw decodeError(`${context} contain duplicates`, value);
13009
+ }
13010
+ names.add(name);
13011
+ fields[index] = Object.freeze({
13012
+ name,
13013
+ direction: requiredChoice(
13014
+ mapping,
13015
+ "direction",
13016
+ "FLOW.QUERY.INDEXES index field",
13017
+ ["asc", "desc"]
13018
+ ),
13019
+ encoding: requiredChoice(
13020
+ mapping,
13021
+ "encoding",
13022
+ "FLOW.QUERY.INDEXES index field",
13023
+ ["hashed", "ordered"]
13024
+ ),
13025
+ raw: freezeMap(mapping)
13026
+ });
13027
+ }
13028
+ return Object.freeze(fields);
13029
+ }
13030
+ function decodeCountPrefixes(value, fieldCount) {
13031
+ const context = "FLOW.QUERY.INDEXES index count_prefixes";
13032
+ if (!Array.isArray(value) || value.length > fieldCount) {
13033
+ throw decodeError(`${context} are invalid`, value);
13034
+ }
13035
+ const prefixes = new Array(value.length);
13036
+ let previous = 0;
13037
+ for (let index = 0; index < value.length; index += 1) {
13038
+ if (!Object.hasOwn(value, index)) {
13039
+ throw decodeError(`${context} must be dense`, value);
13040
+ }
13041
+ const prefix = positiveInteger(value[index], `${context} entry`);
13042
+ if (prefix > fieldCount || prefix <= previous) {
13043
+ throw decodeError(`${context} are invalid`, value);
13044
+ }
13045
+ prefixes[index] = prefix;
13046
+ previous = prefix;
13047
+ }
13048
+ return Object.freeze(prefixes);
13049
+ }
12260
13050
  function decodeCoveringFields(mapping) {
12261
13051
  const raw = field(mapping, "covering_fields");
12262
13052
  if (!Array.isArray(raw) || raw.length > 32) {
@@ -12269,7 +13059,10 @@ function decodeCoveringFields(mapping) {
12269
13059
  const seen = /* @__PURE__ */ new Set();
12270
13060
  for (let index = 0; index < raw.length; index += 1) {
12271
13061
  if (!Object.hasOwn(raw, index)) {
12272
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields must be dense", raw);
13062
+ throw decodeError(
13063
+ "FLOW.QUERY.INDEXES index covering_fields must be dense",
13064
+ raw
13065
+ );
12273
13066
  }
12274
13067
  const value = boundedText(
12275
13068
  raw[index],
@@ -12277,7 +13070,10 @@ function decodeCoveringFields(mapping) {
12277
13070
  512
12278
13071
  );
12279
13072
  if (seen.has(value)) {
12280
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields contains duplicates", raw);
13073
+ throw decodeError(
13074
+ "FLOW.QUERY.INDEXES index covering_fields contains duplicates",
13075
+ raw
13076
+ );
12281
13077
  }
12282
13078
  seen.add(value);
12283
13079
  fields[index] = value;
@@ -12285,30 +13081,341 @@ function decodeCoveringFields(mapping) {
12285
13081
  return Object.freeze(fields);
12286
13082
  }
12287
13083
  function decodeIndexFormat(mapping) {
12288
- const raw = requiredMap2(field(mapping, "format"), "FLOW.QUERY.INDEXES index format");
13084
+ const raw = requiredMap2(
13085
+ field(mapping, "format"),
13086
+ "FLOW.QUERY.INDEXES index format"
13087
+ );
12289
13088
  if (!hasKey(raw, "counter")) {
12290
- throw decodeError("FLOW.QUERY.INDEXES index format counter is missing", raw);
13089
+ throw decodeError(
13090
+ "FLOW.QUERY.INDEXES index format counter is missing",
13091
+ raw
13092
+ );
12291
13093
  }
12292
13094
  const counterValue = field(raw, "counter");
12293
- const counter = counterValue === null ? void 0 : boundedText(counterValue, "FLOW.QUERY.INDEXES index format counter", 128);
13095
+ const counter = counterValue === null ? void 0 : boundedText(
13096
+ counterValue,
13097
+ "FLOW.QUERY.INDEXES index format counter",
13098
+ 128
13099
+ );
12294
13100
  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),
13101
+ queryRow: requiredBoundedText2(
13102
+ raw,
13103
+ "query_row",
13104
+ "FLOW.QUERY.INDEXES index format",
13105
+ 128
13106
+ ),
13107
+ key: requiredBoundedText2(
13108
+ raw,
13109
+ "key",
13110
+ "FLOW.QUERY.INDEXES index format",
13111
+ 128
13112
+ ),
13113
+ entry: requiredBoundedText2(
13114
+ raw,
13115
+ "entry",
13116
+ "FLOW.QUERY.INDEXES index format",
13117
+ 128
13118
+ ),
13119
+ reverse: requiredBoundedText2(
13120
+ raw,
13121
+ "reverse",
13122
+ "FLOW.QUERY.INDEXES index format",
13123
+ 128
13124
+ ),
12299
13125
  counter,
12300
13126
  raw: freezeMap(raw)
12301
13127
  });
12302
13128
  }
13129
+
13130
+ // src/flow-query-response.ts
13131
+ var FLOW_QUERY_RESULT_CONTRACT = "ferric.flow.query.result/v1";
13132
+ var FLOW_EXPLAIN_CONTRACT = "ferric.flow.explain/v1";
13133
+ var USAGE_FIELDS2 = [
13134
+ ["range_seeks", "rangeSeeks"],
13135
+ ["range_pages", "rangePages"],
13136
+ ["scanned_entries", "scannedEntries"],
13137
+ ["scanned_bytes", "scannedBytes"],
13138
+ ["hydrated_records", "hydratedRecords"],
13139
+ ["residual_checks", "residualChecks"],
13140
+ ["duplicate_entries", "duplicateEntries"],
13141
+ ["result_records", "resultRecords"],
13142
+ ["response_bytes", "responseBytes"],
13143
+ ["memory_high_water_bytes", "memoryHighWaterBytes"],
13144
+ ["wall_time_us", "wallTimeUs"]
13145
+ ];
13146
+ var MAX_SIGNED_643 = (1n << 63n) - 1n;
13147
+ var QUALITY_VALUES2 = {
13148
+ exactness: ["authoritative", "projected_exact", "exact", "not_applicable"],
13149
+ freshness: ["current", "projection_watermark", "not_applicable"],
13150
+ coverage: ["complete", "unavailable"],
13151
+ pagination: ["none", "complete", "authenticated_seek", "live_seek"]
13152
+ };
13153
+ function decodeFlowQueryResult(value) {
13154
+ const result = decodeFlowQueryResponse(value, (mapping) => freezeMap(mapping));
13155
+ if (result.kind === "records") {
13156
+ return Object.freeze({
13157
+ kind: "records",
13158
+ version: FLOW_QUERY_RESULT_CONTRACT,
13159
+ records: Object.freeze(result.records),
13160
+ page: result.page,
13161
+ quality: result.quality,
13162
+ usage: result.usage,
13163
+ raw: result.raw
13164
+ });
13165
+ }
13166
+ return Object.freeze({
13167
+ kind: "count",
13168
+ version: FLOW_QUERY_RESULT_CONTRACT,
13169
+ count: result.count,
13170
+ quality: result.quality,
13171
+ usage: result.usage,
13172
+ raw: result.raw
13173
+ });
13174
+ }
13175
+ function decodeFlowQueryRecords(value, decodeRecord) {
13176
+ const result = decodeFlowQueryResponse(
13177
+ value,
13178
+ (mapping, index) => decodeRecord(mapping instanceof Map ? freezeMap(mapping) : mapping, index)
13179
+ );
13180
+ if (result.kind === "count") {
13181
+ throw new FerricStoreError("Flow record convenience query returned a count result", {
13182
+ raw: result.raw
13183
+ });
13184
+ }
13185
+ return result.records;
13186
+ }
13187
+ function decodeFlowQueryResponse(value, decodeRecord) {
13188
+ const mapping = requiredMap2(value, "FLOW.QUERY result");
13189
+ requireContract(mapping, "version", FLOW_QUERY_RESULT_CONTRACT, "FLOW.QUERY result");
13190
+ const quality = decodeQuality(field(mapping, "quality"));
13191
+ const usage = decodeUsage(field(mapping, "usage"));
13192
+ const hasRecords = hasKey(mapping, "records");
13193
+ const hasCount = hasKey(mapping, "result");
13194
+ if (hasRecords === hasCount) {
13195
+ throw decodeError("FLOW.QUERY result must contain exactly one records or count shape", value);
13196
+ }
13197
+ const raw = freezeMap(mapping);
13198
+ if (hasRecords) {
13199
+ const rawRecords = field(mapping, "records");
13200
+ if (!Array.isArray(rawRecords) || rawRecords.length > 100) {
13201
+ throw decodeError("FLOW.QUERY records must be an array of at most 100 maps", value);
13202
+ }
13203
+ const records = new Array(rawRecords.length);
13204
+ for (let index = 0; index < rawRecords.length; index += 1) {
13205
+ if (!Object.hasOwn(rawRecords, index)) {
13206
+ throw decodeError("FLOW.QUERY records must be a dense array", value);
13207
+ }
13208
+ records[index] = decodeRecord(
13209
+ requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
13210
+ index
13211
+ );
13212
+ }
13213
+ if (usage.resultRecords !== records.length || usage.resultRecords > usage.scannedEntries) {
13214
+ throw decodeError("FLOW.QUERY usage result_records is inconsistent with records", value);
13215
+ }
13216
+ return {
13217
+ kind: "records",
13218
+ records,
13219
+ page: decodePage(field(mapping, "page")),
13220
+ quality,
13221
+ usage,
13222
+ raw
13223
+ };
13224
+ }
13225
+ if (hasKey(mapping, "page")) {
13226
+ throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
13227
+ }
13228
+ const countResult = requiredMap2(field(mapping, "result"), "FLOW.QUERY count result");
13229
+ if (requiredText2(countResult, "kind", "FLOW.QUERY count result") !== "count") {
13230
+ throw decodeError("FLOW.QUERY count result kind must be count", value);
13231
+ }
13232
+ const count = boundedInteger(
13233
+ field(countResult, "value"),
13234
+ MAX_SIGNED_643,
13235
+ "FLOW.QUERY count value"
13236
+ );
13237
+ if (usage.resultRecords !== 1) {
13238
+ throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
13239
+ }
13240
+ return {
13241
+ kind: "count",
13242
+ count,
13243
+ quality,
13244
+ usage,
13245
+ raw
13246
+ };
13247
+ }
13248
+ function decodeFlowExplainResult(value) {
13249
+ const mapping = requiredMap2(value, "FLOW.QUERY explain");
13250
+ requireContract(mapping, "version", FLOW_EXPLAIN_CONTRACT, "FLOW.QUERY explain");
13251
+ const queryFingerprint = requiredText2(mapping, "query_fingerprint", "FLOW.QUERY explain");
13252
+ if (!/^[0-9a-f]{64}$/iu.test(queryFingerprint)) {
13253
+ throw decodeError("FLOW.QUERY explain query_fingerprint is invalid", value);
13254
+ }
13255
+ const status = requiredText2(mapping, "status", "FLOW.QUERY explain");
13256
+ if (status !== "planned" && status !== "rejected" && status !== "executed") {
13257
+ throw decodeError(`FLOW.QUERY explain status ${JSON.stringify(status)} is unsupported`, value);
13258
+ }
13259
+ const plan = freezeMetadataMap(
13260
+ requiredMap2(field(mapping, "plan"), "FLOW.QUERY explain plan"),
13261
+ "FLOW.QUERY explain plan"
13262
+ );
13263
+ const estimate = freezeMetadataMap(
13264
+ requiredMap2(field(mapping, "estimate"), "FLOW.QUERY explain estimate"),
13265
+ "FLOW.QUERY explain estimate"
13266
+ );
13267
+ const bounds = freezeMetadataMap(
13268
+ requiredMap2(field(mapping, "bounds"), "FLOW.QUERY explain bounds"),
13269
+ "FLOW.QUERY explain bounds"
13270
+ );
13271
+ const capabilities = decodeExplainCapabilities(mapping);
13272
+ const extendedFields = ["stats", "quality", "pressure", "decision", "alternatives"];
13273
+ const extendedPresence = extendedFields.map((name) => hasKey(mapping, name));
13274
+ const specialized = capabilities != null && !extendedPresence.some(Boolean);
13275
+ let stats;
13276
+ let quality;
13277
+ let pressure;
13278
+ let decision;
13279
+ let alternatives;
13280
+ if (specialized) {
13281
+ if (status !== "planned") {
13282
+ throw decodeError("FLOW.QUERY specialized explain must be planned", value);
13283
+ }
13284
+ if (hasKey(mapping, "actual") || hasKey(mapping, "diagnostic")) {
13285
+ throw decodeError("FLOW.QUERY specialized explain has extended status fields", value);
13286
+ }
13287
+ alternatives = Object.freeze([]);
13288
+ } else {
13289
+ if (!extendedPresence.every(Boolean) || !hasKey(mapping, "actual") || !hasKey(mapping, "diagnostic")) {
13290
+ throw decodeError("FLOW.QUERY explain is missing required v1 fields", value);
13291
+ }
13292
+ stats = freezeMetadataMap(
13293
+ requiredMap2(field(mapping, "stats"), "FLOW.QUERY explain stats"),
13294
+ "FLOW.QUERY explain stats"
13295
+ );
13296
+ quality = decodeQuality(field(mapping, "quality"));
13297
+ pressure = freezeMetadataMap(
13298
+ requiredMap2(field(mapping, "pressure"), "FLOW.QUERY explain pressure"),
13299
+ "FLOW.QUERY explain pressure"
13300
+ );
13301
+ decision = freezeMetadataMap(
13302
+ requiredMap2(field(mapping, "decision"), "FLOW.QUERY explain decision"),
13303
+ "FLOW.QUERY explain decision"
13304
+ );
13305
+ alternatives = decodeExplainAlternatives(field(mapping, "alternatives"));
13306
+ }
13307
+ const actualValue = field(mapping, "actual");
13308
+ let actual;
13309
+ if (status === "executed") {
13310
+ if (actualValue == null) {
13311
+ throw decodeError("FLOW.QUERY executed explain is missing actual usage", value);
13312
+ }
13313
+ actual = decodeUsage(actualValue);
13314
+ } else if (actualValue != null) {
13315
+ throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
13316
+ }
13317
+ const diagnosticValue = field(mapping, "diagnostic");
13318
+ let diagnostic;
13319
+ if (status === "rejected") {
13320
+ diagnostic = tryDecodeFlowQueryError(diagnosticValue);
13321
+ if (diagnostic == null) {
13322
+ throw decodeError("FLOW.QUERY rejected explain has an invalid diagnostic", value);
13323
+ }
13324
+ } else if (diagnosticValue != null) {
13325
+ throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
13326
+ }
13327
+ return Object.freeze({
13328
+ version: FLOW_EXPLAIN_CONTRACT,
13329
+ queryFingerprint,
13330
+ status,
13331
+ plan,
13332
+ estimate,
13333
+ stats,
13334
+ quality,
13335
+ bounds,
13336
+ pressure,
13337
+ decision,
13338
+ alternatives,
13339
+ capabilities,
13340
+ actual,
13341
+ diagnostic,
13342
+ raw: freezeMap(mapping)
13343
+ });
13344
+ }
13345
+ function decodeExplainCapabilities(mapping) {
13346
+ if (!hasKey(mapping, "capabilities")) return void 0;
13347
+ const value = requiredMap2(
13348
+ field(mapping, "capabilities"),
13349
+ "FLOW.QUERY explain capabilities"
13350
+ );
13351
+ return Object.freeze({
13352
+ requested: decodeExplainCapabilityList(field(value, "requested"), "requested"),
13353
+ available: decodeExplainCapabilityList(field(value, "available"), "available"),
13354
+ missing: decodeExplainCapabilityList(field(value, "missing"), "missing"),
13355
+ raw: freezeMetadataMap(value, "FLOW.QUERY explain capabilities")
13356
+ });
13357
+ }
13358
+ function decodeExplainCapabilityList(value, name) {
13359
+ if (!Array.isArray(value) || value.length > 64) {
13360
+ throw decodeError(
13361
+ `FLOW.QUERY explain capabilities ${name} must contain at most 64 entries`,
13362
+ value
13363
+ );
13364
+ }
13365
+ const result = new Array(value.length);
13366
+ const seen = /* @__PURE__ */ new Set();
13367
+ for (let index = 0; index < value.length; index += 1) {
13368
+ if (!Object.hasOwn(value, index)) {
13369
+ throw decodeError(`FLOW.QUERY explain capabilities ${name} must be dense`, value);
13370
+ }
13371
+ const capability = boundedText(
13372
+ value[index],
13373
+ `FLOW.QUERY explain capabilities ${name} entry ${index}`,
13374
+ 128
13375
+ );
13376
+ if (seen.has(capability)) {
13377
+ throw decodeError(
13378
+ `FLOW.QUERY explain capabilities ${name} contains duplicates`,
13379
+ value
13380
+ );
13381
+ }
13382
+ seen.add(capability);
13383
+ result[index] = capability;
13384
+ }
13385
+ return Object.freeze(result);
13386
+ }
13387
+ function decodeExplainAlternatives(value) {
13388
+ if (!Array.isArray(value) || value.length > 31) {
13389
+ throw decodeError("FLOW.QUERY explain alternatives must be an array of at most 31 maps", value);
13390
+ }
13391
+ const alternatives = new Array(value.length);
13392
+ for (let index = 0; index < value.length; index += 1) {
13393
+ if (!Object.hasOwn(value, index)) {
13394
+ throw decodeError("FLOW.QUERY explain alternatives must be dense", value);
13395
+ }
13396
+ alternatives[index] = freezeMetadataMap(
13397
+ requiredMap2(value[index], `FLOW.QUERY explain alternative ${index}`),
13398
+ `FLOW.QUERY explain alternative ${index}`
13399
+ );
13400
+ }
13401
+ return Object.freeze(alternatives);
13402
+ }
12303
13403
  function decodeQuality(value) {
12304
13404
  const mapping = requiredMap2(value, "FLOW.QUERY quality");
12305
13405
  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)
13406
+ exactness: decodeQualityValue(mapping, "exactness", QUALITY_VALUES2.exactness),
13407
+ freshness: decodeQualityValue(mapping, "freshness", QUALITY_VALUES2.freshness),
13408
+ coverage: decodeQualityValue(mapping, "coverage", QUALITY_VALUES2.coverage),
13409
+ pagination: decodeQualityValue(mapping, "pagination", QUALITY_VALUES2.pagination)
12310
13410
  });
12311
13411
  }
13412
+ function decodeQualityValue(mapping, name, allowed) {
13413
+ const value = requiredBoundedText2(mapping, name, "FLOW.QUERY quality", 64);
13414
+ if (!allowed.includes(value)) {
13415
+ throw decodeError(`FLOW.QUERY quality ${name} is unsupported`, mapping);
13416
+ }
13417
+ return value;
13418
+ }
12312
13419
  function decodeUsage(value) {
12313
13420
  const mapping = requiredMap2(value, "FLOW.QUERY usage");
12314
13421
  const usage = /* @__PURE__ */ Object.create(null);
@@ -12318,13 +13425,17 @@ function decodeUsage(value) {
12318
13425
  `FLOW.QUERY usage ${wireName}`
12319
13426
  );
12320
13427
  }
12321
- return Object.freeze(usage);
13428
+ const decoded = usage;
13429
+ if (decoded.hydratedRecords > decoded.scannedEntries || decoded.duplicateEntries > decoded.scannedEntries || decoded.rangePages > decoded.scannedEntries + decoded.rangeSeeks || decoded.residualChecks > decoded.scannedEntries * 12) {
13430
+ throw decodeError("FLOW.QUERY usage counters are inconsistent", value);
13431
+ }
13432
+ return Object.freeze(decoded);
12322
13433
  }
12323
13434
  function decodePage(value) {
12324
13435
  const mapping = requiredMap2(value, "FLOW.QUERY page");
12325
13436
  const hasMore = requiredBoolean(mapping, "has_more", "FLOW.QUERY page");
12326
13437
  const cursor = optionalText2(mapping, "cursor", "FLOW.QUERY page");
12327
- if (cursor != null && (!cursor.startsWith("fqc1_") || Buffer45.byteLength(cursor) > 4096)) {
13438
+ if (cursor != null && (!cursor.startsWith("fqc1_") || Buffer48.byteLength(cursor) < 16 || Buffer48.byteLength(cursor) > 4096)) {
12328
13439
  throw decodeError("FLOW.QUERY page cursor is invalid", value);
12329
13440
  }
12330
13441
  if (hasMore !== (cursor != null)) {
@@ -12332,26 +13443,14 @@ function decodePage(value) {
12332
13443
  }
12333
13444
  return Object.freeze({ hasMore, ...cursor == null ? {} : { cursor } });
12334
13445
  }
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
13446
 
12348
13447
  // src/client-core.ts
12349
13448
  import "buffer";
12350
13449
 
12351
13450
  // src/client-core-helpers.ts
12352
- import { Buffer as Buffer46 } from "buffer";
13451
+ import { Buffer as Buffer49 } from "buffer";
12353
13452
  function bgsaveResponse(response) {
12354
- if ((typeof response === "string" || Buffer46.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
13453
+ if ((typeof response === "string" || Buffer49.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
12355
13454
  return true;
12356
13455
  }
12357
13456
  return okResponse(response);
@@ -12365,7 +13464,7 @@ function fetchOrComputeCompletionToken(options) {
12365
13464
  "fetch-or-compute completion requires computeToken"
12366
13465
  );
12367
13466
  }
12368
- if (!Buffer46.isBuffer(options.computeToken)) {
13467
+ if (!Buffer49.isBuffer(options.computeToken)) {
12369
13468
  throw new TypeError("fetch-or-compute computeToken must be a Buffer");
12370
13469
  }
12371
13470
  return options.computeToken;
@@ -12725,14 +13824,14 @@ var FerricStoreAdministrationClient = class extends FerricStoreClientBase {
12725
13824
  };
12726
13825
 
12727
13826
  // src/store-utilities.ts
12728
- import { Buffer as Buffer49 } from "buffer";
13827
+ import { Buffer as Buffer52 } from "buffer";
12729
13828
  function encode(codec, value) {
12730
13829
  return codec.encode(value);
12731
13830
  }
12732
13831
  function decode(codec, value) {
12733
13832
  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));
13833
+ if (Buffer52.isBuffer(value)) return codec.decode(value);
13834
+ if (value instanceof Uint8Array) return codec.decode(Buffer52.from(value));
12736
13835
  return value;
12737
13836
  }
12738
13837
  function number(value) {
@@ -14967,7 +16066,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
14967
16066
  args.push(indexId);
14968
16067
  }
14969
16068
  try {
14970
- return decodeFlowQueryIndexStatus(await this.commandArgs(args));
16069
+ return decodeFlowQueryIndexStatus(await this.commandArgs(args), indexId);
14971
16070
  } catch (error) {
14972
16071
  this.throwFlowQueryError(error);
14973
16072
  }
@@ -15029,7 +16128,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
15029
16128
  if (hasFlowExplainPrefix(query)) {
15030
16129
  throw new TypeError("query already contains an EXPLAIN prefix");
15031
16130
  }
15032
- return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query.trim(), params));
16131
+ return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query, params));
15033
16132
  }
15034
16133
  throwFlowQueryError(error) {
15035
16134
  if (error instanceof FerricStoreError) {
@@ -15062,7 +16161,7 @@ function throwIfClosed(signal) {
15062
16161
  }
15063
16162
 
15064
16163
  // src/flow-policy.ts
15065
- import { Buffer as Buffer51 } from "buffer";
16164
+ import { Buffer as Buffer54 } from "buffer";
15066
16165
  var MAX_FLOW_POLICY_GENERATION = Number.MAX_SAFE_INTEGER;
15067
16166
  function assertFlowPolicyGeneration(value) {
15068
16167
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
@@ -15152,7 +16251,7 @@ function requiredRecord(value, context) {
15152
16251
  }
15153
16252
  return result;
15154
16253
  }
15155
- if (typeof value === "object" && value != null && !Array.isArray(value) && !Buffer51.isBuffer(value) && !(value instanceof Uint8Array)) return value;
16254
+ if (typeof value === "object" && value != null && !Array.isArray(value) && !Buffer54.isBuffer(value) && !(value instanceof Uint8Array)) return value;
15156
16255
  throw new TypeError(`${context} returned an unexpected response`);
15157
16256
  }
15158
16257
  function optionalRecord(value, context) {
@@ -15173,8 +16272,8 @@ function stringArray(value, context) {
15173
16272
  }
15174
16273
  function requiredText3(value, context) {
15175
16274
  if (typeof value === "string") return value;
15176
- if (Buffer51.isBuffer(value) || value instanceof Uint8Array) {
15177
- return Buffer51.from(value).toString("utf8");
16275
+ if (Buffer54.isBuffer(value) || value instanceof Uint8Array) {
16276
+ return Buffer54.from(value).toString("utf8");
15178
16277
  }
15179
16278
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15180
16279
  }
@@ -15430,7 +16529,7 @@ var FerricStoreFlowSupportClient = class extends FerricStoreFlowQueryClient {
15430
16529
  };
15431
16530
 
15432
16531
  // src/flow-many-snapshot.ts
15433
- import { Buffer as Buffer53 } from "buffer";
16532
+ import { Buffer as Buffer56 } from "buffer";
15434
16533
  var encodedOptionFields = ["error", "payload", "reason", "result"];
15435
16534
  function snapshotCreateItem(item, codec) {
15436
16535
  const snapshot = {
@@ -15470,7 +16569,7 @@ function snapshotFlowManyOptions(options, codec) {
15470
16569
  }
15471
16570
  function snapshotClaimedItem(item) {
15472
16571
  const wire = item[CLAIMED_ITEM_WIRE];
15473
- const leaseToken = Buffer53.from(wire?.leaseToken ?? item.leaseToken);
16572
+ const leaseToken = Buffer56.from(wire?.leaseToken ?? item.leaseToken);
15474
16573
  const snapshot = {
15475
16574
  ...item,
15476
16575
  fencingToken: wire?.fencingToken ?? item.fencingToken,
@@ -15487,15 +16586,15 @@ function snapshotClaimedItem(item) {
15487
16586
  function snapshotFencedItem(item) {
15488
16587
  return Object.freeze({
15489
16588
  ...item,
15490
- ...item.leaseToken == null ? {} : { leaseToken: Buffer53.from(item.leaseToken) }
16589
+ ...item.leaseToken == null ? {} : { leaseToken: Buffer56.from(item.leaseToken) }
15491
16590
  });
15492
16591
  }
15493
16592
  function snapshotClaimedItemWire(wire, leaseToken) {
15494
16593
  return Object.freeze({
15495
16594
  fencingToken: wire.fencingToken,
15496
- id: Buffer53.from(wire.id),
16595
+ id: Buffer56.from(wire.id),
15497
16596
  leaseToken,
15498
- partitionKey: wire.partitionKey == null ? wire.partitionKey : Buffer53.from(wire.partitionKey)
16597
+ partitionKey: wire.partitionKey == null ? wire.partitionKey : Buffer56.from(wire.partitionKey)
15499
16598
  });
15500
16599
  }
15501
16600
  function snapshotArray(values) {
@@ -15506,7 +16605,7 @@ function snapshotArray(values) {
15506
16605
  return Object.freeze(snapshot);
15507
16606
  }
15508
16607
  function snapshotStateMeta(stateMeta) {
15509
- return snapshotRecord(stateMeta, (value) => Buffer53.isBuffer(value) ? Buffer53.from(value) : value);
16608
+ return snapshotRecord(stateMeta, (value) => Buffer56.isBuffer(value) ? Buffer56.from(value) : value);
15510
16609
  }
15511
16610
  function snapshotCommandRecord(values) {
15512
16611
  const seen = /* @__PURE__ */ new WeakMap();
@@ -15524,7 +16623,7 @@ function snapshotRecord(values, capture) {
15524
16623
  }
15525
16624
  function snapshotCommandArgument(value, seen) {
15526
16625
  if (typeof value !== "object" || value == null) return value;
15527
- if (Buffer53.isBuffer(value) || value instanceof Uint8Array) return Buffer53.from(value);
16626
+ if (Buffer56.isBuffer(value) || value instanceof Uint8Array) return Buffer56.from(value);
15528
16627
  const objectValue = value;
15529
16628
  const existing = seen.get(objectValue);
15530
16629
  if (existing != null) return existing;
@@ -16512,7 +17611,7 @@ function nativeOptionsForBootstrap(options, signal) {
16512
17611
  }
16513
17612
 
16514
17613
  // src/flow-query-projection.ts
16515
- import { Buffer as Buffer54 } from "buffer";
17614
+ import { Buffer as Buffer57 } from "buffer";
16516
17615
  var MAX_PROJECTION_FIELDS = 32;
16517
17616
  var MAX_DYNAMIC_NAME_BYTES = 64;
16518
17617
  var FIELD_BRAND = /* @__PURE__ */ Symbol("FerricStoreFlowProjectionField");
@@ -16580,20 +17679,38 @@ function projectFlowQuery(query, shape, ...fields) {
16580
17679
  if (containsReturnKeyword(query)) {
16581
17680
  throw new TypeError("Flow query already contains a RETURN clause");
16582
17681
  }
16583
- const trimmed = query.trim();
16584
- const base = trimmed.endsWith(";") ? trimmed.slice(0, -1).trimEnd() : trimmed;
17682
+ const base = stripOptionalTerminator(query);
16585
17683
  const result = `${base} RETURN ${shape.toUpperCase()} (${selectors.join(", ")})`;
16586
- if (Buffer54.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
17684
+ if (Buffer57.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
16587
17685
  throw new TypeError(`FLOW.QUERY query exceeds ${FLOW_QUERY_MAX_BYTES} bytes`);
16588
17686
  }
16589
17687
  return result;
16590
17688
  }
17689
+ function stripOptionalTerminator(query) {
17690
+ const trimmed = trimFlowQueryAsciiWhitespace(query);
17691
+ if (!trimmed.endsWith(";")) return trimmed;
17692
+ const base = trimFlowQueryAsciiWhitespace(trimmed.slice(0, -1));
17693
+ if (base.endsWith(";")) {
17694
+ throw new TypeError("Flow query accepts at most one trailing semicolon");
17695
+ }
17696
+ return base;
17697
+ }
16591
17698
  function querySource(query) {
16592
- const match = /^\s*FROM\s+(runs|events)\b/iu.exec(query);
16593
- if (match?.[1] == null) {
17699
+ const source = trimFlowQueryAsciiWhitespaceStart(query);
17700
+ if (!matchesFlowQueryAsciiKeyword(source, 0, "FROM") || !isFlowQueryAsciiWhitespaceCode(source.charCodeAt(4))) {
16594
17701
  throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16595
17702
  }
16596
- return match[1].toLowerCase();
17703
+ let offset = 4;
17704
+ while (isFlowQueryAsciiWhitespaceCode(source.charCodeAt(offset))) offset += 1;
17705
+ for (const candidate of ["runs", "events"]) {
17706
+ if (matchesFlowQueryAsciiKeyword(source, offset, candidate.toUpperCase())) {
17707
+ const boundary = source[offset + candidate.length];
17708
+ if (boundary == null || isFlowQueryAsciiWhitespaceCode(boundary.charCodeAt(0))) {
17709
+ return candidate;
17710
+ }
17711
+ }
17712
+ }
17713
+ throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16597
17714
  }
16598
17715
  function containsReturnKeyword(query) {
16599
17716
  let quoted = false;
@@ -16624,7 +17741,7 @@ function quoteName(value, allowPrivate) {
16624
17741
  throw new TypeError("Flow query projection metadata name must be text");
16625
17742
  }
16626
17743
  validateUnicodeScalarText2(value);
16627
- const size = Buffer54.byteLength(value, "utf8");
17744
+ const size = Buffer57.byteLength(value, "utf8");
16628
17745
  if (size === 0 || size > MAX_DYNAMIC_NAME_BYTES || !allowPrivate && value.startsWith("__")) {
16629
17746
  throw new TypeError(
16630
17747
  `Flow query projection metadata names must be 1..${MAX_DYNAMIC_NAME_BYTES} UTF-8 bytes`
@@ -16658,7 +17775,7 @@ function complete(options = {}) {
16658
17775
  function retry(options = {}) {
16659
17776
  return brandOutcome({ ...options, kind: "retry" });
16660
17777
  }
16661
- function fail(options = {}) {
17778
+ function fail2(options = {}) {
16662
17779
  return brandOutcome({ ...options, kind: "fail" });
16663
17780
  }
16664
17781
  function isOutcome(value) {
@@ -17455,7 +18572,7 @@ async function applyQueueException(queue, options, job, error) {
17455
18572
  const policy = normalizeExceptionPolicy(options.exceptionPolicy);
17456
18573
  if (policy === "raise") throw error;
17457
18574
  const payload = workerErrorPayload(error, options, queue.client.codec);
17458
- await applyQueueOutcome(queue, job, policy === "fail" ? fail({ error: payload }) : retry({ error: payload }));
18575
+ await applyQueueOutcome(queue, job, policy === "fail" ? fail2({ error: payload }) : retry({ error: payload }));
17459
18576
  }
17460
18577
 
17461
18578
  // src/queue-pending-completions.ts
@@ -17702,7 +18819,7 @@ var QueueWorker = class {
17702
18819
  const policy = normalizeExceptionPolicy(this.options.exceptionPolicy);
17703
18820
  if (policy !== "raise") {
17704
18821
  const payload = workerErrorPayload(error, this.options, this.queue.client.codec);
17705
- outcome = policy === "fail" ? fail({ error: payload }) : retry({ error: payload });
18822
+ outcome = policy === "fail" ? fail2({ error: payload }) : retry({ error: payload });
17706
18823
  }
17707
18824
  }
17708
18825
  const stopped = await stopQueueBatchGuards(guards.map((guard) => guard.job), guards);
@@ -18615,7 +19732,7 @@ var WorkflowWorker = class {
18615
19732
  await applyWorkflowOutcome(
18616
19733
  this.workflow.client,
18617
19734
  ctx,
18618
- fail({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
19735
+ fail2({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
18619
19736
  registration.returnRecord
18620
19737
  );
18621
19738
  return;
@@ -18630,7 +19747,7 @@ var WorkflowWorker = class {
18630
19747
  };
18631
19748
 
18632
19749
  // src/version.ts
18633
- var FERRICSTORE_SDK_VERSION = "0.5.0";
19750
+ var FERRICSTORE_SDK_VERSION = "0.5.2";
18634
19751
  var FERRICSTORE_MINIMUM_SERVER_VERSION = "0.11.0";
18635
19752
  var FERRICSTORE_NATIVE_PROTOCOL_VERSION = 1;
18636
19753
  export {
@@ -18689,7 +19806,7 @@ export {
18689
19806
  WorkflowWorker,
18690
19807
  classifyServerError,
18691
19808
  complete,
18692
- fail,
19809
+ fail2 as fail,
18693
19810
  isOutcome,
18694
19811
  isReconnectableClosedConnectionError,
18695
19812
  mapException,