@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.cjs CHANGED
@@ -85,7 +85,7 @@ __export(index_exports, {
85
85
  WorkflowWorker: () => WorkflowWorker,
86
86
  classifyServerError: () => classifyServerError,
87
87
  complete: () => complete,
88
- fail: () => fail,
88
+ fail: () => fail2,
89
89
  isOutcome: () => isOutcome,
90
90
  isReconnectableClosedConnectionError: () => isReconnectableClosedConnectionError,
91
91
  mapException: () => mapException,
@@ -107,7 +107,7 @@ function assertSafeVariadicDispatch(argumentCount, arrayCapability) {
107
107
  // src/native-adapter.ts
108
108
  var import_node_net3 = require("net");
109
109
  var import_node_tls3 = require("tls");
110
- var import_node_buffer31 = require("buffer");
110
+ var import_node_buffer32 = require("buffer");
111
111
 
112
112
  // src/errors.ts
113
113
  var FerricStoreError = class extends Error {
@@ -1237,10 +1237,10 @@ var NativeFlowControl = class {
1237
1237
 
1238
1238
  // src/native-heartbeat.ts
1239
1239
  var NativeHeartbeat = class {
1240
- constructor(intervalMs, send, fail2) {
1240
+ constructor(intervalMs, send, fail3) {
1241
1241
  this.intervalMs = intervalMs;
1242
1242
  this.send = send;
1243
- this.fail = fail2;
1243
+ this.fail = fail3;
1244
1244
  }
1245
1245
  intervalMs;
1246
1246
  send;
@@ -1287,9 +1287,11 @@ var FLOW_QUERY_LANGUAGE_VERSION = "FQL1";
1287
1287
  var FLOW_QUERY_MAX_BYTES = 16 * 1024;
1288
1288
  var FLOW_QUERY_MAX_PARAMETERS = 64;
1289
1289
  var FLOW_QUERY_MAX_PARAMETER_NAME_BYTES = 128;
1290
+ var FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES = 65535;
1290
1291
  var MIN_I64 = -(1n << 63n);
1291
1292
  var MAX_I64 = (1n << 63n) - 1n;
1292
1293
  var INDEX_ID = /^[A-Za-z0-9_.:-]{1,64}$/u;
1294
+ var PARAMETER_NAME = /^[A-Za-z0-9_.-]+$/u;
1293
1295
  function flowQueryArgs(query, params = {}) {
1294
1296
  validateFlowQueryText(query);
1295
1297
  const entries = flowQueryParameterEntries(params);
@@ -1343,9 +1345,34 @@ function validateFlowQueryIndexId(indexId) {
1343
1345
  }
1344
1346
  }
1345
1347
  function hasFlowExplainPrefix(query) {
1346
- const source = query.trimStart();
1347
- const prefix = source.slice(0, 7);
1348
- return prefix.toUpperCase() === "EXPLAIN" && (source.length === 7 || /[\t\n\r ]/u.test(source[7] ?? ""));
1348
+ const source = trimFlowQueryAsciiWhitespaceStart(query);
1349
+ return matchesFlowQueryAsciiKeyword(source, 0, "EXPLAIN") && (source.length === 7 || isFlowQueryAsciiWhitespaceCode(source.charCodeAt(7)));
1350
+ }
1351
+ function matchesFlowQueryAsciiKeyword(value, offset, keyword) {
1352
+ if (offset < 0 || offset + keyword.length > value.length) return false;
1353
+ for (let index = 0; index < keyword.length; index += 1) {
1354
+ let actual = value.charCodeAt(offset + index);
1355
+ if (actual >= 97 && actual <= 122) actual -= 32;
1356
+ if (actual !== keyword.charCodeAt(index)) return false;
1357
+ }
1358
+ return true;
1359
+ }
1360
+ function trimFlowQueryAsciiWhitespaceStart(value) {
1361
+ let start = 0;
1362
+ while (start < value.length && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(start))) {
1363
+ start += 1;
1364
+ }
1365
+ return start === 0 ? value : value.slice(start);
1366
+ }
1367
+ function trimFlowQueryAsciiWhitespace(value) {
1368
+ let start = 0;
1369
+ let end = value.length;
1370
+ while (start < end && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(start))) start += 1;
1371
+ while (end > start && isFlowQueryAsciiWhitespaceCode(value.charCodeAt(end - 1))) end -= 1;
1372
+ return start === 0 && end === value.length ? value : value.slice(start, end);
1373
+ }
1374
+ function isFlowQueryAsciiWhitespaceCode(code) {
1375
+ return code === 32 || code === 9 || code === 10 || code === 13;
1349
1376
  }
1350
1377
  function flowQueryParameterEntries(params) {
1351
1378
  if (typeof params !== "object" || params == null || Array.isArray(params)) {
@@ -1371,19 +1398,26 @@ function flowQueryParameterEntries(params) {
1371
1398
  function validateFlowQueryParameterName(name) {
1372
1399
  validateUnicodeScalarText(name, "FLOW.QUERY parameter name");
1373
1400
  const size = import_node_buffer3.Buffer.byteLength(name, "utf8");
1374
- if (size === 0 || size > FLOW_QUERY_MAX_PARAMETER_NAME_BYTES) {
1401
+ if (size === 0 || size > FLOW_QUERY_MAX_PARAMETER_NAME_BYTES || !PARAMETER_NAME.test(name)) {
1375
1402
  throw new TypeError(
1376
- `FLOW.QUERY parameter names must be 1..${FLOW_QUERY_MAX_PARAMETER_NAME_BYTES} bytes`
1403
+ `FLOW.QUERY parameter names must be 1..${FLOW_QUERY_MAX_PARAMETER_NAME_BYTES} ASCII letters, digits, '_', '.', or '-'`
1377
1404
  );
1378
1405
  }
1379
1406
  }
1380
1407
  function normalizeFlowQueryParameter(value, name) {
1381
1408
  if (typeof value === "string") {
1382
1409
  validateUnicodeScalarText(value, `FLOW.QUERY parameter ${JSON.stringify(name)}`);
1410
+ validateParameterValueSize(import_node_buffer3.Buffer.byteLength(value, "utf8"), name);
1411
+ return value;
1412
+ }
1413
+ if (import_node_buffer3.Buffer.isBuffer(value)) {
1414
+ validateParameterValueSize(value.byteLength, name);
1383
1415
  return value;
1384
1416
  }
1385
- if (import_node_buffer3.Buffer.isBuffer(value)) return value;
1386
- if (value instanceof Uint8Array) return import_node_buffer3.Buffer.from(value);
1417
+ if (value instanceof Uint8Array) {
1418
+ validateParameterValueSize(value.byteLength, name);
1419
+ return import_node_buffer3.Buffer.from(value);
1420
+ }
1387
1421
  if (typeof value === "boolean") return value;
1388
1422
  if (typeof value === "bigint" && value >= MIN_I64 && value <= MAX_I64) return value;
1389
1423
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -1393,6 +1427,13 @@ function normalizeFlowQueryParameter(value, name) {
1393
1427
  `FLOW.QUERY parameter ${JSON.stringify(name)} must be text, bytes, boolean, a finite float, or a signed 64-bit integer`
1394
1428
  );
1395
1429
  }
1430
+ function validateParameterValueSize(size, name) {
1431
+ if (size > FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES) {
1432
+ throw new TypeError(
1433
+ `FLOW.QUERY parameter ${JSON.stringify(name)} exceeds ${FLOW_QUERY_MAX_PARAMETER_VALUE_BYTES} bytes`
1434
+ );
1435
+ }
1436
+ }
1396
1437
  function commandText(value, context) {
1397
1438
  if (typeof value === "string") {
1398
1439
  validateUnicodeScalarText(value, context);
@@ -5864,7 +5905,10 @@ function isDecodedRecord(value) {
5864
5905
  // src/protocol-flow-query-result.ts
5865
5906
  var CONTRACT = "ferric.flow.query.result/v1";
5866
5907
  var MAX_RECORDS = 100;
5908
+ var MIN_CURSOR_BYTES = 16;
5867
5909
  var MAX_CURSOR_BYTES = 4096;
5910
+ var CURSOR_PREFIX = import_node_buffer24.Buffer.from("fqc1_");
5911
+ var MAX_SIGNED_64 = (1n << 63n) - 1n;
5868
5912
  var RECORD_FIELDS = [
5869
5913
  "id",
5870
5914
  "type",
@@ -5928,9 +5972,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5928
5972
  const usage = {};
5929
5973
  for (const field3 of USAGE_FIELDS) {
5930
5974
  const read = readU64(data, offset);
5975
+ if (BigInt(read.value) > MAX_SIGNED_64) {
5976
+ throw new FerricStoreError("compact FLOW.QUERY usage exceeds signed 64-bit range");
5977
+ }
5931
5978
  usage[field3] = read.value;
5932
5979
  offset = read.offset;
5933
5980
  }
5981
+ validateCompactUsage(usage);
5934
5982
  let result;
5935
5983
  if (kind === 0) {
5936
5984
  const page = readPage(data, offset);
@@ -5939,6 +5987,7 @@ function decodeCompactFlowQueryResult(data, budget) {
5939
5987
  const count = data.readUInt32BE(offset);
5940
5988
  offset += 4;
5941
5989
  if (count > MAX_RECORDS) throw new FerricStoreError("compact FLOW.QUERY page exceeds 100 records");
5990
+ validateCompactRecordUsage(usage, count);
5942
5991
  consumeDecodeItems(count, budget);
5943
5992
  const records = new Array(count);
5944
5993
  for (let index = 0; index < count; index += 1) {
@@ -5948,7 +5997,13 @@ function decodeCompactFlowQueryResult(data, budget) {
5948
5997
  }
5949
5998
  result = { version: import_node_buffer24.Buffer.from(CONTRACT), records, page: page.value, quality, usage };
5950
5999
  } else if (kind === 1) {
6000
+ if (usageInteger(usage, "result_records") !== 1n) {
6001
+ throw new FerricStoreError("compact FLOW.QUERY count usage is inconsistent");
6002
+ }
5951
6003
  const count = readU64(data, offset);
6004
+ if (BigInt(count.value) > MAX_SIGNED_64) {
6005
+ throw new FerricStoreError("compact FLOW.QUERY count exceeds signed 64-bit range");
6006
+ }
5952
6007
  offset = count.offset;
5953
6008
  result = {
5954
6009
  version: import_node_buffer24.Buffer.from(CONTRACT),
@@ -5973,18 +6028,37 @@ function readPage(data, offset) {
5973
6028
  if (hasMoreCode === 0 && size === NULL_U32) {
5974
6029
  return { value: { has_more: false, cursor: null }, offset };
5975
6030
  }
5976
- if (hasMoreCode !== 1 || size === 0 || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
6031
+ if (hasMoreCode !== 1 || size < MIN_CURSOR_BYTES || size === NULL_U32 || size > MAX_CURSOR_BYTES) {
5977
6032
  throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
5978
6033
  }
5979
6034
  requireAvailable(data, offset, size);
6035
+ const cursor = data.subarray(offset, offset + size);
6036
+ if (!cursor.subarray(0, CURSOR_PREFIX.byteLength).equals(CURSOR_PREFIX) || !(0, import_node_buffer24.isUtf8)(cursor)) {
6037
+ throw new FerricStoreError("invalid compact FLOW.QUERY page cursor");
6038
+ }
5980
6039
  return {
5981
6040
  value: {
5982
6041
  has_more: true,
5983
- cursor: detachDecodedBinary(data.subarray(offset, offset + size), data.byteLength)
6042
+ cursor: detachDecodedBinary(cursor, data.byteLength)
5984
6043
  },
5985
6044
  offset: offset + size
5986
6045
  };
5987
6046
  }
6047
+ function validateCompactUsage(usage) {
6048
+ const scanned = usageInteger(usage, "scanned_entries");
6049
+ 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) {
6050
+ throw new FerricStoreError("compact FLOW.QUERY usage counters are inconsistent");
6051
+ }
6052
+ }
6053
+ function validateCompactRecordUsage(usage, count) {
6054
+ const resultRecords = usageInteger(usage, "result_records");
6055
+ if (resultRecords !== BigInt(count) || resultRecords > usageInteger(usage, "scanned_entries")) {
6056
+ throw new FerricStoreError("compact FLOW.QUERY record usage is inconsistent");
6057
+ }
6058
+ }
6059
+ function usageInteger(usage, field3) {
6060
+ return BigInt(usage[field3]);
6061
+ }
5988
6062
  function readRecord(data, offset, budget) {
5989
6063
  requireAvailable(data, offset, 4);
5990
6064
  const bitmap = data.readUInt32BE(offset);
@@ -8010,6 +8084,63 @@ function nativeStartupPayload(clientName, events) {
8010
8084
  };
8011
8085
  }
8012
8086
 
8087
+ // src/flow-query-deadline.ts
8088
+ var import_node_buffer31 = require("buffer");
8089
+ function withFlowQueryDeadline(command, timeoutMs, nowMs2) {
8090
+ if (timeoutMs == null) return command;
8091
+ if (command.opcode === OPCODES.pipeline) {
8092
+ return withPipelineQueryDeadlines(command, timeoutMs, nowMs2);
8093
+ }
8094
+ if (command.opcode !== OPCODES.flowQuery) return command;
8095
+ const payload = command.payload;
8096
+ if (payload instanceof Map) {
8097
+ const nextPayload = new Map(payload);
8098
+ nextPayload.set("deadline_ms", flowQueryDeadlineMs(timeoutMs, nowMs2));
8099
+ return { ...command, payload: nextPayload };
8100
+ }
8101
+ if (typeof payload !== "object" || payload == null || Array.isArray(payload) || import_node_buffer31.Buffer.isBuffer(payload) || payload instanceof Uint8Array) {
8102
+ return command;
8103
+ }
8104
+ return {
8105
+ ...command,
8106
+ payload: {
8107
+ ...payload,
8108
+ deadline_ms: flowQueryDeadlineMs(timeoutMs, nowMs2)
8109
+ }
8110
+ };
8111
+ }
8112
+ function withPipelineQueryDeadlines(command, timeoutMs, nowMs2) {
8113
+ const payload = plainRecord(command.payload);
8114
+ if (payload == null) return command;
8115
+ const rawCommands = payload.commands;
8116
+ if (!Array.isArray(rawCommands)) return command;
8117
+ let commands;
8118
+ let deadlineMs;
8119
+ for (let index = 0; index < rawCommands.length; index += 1) {
8120
+ const rawItem = rawCommands[index];
8121
+ const item = plainRecord(rawItem);
8122
+ if (item?.opcode !== OPCODES.flowQuery) continue;
8123
+ const body = plainRecord(item.body);
8124
+ if (body == null) continue;
8125
+ commands ??= rawCommands.slice();
8126
+ deadlineMs ??= flowQueryDeadlineMs(timeoutMs, nowMs2);
8127
+ commands[index] = { ...item, body: { ...body, deadline_ms: deadlineMs } };
8128
+ }
8129
+ return commands == null ? command : { ...command, payload: { ...payload, commands } };
8130
+ }
8131
+ function flowQueryDeadlineMs(timeoutMs, nowMs2) {
8132
+ return Math.min(
8133
+ Number.MAX_SAFE_INTEGER,
8134
+ (nowMs2 ?? Date.now()) + Math.max(1, Math.ceil(timeoutMs))
8135
+ );
8136
+ }
8137
+ function plainRecord(value) {
8138
+ if (typeof value !== "object" || value == null || Array.isArray(value) || import_node_buffer31.Buffer.isBuffer(value) || value instanceof Uint8Array) {
8139
+ return void 0;
8140
+ }
8141
+ return value;
8142
+ }
8143
+
8013
8144
  // src/native-adapter.ts
8014
8145
  var NativeAdapter = class _NativeAdapter {
8015
8146
  socket;
@@ -8215,7 +8346,11 @@ var NativeAdapter = class _NativeAdapter {
8215
8346
  let frame;
8216
8347
  try {
8217
8348
  requestId = this.requestScheduler.nextRequestId();
8218
- frame = encodeRequest(command, requestId, this.capabilities.requestFrameBytes);
8349
+ frame = encodeRequest(
8350
+ withFlowQueryDeadline(command, timeoutMs),
8351
+ requestId,
8352
+ this.capabilities.requestFrameBytes
8353
+ );
8219
8354
  } catch (error) {
8220
8355
  if (hasFlowControlCredit) this.flowControl.release(laneId);
8221
8356
  return Promise.reject(error instanceof Error ? error : new FerricStoreError(String(error), { raw: error }));
@@ -8360,10 +8495,10 @@ function executeCommandArgs(executor, args) {
8360
8495
  }
8361
8496
 
8362
8497
  // src/reconnecting-executor.ts
8363
- var import_node_buffer33 = require("buffer");
8498
+ var import_node_buffer34 = require("buffer");
8364
8499
 
8365
8500
  // src/command-retry-policy.ts
8366
- var import_node_buffer32 = require("buffer");
8501
+ var import_node_buffer33 = require("buffer");
8367
8502
  function isCasMutation(args) {
8368
8503
  const offset = commandName2(args[0]) === "COMMAND_EXEC" ? 1 : 0;
8369
8504
  const name = commandName2(args[offset]);
@@ -8377,8 +8512,8 @@ function isCasMutation(args) {
8377
8512
  }
8378
8513
  function commandName2(value) {
8379
8514
  if (typeof value === "string") return value.toUpperCase();
8380
- if (import_node_buffer32.Buffer.isBuffer(value) || value instanceof Uint8Array) {
8381
- return import_node_buffer32.Buffer.from(value).toString("utf8").toUpperCase();
8515
+ if (import_node_buffer33.Buffer.isBuffer(value) || value instanceof Uint8Array) {
8516
+ return import_node_buffer33.Buffer.from(value).toString("utf8").toUpperCase();
8382
8517
  }
8383
8518
  return void 0;
8384
8519
  }
@@ -8590,15 +8725,15 @@ function normalizeNonNegativeInteger2(value, fallback) {
8590
8725
  }
8591
8726
 
8592
8727
  // src/topology.ts
8593
- var import_node_buffer36 = require("buffer");
8728
+ var import_node_buffer37 = require("buffer");
8594
8729
 
8595
8730
  // src/topology-routing.ts
8596
- var import_node_buffer35 = require("buffer");
8731
+ var import_node_buffer36 = require("buffer");
8597
8732
 
8598
8733
  // src/flow-partition-route-cache.ts
8599
- var import_node_buffer34 = require("buffer");
8734
+ var import_node_buffer35 = require("buffer");
8600
8735
  var import_node_crypto = require("crypto");
8601
- var AUTO_PREFIX = import_node_buffer34.Buffer.from("__flow_auto__:", "ascii");
8736
+ var AUTO_PREFIX = import_node_buffer35.Buffer.from("__flow_auto__:", "ascii");
8602
8737
  var MAX_CACHEABLE_PARTITION_BYTES = 4 * 1024;
8603
8738
  var MAX_CACHE_BYTES = 1 * 1024 * 1024;
8604
8739
  var MAX_CACHE_ENTRIES = 1024;
@@ -8608,10 +8743,10 @@ var cacheBytes = 0;
8608
8743
  var cacheHits = 0;
8609
8744
  var cacheMisses = 0;
8610
8745
  function flowLogicalPartitionRoutingKey(value) {
8611
- if (typeof value !== "string" && !import_node_buffer34.Buffer.isBuffer(value)) return void 0;
8746
+ if (typeof value !== "string" && !import_node_buffer35.Buffer.isBuffer(value)) return void 0;
8612
8747
  const autoBucket = flowAutoBucket(value);
8613
8748
  if (autoBucket != null) return `{fa:${autoBucket}}`;
8614
- const bytes2 = import_node_buffer34.Buffer.isBuffer(value) ? value : import_node_buffer34.Buffer.from(value);
8749
+ const bytes2 = import_node_buffer35.Buffer.isBuffer(value) ? value : import_node_buffer35.Buffer.from(value);
8615
8750
  if (bytes2.byteLength > MAX_CACHEABLE_PARTITION_BYTES) return hashRoute(bytes2);
8616
8751
  const cacheKey = bytes2.toString("base64");
8617
8752
  const cached = routeCache.get(cacheKey);
@@ -8685,7 +8820,7 @@ function routingKeyFromProtocolPayload(name, command) {
8685
8820
  "scope"
8686
8821
  ]) {
8687
8822
  const value = getField(command.payload, field3);
8688
- if (typeof value === "string" || import_node_buffer35.Buffer.isBuffer(value)) {
8823
+ if (typeof value === "string" || import_node_buffer36.Buffer.isBuffer(value)) {
8689
8824
  return value;
8690
8825
  }
8691
8826
  }
@@ -8733,7 +8868,7 @@ function flowRoutingData(name, args) {
8733
8868
  if (typeof partition === "string" && partition.toUpperCase() !== "AUTO" && partition.toUpperCase() !== "MIXED") {
8734
8869
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
8735
8870
  }
8736
- if (import_node_buffer35.Buffer.isBuffer(partition)) {
8871
+ if (import_node_buffer36.Buffer.isBuffer(partition)) {
8737
8872
  const text3 = partition.toString("utf8").toUpperCase();
8738
8873
  if (text3 !== "AUTO" && text3 !== "MIXED") {
8739
8874
  return flowRoutingResult(flowLogicalPartitionRoutingKey(partition));
@@ -8824,17 +8959,17 @@ function flowPartitionRoutingKeyFromCommand(command, claim) {
8824
8959
  return { handled: false };
8825
8960
  }
8826
8961
  function isRoutingKey(value) {
8827
- return typeof value === "string" || import_node_buffer35.Buffer.isBuffer(value);
8962
+ return typeof value === "string" || import_node_buffer36.Buffer.isBuffer(value);
8828
8963
  }
8829
8964
  function flowAutoIdRoutingKey(value) {
8830
- if (typeof value !== "string" && !import_node_buffer35.Buffer.isBuffer(value)) {
8965
+ if (typeof value !== "string" && !import_node_buffer36.Buffer.isBuffer(value)) {
8831
8966
  return void 0;
8832
8967
  }
8833
- const bucket = (import_node_buffer35.Buffer.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8968
+ const bucket = (import_node_buffer36.Buffer.isBuffer(value) ? crc32(value) : crc32Utf8(value)) & 255;
8834
8969
  return `{fa:${bucket}}`;
8835
8970
  }
8836
8971
  function flowClaimLogicalPartitionRoutingKey(value) {
8837
- if (typeof value !== "string" && !import_node_buffer35.Buffer.isBuffer(value)) return void 0;
8972
+ if (typeof value !== "string" && !import_node_buffer36.Buffer.isBuffer(value)) return void 0;
8838
8973
  const selector = commandPart(value);
8839
8974
  if (selector === "AUTO" || selector === "ANY") return void 0;
8840
8975
  if (selector === "GLOBAL") return "{f}";
@@ -8849,7 +8984,7 @@ function singleShardFlowClaimPartitionKey(values) {
8849
8984
  return keys.some((key) => key == null) ? void 0 : singleShardKey(keys);
8850
8985
  }
8851
8986
  function singleShardKey(keys) {
8852
- if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !import_node_buffer35.Buffer.isBuffer(key))) {
8987
+ if (keys.length === 0 || keys.some((key) => typeof key !== "string" && !import_node_buffer36.Buffer.isBuffer(key))) {
8853
8988
  return void 0;
8854
8989
  }
8855
8990
  const usable = keys;
@@ -8882,7 +9017,7 @@ function routedKeyGroups(keys, routeKey) {
8882
9017
  const groups = /* @__PURE__ */ new Map();
8883
9018
  for (let index = 0; index < keys.length; index += 1) {
8884
9019
  const key = keys[index];
8885
- if (typeof key !== "string" && !import_node_buffer35.Buffer.isBuffer(key)) return void 0;
9020
+ if (typeof key !== "string" && !import_node_buffer36.Buffer.isBuffer(key)) return void 0;
8886
9021
  const route = routeKey(key);
8887
9022
  const groupKey = `${route.endpointKey}\0${route.laneId}`;
8888
9023
  const group = groups.get(groupKey);
@@ -9971,7 +10106,7 @@ var TopologyNativeAdapterPool = class _TopologyNativeAdapterPool {
9971
10106
  };
9972
10107
 
9973
10108
  // src/response-map-preservation.ts
9974
- var import_node_buffer37 = require("buffer");
10109
+ var import_node_buffer38 = require("buffer");
9975
10110
  function toStringKeyMapPreservingValues(value) {
9976
10111
  if (value == null) return void 0;
9977
10112
  const result = {};
@@ -9981,7 +10116,7 @@ function toStringKeyMapPreservingValues(value) {
9981
10116
  }
9982
10117
  return result;
9983
10118
  }
9984
- if (typeof value === "object" && !Array.isArray(value) && !import_node_buffer37.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
10119
+ if (typeof value === "object" && !Array.isArray(value) && !import_node_buffer38.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
9985
10120
  for (const [key, item] of Object.entries(value)) {
9986
10121
  setOwnValue(result, text(key), normalizeMapStructurePreservingBytes(item));
9987
10122
  }
@@ -9990,7 +10125,7 @@ function toStringKeyMapPreservingValues(value) {
9990
10125
  return void 0;
9991
10126
  }
9992
10127
  function normalizeMapStructurePreservingBytes(value) {
9993
- if (import_node_buffer37.Buffer.isBuffer(value) || value instanceof Uint8Array) return value;
10128
+ if (import_node_buffer38.Buffer.isBuffer(value) || value instanceof Uint8Array) return value;
9994
10129
  if (value instanceof Map) {
9995
10130
  const result = {};
9996
10131
  for (const [key, item] of value.entries()) {
@@ -10012,7 +10147,7 @@ function normalizeMapStructurePreservingBytes(value) {
10012
10147
  }
10013
10148
 
10014
10149
  // src/native-kv-responses.ts
10015
- var import_node_buffer38 = require("buffer");
10150
+ var import_node_buffer39 = require("buffer");
10016
10151
  function rateLimitResultFromResp(value) {
10017
10152
  if (!Array.isArray(value) || value.length !== 4) {
10018
10153
  throw new TypeError("RATELIMIT.ADD returned an unexpected response");
@@ -10069,13 +10204,13 @@ function fetchOrComputeResultFromResp(value, codec) {
10069
10204
  }
10070
10205
  function decodePayload(codec, value) {
10071
10206
  if (value == null) return null;
10072
- if (import_node_buffer38.Buffer.isBuffer(value)) return codec.decode(value);
10073
- if (value instanceof Uint8Array) return codec.decode(import_node_buffer38.Buffer.from(value));
10074
- if (typeof value === "string") return codec.decode(import_node_buffer38.Buffer.from(value));
10207
+ if (import_node_buffer39.Buffer.isBuffer(value)) return codec.decode(value);
10208
+ if (value instanceof Uint8Array) return codec.decode(import_node_buffer39.Buffer.from(value));
10209
+ if (typeof value === "string") return codec.decode(import_node_buffer39.Buffer.from(value));
10075
10210
  return normalizeRefMeta(value);
10076
10211
  }
10077
10212
  function requiredResponseString(value, context) {
10078
- if (typeof value !== "string" && !import_node_buffer38.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
10213
+ if (typeof value !== "string" && !import_node_buffer39.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
10079
10214
  throw new TypeError(`${context} returned an unexpected response`);
10080
10215
  }
10081
10216
  const result = text(value);
@@ -10092,7 +10227,7 @@ function requiredNonNegativeInteger(value, context) {
10092
10227
  return result;
10093
10228
  }
10094
10229
  function responseBytes(value, context) {
10095
- if (typeof value !== "string" && !import_node_buffer38.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
10230
+ if (typeof value !== "string" && !import_node_buffer39.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
10096
10231
  throw new TypeError(`${context} returned an unexpected response`);
10097
10232
  }
10098
10233
  return bytes(value);
@@ -10775,7 +10910,7 @@ function groupAutoPartitionItems(items) {
10775
10910
  }
10776
10911
 
10777
10912
  // src/client-values.ts
10778
- var import_node_buffer39 = require("buffer");
10913
+ var import_node_buffer40 = require("buffer");
10779
10914
  async function valueMGetEntries(client, refs, options = {}) {
10780
10915
  const refCount = refs.length;
10781
10916
  if (refCount === 0) {
@@ -10810,12 +10945,12 @@ async function valueMGetEntries(client, refs, options = {}) {
10810
10945
  const item = response[index];
10811
10946
  if (item == null) {
10812
10947
  entries[index] = { found: false };
10813
- } else if (import_node_buffer39.Buffer.isBuffer(item)) {
10948
+ } else if (import_node_buffer40.Buffer.isBuffer(item)) {
10814
10949
  entries[index] = { found: true, value: client.codec.decode(item) };
10815
10950
  } else if (item instanceof Uint8Array) {
10816
- entries[index] = { found: true, value: client.codec.decode(import_node_buffer39.Buffer.from(item)) };
10951
+ entries[index] = { found: true, value: client.codec.decode(import_node_buffer40.Buffer.from(item)) };
10817
10952
  } else if (typeof item === "string") {
10818
- entries[index] = { found: true, value: client.codec.decode(import_node_buffer39.Buffer.from(item)) };
10953
+ entries[index] = { found: true, value: client.codec.decode(import_node_buffer40.Buffer.from(item)) };
10819
10954
  } else {
10820
10955
  entries[index] = { found: true, value: item };
10821
10956
  }
@@ -10824,10 +10959,10 @@ async function valueMGetEntries(client, refs, options = {}) {
10824
10959
  }
10825
10960
 
10826
10961
  // src/auto-batch.ts
10827
- var import_node_buffer41 = require("buffer");
10962
+ var import_node_buffer42 = require("buffer");
10828
10963
 
10829
10964
  // src/auto-batch-ordering.ts
10830
- var import_node_buffer40 = require("buffer");
10965
+ var import_node_buffer41 = require("buffer");
10831
10966
  function autoBatchOrderingPlan(batch) {
10832
10967
  const accesses = /* @__PURE__ */ new Map();
10833
10968
  const fallbackDependencies = [];
@@ -10942,13 +11077,13 @@ function flowManyAutoBatchIds(command, name) {
10942
11077
  return fixedFlowItemIds(
10943
11078
  command,
10944
11079
  mixed ? 4 : 3,
10945
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && isFencingToken(command[itemIndex + (mixed ? 2 : 1)]) && import_node_buffer40.Buffer.isBuffer(command[itemIndex + (mixed ? 3 : 2)])
11080
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && isFencingToken(command[itemIndex + (mixed ? 2 : 1)]) && import_node_buffer41.Buffer.isBuffer(command[itemIndex + (mixed ? 3 : 2)])
10946
11081
  );
10947
11082
  }
10948
11083
  return fixedFlowItemIds(
10949
11084
  command,
10950
11085
  mixed ? 4 : 3,
10951
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && import_node_buffer40.Buffer.isBuffer(command[itemIndex + (mixed ? 2 : 1)]) && isFencingToken(command[itemIndex + (mixed ? 3 : 2)])
11086
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && import_node_buffer41.Buffer.isBuffer(command[itemIndex + (mixed ? 2 : 1)]) && isFencingToken(command[itemIndex + (mixed ? 3 : 2)])
10952
11087
  );
10953
11088
  }
10954
11089
  function createManyAutoBatchIds(command) {
@@ -10961,7 +11096,7 @@ function createManyAutoBatchIds(command) {
10961
11096
  const ids = fixedFlowItemIds(
10962
11097
  command,
10963
11098
  width,
10964
- (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && import_node_buffer40.Buffer.isBuffer(command[itemIndex + width - 1]),
11099
+ (itemIndex) => isAutoBatchResourceValue(command[itemIndex]) && (!mixed || isAutoBatchResourceValue(command[itemIndex + 1])) && import_node_buffer41.Buffer.isBuffer(command[itemIndex + width - 1]),
10965
11100
  markerIndex
10966
11101
  );
10967
11102
  if (ids != null) return ids;
@@ -10979,7 +11114,7 @@ function extendedCreateManyAutoBatchIds(command, markerIndex) {
10979
11114
  let cursor = markerIndex + 2;
10980
11115
  for (let itemIndex = 0; itemIndex < itemCount; itemIndex += 1) {
10981
11116
  const id = command[cursor];
10982
- if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !import_node_buffer40.Buffer.isBuffer(command[cursor + 2])) return void 0;
11117
+ if (!isAutoBatchResourceValue(id) || !isAutoBatchResourceValue(command[cursor + 1]) || !import_node_buffer41.Buffer.isBuffer(command[cursor + 2])) return void 0;
10983
11118
  ids.push(id);
10984
11119
  cursor += 3;
10985
11120
  const afterValues = skipExtendedNamedItems(command, cursor, true);
@@ -10998,7 +11133,7 @@ function skipExtendedNamedItems(command, countIndex, encodedValues) {
10998
11133
  for (let index = 0; index < count; index += 1) {
10999
11134
  const name = command[firstItem + index * 2];
11000
11135
  const value = command[firstItem + index * 2 + 1];
11001
- if (!isAutoBatchResourceValue(name) || (encodedValues ? !import_node_buffer40.Buffer.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
11136
+ if (!isAutoBatchResourceValue(name) || (encodedValues ? !import_node_buffer41.Buffer.isBuffer(value) : !isAutoBatchResourceValue(value))) return void 0;
11002
11137
  }
11003
11138
  return firstItem + count * 2;
11004
11139
  }
@@ -11013,7 +11148,7 @@ function runStepsManyAutoBatchIds(command) {
11013
11148
  ids.push(item);
11014
11149
  continue;
11015
11150
  }
11016
- if (typeof item !== "object" || item == null || Array.isArray(item) || import_node_buffer40.Buffer.isBuffer(item)) return void 0;
11151
+ if (typeof item !== "object" || item == null || Array.isArray(item) || import_node_buffer41.Buffer.isBuffer(item)) return void 0;
11017
11152
  const id = item.id;
11018
11153
  if (!isAutoBatchResourceValue(id)) return void 0;
11019
11154
  ids.push(id);
@@ -11054,7 +11189,7 @@ function flowValuePutOwner(command, start) {
11054
11189
  }
11055
11190
  var flowValuePutOptionTokens = /* @__PURE__ */ new Set(["NAME", "NOW", "OVERRIDE", "OWNER_FLOW_ID", "PARTITION", "TTL", "TTL_MS"]);
11056
11191
  function isAutoBatchResourceValue(value) {
11057
- return typeof value === "string" || import_node_buffer40.Buffer.isBuffer(value);
11192
+ return typeof value === "string" || import_node_buffer41.Buffer.isBuffer(value);
11058
11193
  }
11059
11194
  function isFencingToken(value) {
11060
11195
  return typeof value === "number" && Number.isSafeInteger(value) || typeof value === "bigint";
@@ -11063,7 +11198,7 @@ function nonNegativeItemCount(value) {
11063
11198
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
11064
11199
  }
11065
11200
  function autoBatchResourceKey(namespace, value) {
11066
- return `${namespace}:${import_node_buffer40.Buffer.from(value).toString("base64")}`;
11201
+ return `${namespace}:${import_node_buffer41.Buffer.from(value).toString("base64")}`;
11067
11202
  }
11068
11203
  function autoBatchCommandName(command) {
11069
11204
  return commandView(command).name ?? null;
@@ -11472,13 +11607,13 @@ function claimFailure(error) {
11472
11607
  }
11473
11608
 
11474
11609
  // src/client-flow-support.ts
11475
- var import_node_buffer52 = require("buffer");
11610
+ var import_node_buffer55 = require("buffer");
11476
11611
 
11477
11612
  // src/flow-query-builder.ts
11478
- var import_node_buffer43 = require("buffer");
11613
+ var import_node_buffer44 = require("buffer");
11479
11614
 
11480
11615
  // src/flow-query-metadata.ts
11481
- var import_node_buffer42 = require("buffer");
11616
+ var import_node_buffer43 = require("buffer");
11482
11617
  var MAX_FLOW_QUERY_METADATA_KEY_BYTES = 64;
11483
11618
  function normalizeStateMeta(value, state) {
11484
11619
  if (value == null) return {};
@@ -11503,7 +11638,7 @@ function normalizeStateMeta(value, state) {
11503
11638
  function metadataEntries(value, context) {
11504
11639
  const entries = objectEntries(value ?? {}, context).map(([rawName, item]) => {
11505
11640
  const name = rawName.trim();
11506
- const size = import_node_buffer42.Buffer.byteLength(name, "utf8");
11641
+ const size = import_node_buffer43.Buffer.byteLength(name, "utf8");
11507
11642
  if (size === 0 || size > MAX_FLOW_QUERY_METADATA_KEY_BYTES || name.startsWith("__")) {
11508
11643
  throw new TypeError(`${context} key is invalid or reserved`);
11509
11644
  }
@@ -11527,7 +11662,7 @@ function objectEntries(value, context) {
11527
11662
  return keys.map((key) => [key, value[key]]);
11528
11663
  }
11529
11664
  function isPlainRecord(value) {
11530
- if (typeof value !== "object" || value == null || Array.isArray(value) || import_node_buffer42.Buffer.isBuffer(value)) {
11665
+ if (typeof value !== "object" || value == null || Array.isArray(value) || import_node_buffer43.Buffer.isBuffer(value)) {
11531
11666
  return false;
11532
11667
  }
11533
11668
  const prototype = Object.getPrototypeOf(value);
@@ -11597,7 +11732,7 @@ var FlowCollectionQuery = class {
11597
11732
  const states = /* @__PURE__ */ new Set();
11598
11733
  for (const [rawState, metadata] of objectEntries(values, "stateMeta")) {
11599
11734
  const state = requiredText(rawState, "stateMeta state").trim();
11600
- const stateBytes = import_node_buffer43.Buffer.byteLength(state, "utf8");
11735
+ const stateBytes = import_node_buffer44.Buffer.byteLength(state, "utf8");
11601
11736
  if (stateBytes === 0 || stateBytes > MAX_FLOW_QUERY_STATE_BYTES) {
11602
11737
  throw new TypeError(
11603
11738
  `stateMeta state names must be 1..${MAX_FLOW_QUERY_STATE_BYTES} bytes`
@@ -11776,7 +11911,7 @@ function requiredPartition(value) {
11776
11911
  "FLOW.QUERY convenience methods require a partition key"
11777
11912
  );
11778
11913
  }
11779
- const size = import_node_buffer43.Buffer.byteLength(value, "utf8");
11914
+ const size = import_node_buffer44.Buffer.byteLength(value, "utf8");
11780
11915
  if (size === 0 || size > MAX_FLOW_QUERY_PARTITION_BYTES) {
11781
11916
  throw new TypeError(
11782
11917
  `FLOW.QUERY partition key must be 1..${MAX_FLOW_QUERY_PARTITION_BYTES} bytes`
@@ -11824,20 +11959,23 @@ function requiredText(value, context) {
11824
11959
  return value;
11825
11960
  }
11826
11961
  function queryParameter(value, context) {
11827
- if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || import_node_buffer43.Buffer.isBuffer(value)) {
11962
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "number" || import_node_buffer44.Buffer.isBuffer(value)) {
11828
11963
  return value;
11829
11964
  }
11830
11965
  throw new TypeError(`${context} must be a scalar FLOW.QUERY parameter`);
11831
11966
  }
11832
11967
 
11833
11968
  // src/flow-query-response.ts
11834
- var import_node_buffer45 = require("buffer");
11969
+ var import_node_buffer48 = require("buffer");
11970
+
11971
+ // src/flow-query-diagnostic-response.ts
11972
+ var import_node_buffer46 = require("buffer");
11835
11973
 
11836
11974
  // src/flow-query-response-validation.ts
11837
- var import_node_buffer44 = require("buffer");
11975
+ var import_node_buffer45 = require("buffer");
11838
11976
  function requiredMap2(value, context) {
11839
11977
  if (value instanceof Map) return value;
11840
- if (typeof value !== "object" || value == null || Array.isArray(value) || import_node_buffer44.Buffer.isBuffer(value) || value instanceof Uint8Array) {
11978
+ if (typeof value !== "object" || value == null || Array.isArray(value) || import_node_buffer45.Buffer.isBuffer(value) || value instanceof Uint8Array) {
11841
11979
  throw decodeError(`${context} must be a map`, value);
11842
11980
  }
11843
11981
  return value;
@@ -11894,7 +12032,7 @@ function normalizeMetadataValue(value, context, budget, ancestors, depth) {
11894
12032
  if (!value.isWellFormed()) throw decodeError(`${context} contains invalid text`, value);
11895
12033
  return value;
11896
12034
  }
11897
- if (import_node_buffer44.Buffer.isBuffer(value) || value instanceof Uint8Array) {
12035
+ if (import_node_buffer45.Buffer.isBuffer(value) || value instanceof Uint8Array) {
11898
12036
  const decoded = strictText2(value);
11899
12037
  if (decoded == null) throw decodeError(`${context} contains invalid UTF-8`, value);
11900
12038
  return decoded;
@@ -11973,7 +12111,7 @@ function optionalText2(mapping, name, context) {
11973
12111
  }
11974
12112
  function requiredBoundedText2(mapping, name, context, maximumBytes) {
11975
12113
  const value = requiredText2(mapping, name, context);
11976
- if (import_node_buffer44.Buffer.byteLength(value, "utf8") > maximumBytes) {
12114
+ if (import_node_buffer45.Buffer.byteLength(value, "utf8") > maximumBytes) {
11977
12115
  throw decodeError(
11978
12116
  `${context} ${name} exceeds ${maximumBytes} bytes`,
11979
12117
  mapping
@@ -11986,7 +12124,7 @@ function boundedText(value, context, maximumBytes) {
11986
12124
  if (decoded == null || decoded.length === 0) {
11987
12125
  throw decodeError(`${context} must be non-empty text`, value);
11988
12126
  }
11989
- if (import_node_buffer44.Buffer.byteLength(decoded, "utf8") > maximumBytes) {
12127
+ if (import_node_buffer45.Buffer.byteLength(decoded, "utf8") > maximumBytes) {
11990
12128
  throw decodeError(`${context} exceeds ${maximumBytes} bytes`, value);
11991
12129
  }
11992
12130
  return decoded;
@@ -12040,10 +12178,10 @@ function positiveBoundedInteger(value, maximum, context) {
12040
12178
  function hasKey(mapping, name) {
12041
12179
  if (!(mapping instanceof Map)) return Object.hasOwn(mapping, name);
12042
12180
  if (mapping.has(name)) return true;
12043
- const binaryName = import_node_buffer44.Buffer.from(name);
12181
+ const binaryName = import_node_buffer45.Buffer.from(name);
12044
12182
  for (const key of mapping.keys()) {
12045
- if (import_node_buffer44.Buffer.isBuffer(key) && key.equals(binaryName)) return true;
12046
- if (key instanceof Uint8Array && import_node_buffer44.Buffer.from(key).equals(binaryName))
12183
+ if (import_node_buffer45.Buffer.isBuffer(key) && key.equals(binaryName)) return true;
12184
+ if (key instanceof Uint8Array && import_node_buffer45.Buffer.from(key).equals(binaryName))
12047
12185
  return true;
12048
12186
  }
12049
12187
  return false;
@@ -12054,7 +12192,7 @@ function decodeError(message, raw) {
12054
12192
  function strictText2(value) {
12055
12193
  if (typeof value === "string")
12056
12194
  return value.isWellFormed() ? value : void 0;
12057
- if (import_node_buffer44.Buffer.isBuffer(value) || value instanceof Uint8Array) {
12195
+ if (import_node_buffer45.Buffer.isBuffer(value) || value instanceof Uint8Array) {
12058
12196
  try {
12059
12197
  return new TextDecoder("utf-8", { fatal: true }).decode(value);
12060
12198
  } catch {
@@ -12087,189 +12225,35 @@ var FlowQueryError = class extends FerricStoreError {
12087
12225
  }
12088
12226
  };
12089
12227
 
12090
- // src/flow-query-response.ts
12091
- var FLOW_QUERY_RESULT_CONTRACT = "ferric.flow.query.result/v1";
12092
- var FLOW_EXPLAIN_CONTRACT = "ferric.flow.explain/v1";
12093
- var FLOW_QUERY_INDEXES_CONTRACT = "ferric.flow.query.indexes/v1";
12094
- var USAGE_FIELDS2 = [
12095
- ["range_seeks", "rangeSeeks"],
12096
- ["range_pages", "rangePages"],
12097
- ["scanned_entries", "scannedEntries"],
12098
- ["scanned_bytes", "scannedBytes"],
12099
- ["hydrated_records", "hydratedRecords"],
12100
- ["residual_checks", "residualChecks"],
12101
- ["duplicate_entries", "duplicateEntries"],
12102
- ["result_records", "resultRecords"],
12103
- ["response_bytes", "responseBytes"],
12104
- ["memory_high_water_bytes", "memoryHighWaterBytes"],
12105
- ["wall_time_us", "wallTimeUs"]
12106
- ];
12107
- var MAX_SIGNED_64 = (1n << 63n) - 1n;
12108
- var MAX_UNSIGNED_64 = (1n << 64n) - 1n;
12109
- function decodeFlowQueryResult(value) {
12110
- const result = decodeFlowQueryResponse(value, (mapping) => freezeMap(mapping));
12111
- if (result.kind === "records") {
12112
- return Object.freeze({
12113
- kind: "records",
12114
- version: FLOW_QUERY_RESULT_CONTRACT,
12115
- records: Object.freeze(result.records),
12116
- page: result.page,
12117
- quality: result.quality,
12118
- usage: result.usage,
12119
- raw: result.raw
12120
- });
12121
- }
12122
- return Object.freeze({
12123
- kind: "count",
12124
- version: FLOW_QUERY_RESULT_CONTRACT,
12125
- count: result.count,
12126
- quality: result.quality,
12127
- usage: result.usage,
12128
- raw: result.raw
12129
- });
12130
- }
12131
- function decodeFlowQueryRecords(value, decodeRecord) {
12132
- const result = decodeFlowQueryResponse(
12133
- value,
12134
- (mapping, index) => decodeRecord(mapping instanceof Map ? freezeMap(mapping) : mapping, index)
12135
- );
12136
- if (result.kind === "count") {
12137
- throw new FerricStoreError("Flow record convenience query returned a count result", {
12138
- raw: result.raw
12139
- });
12140
- }
12141
- return result.records;
12142
- }
12143
- function decodeFlowQueryResponse(value, decodeRecord) {
12144
- const mapping = requiredMap2(value, "FLOW.QUERY result");
12145
- requireContract(mapping, "version", FLOW_QUERY_RESULT_CONTRACT, "FLOW.QUERY result");
12146
- const quality = decodeQuality(field(mapping, "quality"));
12147
- const usage = decodeUsage(field(mapping, "usage"));
12148
- const hasRecords = hasKey(mapping, "records");
12149
- const hasCount = hasKey(mapping, "result");
12150
- if (hasRecords === hasCount) {
12151
- throw decodeError("FLOW.QUERY result must contain exactly one records or count shape", value);
12152
- }
12153
- const raw = freezeMap(mapping);
12154
- if (hasRecords) {
12155
- const rawRecords = field(mapping, "records");
12156
- if (!Array.isArray(rawRecords) || rawRecords.length > 100) {
12157
- throw decodeError("FLOW.QUERY records must be an array of at most 100 maps", value);
12158
- }
12159
- const records = new Array(rawRecords.length);
12160
- for (let index = 0; index < rawRecords.length; index += 1) {
12161
- if (!Object.hasOwn(rawRecords, index)) {
12162
- throw decodeError("FLOW.QUERY records must be a dense array", value);
12163
- }
12164
- records[index] = decodeRecord(
12165
- requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
12166
- index
12167
- );
12168
- }
12169
- if (usage.resultRecords !== records.length) {
12170
- throw decodeError("FLOW.QUERY usage result_records does not match records", value);
12171
- }
12172
- return {
12173
- kind: "records",
12174
- records,
12175
- page: decodePage(field(mapping, "page")),
12176
- quality,
12177
- usage,
12178
- raw
12179
- };
12180
- }
12181
- if (hasKey(mapping, "page")) {
12182
- throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
12183
- }
12184
- const countResult = requiredMap2(field(mapping, "result"), "FLOW.QUERY count result");
12185
- if (requiredText2(countResult, "kind", "FLOW.QUERY count result") !== "count") {
12186
- throw decodeError("FLOW.QUERY count result kind must be count", value);
12187
- }
12188
- const count = boundedInteger(
12189
- field(countResult, "value"),
12190
- MAX_SIGNED_64,
12191
- "FLOW.QUERY count value"
12192
- );
12193
- if (usage.resultRecords !== 1) {
12194
- throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
12195
- }
12196
- return {
12197
- kind: "count",
12198
- count,
12199
- quality,
12200
- usage,
12201
- raw
12202
- };
12203
- }
12204
- function decodeFlowExplainResult(value) {
12205
- const mapping = requiredMap2(value, "FLOW.QUERY explain");
12206
- requireContract(mapping, "version", FLOW_EXPLAIN_CONTRACT, "FLOW.QUERY explain");
12207
- const queryFingerprint = requiredText2(mapping, "query_fingerprint", "FLOW.QUERY explain");
12208
- if (!/^[0-9a-f]{64}$/iu.test(queryFingerprint)) {
12209
- throw decodeError("FLOW.QUERY explain query_fingerprint is invalid", value);
12210
- }
12211
- const status = requiredText2(mapping, "status", "FLOW.QUERY explain");
12212
- if (status !== "planned" && status !== "rejected" && status !== "executed") {
12213
- throw decodeError(`FLOW.QUERY explain status ${JSON.stringify(status)} is unsupported`, value);
12214
- }
12215
- const plan = freezeMetadataMap(
12216
- requiredMap2(field(mapping, "plan"), "FLOW.QUERY explain plan"),
12217
- "FLOW.QUERY explain plan"
12218
- );
12219
- const estimate = freezeMetadataMap(
12220
- requiredMap2(field(mapping, "estimate"), "FLOW.QUERY explain estimate"),
12221
- "FLOW.QUERY explain estimate"
12222
- );
12223
- const bounds = freezeMetadataMap(
12224
- requiredMap2(field(mapping, "bounds"), "FLOW.QUERY explain bounds"),
12225
- "FLOW.QUERY explain bounds"
12226
- );
12227
- const actualValue = field(mapping, "actual");
12228
- let actual;
12229
- if (status === "executed") {
12230
- if (actualValue == null) {
12231
- throw decodeError("FLOW.QUERY executed explain is missing actual usage", value);
12232
- }
12233
- actual = decodeUsage(actualValue);
12234
- } else if (actualValue != null) {
12235
- throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
12236
- }
12237
- const diagnosticValue = field(mapping, "diagnostic");
12238
- let diagnostic;
12239
- if (status === "rejected") {
12240
- diagnostic = tryDecodeFlowQueryError(diagnosticValue);
12241
- if (diagnostic == null) {
12242
- throw decodeError("FLOW.QUERY rejected explain has an invalid diagnostic", value);
12243
- }
12244
- } else if (diagnosticValue != null) {
12245
- throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
12246
- }
12247
- return Object.freeze({
12248
- version: FLOW_EXPLAIN_CONTRACT,
12249
- queryFingerprint,
12250
- status,
12251
- plan,
12252
- estimate,
12253
- bounds,
12254
- actual,
12255
- diagnostic,
12256
- raw: freezeMap(mapping)
12257
- });
12258
- }
12228
+ // src/flow-query-diagnostic-response.ts
12229
+ var MIN_SIGNED_64 = -(1n << 63n);
12230
+ var MAX_SIGNED_642 = (1n << 63n) - 1n;
12231
+ var DIAGNOSTIC_TEXT_BYTES = 1024;
12232
+ var DIAGNOSTIC_CONTEXT_ENTRIES = 16;
12233
+ var DIAGNOSTIC_CONTEXT_LIST_ITEMS = 32;
12234
+ var DIAGNOSTIC_CONTEXT_KEY_BYTES = 128;
12235
+ var DIAGNOSTIC_CONTEXT_DEPTH = 6;
12236
+ var DIAGNOSTIC_CONTEXT_NODES = 512;
12259
12237
  function tryDecodeFlowQueryError(value, cause) {
12260
12238
  try {
12261
12239
  const mapping = requiredMap2(value, "FLOW.QUERY diagnostic");
12262
- const contextValue = field(mapping, "context");
12263
- const context = contextValue == null ? void 0 : freezeMetadataMap(
12264
- requiredMap2(contextValue, "FLOW.QUERY diagnostic context"),
12265
- "FLOW.QUERY diagnostic context"
12266
- );
12240
+ const context = decodeDiagnosticContext(field(mapping, "context"));
12267
12241
  const position = decodePosition(field(mapping, "position"));
12268
12242
  return new FlowQueryError({
12269
- code: requiredText2(mapping, "code", "FLOW.QUERY diagnostic"),
12270
- message: requiredText2(mapping, "message", "FLOW.QUERY diagnostic"),
12271
- detail: optionalText2(mapping, "detail", "FLOW.QUERY diagnostic"),
12272
- hint: optionalText2(mapping, "hint", "FLOW.QUERY diagnostic"),
12243
+ code: requiredBoundedText2(
12244
+ mapping,
12245
+ "code",
12246
+ "FLOW.QUERY diagnostic",
12247
+ DIAGNOSTIC_TEXT_BYTES
12248
+ ),
12249
+ message: requiredBoundedText2(
12250
+ mapping,
12251
+ "message",
12252
+ "FLOW.QUERY diagnostic",
12253
+ DIAGNOSTIC_TEXT_BYTES
12254
+ ),
12255
+ detail: optionalDiagnosticText(mapping, "detail"),
12256
+ hint: optionalDiagnosticText(mapping, "hint"),
12273
12257
  retryable: requiredBoolean(mapping, "retryable", "FLOW.QUERY diagnostic"),
12274
12258
  safeToRetry: requiredBoolean(mapping, "safe_to_retry", "FLOW.QUERY diagnostic"),
12275
12259
  retryAfterMs: nonNegativeInteger(
@@ -12286,49 +12270,724 @@ function tryDecodeFlowQueryError(value, cause) {
12286
12270
  throw error;
12287
12271
  }
12288
12272
  }
12289
- function decodeFlowQueryIndexStatus(value) {
12290
- const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES");
12291
- requireContract(
12292
- mapping,
12293
- "contract_version",
12294
- FLOW_QUERY_INDEXES_CONTRACT,
12295
- "FLOW.QUERY.INDEXES"
12296
- );
12297
- const registry = requiredMap2(field(mapping, "registry"), "FLOW.QUERY.INDEXES registry");
12298
- const services = freezeMetadataMap(
12299
- requiredMap2(field(mapping, "services"), "FLOW.QUERY.INDEXES services"),
12300
- "FLOW.QUERY.INDEXES services"
12301
- );
12273
+ function optionalDiagnosticText(mapping, name) {
12274
+ const value = optionalText2(mapping, name, "FLOW.QUERY diagnostic");
12275
+ if (value != null && import_node_buffer46.Buffer.byteLength(value, "utf8") > DIAGNOSTIC_TEXT_BYTES) {
12276
+ throw decodeError(
12277
+ `FLOW.QUERY diagnostic ${name} exceeds ${DIAGNOSTIC_TEXT_BYTES} bytes`,
12278
+ mapping
12279
+ );
12280
+ }
12281
+ return value;
12282
+ }
12283
+ function decodeDiagnosticContext(value) {
12284
+ if (value == null) return void 0;
12285
+ const mapping = requiredMap2(value, "FLOW.QUERY diagnostic context");
12286
+ const size = mapping instanceof Map ? mapping.size : Object.keys(mapping).length;
12287
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12288
+ throw decodeError("FLOW.QUERY diagnostic context contains too many entries", value);
12289
+ }
12290
+ validateDiagnosticContextValue(
12291
+ mapping,
12292
+ DIAGNOSTIC_CONTEXT_DEPTH,
12293
+ { remaining: DIAGNOSTIC_CONTEXT_NODES }
12294
+ );
12295
+ return freezeMetadataMap(mapping, "FLOW.QUERY diagnostic context");
12296
+ }
12297
+ function validateDiagnosticContextValue(value, depth, budget) {
12298
+ consumeDiagnosticContextNode(value, budget);
12299
+ if (value == null || typeof value === "boolean") return;
12300
+ if (typeof value === "number") {
12301
+ if (Number.isSafeInteger(value)) return;
12302
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12303
+ }
12304
+ if (typeof value === "bigint") {
12305
+ if (value >= MIN_SIGNED_64 && value <= MAX_SIGNED_642) return;
12306
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid integer", value);
12307
+ }
12308
+ const text3 = diagnosticContextText(value);
12309
+ if (text3 != null) {
12310
+ if (import_node_buffer46.Buffer.byteLength(text3, "utf8") <= DIAGNOSTIC_TEXT_BYTES) return;
12311
+ throw decodeError("FLOW.QUERY diagnostic context contains oversized text", value);
12312
+ }
12313
+ if (depth <= 0) {
12314
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its maximum depth", value);
12315
+ }
12316
+ if (Array.isArray(value)) {
12317
+ if (value.length > DIAGNOSTIC_CONTEXT_LIST_ITEMS) {
12318
+ throw decodeError("FLOW.QUERY diagnostic context list is too large", value);
12319
+ }
12320
+ for (let index = 0; index < value.length; index += 1) {
12321
+ if (!Object.hasOwn(value, index)) {
12322
+ throw decodeError("FLOW.QUERY diagnostic context list must be dense", value);
12323
+ }
12324
+ validateDiagnosticContextValue(value[index], depth - 1, budget);
12325
+ }
12326
+ return;
12327
+ }
12328
+ if (value instanceof Map || isDiagnosticContextRecord(value)) {
12329
+ const entries = value instanceof Map ? value.entries() : Object.entries(value);
12330
+ const size = value instanceof Map ? value.size : Object.keys(value).length;
12331
+ if (size > DIAGNOSTIC_CONTEXT_ENTRIES) {
12332
+ throw decodeError("FLOW.QUERY diagnostic context map is too large", value);
12333
+ }
12334
+ for (const [rawKey, item] of entries) {
12335
+ const key = diagnosticContextText(rawKey);
12336
+ if (key == null || key.length === 0 || import_node_buffer46.Buffer.byteLength(key, "utf8") > DIAGNOSTIC_CONTEXT_KEY_BYTES) {
12337
+ throw decodeError("FLOW.QUERY diagnostic context contains an invalid key", value);
12338
+ }
12339
+ validateDiagnosticContextValue(item, depth - 1, budget);
12340
+ }
12341
+ return;
12342
+ }
12343
+ throw decodeError("FLOW.QUERY diagnostic context contains an unsupported value", value);
12344
+ }
12345
+ function consumeDiagnosticContextNode(value, budget) {
12346
+ if (budget.remaining <= 0) {
12347
+ throw decodeError("FLOW.QUERY diagnostic context exceeds its node limit", value);
12348
+ }
12349
+ budget.remaining -= 1;
12350
+ }
12351
+ function diagnosticContextText(value) {
12352
+ if (typeof value === "string") return value.isWellFormed() ? value : void 0;
12353
+ if (!import_node_buffer46.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) return void 0;
12354
+ try {
12355
+ return new TextDecoder("utf-8", { fatal: true }).decode(value);
12356
+ } catch {
12357
+ return void 0;
12358
+ }
12359
+ }
12360
+ function isDiagnosticContextRecord(value) {
12361
+ if (typeof value !== "object" || value == null) return false;
12362
+ const prototype = Object.getPrototypeOf(value);
12363
+ return prototype === Object.prototype || prototype === null;
12364
+ }
12365
+ function decodePosition(value) {
12366
+ if (value == null) return void 0;
12367
+ const mapping = requiredMap2(value, "FLOW.QUERY diagnostic position");
12368
+ return Object.freeze({
12369
+ byte: positiveInteger(field(mapping, "byte"), "FLOW.QUERY diagnostic position byte"),
12370
+ line: positiveInteger(field(mapping, "line"), "FLOW.QUERY diagnostic position line"),
12371
+ column: positiveInteger(
12372
+ field(mapping, "column"),
12373
+ "FLOW.QUERY diagnostic position column"
12374
+ )
12375
+ });
12376
+ }
12377
+
12378
+ // src/flow-query-index-contract.ts
12379
+ var import_node_buffer47 = require("buffer");
12380
+ var FLOW_QUERY_BUILD_PHASES = ["pending", "snapshot", "backfill", "done"];
12381
+ var FLOW_QUERY_VALIDATION_PHASES = [
12382
+ "pending",
12383
+ "source",
12384
+ "index",
12385
+ "counter",
12386
+ "cleanup",
12387
+ "done"
12388
+ ];
12389
+ var FLOW_QUERY_RETIREMENT_PHASES = [
12390
+ "pending",
12391
+ "fence",
12392
+ "index",
12393
+ "counter",
12394
+ "reverse",
12395
+ "cleanup",
12396
+ "done"
12397
+ ];
12398
+ var IDENTIFIER = /^[A-Za-z0-9_.:-]+$/u;
12399
+ var UNQUOTED_METADATA = /^[A-Za-z0-9_-]+$/u;
12400
+ var ATTRIBUTE_SELECTOR = /^attribute\['((?:[^']|'')*)'\]$/u;
12401
+ var STATE_META_SELECTOR = /^state_meta\['((?:[^']|'')*)'\]\['((?:[^']|'')*)'\]$/u;
12402
+ var INTEGER_FIELDS = /* @__PURE__ */ new Set([
12403
+ "version",
12404
+ "priority",
12405
+ "created_at_ms",
12406
+ "updated_at_ms",
12407
+ "next_run_at_ms",
12408
+ "lease_deadline_ms",
12409
+ "attempts",
12410
+ "max_active_ms"
12411
+ ]);
12412
+ var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
12413
+ ...INTEGER_FIELDS,
12414
+ "partition_key",
12415
+ "run_id",
12416
+ "event_id",
12417
+ "type",
12418
+ "state",
12419
+ "run_state",
12420
+ "parent_flow_id",
12421
+ "root_flow_id",
12422
+ "correlation_id"
12423
+ ]);
12424
+ var RETIREMENT_PROGRESS_FIELDS = [
12425
+ "phase_counts",
12426
+ "current_phases",
12427
+ "completed_shards",
12428
+ "total_shards",
12429
+ "deleted_entries",
12430
+ "deleted_bytes",
12431
+ "rewritten_reverse_rows"
12432
+ ];
12433
+ function validateFlowQueryIndexContract(status, expectedId) {
12434
+ let previousId;
12435
+ let previousVersion;
12436
+ for (const index of status.indexes) {
12437
+ const version = BigInt(index.version);
12438
+ if (previousId != null && (index.id < previousId || index.id === previousId && previousVersion != null && version <= previousVersion)) {
12439
+ fail("indexes must be uniquely sorted by id and version", status.raw);
12440
+ }
12441
+ if (expectedId != null && index.id !== expectedId) {
12442
+ fail("filtered indexes do not match the requested id", status.raw);
12443
+ }
12444
+ previousId = index.id;
12445
+ previousVersion = version;
12446
+ validateIndex(index, status);
12447
+ }
12448
+ if (expectedId != null && status.indexes.length === 0) {
12449
+ fail("filtered indexes do not match the requested id", status.raw);
12450
+ }
12451
+ const hasUnavailableStatistics = status.indexes.some(
12452
+ (index) => index.statistics.status === "unavailable"
12453
+ );
12454
+ if (status.services.statisticsStore === "unavailable") {
12455
+ if (status.indexes.some((index) => index.statistics.status !== "unavailable")) {
12456
+ fail("statistics must be unavailable when the service is unavailable", status.raw);
12457
+ }
12458
+ } else if (hasUnavailableStatistics) {
12459
+ fail("statistics cannot be unavailable while the service is ready", status.raw);
12460
+ }
12461
+ }
12462
+ function validateIndex(index, status) {
12463
+ if (!IDENTIFIER.test(index.id) || index.workloads.some((workload) => !IDENTIFIER.test(workload))) {
12464
+ fail("index identity contains invalid characters", index.raw);
12465
+ }
12466
+ const first = index.fields[0];
12467
+ if (first?.name !== "partition_key" || first.direction !== "asc" || first.encoding !== "hashed") {
12468
+ fail("index must begin with partition_key asc hashed", index.raw);
12469
+ }
12470
+ let attributeFields = 0;
12471
+ for (const indexField of index.fields) {
12472
+ if (indexField.encoding === "hashed" && indexField.direction !== "asc") {
12473
+ fail("hashed index fields must be ascending", index.raw);
12474
+ }
12475
+ const kind = fieldKind(indexField.name);
12476
+ if (kind == null) fail("index contains an unsupported field selector", index.raw);
12477
+ if (indexField.encoding === "ordered" && kind !== "integer") {
12478
+ fail("ordered index fields must be integers", index.raw);
12479
+ }
12480
+ if (kind === "attribute") attributeFields += 1;
12481
+ }
12482
+ if (attributeFields > 1) fail("index may contain at most one attribute field", index.raw);
12483
+ for (const prefix of index.countPrefixes) {
12484
+ if (index.fields.slice(0, prefix).some((field3) => field3.encoding !== "hashed")) {
12485
+ fail("count prefixes may cover only hashed fields", index.raw);
12486
+ }
12487
+ }
12488
+ if (index.coveringFields.length > 0) {
12489
+ if (index.coveringFields.some((field3) => fieldKind(field3) == null)) {
12490
+ fail("index contains an unsupported covering field selector", index.raw);
12491
+ }
12492
+ const required = /* @__PURE__ */ new Set(["run_id", "version", ...index.fields.map((field3) => field3.name)]);
12493
+ if ([...required].some((field3) => !index.coveringFields.includes(field3))) {
12494
+ fail("index covering fields omit an identity or index field", index.raw);
12495
+ }
12496
+ }
12497
+ if (index.countPrefixes.length > 0 !== (index.format.counter != null)) {
12498
+ fail("index counter format is inconsistent with count prefixes", index.raw);
12499
+ }
12500
+ validateProgress(index.build, FLOW_QUERY_BUILD_PHASES, "build");
12501
+ validateProgress(index.validation, FLOW_QUERY_VALIDATION_PHASES, "validation");
12502
+ if (index.retirement.status === "not_applicable") {
12503
+ if (RETIREMENT_PROGRESS_FIELDS.some((name) => hasKey(index.retirement.raw, name))) {
12504
+ fail("not_applicable retirement must not contain progress", index.retirement.raw);
12505
+ }
12506
+ } else {
12507
+ const retirement = index.retirement;
12508
+ if (retirement.phaseCounts == null || retirement.currentPhases == null || retirement.completedShards == null || retirement.totalShards == null) {
12509
+ fail("retirement progress is incomplete", retirement.raw);
12510
+ }
12511
+ validateProgress(
12512
+ {
12513
+ phaseCounts: retirement.phaseCounts,
12514
+ currentPhases: retirement.currentPhases,
12515
+ completedShards: retirement.completedShards,
12516
+ totalShards: retirement.totalShards,
12517
+ raw: retirement.raw
12518
+ },
12519
+ FLOW_QUERY_RETIREMENT_PHASES,
12520
+ "retirement"
12521
+ );
12522
+ }
12523
+ const totals = new Set(
12524
+ [
12525
+ index.coverage.totalShards,
12526
+ index.build.totalShards,
12527
+ index.validation.totalShards,
12528
+ ...index.retirement.totalShards == null ? [] : [index.retirement.totalShards]
12529
+ ].map(indexInteger)
12530
+ );
12531
+ if (totals.size !== 1) fail("index shard totals are inconsistent", index.raw);
12532
+ if (!sameIndexInteger(index.coverage.completeShards, index.build.completedShards)) {
12533
+ fail("coverage and build completion are inconsistent", index.raw);
12534
+ }
12535
+ if (index.coverage.validation !== index.validation.status) {
12536
+ fail("coverage and validation status are inconsistent", index.raw);
12537
+ }
12538
+ const queryable = index.state === "active" && sameIndexInteger(
12539
+ index.coverage.completeShards,
12540
+ index.coverage.totalShards
12541
+ ) && index.coverage.validation === "passed";
12542
+ if (index.queryable !== queryable) fail("index queryable flag is inconsistent", index.raw);
12543
+ validateLifecycle(index);
12544
+ validateValidation(index);
12545
+ validateStatistics(index, status);
12546
+ }
12547
+ function validateProgress(progress, phases, section) {
12548
+ const entries = Object.entries(progress.phaseCounts);
12549
+ if (entries.length === 0 || entries.some(
12550
+ ([phase, count]) => !phases.includes(phase) || indexInteger(count) <= 0n
12551
+ ) || entries.reduce((sum, [, count]) => sum + indexInteger(count), 0n) !== indexInteger(progress.totalShards)) {
12552
+ fail(`${section} phase_counts are invalid`, progress.raw);
12553
+ }
12554
+ const current = phases.filter((phase) => Object.hasOwn(progress.phaseCounts, phase));
12555
+ if (current.length !== progress.currentPhases.length || current.some((phase, position) => phase !== progress.currentPhases[position])) {
12556
+ fail(`${section} current_phases are inconsistent`, progress.raw);
12557
+ }
12558
+ if (!sameIndexInteger(
12559
+ progress.completedShards,
12560
+ progress.phaseCounts.done ?? 0
12561
+ )) {
12562
+ fail(`${section} completed_shards is inconsistent`, progress.raw);
12563
+ }
12564
+ }
12565
+ function validateLifecycle(index) {
12566
+ const built = sameIndexInteger(
12567
+ index.build.completedShards,
12568
+ index.build.totalShards
12569
+ );
12570
+ const validation = index.validation.status;
12571
+ const retirement = index.retirement.status;
12572
+ 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";
12573
+ if (!valid) fail("index lifecycle fields are inconsistent", index.raw);
12574
+ }
12575
+ function validateValidation(index) {
12576
+ const validation = index.validation;
12577
+ 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;
12578
+ if (!valid) fail("validation status fields are inconsistent", validation.raw);
12579
+ }
12580
+ function validateStatistics(index, status) {
12581
+ const statistics = index.statistics;
12582
+ const times = [
12583
+ statistics.oldestCollectedAtMs,
12584
+ statistics.newestCollectedAtMs,
12585
+ statistics.oldestAgeMs,
12586
+ statistics.newestAgeMs
12587
+ ];
12588
+ if (indexInteger(statistics.samples) === 0n) {
12589
+ if (statistics.status !== "missing" && statistics.status !== "unavailable" || times.some((value) => value != null)) {
12590
+ fail("empty statistics fields are inconsistent", statistics.raw);
12591
+ }
12592
+ return;
12593
+ }
12594
+ if (times.some((value) => value == null)) {
12595
+ fail("sampled statistics require timestamps and ages", statistics.raw);
12596
+ }
12597
+ const [oldest, newest, oldestAge, newestAge] = times;
12598
+ if (indexInteger(oldest) > indexInteger(newest) || indexInteger(oldestAge) !== indexAge(status.observedAtMs, oldest) || indexInteger(newestAge) !== indexAge(status.observedAtMs, newest)) {
12599
+ fail("statistics timestamps or ages are inconsistent", statistics.raw);
12600
+ }
12601
+ const expected = sameIndexInteger(statistics.freshSamples, statistics.samples) ? ["fresh"] : indexInteger(statistics.freshSamples) === 0n ? indexInteger(statistics.futureSamples) > 0n ? ["stale", "future"] : ["stale"] : ["mixed"];
12602
+ if (!expected.includes(statistics.status)) {
12603
+ fail("statistics status does not match sample counters", statistics.raw);
12604
+ }
12605
+ }
12606
+ function fieldKind(name) {
12607
+ if (BUILTIN_FIELDS.has(name)) return INTEGER_FIELDS.has(name) ? "integer" : "keyword";
12608
+ const parts = name.split(".");
12609
+ if (parts.length === 2 && parts[0] === "attribute" && validUnquoted(parts[1] ?? "")) return "attribute";
12610
+ if (parts.length === 3 && parts[0] === "state_meta" && validUnquoted(parts[1] ?? "") && validUnquoted(parts[2] ?? "")) return "state_meta";
12611
+ const attribute = ATTRIBUTE_SELECTOR.exec(name);
12612
+ if (attribute != null) {
12613
+ const metadata = (attribute[1] ?? "").replaceAll("''", "'");
12614
+ if (validMetadata(metadata, true) && name === externalSelector("attribute", metadata)) return "attribute";
12615
+ }
12616
+ const stateMeta = STATE_META_SELECTOR.exec(name);
12617
+ if (stateMeta != null) {
12618
+ const state = (stateMeta[1] ?? "").replaceAll("''", "'");
12619
+ const metadata = (stateMeta[2] ?? "").replaceAll("''", "'");
12620
+ if (validMetadata(state, false) && validMetadata(metadata, true) && name === externalSelector("state_meta", state, metadata)) return "state_meta";
12621
+ }
12622
+ return void 0;
12623
+ }
12624
+ function validUnquoted(value) {
12625
+ return !value.startsWith("__") && UNQUOTED_METADATA.test(value) && import_node_buffer47.Buffer.byteLength(value, "ascii") <= 64;
12626
+ }
12627
+ function validMetadata(value, rejectReserved) {
12628
+ return value.length > 0 && import_node_buffer47.Buffer.byteLength(value, "utf8") <= 64 && (!rejectReserved || !value.startsWith("__"));
12629
+ }
12630
+ function externalSelector(root, ...segments) {
12631
+ return segments.every(validUnquoted) ? [root, ...segments].join(".") : root + segments.map((segment) => `['${segment.replaceAll("'", "''")}']`).join("");
12632
+ }
12633
+ function sameIndexInteger(left, right) {
12634
+ return indexInteger(left) === indexInteger(right);
12635
+ }
12636
+ function indexAge(observedAtMs, collectedAtMs) {
12637
+ const age = indexInteger(observedAtMs) - indexInteger(collectedAtMs);
12638
+ return age < 0n ? 0n : age;
12639
+ }
12640
+ function indexInteger(value) {
12641
+ return BigInt(value);
12642
+ }
12643
+ function fail(message, raw) {
12644
+ throw decodeError(`FLOW.QUERY.INDEXES ${message}`, raw);
12645
+ }
12646
+
12647
+ // src/flow-query-index-decode.ts
12648
+ var MAX_UNSIGNED_64 = (1n << 64n) - 1n;
12649
+ function decodeUniqueTextArray(value, context, maximum, maximumBytes) {
12650
+ if (!Array.isArray(value) || value.length > maximum) {
12651
+ throw decodeError(`${context} must be a bounded array`, value);
12652
+ }
12653
+ const result = new Array(value.length);
12654
+ const seen = /* @__PURE__ */ new Set();
12655
+ for (let index = 0; index < value.length; index += 1) {
12656
+ if (!Object.hasOwn(value, index)) {
12657
+ throw decodeError(`${context} must be dense`, value);
12658
+ }
12659
+ const text3 = boundedText(
12660
+ value[index],
12661
+ `${context} entry ${index}`,
12662
+ maximumBytes
12663
+ );
12664
+ if (seen.has(text3)) {
12665
+ throw decodeError(`${context} contains duplicates`, value);
12666
+ }
12667
+ seen.add(text3);
12668
+ result[index] = text3;
12669
+ }
12670
+ return Object.freeze(result);
12671
+ }
12672
+ function requiredChoice(mapping, name, context, choices) {
12673
+ const value = requiredBoundedText2(mapping, name, context, 64);
12674
+ if (!choices.includes(value)) {
12675
+ throw decodeError(`${context} ${name} is unsupported`, mapping);
12676
+ }
12677
+ return value;
12678
+ }
12679
+ function optionalBoundedText(mapping, name, context, maximumBytes) {
12680
+ const value = field(mapping, name);
12681
+ if (value == null) return void 0;
12682
+ return boundedText(value, `${context} ${name}`, maximumBytes);
12683
+ }
12684
+ function optionalIndexInteger(mapping, name, context) {
12685
+ const value = field(mapping, name);
12686
+ return value == null ? void 0 : unsignedIndexInteger(value, `${context} ${name}`);
12687
+ }
12688
+ function indexCounter(mapping, name, section) {
12689
+ return unsignedIndexInteger(
12690
+ field(mapping, name),
12691
+ `FLOW.QUERY.INDEXES index ${section} ${name}`
12692
+ );
12693
+ }
12694
+ function unsignedIndexInteger(value, context) {
12695
+ return boundedInteger(value, MAX_UNSIGNED_64, context);
12696
+ }
12697
+ function positiveIndexInteger(value, context) {
12698
+ return positiveBoundedInteger(value, MAX_UNSIGNED_64, context);
12699
+ }
12700
+ function indexInteger2(value) {
12701
+ return BigInt(value);
12702
+ }
12703
+
12704
+ // src/flow-query-index-lifecycle-response.ts
12705
+ function decodeIndexCoverage(value) {
12706
+ const context = "FLOW.QUERY.INDEXES index coverage";
12707
+ const mapping = requiredMap2(value, context);
12708
+ const completeShards = unsignedIndexInteger(
12709
+ field(mapping, "complete_shards"),
12710
+ `${context} complete_shards`
12711
+ );
12712
+ const totalShards = positiveIndexInteger(
12713
+ field(mapping, "total_shards"),
12714
+ `${context} total_shards`
12715
+ );
12716
+ if (indexInteger2(completeShards) > indexInteger2(totalShards)) {
12717
+ throw decodeError(`${context} complete_shards exceeds total_shards`, value);
12718
+ }
12719
+ return Object.freeze({
12720
+ completeShards,
12721
+ totalShards,
12722
+ validation: requiredChoice(mapping, "validation", context, [
12723
+ "pending",
12724
+ "passed",
12725
+ "failed"
12726
+ ]),
12727
+ raw: freezeMap(mapping)
12728
+ });
12729
+ }
12730
+ function decodeIndexBuild(value) {
12731
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES index build");
12732
+ const progress = decodeIndexProgress(
12733
+ mapping,
12734
+ "build",
12735
+ FLOW_QUERY_BUILD_PHASES
12736
+ );
12737
+ return Object.freeze({
12738
+ ...progress,
12739
+ scannedRecords: indexCounter(mapping, "scanned_records", "build"),
12740
+ writtenEntries: indexCounter(mapping, "written_entries", "build"),
12741
+ writtenBytes: indexCounter(mapping, "written_bytes", "build")
12742
+ });
12743
+ }
12744
+ function decodeIndexValidation(value) {
12745
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES index validation");
12746
+ if (!hasKey(mapping, "failure_reason") || !hasKey(mapping, "validated_at_ms")) {
12747
+ throw decodeError(
12748
+ "FLOW.QUERY.INDEXES index validation is missing nullable fields",
12749
+ value
12750
+ );
12751
+ }
12752
+ const progress = decodeIndexProgress(
12753
+ mapping,
12754
+ "validation",
12755
+ FLOW_QUERY_VALIDATION_PHASES
12756
+ );
12757
+ return Object.freeze({
12758
+ ...progress,
12759
+ status: requiredChoice(
12760
+ mapping,
12761
+ "status",
12762
+ "FLOW.QUERY.INDEXES index validation",
12763
+ ["pending", "passed", "failed"]
12764
+ ),
12765
+ checkedRecords: indexCounter(mapping, "checked_records", "validation"),
12766
+ checkedEntries: indexCounter(mapping, "checked_entries", "validation"),
12767
+ mismatches: indexCounter(mapping, "mismatches", "validation"),
12768
+ failureReason: optionalBoundedText(
12769
+ mapping,
12770
+ "failure_reason",
12771
+ "FLOW.QUERY.INDEXES index validation",
12772
+ 128
12773
+ ),
12774
+ validatedAtMs: optionalIndexInteger(
12775
+ mapping,
12776
+ "validated_at_ms",
12777
+ "FLOW.QUERY.INDEXES index validation"
12778
+ )
12779
+ });
12780
+ }
12781
+ function decodeIndexRetirement(value) {
12782
+ const context = "FLOW.QUERY.INDEXES index retirement";
12783
+ const mapping = requiredMap2(value, context);
12784
+ const status = requiredChoice(mapping, "status", context, [
12785
+ "not_applicable",
12786
+ "pending",
12787
+ "complete"
12788
+ ]);
12789
+ if (status === "not_applicable") {
12790
+ return Object.freeze({ status, raw: freezeMap(mapping) });
12791
+ }
12792
+ const completedShards = unsignedIndexInteger(
12793
+ field(mapping, "completed_shards"),
12794
+ `${context} completed_shards`
12795
+ );
12796
+ const totalShards = positiveIndexInteger(
12797
+ field(mapping, "total_shards"),
12798
+ `${context} total_shards`
12799
+ );
12800
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12801
+ throw decodeError(
12802
+ `${context} completed_shards exceeds total_shards`,
12803
+ mapping
12804
+ );
12805
+ }
12806
+ return Object.freeze({
12807
+ status,
12808
+ phaseCounts: decodePhaseCounts(
12809
+ field(mapping, "phase_counts"),
12810
+ "retirement"
12811
+ ),
12812
+ currentPhases: decodeAllowedPhases(
12813
+ field(mapping, "current_phases"),
12814
+ "retirement",
12815
+ FLOW_QUERY_RETIREMENT_PHASES
12816
+ ),
12817
+ completedShards,
12818
+ totalShards,
12819
+ deletedEntries: indexCounter(mapping, "deleted_entries", "retirement"),
12820
+ deletedBytes: indexCounter(mapping, "deleted_bytes", "retirement"),
12821
+ rewrittenReverseRows: indexCounter(
12822
+ mapping,
12823
+ "rewritten_reverse_rows",
12824
+ "retirement"
12825
+ ),
12826
+ raw: freezeMap(mapping)
12827
+ });
12828
+ }
12829
+ function decodeIndexStatistics(value) {
12830
+ const context = "FLOW.QUERY.INDEXES index statistics";
12831
+ const mapping = requiredMap2(value, context);
12832
+ for (const name of [
12833
+ "oldest_collected_at_ms",
12834
+ "newest_collected_at_ms",
12835
+ "oldest_age_ms",
12836
+ "newest_age_ms"
12837
+ ]) {
12838
+ if (!hasKey(mapping, name)) {
12839
+ throw decodeError(
12840
+ `${context} is missing required nullable fields`,
12841
+ value
12842
+ );
12843
+ }
12844
+ }
12845
+ const samples = indexCounter(mapping, "samples", "statistics");
12846
+ const freshSamples = indexCounter(mapping, "fresh_samples", "statistics");
12847
+ const staleSamples = indexCounter(mapping, "stale_samples", "statistics");
12848
+ const futureSamples = indexCounter(mapping, "future_samples", "statistics");
12849
+ if (indexInteger2(freshSamples) + indexInteger2(staleSamples) !== indexInteger2(samples) || indexInteger2(futureSamples) > indexInteger2(staleSamples)) {
12850
+ throw decodeError(`${context} counters are inconsistent`, value);
12851
+ }
12852
+ return Object.freeze({
12853
+ status: requiredChoice(mapping, "status", context, [
12854
+ "fresh",
12855
+ "stale",
12856
+ "future",
12857
+ "mixed",
12858
+ "missing",
12859
+ "unavailable"
12860
+ ]),
12861
+ samples,
12862
+ freshSamples,
12863
+ staleSamples,
12864
+ futureSamples,
12865
+ oldestCollectedAtMs: optionalIndexInteger(
12866
+ mapping,
12867
+ "oldest_collected_at_ms",
12868
+ context
12869
+ ),
12870
+ newestCollectedAtMs: optionalIndexInteger(
12871
+ mapping,
12872
+ "newest_collected_at_ms",
12873
+ context
12874
+ ),
12875
+ oldestAgeMs: optionalIndexInteger(mapping, "oldest_age_ms", context),
12876
+ newestAgeMs: optionalIndexInteger(mapping, "newest_age_ms", context),
12877
+ raw: freezeMap(mapping)
12878
+ });
12879
+ }
12880
+ function decodeIndexProgress(mapping, section, allowedPhases) {
12881
+ const context = `FLOW.QUERY.INDEXES index ${section}`;
12882
+ const completedShards = unsignedIndexInteger(
12883
+ field(mapping, "completed_shards"),
12884
+ `${context} completed_shards`
12885
+ );
12886
+ const totalShards = positiveIndexInteger(
12887
+ field(mapping, "total_shards"),
12888
+ `${context} total_shards`
12889
+ );
12890
+ if (indexInteger2(completedShards) > indexInteger2(totalShards)) {
12891
+ throw decodeError(
12892
+ `${context} completed_shards exceeds total_shards`,
12893
+ mapping
12894
+ );
12895
+ }
12896
+ return Object.freeze({
12897
+ scope: requiredChoice(mapping, "scope", context, ["catalog_build"]),
12898
+ phaseCounts: decodePhaseCounts(field(mapping, "phase_counts"), section),
12899
+ currentPhases: decodeAllowedPhases(
12900
+ field(mapping, "current_phases"),
12901
+ section,
12902
+ allowedPhases
12903
+ ),
12904
+ completedShards,
12905
+ totalShards,
12906
+ raw: freezeMap(mapping)
12907
+ });
12908
+ }
12909
+ function decodePhaseCounts(value, section) {
12910
+ const context = `FLOW.QUERY.INDEXES index ${section} phase_counts`;
12911
+ const mapping = requiredMap2(value, context);
12912
+ const entries = mapping instanceof Map ? [...mapping.entries()] : Object.entries(mapping);
12913
+ if (entries.length > 16) {
12914
+ throw decodeError(`${context} contains too many entries`, value);
12915
+ }
12916
+ const result = /* @__PURE__ */ Object.create(null);
12917
+ for (const [rawPhase, rawCount] of entries) {
12918
+ const phase = boundedText(rawPhase, `${context} phase`, 64);
12919
+ if (Object.hasOwn(result, phase)) {
12920
+ throw decodeError(`${context} contains duplicate phases`, value);
12921
+ }
12922
+ result[phase] = unsignedIndexInteger(rawCount, `${context} ${phase}`);
12923
+ }
12924
+ return Object.freeze(result);
12925
+ }
12926
+ function decodeAllowedPhases(value, section, allowed) {
12927
+ const phases = decodeUniqueTextArray(
12928
+ value,
12929
+ `FLOW.QUERY.INDEXES index ${section} current_phases`,
12930
+ allowed.length,
12931
+ 64
12932
+ );
12933
+ if (phases.some((phase) => !allowed.includes(phase))) {
12934
+ throw decodeError(
12935
+ `FLOW.QUERY.INDEXES index ${section} current_phases are invalid`,
12936
+ value
12937
+ );
12938
+ }
12939
+ return phases;
12940
+ }
12941
+
12942
+ // src/flow-query-index-response.ts
12943
+ var FLOW_QUERY_INDEXES_CONTRACT = "ferric.flow.query.indexes/v1";
12944
+ function decodeFlowQueryIndexStatus(value, expectedId) {
12945
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES");
12946
+ requireContract(
12947
+ mapping,
12948
+ "contract_version",
12949
+ FLOW_QUERY_INDEXES_CONTRACT,
12950
+ "FLOW.QUERY.INDEXES"
12951
+ );
12952
+ const registry = requiredMap2(
12953
+ field(mapping, "registry"),
12954
+ "FLOW.QUERY.INDEXES registry"
12955
+ );
12956
+ const services = decodeIndexServices(field(mapping, "services"));
12302
12957
  const rawIndexes = field(mapping, "indexes");
12303
12958
  if (!Array.isArray(rawIndexes) || rawIndexes.length > 32) {
12304
- throw decodeError("FLOW.QUERY.INDEXES indexes must contain at most 32 entries", value);
12959
+ throw decodeError(
12960
+ "FLOW.QUERY.INDEXES indexes must contain at most 32 entries",
12961
+ value
12962
+ );
12305
12963
  }
12306
12964
  const indexes = new Array(rawIndexes.length);
12307
12965
  for (let index = 0; index < rawIndexes.length; index += 1) {
12308
12966
  if (!Object.hasOwn(rawIndexes, index)) {
12309
- throw decodeError("FLOW.QUERY.INDEXES indexes must be a dense array", value);
12967
+ throw decodeError(
12968
+ "FLOW.QUERY.INDEXES indexes must be a dense array",
12969
+ value
12970
+ );
12310
12971
  }
12311
12972
  indexes[index] = decodeIndex(rawIndexes[index], index);
12312
12973
  }
12313
- return Object.freeze({
12974
+ const status = Object.freeze({
12314
12975
  contractVersion: FLOW_QUERY_INDEXES_CONTRACT,
12315
- observedAtMs: nonNegativeInteger(
12976
+ observedAtMs: unsignedIndexInteger(
12316
12977
  field(mapping, "observed_at_ms"),
12317
12978
  "FLOW.QUERY.INDEXES observed_at_ms"
12318
12979
  ),
12319
- statisticsMaxAgeMs: nonNegativeInteger(
12980
+ statisticsMaxAgeMs: unsignedIndexInteger(
12320
12981
  field(mapping, "statistics_max_age_ms"),
12321
12982
  "FLOW.QUERY.INDEXES statistics_max_age_ms"
12322
12983
  ),
12323
12984
  registry: Object.freeze({
12324
- epoch: boundedInteger(
12985
+ epoch: unsignedIndexInteger(
12325
12986
  field(registry, "epoch"),
12326
- MAX_UNSIGNED_64,
12327
12987
  "FLOW.QUERY.INDEXES epoch"
12328
12988
  ),
12329
- catalogVersion: positiveBoundedInteger(
12989
+ catalogVersion: positiveIndexInteger(
12330
12990
  field(registry, "catalog_version"),
12331
- MAX_UNSIGNED_64,
12332
12991
  "FLOW.QUERY.INDEXES catalog_version"
12333
12992
  )
12334
12993
  }),
@@ -12336,24 +12995,155 @@ function decodeFlowQueryIndexStatus(value) {
12336
12995
  indexes: Object.freeze(indexes),
12337
12996
  raw: freezeMap(mapping)
12338
12997
  });
12998
+ validateFlowQueryIndexContract(status, expectedId);
12999
+ return status;
12339
13000
  }
12340
13001
  function decodeIndex(value, index) {
12341
13002
  const mapping = requiredMap2(value, `FLOW.QUERY.INDEXES index ${index}`);
13003
+ const fields = decodeIndexFields(field(mapping, "fields"));
12342
13004
  return Object.freeze({
12343
- id: requiredText2(mapping, "id", "FLOW.QUERY.INDEXES index"),
12344
- version: positiveBoundedInteger(
13005
+ id: requiredBoundedText2(mapping, "id", "FLOW.QUERY.INDEXES index", 64),
13006
+ version: positiveIndexInteger(
12345
13007
  field(mapping, "version"),
12346
- MAX_UNSIGNED_64,
12347
13008
  "FLOW.QUERY.INDEXES index version"
12348
13009
  ),
12349
- buildId: requiredText2(mapping, "build_id", "FLOW.QUERY.INDEXES index"),
12350
- state: requiredText2(mapping, "state", "FLOW.QUERY.INDEXES index"),
12351
- queryable: requiredBoolean(mapping, "queryable", "FLOW.QUERY.INDEXES index"),
13010
+ buildId: requiredBoundedText2(
13011
+ mapping,
13012
+ "build_id",
13013
+ "FLOW.QUERY.INDEXES index",
13014
+ 128
13015
+ ),
13016
+ source: requiredChoice(mapping, "source", "FLOW.QUERY.INDEXES index", [
13017
+ "runs"
13018
+ ]),
13019
+ state: requiredChoice(mapping, "state", "FLOW.QUERY.INDEXES index", [
13020
+ "building",
13021
+ "validating",
13022
+ "active",
13023
+ "retiring",
13024
+ "failed"
13025
+ ]),
13026
+ queryable: requiredBoolean(
13027
+ mapping,
13028
+ "queryable",
13029
+ "FLOW.QUERY.INDEXES index"
13030
+ ),
13031
+ fields,
13032
+ workloads: decodeUniqueTextArray(
13033
+ field(mapping, "workloads"),
13034
+ "FLOW.QUERY.INDEXES index workloads",
13035
+ 16,
13036
+ 64
13037
+ ),
13038
+ countPrefixes: decodeCountPrefixes(
13039
+ field(mapping, "count_prefixes"),
13040
+ fields.length
13041
+ ),
12352
13042
  coveringFields: decodeCoveringFields(mapping),
12353
13043
  format: decodeIndexFormat(mapping),
13044
+ coverage: decodeIndexCoverage(field(mapping, "coverage")),
13045
+ build: decodeIndexBuild(field(mapping, "build")),
13046
+ validation: decodeIndexValidation(field(mapping, "validation")),
13047
+ retirement: decodeIndexRetirement(field(mapping, "retirement")),
13048
+ statistics: decodeIndexStatistics(field(mapping, "statistics")),
12354
13049
  raw: freezeMap(mapping)
12355
13050
  });
12356
13051
  }
13052
+ function decodeIndexServices(value) {
13053
+ const mapping = requiredMap2(value, "FLOW.QUERY.INDEXES services");
13054
+ const choices = ["ready", "unavailable"];
13055
+ return Object.freeze({
13056
+ registry: requiredChoice(
13057
+ mapping,
13058
+ "registry",
13059
+ "FLOW.QUERY.INDEXES services",
13060
+ choices
13061
+ ),
13062
+ lifecycleWorker: requiredChoice(
13063
+ mapping,
13064
+ "lifecycle_worker",
13065
+ "FLOW.QUERY.INDEXES services",
13066
+ choices
13067
+ ),
13068
+ statisticsStore: requiredChoice(
13069
+ mapping,
13070
+ "statistics_store",
13071
+ "FLOW.QUERY.INDEXES services",
13072
+ choices
13073
+ ),
13074
+ statisticsWorker: requiredChoice(
13075
+ mapping,
13076
+ "statistics_worker",
13077
+ "FLOW.QUERY.INDEXES services",
13078
+ choices
13079
+ ),
13080
+ raw: freezeMap(mapping)
13081
+ });
13082
+ }
13083
+ function decodeIndexFields(value) {
13084
+ const context = "FLOW.QUERY.INDEXES index fields";
13085
+ if (!Array.isArray(value) || value.length < 2 || value.length > 8) {
13086
+ throw decodeError(`${context} must contain 2 to 8 entries`, value);
13087
+ }
13088
+ const fields = new Array(value.length);
13089
+ const names = /* @__PURE__ */ new Set();
13090
+ for (let index = 0; index < value.length; index += 1) {
13091
+ if (!Object.hasOwn(value, index)) {
13092
+ throw decodeError(`${context} must be dense`, value);
13093
+ }
13094
+ const mapping = requiredMap2(
13095
+ value[index],
13096
+ `FLOW.QUERY.INDEXES index field ${index}`
13097
+ );
13098
+ const name = requiredBoundedText2(
13099
+ mapping,
13100
+ "name",
13101
+ "FLOW.QUERY.INDEXES index field",
13102
+ 512
13103
+ );
13104
+ if (names.has(name)) {
13105
+ throw decodeError(`${context} contain duplicates`, value);
13106
+ }
13107
+ names.add(name);
13108
+ fields[index] = Object.freeze({
13109
+ name,
13110
+ direction: requiredChoice(
13111
+ mapping,
13112
+ "direction",
13113
+ "FLOW.QUERY.INDEXES index field",
13114
+ ["asc", "desc"]
13115
+ ),
13116
+ encoding: requiredChoice(
13117
+ mapping,
13118
+ "encoding",
13119
+ "FLOW.QUERY.INDEXES index field",
13120
+ ["hashed", "ordered"]
13121
+ ),
13122
+ raw: freezeMap(mapping)
13123
+ });
13124
+ }
13125
+ return Object.freeze(fields);
13126
+ }
13127
+ function decodeCountPrefixes(value, fieldCount) {
13128
+ const context = "FLOW.QUERY.INDEXES index count_prefixes";
13129
+ if (!Array.isArray(value) || value.length > fieldCount) {
13130
+ throw decodeError(`${context} are invalid`, value);
13131
+ }
13132
+ const prefixes = new Array(value.length);
13133
+ let previous = 0;
13134
+ for (let index = 0; index < value.length; index += 1) {
13135
+ if (!Object.hasOwn(value, index)) {
13136
+ throw decodeError(`${context} must be dense`, value);
13137
+ }
13138
+ const prefix = positiveInteger(value[index], `${context} entry`);
13139
+ if (prefix > fieldCount || prefix <= previous) {
13140
+ throw decodeError(`${context} are invalid`, value);
13141
+ }
13142
+ prefixes[index] = prefix;
13143
+ previous = prefix;
13144
+ }
13145
+ return Object.freeze(prefixes);
13146
+ }
12357
13147
  function decodeCoveringFields(mapping) {
12358
13148
  const raw = field(mapping, "covering_fields");
12359
13149
  if (!Array.isArray(raw) || raw.length > 32) {
@@ -12366,7 +13156,10 @@ function decodeCoveringFields(mapping) {
12366
13156
  const seen = /* @__PURE__ */ new Set();
12367
13157
  for (let index = 0; index < raw.length; index += 1) {
12368
13158
  if (!Object.hasOwn(raw, index)) {
12369
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields must be dense", raw);
13159
+ throw decodeError(
13160
+ "FLOW.QUERY.INDEXES index covering_fields must be dense",
13161
+ raw
13162
+ );
12370
13163
  }
12371
13164
  const value = boundedText(
12372
13165
  raw[index],
@@ -12374,7 +13167,10 @@ function decodeCoveringFields(mapping) {
12374
13167
  512
12375
13168
  );
12376
13169
  if (seen.has(value)) {
12377
- throw decodeError("FLOW.QUERY.INDEXES index covering_fields contains duplicates", raw);
13170
+ throw decodeError(
13171
+ "FLOW.QUERY.INDEXES index covering_fields contains duplicates",
13172
+ raw
13173
+ );
12378
13174
  }
12379
13175
  seen.add(value);
12380
13176
  fields[index] = value;
@@ -12382,30 +13178,341 @@ function decodeCoveringFields(mapping) {
12382
13178
  return Object.freeze(fields);
12383
13179
  }
12384
13180
  function decodeIndexFormat(mapping) {
12385
- const raw = requiredMap2(field(mapping, "format"), "FLOW.QUERY.INDEXES index format");
13181
+ const raw = requiredMap2(
13182
+ field(mapping, "format"),
13183
+ "FLOW.QUERY.INDEXES index format"
13184
+ );
12386
13185
  if (!hasKey(raw, "counter")) {
12387
- throw decodeError("FLOW.QUERY.INDEXES index format counter is missing", raw);
13186
+ throw decodeError(
13187
+ "FLOW.QUERY.INDEXES index format counter is missing",
13188
+ raw
13189
+ );
12388
13190
  }
12389
13191
  const counterValue = field(raw, "counter");
12390
- const counter = counterValue === null ? void 0 : boundedText(counterValue, "FLOW.QUERY.INDEXES index format counter", 128);
13192
+ const counter = counterValue === null ? void 0 : boundedText(
13193
+ counterValue,
13194
+ "FLOW.QUERY.INDEXES index format counter",
13195
+ 128
13196
+ );
12391
13197
  return Object.freeze({
12392
- queryRow: requiredBoundedText2(raw, "query_row", "FLOW.QUERY.INDEXES index format", 128),
12393
- key: requiredBoundedText2(raw, "key", "FLOW.QUERY.INDEXES index format", 128),
12394
- entry: requiredBoundedText2(raw, "entry", "FLOW.QUERY.INDEXES index format", 128),
12395
- reverse: requiredBoundedText2(raw, "reverse", "FLOW.QUERY.INDEXES index format", 128),
13198
+ queryRow: requiredBoundedText2(
13199
+ raw,
13200
+ "query_row",
13201
+ "FLOW.QUERY.INDEXES index format",
13202
+ 128
13203
+ ),
13204
+ key: requiredBoundedText2(
13205
+ raw,
13206
+ "key",
13207
+ "FLOW.QUERY.INDEXES index format",
13208
+ 128
13209
+ ),
13210
+ entry: requiredBoundedText2(
13211
+ raw,
13212
+ "entry",
13213
+ "FLOW.QUERY.INDEXES index format",
13214
+ 128
13215
+ ),
13216
+ reverse: requiredBoundedText2(
13217
+ raw,
13218
+ "reverse",
13219
+ "FLOW.QUERY.INDEXES index format",
13220
+ 128
13221
+ ),
12396
13222
  counter,
12397
13223
  raw: freezeMap(raw)
12398
13224
  });
12399
13225
  }
13226
+
13227
+ // src/flow-query-response.ts
13228
+ var FLOW_QUERY_RESULT_CONTRACT = "ferric.flow.query.result/v1";
13229
+ var FLOW_EXPLAIN_CONTRACT = "ferric.flow.explain/v1";
13230
+ var USAGE_FIELDS2 = [
13231
+ ["range_seeks", "rangeSeeks"],
13232
+ ["range_pages", "rangePages"],
13233
+ ["scanned_entries", "scannedEntries"],
13234
+ ["scanned_bytes", "scannedBytes"],
13235
+ ["hydrated_records", "hydratedRecords"],
13236
+ ["residual_checks", "residualChecks"],
13237
+ ["duplicate_entries", "duplicateEntries"],
13238
+ ["result_records", "resultRecords"],
13239
+ ["response_bytes", "responseBytes"],
13240
+ ["memory_high_water_bytes", "memoryHighWaterBytes"],
13241
+ ["wall_time_us", "wallTimeUs"]
13242
+ ];
13243
+ var MAX_SIGNED_643 = (1n << 63n) - 1n;
13244
+ var QUALITY_VALUES2 = {
13245
+ exactness: ["authoritative", "projected_exact", "exact", "not_applicable"],
13246
+ freshness: ["current", "projection_watermark", "not_applicable"],
13247
+ coverage: ["complete", "unavailable"],
13248
+ pagination: ["none", "complete", "authenticated_seek", "live_seek"]
13249
+ };
13250
+ function decodeFlowQueryResult(value) {
13251
+ const result = decodeFlowQueryResponse(value, (mapping) => freezeMap(mapping));
13252
+ if (result.kind === "records") {
13253
+ return Object.freeze({
13254
+ kind: "records",
13255
+ version: FLOW_QUERY_RESULT_CONTRACT,
13256
+ records: Object.freeze(result.records),
13257
+ page: result.page,
13258
+ quality: result.quality,
13259
+ usage: result.usage,
13260
+ raw: result.raw
13261
+ });
13262
+ }
13263
+ return Object.freeze({
13264
+ kind: "count",
13265
+ version: FLOW_QUERY_RESULT_CONTRACT,
13266
+ count: result.count,
13267
+ quality: result.quality,
13268
+ usage: result.usage,
13269
+ raw: result.raw
13270
+ });
13271
+ }
13272
+ function decodeFlowQueryRecords(value, decodeRecord) {
13273
+ const result = decodeFlowQueryResponse(
13274
+ value,
13275
+ (mapping, index) => decodeRecord(mapping instanceof Map ? freezeMap(mapping) : mapping, index)
13276
+ );
13277
+ if (result.kind === "count") {
13278
+ throw new FerricStoreError("Flow record convenience query returned a count result", {
13279
+ raw: result.raw
13280
+ });
13281
+ }
13282
+ return result.records;
13283
+ }
13284
+ function decodeFlowQueryResponse(value, decodeRecord) {
13285
+ const mapping = requiredMap2(value, "FLOW.QUERY result");
13286
+ requireContract(mapping, "version", FLOW_QUERY_RESULT_CONTRACT, "FLOW.QUERY result");
13287
+ const quality = decodeQuality(field(mapping, "quality"));
13288
+ const usage = decodeUsage(field(mapping, "usage"));
13289
+ const hasRecords = hasKey(mapping, "records");
13290
+ const hasCount = hasKey(mapping, "result");
13291
+ if (hasRecords === hasCount) {
13292
+ throw decodeError("FLOW.QUERY result must contain exactly one records or count shape", value);
13293
+ }
13294
+ const raw = freezeMap(mapping);
13295
+ if (hasRecords) {
13296
+ const rawRecords = field(mapping, "records");
13297
+ if (!Array.isArray(rawRecords) || rawRecords.length > 100) {
13298
+ throw decodeError("FLOW.QUERY records must be an array of at most 100 maps", value);
13299
+ }
13300
+ const records = new Array(rawRecords.length);
13301
+ for (let index = 0; index < rawRecords.length; index += 1) {
13302
+ if (!Object.hasOwn(rawRecords, index)) {
13303
+ throw decodeError("FLOW.QUERY records must be a dense array", value);
13304
+ }
13305
+ records[index] = decodeRecord(
13306
+ requiredMap2(rawRecords[index], `FLOW.QUERY record ${index}`),
13307
+ index
13308
+ );
13309
+ }
13310
+ if (usage.resultRecords !== records.length || usage.resultRecords > usage.scannedEntries) {
13311
+ throw decodeError("FLOW.QUERY usage result_records is inconsistent with records", value);
13312
+ }
13313
+ return {
13314
+ kind: "records",
13315
+ records,
13316
+ page: decodePage(field(mapping, "page")),
13317
+ quality,
13318
+ usage,
13319
+ raw
13320
+ };
13321
+ }
13322
+ if (hasKey(mapping, "page")) {
13323
+ throw decodeError("FLOW.QUERY count result contains an unexpected page", value);
13324
+ }
13325
+ const countResult = requiredMap2(field(mapping, "result"), "FLOW.QUERY count result");
13326
+ if (requiredText2(countResult, "kind", "FLOW.QUERY count result") !== "count") {
13327
+ throw decodeError("FLOW.QUERY count result kind must be count", value);
13328
+ }
13329
+ const count = boundedInteger(
13330
+ field(countResult, "value"),
13331
+ MAX_SIGNED_643,
13332
+ "FLOW.QUERY count value"
13333
+ );
13334
+ if (usage.resultRecords !== 1) {
13335
+ throw decodeError("FLOW.QUERY count usage result_records must be 1", value);
13336
+ }
13337
+ return {
13338
+ kind: "count",
13339
+ count,
13340
+ quality,
13341
+ usage,
13342
+ raw
13343
+ };
13344
+ }
13345
+ function decodeFlowExplainResult(value) {
13346
+ const mapping = requiredMap2(value, "FLOW.QUERY explain");
13347
+ requireContract(mapping, "version", FLOW_EXPLAIN_CONTRACT, "FLOW.QUERY explain");
13348
+ const queryFingerprint = requiredText2(mapping, "query_fingerprint", "FLOW.QUERY explain");
13349
+ if (!/^[0-9a-f]{64}$/iu.test(queryFingerprint)) {
13350
+ throw decodeError("FLOW.QUERY explain query_fingerprint is invalid", value);
13351
+ }
13352
+ const status = requiredText2(mapping, "status", "FLOW.QUERY explain");
13353
+ if (status !== "planned" && status !== "rejected" && status !== "executed") {
13354
+ throw decodeError(`FLOW.QUERY explain status ${JSON.stringify(status)} is unsupported`, value);
13355
+ }
13356
+ const plan = freezeMetadataMap(
13357
+ requiredMap2(field(mapping, "plan"), "FLOW.QUERY explain plan"),
13358
+ "FLOW.QUERY explain plan"
13359
+ );
13360
+ const estimate = freezeMetadataMap(
13361
+ requiredMap2(field(mapping, "estimate"), "FLOW.QUERY explain estimate"),
13362
+ "FLOW.QUERY explain estimate"
13363
+ );
13364
+ const bounds = freezeMetadataMap(
13365
+ requiredMap2(field(mapping, "bounds"), "FLOW.QUERY explain bounds"),
13366
+ "FLOW.QUERY explain bounds"
13367
+ );
13368
+ const capabilities = decodeExplainCapabilities(mapping);
13369
+ const extendedFields = ["stats", "quality", "pressure", "decision", "alternatives"];
13370
+ const extendedPresence = extendedFields.map((name) => hasKey(mapping, name));
13371
+ const specialized = capabilities != null && !extendedPresence.some(Boolean);
13372
+ let stats;
13373
+ let quality;
13374
+ let pressure;
13375
+ let decision;
13376
+ let alternatives;
13377
+ if (specialized) {
13378
+ if (status !== "planned") {
13379
+ throw decodeError("FLOW.QUERY specialized explain must be planned", value);
13380
+ }
13381
+ if (hasKey(mapping, "actual") || hasKey(mapping, "diagnostic")) {
13382
+ throw decodeError("FLOW.QUERY specialized explain has extended status fields", value);
13383
+ }
13384
+ alternatives = Object.freeze([]);
13385
+ } else {
13386
+ if (!extendedPresence.every(Boolean) || !hasKey(mapping, "actual") || !hasKey(mapping, "diagnostic")) {
13387
+ throw decodeError("FLOW.QUERY explain is missing required v1 fields", value);
13388
+ }
13389
+ stats = freezeMetadataMap(
13390
+ requiredMap2(field(mapping, "stats"), "FLOW.QUERY explain stats"),
13391
+ "FLOW.QUERY explain stats"
13392
+ );
13393
+ quality = decodeQuality(field(mapping, "quality"));
13394
+ pressure = freezeMetadataMap(
13395
+ requiredMap2(field(mapping, "pressure"), "FLOW.QUERY explain pressure"),
13396
+ "FLOW.QUERY explain pressure"
13397
+ );
13398
+ decision = freezeMetadataMap(
13399
+ requiredMap2(field(mapping, "decision"), "FLOW.QUERY explain decision"),
13400
+ "FLOW.QUERY explain decision"
13401
+ );
13402
+ alternatives = decodeExplainAlternatives(field(mapping, "alternatives"));
13403
+ }
13404
+ const actualValue = field(mapping, "actual");
13405
+ let actual;
13406
+ if (status === "executed") {
13407
+ if (actualValue == null) {
13408
+ throw decodeError("FLOW.QUERY executed explain is missing actual usage", value);
13409
+ }
13410
+ actual = decodeUsage(actualValue);
13411
+ } else if (actualValue != null) {
13412
+ throw decodeError("FLOW.QUERY non-executed explain contains actual usage", value);
13413
+ }
13414
+ const diagnosticValue = field(mapping, "diagnostic");
13415
+ let diagnostic;
13416
+ if (status === "rejected") {
13417
+ diagnostic = tryDecodeFlowQueryError(diagnosticValue);
13418
+ if (diagnostic == null) {
13419
+ throw decodeError("FLOW.QUERY rejected explain has an invalid diagnostic", value);
13420
+ }
13421
+ } else if (diagnosticValue != null) {
13422
+ throw decodeError("FLOW.QUERY non-rejected explain contains a diagnostic", value);
13423
+ }
13424
+ return Object.freeze({
13425
+ version: FLOW_EXPLAIN_CONTRACT,
13426
+ queryFingerprint,
13427
+ status,
13428
+ plan,
13429
+ estimate,
13430
+ stats,
13431
+ quality,
13432
+ bounds,
13433
+ pressure,
13434
+ decision,
13435
+ alternatives,
13436
+ capabilities,
13437
+ actual,
13438
+ diagnostic,
13439
+ raw: freezeMap(mapping)
13440
+ });
13441
+ }
13442
+ function decodeExplainCapabilities(mapping) {
13443
+ if (!hasKey(mapping, "capabilities")) return void 0;
13444
+ const value = requiredMap2(
13445
+ field(mapping, "capabilities"),
13446
+ "FLOW.QUERY explain capabilities"
13447
+ );
13448
+ return Object.freeze({
13449
+ requested: decodeExplainCapabilityList(field(value, "requested"), "requested"),
13450
+ available: decodeExplainCapabilityList(field(value, "available"), "available"),
13451
+ missing: decodeExplainCapabilityList(field(value, "missing"), "missing"),
13452
+ raw: freezeMetadataMap(value, "FLOW.QUERY explain capabilities")
13453
+ });
13454
+ }
13455
+ function decodeExplainCapabilityList(value, name) {
13456
+ if (!Array.isArray(value) || value.length > 64) {
13457
+ throw decodeError(
13458
+ `FLOW.QUERY explain capabilities ${name} must contain at most 64 entries`,
13459
+ value
13460
+ );
13461
+ }
13462
+ const result = new Array(value.length);
13463
+ const seen = /* @__PURE__ */ new Set();
13464
+ for (let index = 0; index < value.length; index += 1) {
13465
+ if (!Object.hasOwn(value, index)) {
13466
+ throw decodeError(`FLOW.QUERY explain capabilities ${name} must be dense`, value);
13467
+ }
13468
+ const capability = boundedText(
13469
+ value[index],
13470
+ `FLOW.QUERY explain capabilities ${name} entry ${index}`,
13471
+ 128
13472
+ );
13473
+ if (seen.has(capability)) {
13474
+ throw decodeError(
13475
+ `FLOW.QUERY explain capabilities ${name} contains duplicates`,
13476
+ value
13477
+ );
13478
+ }
13479
+ seen.add(capability);
13480
+ result[index] = capability;
13481
+ }
13482
+ return Object.freeze(result);
13483
+ }
13484
+ function decodeExplainAlternatives(value) {
13485
+ if (!Array.isArray(value) || value.length > 31) {
13486
+ throw decodeError("FLOW.QUERY explain alternatives must be an array of at most 31 maps", value);
13487
+ }
13488
+ const alternatives = new Array(value.length);
13489
+ for (let index = 0; index < value.length; index += 1) {
13490
+ if (!Object.hasOwn(value, index)) {
13491
+ throw decodeError("FLOW.QUERY explain alternatives must be dense", value);
13492
+ }
13493
+ alternatives[index] = freezeMetadataMap(
13494
+ requiredMap2(value[index], `FLOW.QUERY explain alternative ${index}`),
13495
+ `FLOW.QUERY explain alternative ${index}`
13496
+ );
13497
+ }
13498
+ return Object.freeze(alternatives);
13499
+ }
12400
13500
  function decodeQuality(value) {
12401
13501
  const mapping = requiredMap2(value, "FLOW.QUERY quality");
12402
13502
  return Object.freeze({
12403
- exactness: requiredBoundedText2(mapping, "exactness", "FLOW.QUERY quality", 64),
12404
- freshness: requiredBoundedText2(mapping, "freshness", "FLOW.QUERY quality", 64),
12405
- coverage: requiredBoundedText2(mapping, "coverage", "FLOW.QUERY quality", 64),
12406
- pagination: requiredBoundedText2(mapping, "pagination", "FLOW.QUERY quality", 64)
13503
+ exactness: decodeQualityValue(mapping, "exactness", QUALITY_VALUES2.exactness),
13504
+ freshness: decodeQualityValue(mapping, "freshness", QUALITY_VALUES2.freshness),
13505
+ coverage: decodeQualityValue(mapping, "coverage", QUALITY_VALUES2.coverage),
13506
+ pagination: decodeQualityValue(mapping, "pagination", QUALITY_VALUES2.pagination)
12407
13507
  });
12408
13508
  }
13509
+ function decodeQualityValue(mapping, name, allowed) {
13510
+ const value = requiredBoundedText2(mapping, name, "FLOW.QUERY quality", 64);
13511
+ if (!allowed.includes(value)) {
13512
+ throw decodeError(`FLOW.QUERY quality ${name} is unsupported`, mapping);
13513
+ }
13514
+ return value;
13515
+ }
12409
13516
  function decodeUsage(value) {
12410
13517
  const mapping = requiredMap2(value, "FLOW.QUERY usage");
12411
13518
  const usage = /* @__PURE__ */ Object.create(null);
@@ -12415,13 +13522,17 @@ function decodeUsage(value) {
12415
13522
  `FLOW.QUERY usage ${wireName}`
12416
13523
  );
12417
13524
  }
12418
- return Object.freeze(usage);
13525
+ const decoded = usage;
13526
+ if (decoded.hydratedRecords > decoded.scannedEntries || decoded.duplicateEntries > decoded.scannedEntries || decoded.rangePages > decoded.scannedEntries + decoded.rangeSeeks || decoded.residualChecks > decoded.scannedEntries * 12) {
13527
+ throw decodeError("FLOW.QUERY usage counters are inconsistent", value);
13528
+ }
13529
+ return Object.freeze(decoded);
12419
13530
  }
12420
13531
  function decodePage(value) {
12421
13532
  const mapping = requiredMap2(value, "FLOW.QUERY page");
12422
13533
  const hasMore = requiredBoolean(mapping, "has_more", "FLOW.QUERY page");
12423
13534
  const cursor = optionalText2(mapping, "cursor", "FLOW.QUERY page");
12424
- if (cursor != null && (!cursor.startsWith("fqc1_") || import_node_buffer45.Buffer.byteLength(cursor) > 4096)) {
13535
+ if (cursor != null && (!cursor.startsWith("fqc1_") || import_node_buffer48.Buffer.byteLength(cursor) < 16 || import_node_buffer48.Buffer.byteLength(cursor) > 4096)) {
12425
13536
  throw decodeError("FLOW.QUERY page cursor is invalid", value);
12426
13537
  }
12427
13538
  if (hasMore !== (cursor != null)) {
@@ -12429,26 +13540,14 @@ function decodePage(value) {
12429
13540
  }
12430
13541
  return Object.freeze({ hasMore, ...cursor == null ? {} : { cursor } });
12431
13542
  }
12432
- function decodePosition(value) {
12433
- if (value == null) return void 0;
12434
- const mapping = requiredMap2(value, "FLOW.QUERY diagnostic position");
12435
- return Object.freeze({
12436
- byte: positiveInteger(field(mapping, "byte"), "FLOW.QUERY diagnostic position byte"),
12437
- line: positiveInteger(field(mapping, "line"), "FLOW.QUERY diagnostic position line"),
12438
- column: positiveInteger(
12439
- field(mapping, "column"),
12440
- "FLOW.QUERY diagnostic position column"
12441
- )
12442
- });
12443
- }
12444
13543
 
12445
13544
  // src/client-core.ts
12446
- var import_node_buffer50 = require("buffer");
13545
+ var import_node_buffer53 = require("buffer");
12447
13546
 
12448
13547
  // src/client-core-helpers.ts
12449
- var import_node_buffer46 = require("buffer");
13548
+ var import_node_buffer49 = require("buffer");
12450
13549
  function bgsaveResponse(response) {
12451
- if ((typeof response === "string" || import_node_buffer46.Buffer.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
13550
+ if ((typeof response === "string" || import_node_buffer49.Buffer.isBuffer(response) || response instanceof Uint8Array) && text(response) === "Background saving started") {
12452
13551
  return true;
12453
13552
  }
12454
13553
  return okResponse(response);
@@ -12462,7 +13561,7 @@ function fetchOrComputeCompletionToken(options) {
12462
13561
  "fetch-or-compute completion requires computeToken"
12463
13562
  );
12464
13563
  }
12465
- if (!import_node_buffer46.Buffer.isBuffer(options.computeToken)) {
13564
+ if (!import_node_buffer49.Buffer.isBuffer(options.computeToken)) {
12466
13565
  throw new TypeError("fetch-or-compute computeToken must be a Buffer");
12467
13566
  }
12468
13567
  return options.computeToken;
@@ -12485,10 +13584,10 @@ function unsupportedClientCaching() {
12485
13584
  }
12486
13585
 
12487
13586
  // src/client-base.ts
12488
- var import_node_buffer48 = require("buffer");
13587
+ var import_node_buffer51 = require("buffer");
12489
13588
 
12490
13589
  // src/client-executor.ts
12491
- var import_node_buffer47 = require("buffer");
13590
+ var import_node_buffer50 = require("buffer");
12492
13591
  var ErrorMappingExecutor = class {
12493
13592
  constructor(executor) {
12494
13593
  this.executor = executor;
@@ -12822,14 +13921,14 @@ var FerricStoreAdministrationClient = class extends FerricStoreClientBase {
12822
13921
  };
12823
13922
 
12824
13923
  // src/store-utilities.ts
12825
- var import_node_buffer49 = require("buffer");
13924
+ var import_node_buffer52 = require("buffer");
12826
13925
  function encode(codec, value) {
12827
13926
  return codec.encode(value);
12828
13927
  }
12829
13928
  function decode(codec, value) {
12830
13929
  if (value == null) return null;
12831
- if (import_node_buffer49.Buffer.isBuffer(value)) return codec.decode(value);
12832
- if (value instanceof Uint8Array) return codec.decode(import_node_buffer49.Buffer.from(value));
13930
+ if (import_node_buffer52.Buffer.isBuffer(value)) return codec.decode(value);
13931
+ if (value instanceof Uint8Array) return codec.decode(import_node_buffer52.Buffer.from(value));
12833
13932
  return value;
12834
13933
  }
12835
13934
  function number(value) {
@@ -15064,7 +16163,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
15064
16163
  args.push(indexId);
15065
16164
  }
15066
16165
  try {
15067
- return decodeFlowQueryIndexStatus(await this.commandArgs(args));
16166
+ return decodeFlowQueryIndexStatus(await this.commandArgs(args), indexId);
15068
16167
  } catch (error) {
15069
16168
  this.throwFlowQueryError(error);
15070
16169
  }
@@ -15126,7 +16225,7 @@ var FerricStoreFlowQueryClient = class extends FerricStoreManagementClient {
15126
16225
  if (hasFlowExplainPrefix(query)) {
15127
16226
  throw new TypeError("query already contains an EXPLAIN prefix");
15128
16227
  }
15129
- return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query.trim(), params));
16228
+ return decodeFlowExplainResult(await this.executeFlowQuery(prefix + query, params));
15130
16229
  }
15131
16230
  throwFlowQueryError(error) {
15132
16231
  if (error instanceof FerricStoreError) {
@@ -15159,7 +16258,7 @@ function throwIfClosed(signal) {
15159
16258
  }
15160
16259
 
15161
16260
  // src/flow-policy.ts
15162
- var import_node_buffer51 = require("buffer");
16261
+ var import_node_buffer54 = require("buffer");
15163
16262
  var MAX_FLOW_POLICY_GENERATION = Number.MAX_SAFE_INTEGER;
15164
16263
  function assertFlowPolicyGeneration(value) {
15165
16264
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
@@ -15249,7 +16348,7 @@ function requiredRecord(value, context) {
15249
16348
  }
15250
16349
  return result;
15251
16350
  }
15252
- if (typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer51.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) return value;
16351
+ if (typeof value === "object" && value != null && !Array.isArray(value) && !import_node_buffer54.Buffer.isBuffer(value) && !(value instanceof Uint8Array)) return value;
15253
16352
  throw new TypeError(`${context} returned an unexpected response`);
15254
16353
  }
15255
16354
  function optionalRecord(value, context) {
@@ -15270,8 +16369,8 @@ function stringArray(value, context) {
15270
16369
  }
15271
16370
  function requiredText3(value, context) {
15272
16371
  if (typeof value === "string") return value;
15273
- if (import_node_buffer51.Buffer.isBuffer(value) || value instanceof Uint8Array) {
15274
- return import_node_buffer51.Buffer.from(value).toString("utf8");
16372
+ if (import_node_buffer54.Buffer.isBuffer(value) || value instanceof Uint8Array) {
16373
+ return import_node_buffer54.Buffer.from(value).toString("utf8");
15275
16374
  }
15276
16375
  throw new TypeError(`FLOW policy ${context} returned an unexpected response`);
15277
16376
  }
@@ -15527,7 +16626,7 @@ var FerricStoreFlowSupportClient = class extends FerricStoreFlowQueryClient {
15527
16626
  };
15528
16627
 
15529
16628
  // src/flow-many-snapshot.ts
15530
- var import_node_buffer53 = require("buffer");
16629
+ var import_node_buffer56 = require("buffer");
15531
16630
  var encodedOptionFields = ["error", "payload", "reason", "result"];
15532
16631
  function snapshotCreateItem(item, codec) {
15533
16632
  const snapshot = {
@@ -15567,7 +16666,7 @@ function snapshotFlowManyOptions(options, codec) {
15567
16666
  }
15568
16667
  function snapshotClaimedItem(item) {
15569
16668
  const wire = item[CLAIMED_ITEM_WIRE];
15570
- const leaseToken = import_node_buffer53.Buffer.from(wire?.leaseToken ?? item.leaseToken);
16669
+ const leaseToken = import_node_buffer56.Buffer.from(wire?.leaseToken ?? item.leaseToken);
15571
16670
  const snapshot = {
15572
16671
  ...item,
15573
16672
  fencingToken: wire?.fencingToken ?? item.fencingToken,
@@ -15584,15 +16683,15 @@ function snapshotClaimedItem(item) {
15584
16683
  function snapshotFencedItem(item) {
15585
16684
  return Object.freeze({
15586
16685
  ...item,
15587
- ...item.leaseToken == null ? {} : { leaseToken: import_node_buffer53.Buffer.from(item.leaseToken) }
16686
+ ...item.leaseToken == null ? {} : { leaseToken: import_node_buffer56.Buffer.from(item.leaseToken) }
15588
16687
  });
15589
16688
  }
15590
16689
  function snapshotClaimedItemWire(wire, leaseToken) {
15591
16690
  return Object.freeze({
15592
16691
  fencingToken: wire.fencingToken,
15593
- id: import_node_buffer53.Buffer.from(wire.id),
16692
+ id: import_node_buffer56.Buffer.from(wire.id),
15594
16693
  leaseToken,
15595
- partitionKey: wire.partitionKey == null ? wire.partitionKey : import_node_buffer53.Buffer.from(wire.partitionKey)
16694
+ partitionKey: wire.partitionKey == null ? wire.partitionKey : import_node_buffer56.Buffer.from(wire.partitionKey)
15596
16695
  });
15597
16696
  }
15598
16697
  function snapshotArray(values) {
@@ -15603,7 +16702,7 @@ function snapshotArray(values) {
15603
16702
  return Object.freeze(snapshot);
15604
16703
  }
15605
16704
  function snapshotStateMeta(stateMeta) {
15606
- return snapshotRecord(stateMeta, (value) => import_node_buffer53.Buffer.isBuffer(value) ? import_node_buffer53.Buffer.from(value) : value);
16705
+ return snapshotRecord(stateMeta, (value) => import_node_buffer56.Buffer.isBuffer(value) ? import_node_buffer56.Buffer.from(value) : value);
15607
16706
  }
15608
16707
  function snapshotCommandRecord(values) {
15609
16708
  const seen = /* @__PURE__ */ new WeakMap();
@@ -15621,7 +16720,7 @@ function snapshotRecord(values, capture) {
15621
16720
  }
15622
16721
  function snapshotCommandArgument(value, seen) {
15623
16722
  if (typeof value !== "object" || value == null) return value;
15624
- if (import_node_buffer53.Buffer.isBuffer(value) || value instanceof Uint8Array) return import_node_buffer53.Buffer.from(value);
16723
+ if (import_node_buffer56.Buffer.isBuffer(value) || value instanceof Uint8Array) return import_node_buffer56.Buffer.from(value);
15625
16724
  const objectValue = value;
15626
16725
  const existing = seen.get(objectValue);
15627
16726
  if (existing != null) return existing;
@@ -16609,7 +17708,7 @@ function nativeOptionsForBootstrap(options, signal) {
16609
17708
  }
16610
17709
 
16611
17710
  // src/flow-query-projection.ts
16612
- var import_node_buffer54 = require("buffer");
17711
+ var import_node_buffer57 = require("buffer");
16613
17712
  var MAX_PROJECTION_FIELDS = 32;
16614
17713
  var MAX_DYNAMIC_NAME_BYTES = 64;
16615
17714
  var FIELD_BRAND = /* @__PURE__ */ Symbol("FerricStoreFlowProjectionField");
@@ -16677,20 +17776,38 @@ function projectFlowQuery(query, shape, ...fields) {
16677
17776
  if (containsReturnKeyword(query)) {
16678
17777
  throw new TypeError("Flow query already contains a RETURN clause");
16679
17778
  }
16680
- const trimmed = query.trim();
16681
- const base = trimmed.endsWith(";") ? trimmed.slice(0, -1).trimEnd() : trimmed;
17779
+ const base = stripOptionalTerminator(query);
16682
17780
  const result = `${base} RETURN ${shape.toUpperCase()} (${selectors.join(", ")})`;
16683
- if (import_node_buffer54.Buffer.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
17781
+ if (import_node_buffer57.Buffer.byteLength(result, "utf8") > FLOW_QUERY_MAX_BYTES) {
16684
17782
  throw new TypeError(`FLOW.QUERY query exceeds ${FLOW_QUERY_MAX_BYTES} bytes`);
16685
17783
  }
16686
17784
  return result;
16687
17785
  }
17786
+ function stripOptionalTerminator(query) {
17787
+ const trimmed = trimFlowQueryAsciiWhitespace(query);
17788
+ if (!trimmed.endsWith(";")) return trimmed;
17789
+ const base = trimFlowQueryAsciiWhitespace(trimmed.slice(0, -1));
17790
+ if (base.endsWith(";")) {
17791
+ throw new TypeError("Flow query accepts at most one trailing semicolon");
17792
+ }
17793
+ return base;
17794
+ }
16688
17795
  function querySource(query) {
16689
- const match = /^\s*FROM\s+(runs|events)\b/iu.exec(query);
16690
- if (match?.[1] == null) {
17796
+ const source = trimFlowQueryAsciiWhitespaceStart(query);
17797
+ if (!matchesFlowQueryAsciiKeyword(source, 0, "FROM") || !isFlowQueryAsciiWhitespaceCode(source.charCodeAt(4))) {
16691
17798
  throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16692
17799
  }
16693
- return match[1].toLowerCase();
17800
+ let offset = 4;
17801
+ while (isFlowQueryAsciiWhitespaceCode(source.charCodeAt(offset))) offset += 1;
17802
+ for (const candidate of ["runs", "events"]) {
17803
+ if (matchesFlowQueryAsciiKeyword(source, offset, candidate.toUpperCase())) {
17804
+ const boundary = source[offset + candidate.length];
17805
+ if (boundary == null || isFlowQueryAsciiWhitespaceCode(boundary.charCodeAt(0))) {
17806
+ return candidate;
17807
+ }
17808
+ }
17809
+ }
17810
+ throw new TypeError("Projected Flow query must start with FROM runs or FROM events");
16694
17811
  }
16695
17812
  function containsReturnKeyword(query) {
16696
17813
  let quoted = false;
@@ -16721,7 +17838,7 @@ function quoteName(value, allowPrivate) {
16721
17838
  throw new TypeError("Flow query projection metadata name must be text");
16722
17839
  }
16723
17840
  validateUnicodeScalarText2(value);
16724
- const size = import_node_buffer54.Buffer.byteLength(value, "utf8");
17841
+ const size = import_node_buffer57.Buffer.byteLength(value, "utf8");
16725
17842
  if (size === 0 || size > MAX_DYNAMIC_NAME_BYTES || !allowPrivate && value.startsWith("__")) {
16726
17843
  throw new TypeError(
16727
17844
  `Flow query projection metadata names must be 1..${MAX_DYNAMIC_NAME_BYTES} UTF-8 bytes`
@@ -16755,7 +17872,7 @@ function complete(options = {}) {
16755
17872
  function retry(options = {}) {
16756
17873
  return brandOutcome({ ...options, kind: "retry" });
16757
17874
  }
16758
- function fail(options = {}) {
17875
+ function fail2(options = {}) {
16759
17876
  return brandOutcome({ ...options, kind: "fail" });
16760
17877
  }
16761
17878
  function isOutcome(value) {
@@ -17552,7 +18669,7 @@ async function applyQueueException(queue, options, job, error) {
17552
18669
  const policy = normalizeExceptionPolicy(options.exceptionPolicy);
17553
18670
  if (policy === "raise") throw error;
17554
18671
  const payload = workerErrorPayload(error, options, queue.client.codec);
17555
- await applyQueueOutcome(queue, job, policy === "fail" ? fail({ error: payload }) : retry({ error: payload }));
18672
+ await applyQueueOutcome(queue, job, policy === "fail" ? fail2({ error: payload }) : retry({ error: payload }));
17556
18673
  }
17557
18674
 
17558
18675
  // src/queue-pending-completions.ts
@@ -17799,7 +18916,7 @@ var QueueWorker = class {
17799
18916
  const policy = normalizeExceptionPolicy(this.options.exceptionPolicy);
17800
18917
  if (policy !== "raise") {
17801
18918
  const payload = workerErrorPayload(error, this.options, this.queue.client.codec);
17802
- outcome = policy === "fail" ? fail({ error: payload }) : retry({ error: payload });
18919
+ outcome = policy === "fail" ? fail2({ error: payload }) : retry({ error: payload });
17803
18920
  }
17804
18921
  }
17805
18922
  const stopped = await stopQueueBatchGuards(guards.map((guard) => guard.job), guards);
@@ -18712,7 +19829,7 @@ var WorkflowWorker = class {
18712
19829
  await applyWorkflowOutcome(
18713
19830
  this.workflow.client,
18714
19831
  ctx,
18715
- fail({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
19832
+ fail2({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
18716
19833
  registration.returnRecord
18717
19834
  );
18718
19835
  return;
@@ -18727,7 +19844,7 @@ var WorkflowWorker = class {
18727
19844
  };
18728
19845
 
18729
19846
  // src/version.ts
18730
- var FERRICSTORE_SDK_VERSION = "0.5.0";
19847
+ var FERRICSTORE_SDK_VERSION = "0.5.2";
18731
19848
  var FERRICSTORE_MINIMUM_SERVER_VERSION = "0.11.0";
18732
19849
  var FERRICSTORE_NATIVE_PROTOCOL_VERSION = 1;
18733
19850
  // Annotate the CommonJS export names for ESM import in node: