@bcts/envelope-pattern 1.0.0-alpha.23 → 1.0.0-beta.1

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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1302 -774
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +101 -59
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +102 -60
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.iife.js +1299 -771
  9. package/dist/index.iife.js.map +1 -1
  10. package/dist/index.mjs +1299 -774
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +11 -9
  13. package/src/format.ts +19 -31
  14. package/src/parse/index.ts +16 -1009
  15. package/src/parse/leaf/array-parser.ts +36 -0
  16. package/src/parse/leaf/cbor-parser.ts +43 -0
  17. package/src/parse/leaf/date-parser.ts +81 -0
  18. package/src/parse/leaf/known-value-parser.ts +73 -0
  19. package/src/parse/leaf/null-parser.ts +16 -0
  20. package/src/parse/leaf/number-parser.ts +90 -0
  21. package/src/parse/leaf/tag-parser.ts +160 -0
  22. package/src/parse/meta/and-parser.ts +40 -0
  23. package/src/parse/meta/capture-parser.ts +50 -0
  24. package/src/parse/meta/group-parser.ts +77 -0
  25. package/src/parse/meta/not-parser.ts +30 -0
  26. package/src/parse/meta/or-parser.ts +36 -0
  27. package/src/parse/meta/primary-parser.ts +234 -0
  28. package/src/parse/meta/search-parser.ts +41 -0
  29. package/src/parse/meta/traverse-parser.ts +42 -0
  30. package/src/parse/structure/assertion-obj-parser.ts +44 -0
  31. package/src/parse/structure/assertion-parser.ts +22 -0
  32. package/src/parse/structure/assertion-pred-parser.ts +45 -0
  33. package/src/parse/structure/compressed-parser.ts +17 -0
  34. package/src/parse/structure/digest-parser.ts +132 -0
  35. package/src/parse/structure/elided-parser.ts +17 -0
  36. package/src/parse/structure/encrypted-parser.ts +17 -0
  37. package/src/parse/structure/node-parser.ts +54 -0
  38. package/src/parse/structure/object-parser.ts +32 -0
  39. package/src/parse/structure/obscured-parser.ts +17 -0
  40. package/src/parse/structure/predicate-parser.ts +32 -0
  41. package/src/parse/structure/subject-parser.ts +32 -0
  42. package/src/parse/structure/wrapped-parser.ts +36 -0
  43. package/src/pattern/dcbor-integration.ts +40 -8
  44. package/src/pattern/index.ts +29 -0
  45. package/src/pattern/leaf/array-pattern.ts +67 -169
  46. package/src/pattern/leaf/cbor-pattern.ts +37 -23
  47. package/src/pattern/leaf/index.ts +1 -1
  48. package/src/pattern/leaf/map-pattern.ts +21 -2
  49. package/src/pattern/leaf/tagged-pattern.ts +6 -1
  50. package/src/pattern/meta/search-pattern.ts +13 -38
  51. package/src/pattern/meta/traverse-pattern.ts +2 -2
  52. package/src/pattern/structure/assertions-pattern.ts +19 -53
  53. package/src/pattern/structure/digest-pattern.ts +18 -22
  54. package/src/pattern/structure/index.ts +3 -0
  55. package/src/pattern/structure/node-pattern.ts +10 -29
  56. package/src/pattern/structure/object-pattern.ts +2 -2
  57. package/src/pattern/structure/predicate-pattern.ts +2 -2
  58. package/src/pattern/structure/subject-pattern.ts +31 -4
  59. package/src/pattern/structure/wrapped-pattern.ts +28 -9
  60. package/src/pattern/vm.ts +4 -4
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _bcts_dcbor_pattern = require("@bcts/dcbor-pattern");
3
3
  let _bcts_known_values = require("@bcts/known-values");
4
- let _bcts_envelope = require("@bcts/envelope");
5
4
  let _bcts_dcbor = require("@bcts/dcbor");
5
+ let _bcts_envelope = require("@bcts/envelope");
6
6
  let _bcts_dcbor_parse = require("@bcts/dcbor-parse");
7
7
  //#region src/error.ts
8
8
  /**
@@ -306,6 +306,14 @@ function formatPathsOpts() {
306
306
  /**
307
307
  * Gets a summary of an envelope for display.
308
308
  *
309
+ * Mirrors Rust `envelope_summary` in `format.rs`: defers to
310
+ * `Envelope::format_flat()` for nodes / wrapped / assertions and to
311
+ * `cbor.envelope_summary(usize::MAX, ...)` for raw CBOR leaves. The
312
+ * obscured cases (`elided` / `encrypted` / `compressed`) emit just the
313
+ * keyword. KnownValue envelopes look up the canonical name via
314
+ * `KnownValue.name()`, matching the Rust call to
315
+ * `KnownValuesStore::known_value_for_raw_value(value, …)`.
316
+ *
309
317
  * @param env - The envelope to summarize
310
318
  * @returns A string summary of the envelope
311
319
  */
@@ -314,25 +322,17 @@ function envelopeSummary(env) {
314
322
  const c = env.case();
315
323
  let summary;
316
324
  switch (c.type) {
317
- case "node": {
318
- const subjectSummary = env.subject().summary(Number.MAX_SAFE_INTEGER);
319
- const assertions = env.assertions();
320
- if (assertions.length > 0) summary = `NODE ${subjectSummary} [ ${assertions.map((a) => {
321
- const ac = a.case();
322
- if (ac.type === "assertion") return `${ac.assertion.predicate().summary(Number.MAX_SAFE_INTEGER)}: ${ac.assertion.object().summary(Number.MAX_SAFE_INTEGER)}`;
323
- return a.summary(Number.MAX_SAFE_INTEGER);
324
- }).join(", ")} ]`;
325
- else summary = `NODE ${subjectSummary}`;
325
+ case "node":
326
+ summary = `NODE ${env.formatFlat()}`;
326
327
  break;
327
- }
328
328
  case "leaf":
329
329
  summary = `LEAF ${env.summary(Number.MAX_SAFE_INTEGER)}`;
330
330
  break;
331
331
  case "wrapped":
332
- summary = `WRAPPED ${env.summary(Number.MAX_SAFE_INTEGER)}`;
332
+ summary = `WRAPPED ${env.formatFlat()}`;
333
333
  break;
334
334
  case "assertion":
335
- summary = `ASSERTION ${c.assertion.predicate().summary(Number.MAX_SAFE_INTEGER)}: ${c.assertion.object().summary(Number.MAX_SAFE_INTEGER)}`;
335
+ summary = `ASSERTION ${env.formatFlat()}`;
336
336
  break;
337
337
  case "elided":
338
338
  summary = "ELIDED";
@@ -378,8 +378,8 @@ function formatPathOpt(path, opts = defaultFormatPathsOpts()) {
378
378
  if (element === void 0) return "";
379
379
  switch (opts.elementFormat.type) {
380
380
  case "Summary": return truncateWithEllipsis(envelopeSummary(element), opts.elementFormat.maxLength);
381
- case "EnvelopeUR": return element.digest().toString();
382
- case "DigestUR": return element.digest().toString();
381
+ case "EnvelopeUR": return element.urString();
382
+ case "DigestUR": return element.digest().urString();
383
383
  }
384
384
  }
385
385
  switch (opts.elementFormat.type) {
@@ -394,8 +394,8 @@ function formatPathOpt(path, opts = defaultFormatPathsOpts()) {
394
394
  }
395
395
  return lines.join("\n");
396
396
  }
397
- case "EnvelopeUR": return path.map((element) => element.digest().toString()).join(" ");
398
- case "DigestUR": return path.map((element) => element.digest().toString()).join(" ");
397
+ case "EnvelopeUR": return path.map((element) => element.urString()).join(" ");
398
+ case "DigestUR": return path.map((element) => element.digest().urString()).join(" ");
399
399
  }
400
400
  }
401
401
  /**
@@ -1237,25 +1237,16 @@ var DatePattern = class DatePattern {
1237
1237
  };
1238
1238
  //#endregion
1239
1239
  //#region src/pattern/leaf/array-pattern.ts
1240
- /**
1241
- * Copyright © 2023-2026 Blockchain Commons, LLC
1242
- * Copyright © 2025-2026 Parity Technologies
1243
- *
1244
- *
1245
- * @bcts/envelope-pattern - Array pattern matching
1246
- *
1247
- * This is a 1:1 TypeScript port of bc-envelope-pattern-rust array_pattern.rs
1248
- *
1249
- * @module envelope-pattern/pattern/leaf/array-pattern
1250
- */
1251
1240
  let createLeafArrayPattern;
1252
1241
  function registerArrayPatternFactory(factory) {
1253
1242
  createLeafArrayPattern = factory;
1254
1243
  }
1255
1244
  /**
1256
- * Pattern for matching array values in envelope leaf nodes.
1245
+ * Pattern for matching arrays.
1257
1246
  *
1258
- * Corresponds to the Rust `ArrayPattern` struct in array_pattern.rs
1247
+ * Mirrors Rust `ArrayPattern(dcbor_pattern::ArrayPattern)` from
1248
+ * `bc-envelope-pattern-rust/src/pattern/leaf/array_pattern.rs`. All
1249
+ * matching, display, and equality is delegated to dcbor-pattern.
1259
1250
  */
1260
1251
  var ArrayPattern = class ArrayPattern {
1261
1252
  _pattern;
@@ -1266,93 +1257,54 @@ var ArrayPattern = class ArrayPattern {
1266
1257
  * Creates a new ArrayPattern that matches any array.
1267
1258
  */
1268
1259
  static any() {
1269
- return new ArrayPattern({ type: "Any" });
1260
+ return new ArrayPattern((0, _bcts_dcbor_pattern.arrayPatternAny)());
1270
1261
  }
1271
1262
  /**
1272
1263
  * Creates a new ArrayPattern that matches arrays with a specific length.
1273
1264
  */
1274
1265
  static count(count) {
1275
- return new ArrayPattern({
1276
- type: "Interval",
1277
- interval: _bcts_dcbor_pattern.Interval.exactly(count)
1278
- });
1266
+ return new ArrayPattern((0, _bcts_dcbor_pattern.arrayPatternWithLength)(count));
1279
1267
  }
1280
1268
  /**
1281
1269
  * Creates a new ArrayPattern that matches arrays within a length range.
1282
1270
  */
1283
1271
  static interval(min, max) {
1284
- return new ArrayPattern({
1285
- type: "Interval",
1286
- interval: max !== void 0 ? _bcts_dcbor_pattern.Interval.from(min, max) : _bcts_dcbor_pattern.Interval.atLeast(min)
1287
- });
1272
+ return new ArrayPattern((0, _bcts_dcbor_pattern.arrayPatternWithLengthRange)(min, max));
1288
1273
  }
1289
1274
  /**
1290
- * Creates a new ArrayPattern from a dcbor-pattern.
1275
+ * Creates a new ArrayPattern from a length Interval.
1291
1276
  */
1292
- static fromDcborPattern(dcborPattern) {
1293
- return new ArrayPattern({
1294
- type: "DCBORPattern",
1295
- pattern: dcborPattern
1296
- });
1277
+ static fromInterval(interval) {
1278
+ return new ArrayPattern((0, _bcts_dcbor_pattern.arrayPatternWithLengthInterval)(interval));
1297
1279
  }
1298
1280
  /**
1299
- * Creates a new ArrayPattern with envelope patterns for element matching.
1281
+ * Creates a new ArrayPattern from a top-level dcbor-pattern.
1282
+ *
1283
+ * Mirrors Rust `ArrayPattern::from_dcbor_pattern`, which constructs an
1284
+ * `ArrayPattern::Elements`-style dcbor array pattern.
1300
1285
  */
1301
- static withPatterns(patterns) {
1302
- return new ArrayPattern({
1303
- type: "WithPatterns",
1304
- patterns
1305
- });
1286
+ static fromDcborPattern(pattern) {
1287
+ return new ArrayPattern((0, _bcts_dcbor_pattern.arrayPatternWithElements)(pattern));
1306
1288
  }
1307
1289
  /**
1308
- * Gets the pattern type.
1290
+ * Creates a new ArrayPattern from an existing dcbor-pattern ArrayPattern.
1291
+ *
1292
+ * Mirrors Rust `ArrayPattern::from_dcbor_array_pattern`.
1309
1293
  */
1310
- get pattern() {
1294
+ static fromDcborArrayPattern(arrayPattern) {
1295
+ return new ArrayPattern(arrayPattern);
1296
+ }
1297
+ /**
1298
+ * Returns the underlying dcbor-pattern ArrayPattern.
1299
+ */
1300
+ inner() {
1311
1301
  return this._pattern;
1312
1302
  }
1313
1303
  pathsWithCaptures(haystack) {
1314
1304
  const cbor = haystack.subject().asLeaf();
1315
1305
  if (cbor === void 0) return [[], /* @__PURE__ */ new Map()];
1316
- const array = (0, _bcts_dcbor.asCborArray)(cbor);
1317
- if (array === void 0) return [[], /* @__PURE__ */ new Map()];
1318
- switch (this._pattern.type) {
1319
- case "Any": return [[[haystack]], /* @__PURE__ */ new Map()];
1320
- case "Interval": {
1321
- const length = array.length;
1322
- if (this._pattern.interval.contains(length)) return [[[haystack]], /* @__PURE__ */ new Map()];
1323
- return [[], /* @__PURE__ */ new Map()];
1324
- }
1325
- case "DCBORPattern": {
1326
- const { paths: dcborPaths, captures: dcborCaptures } = (0, _bcts_dcbor_pattern.patternPathsWithCaptures)(this._pattern.pattern, cbor);
1327
- if (dcborPaths.length > 0) {
1328
- const envelopePaths = dcborPaths.map((dcborPath) => {
1329
- const envPath = [haystack];
1330
- for (let i = 1; i < dcborPath.length; i++) {
1331
- const elem = dcborPath[i];
1332
- if (elem !== void 0) envPath.push(_bcts_envelope.Envelope.newLeaf(elem));
1333
- }
1334
- return envPath;
1335
- });
1336
- const envelopeCaptures = /* @__PURE__ */ new Map();
1337
- for (const [name, capturePaths] of dcborCaptures) {
1338
- const envCapturePaths = capturePaths.map((dcborPath) => {
1339
- const envPath = [haystack];
1340
- for (let i = 1; i < dcborPath.length; i++) {
1341
- const elem = dcborPath[i];
1342
- if (elem !== void 0) envPath.push(_bcts_envelope.Envelope.newLeaf(elem));
1343
- }
1344
- return envPath;
1345
- });
1346
- envelopeCaptures.set(name, envCapturePaths);
1347
- }
1348
- return [envelopePaths, envelopeCaptures];
1349
- }
1350
- return [[], /* @__PURE__ */ new Map()];
1351
- }
1352
- case "WithPatterns":
1353
- if (array.length === this._pattern.patterns.length) return [[[haystack]], /* @__PURE__ */ new Map()];
1354
- return [[], /* @__PURE__ */ new Map()];
1355
- }
1306
+ if ((0, _bcts_dcbor_pattern.arrayPatternMatches)(this._pattern, cbor)) return [[[haystack]], /* @__PURE__ */ new Map()];
1307
+ return [[], /* @__PURE__ */ new Map()];
1356
1308
  }
1357
1309
  paths(haystack) {
1358
1310
  return this.pathsWithCaptures(haystack)[0];
@@ -1368,54 +1320,31 @@ var ArrayPattern = class ArrayPattern {
1368
1320
  return false;
1369
1321
  }
1370
1322
  toString() {
1371
- switch (this._pattern.type) {
1372
- case "Any": return "[*]";
1373
- case "Interval": return `[{${this._pattern.interval.toString()}}]`;
1374
- case "DCBORPattern": return (0, _bcts_dcbor_pattern.patternDisplay)(this._pattern.pattern);
1375
- case "WithPatterns": return `[${this._pattern.patterns.map(String).join(", ")}]`;
1376
- }
1323
+ return (0, _bcts_dcbor_pattern.arrayPatternDisplay)(this._pattern, _bcts_dcbor_pattern.patternDisplay);
1377
1324
  }
1378
1325
  /**
1379
- * Equality comparison.
1326
+ * Equality comparison. Delegates to dcbor-pattern's structural equality
1327
+ * with a display-string fallback for pattern-equality (mirrors Rust's
1328
+ * `Hash` impl that hashes the display, since dcbor `ArrayPattern`
1329
+ * itself does not derive `Hash`).
1380
1330
  */
1381
1331
  equals(other) {
1382
- if (this._pattern.type !== other._pattern.type) return false;
1383
- switch (this._pattern.type) {
1384
- case "Any": return true;
1385
- case "Interval": return this._pattern.interval.equals(other._pattern.interval);
1386
- case "DCBORPattern": return (0, _bcts_dcbor_pattern.patternDisplay)(this._pattern.pattern) === (0, _bcts_dcbor_pattern.patternDisplay)(other._pattern.pattern);
1387
- case "WithPatterns": {
1388
- const otherPatterns = other._pattern.patterns;
1389
- if (this._pattern.patterns.length !== otherPatterns.length) return false;
1390
- for (let i = 0; i < this._pattern.patterns.length; i++) if (this._pattern.patterns[i] !== otherPatterns[i]) return false;
1391
- return true;
1392
- }
1393
- }
1332
+ return (0, _bcts_dcbor_pattern.arrayPatternEquals)(this._pattern, other._pattern, (a, b) => (0, _bcts_dcbor_pattern.patternDisplay)(a) === (0, _bcts_dcbor_pattern.patternDisplay)(b));
1394
1333
  }
1395
1334
  /**
1396
- * Hash code for use in Maps/Sets.
1335
+ * Hash code for use in Maps/Sets. Mirrors Rust's
1336
+ * "hash the string representation" approach.
1397
1337
  */
1398
1338
  hashCode() {
1399
- switch (this._pattern.type) {
1400
- case "Any": return 0;
1401
- case "Interval": return this._pattern.interval.min() * 31 + (this._pattern.interval.max() ?? 0);
1402
- case "DCBORPattern": return simpleStringHash$3((0, _bcts_dcbor_pattern.patternDisplay)(this._pattern.pattern));
1403
- case "WithPatterns": return this._pattern.patterns.length;
1339
+ let hash = 0;
1340
+ const str = this.toString();
1341
+ for (let i = 0; i < str.length; i++) {
1342
+ hash = (hash << 5) - hash + str.charCodeAt(i);
1343
+ hash = hash & hash;
1404
1344
  }
1345
+ return hash;
1405
1346
  }
1406
1347
  };
1407
- /**
1408
- * Simple string hash function for hashCode implementations.
1409
- */
1410
- function simpleStringHash$3(str) {
1411
- let hash = 0;
1412
- for (let i = 0; i < str.length; i++) {
1413
- const char = str.charCodeAt(i);
1414
- hash = (hash << 5) - hash + char;
1415
- hash = hash & hash;
1416
- }
1417
- return hash;
1418
- }
1419
1348
  //#endregion
1420
1349
  //#region src/pattern/leaf/map-pattern.ts
1421
1350
  let createLeafMapPattern;
@@ -1448,6 +1377,19 @@ var MapPattern = class MapPattern {
1448
1377
  });
1449
1378
  }
1450
1379
  /**
1380
+ * Creates a new MapPattern from a length Interval.
1381
+ *
1382
+ * Mirrors Rust `MapPattern::from_interval`. Used by the
1383
+ * dcbor-pattern → envelope-pattern bridge to preserve `{{n,m}}`
1384
+ * length info.
1385
+ */
1386
+ static fromInterval(interval) {
1387
+ return new MapPattern({
1388
+ type: "Interval",
1389
+ interval
1390
+ });
1391
+ }
1392
+ /**
1451
1393
  * Gets the pattern type.
1452
1394
  */
1453
1395
  get pattern() {
@@ -1482,8 +1424,8 @@ var MapPattern = class MapPattern {
1482
1424
  }
1483
1425
  toString() {
1484
1426
  switch (this._pattern.type) {
1485
- case "Any": return "{*}";
1486
- case "Interval": return `{{${this._pattern.interval.toString()}}}`;
1427
+ case "Any": return "map";
1428
+ case "Interval": return `{${this._pattern.interval.toString()}}`;
1487
1429
  }
1488
1430
  }
1489
1431
  /**
@@ -1736,7 +1678,7 @@ var TaggedPattern = class TaggedPattern {
1736
1678
  return false;
1737
1679
  }
1738
1680
  toString() {
1739
- return (0, _bcts_dcbor_pattern.taggedPatternDisplay)(this._inner, _bcts_dcbor_pattern.patternDisplay);
1681
+ return (0, _bcts_dcbor_pattern.taggedPatternDisplay)(this._inner, _bcts_dcbor_pattern.patternDisplay).replace(", ", ", ");
1740
1682
  }
1741
1683
  /**
1742
1684
  * Equality comparison.
@@ -1862,10 +1804,18 @@ var CBORPattern = class CBORPattern {
1862
1804
  }
1863
1805
  /**
1864
1806
  * Convert a single dcbor path to an envelope path.
1807
+ *
1808
+ * Uses canonical CBOR-byte equality (`cborEquals`) for the "skip the
1809
+ * dcbor root if it duplicates our base envelope" check, mirroring
1810
+ * Rust's `dcbor_path.first().map(|first| first == &base_cbor)`. The
1811
+ * earlier port compared diagnostic strings, which collapses values
1812
+ * that share a textual representation but differ structurally
1813
+ * (e.g. NaN payloads).
1865
1814
  */
1866
1815
  _convertDcborPathToEnvelopePath(dcborPath, baseEnvelope, baseCbor) {
1867
1816
  const envelopePath = [baseEnvelope];
1868
- const elementsToAdd = dcborPath.length > 0 && dcborPath[0]?.toDiagnostic() === baseCbor.toDiagnostic() ? dcborPath.slice(1) : dcborPath;
1817
+ const first = dcborPath[0];
1818
+ const elementsToAdd = first !== void 0 && (0, _bcts_dcbor.cborEquals)(first, baseCbor) ? dcborPath.slice(1) : dcborPath;
1869
1819
  for (const cborElement of elementsToAdd) envelopePath.push(_bcts_envelope.Envelope.newLeaf(cborElement));
1870
1820
  return envelopePath;
1871
1821
  }
@@ -1892,7 +1842,7 @@ var CBORPattern = class CBORPattern {
1892
1842
  switch (this._pattern.type) {
1893
1843
  case "Any": return [[[haystack]], /* @__PURE__ */ new Map()];
1894
1844
  case "Value":
1895
- if (knownValueCbor.toDiagnostic() === this._pattern.cbor.toDiagnostic()) return [[[haystack]], /* @__PURE__ */ new Map()];
1845
+ if ((0, _bcts_dcbor.cborEquals)(knownValueCbor, this._pattern.cbor)) return [[[haystack]], /* @__PURE__ */ new Map()];
1896
1846
  return [[], /* @__PURE__ */ new Map()];
1897
1847
  case "Pattern": {
1898
1848
  const { paths: dcborPaths, captures: dcborCaptures } = (0, _bcts_dcbor_pattern.patternPathsWithCaptures)(this._pattern.pattern, knownValueCbor);
@@ -1916,7 +1866,7 @@ var CBORPattern = class CBORPattern {
1916
1866
  switch (this._pattern.type) {
1917
1867
  case "Any": return [[[haystack]], /* @__PURE__ */ new Map()];
1918
1868
  case "Value":
1919
- if (leafCbor.toDiagnostic() === this._pattern.cbor.toDiagnostic()) return [[[haystack]], /* @__PURE__ */ new Map()];
1869
+ if ((0, _bcts_dcbor.cborEquals)(leafCbor, this._pattern.cbor)) return [[[haystack]], /* @__PURE__ */ new Map()];
1920
1870
  return [[], /* @__PURE__ */ new Map()];
1921
1871
  case "Pattern": {
1922
1872
  const { paths: dcborPaths, captures: dcborCaptures } = (0, _bcts_dcbor_pattern.patternPathsWithCaptures)(this._pattern.pattern, leafCbor);
@@ -1924,7 +1874,8 @@ var CBORPattern = class CBORPattern {
1924
1874
  const basePath = [haystack];
1925
1875
  return [dcborPaths.map((dcborPath) => {
1926
1876
  const extendedPath = [...basePath];
1927
- const elementsToAdd = dcborPath.length > 0 && dcborPath[0]?.toDiagnostic() === leafCbor.toDiagnostic() ? dcborPath.slice(1) : dcborPath;
1877
+ const first = dcborPath[0];
1878
+ const elementsToAdd = first !== void 0 && (0, _bcts_dcbor.cborEquals)(first, leafCbor) ? dcborPath.slice(1) : dcborPath;
1928
1879
  for (const cborElement of elementsToAdd) extendedPath.push(_bcts_envelope.Envelope.newLeaf(cborElement));
1929
1880
  return extendedPath;
1930
1881
  }), this._convertDcborCapturesToEnvelopeCaptures(dcborCaptures, haystack, leafCbor)];
@@ -1959,13 +1910,16 @@ var CBORPattern = class CBORPattern {
1959
1910
  }
1960
1911
  }
1961
1912
  /**
1962
- * Equality comparison.
1913
+ * Equality comparison. `Value` variants compare by canonical CBOR
1914
+ * byte sequence (mirrors Rust `==` on `CBOR`); `Pattern` variants fall
1915
+ * back to display-string compare since `DCBORPattern` doesn't expose
1916
+ * structural equality outside the crate.
1963
1917
  */
1964
1918
  equals(other) {
1965
1919
  if (this._pattern.type !== other._pattern.type) return false;
1966
1920
  switch (this._pattern.type) {
1967
1921
  case "Any": return true;
1968
- case "Value": return this._pattern.cbor.toDiagnostic() === other._pattern.cbor.toDiagnostic();
1922
+ case "Value": return (0, _bcts_dcbor.cborEquals)(this._pattern.cbor, other._pattern.cbor);
1969
1923
  case "Pattern": return (0, _bcts_dcbor_pattern.patternDisplay)(this._pattern.pattern) === (0, _bcts_dcbor_pattern.patternDisplay)(other._pattern.pattern);
1970
1924
  }
1971
1925
  }
@@ -1975,7 +1929,12 @@ var CBORPattern = class CBORPattern {
1975
1929
  hashCode() {
1976
1930
  switch (this._pattern.type) {
1977
1931
  case "Any": return 0;
1978
- case "Value": return simpleStringHash(this._pattern.cbor.toDiagnostic());
1932
+ case "Value": {
1933
+ const bytes = (0, _bcts_dcbor.cborData)(this._pattern.cbor);
1934
+ let hash = 0;
1935
+ for (const byte of bytes) hash = hash * 31 + byte | 0;
1936
+ return hash;
1937
+ }
1979
1938
  case "Pattern": return simpleStringHash((0, _bcts_dcbor_pattern.patternDisplay)(this._pattern.pattern));
1980
1939
  }
1981
1940
  }
@@ -2244,7 +2203,13 @@ var LeafStructurePattern = class LeafStructurePattern {
2244
2203
  return 0;
2245
2204
  }
2246
2205
  };
2206
+ let dispatchPatternCompile$1;
2207
+ let dispatchPatternToString$3;
2247
2208
  function registerSubjectPatternFactory(factory) {}
2209
+ function registerSubjectPatternDispatch(dispatch) {
2210
+ dispatchPatternCompile$1 = dispatch.compile;
2211
+ dispatchPatternToString$3 = dispatch.toString;
2212
+ }
2248
2213
  /**
2249
2214
  * Pattern for matching subjects in envelopes.
2250
2215
  *
@@ -2308,9 +2273,10 @@ var SubjectPattern = class SubjectPattern {
2308
2273
  code.push({ type: "NavigateSubject" });
2309
2274
  break;
2310
2275
  case "Pattern":
2276
+ if (dispatchPatternCompile$1 === void 0) throw new Error("SubjectPattern.compile requires the top-level Pattern compile dispatch; not registered");
2311
2277
  code.push({ type: "NavigateSubject" });
2312
2278
  code.push({ type: "ExtendTraversal" });
2313
- this._pattern.pattern.compile(code, literals, captures);
2279
+ dispatchPatternCompile$1(this._pattern.pattern, code, literals, captures);
2314
2280
  code.push({ type: "CombineTraversal" });
2315
2281
  break;
2316
2282
  }
@@ -2321,7 +2287,10 @@ var SubjectPattern = class SubjectPattern {
2321
2287
  toString() {
2322
2288
  switch (this._pattern.type) {
2323
2289
  case "Any": return "subj";
2324
- case "Pattern": return `subj(${this._pattern.pattern.toString()})`;
2290
+ case "Pattern": {
2291
+ const fmt = dispatchPatternToString$3;
2292
+ return `subj(${fmt !== void 0 ? fmt(this._pattern.pattern) : "?"})`;
2293
+ }
2325
2294
  }
2326
2295
  }
2327
2296
  /**
@@ -2418,7 +2387,7 @@ var PredicatePattern = class PredicatePattern {
2418
2387
  toString() {
2419
2388
  switch (this._pattern.type) {
2420
2389
  case "Any": return "pred";
2421
- case "Pattern": return `pred(${this._pattern.pattern.toString()})`;
2390
+ case "Pattern": return `pred(${dispatchPatternToString(this._pattern.pattern)})`;
2422
2391
  }
2423
2392
  }
2424
2393
  /**
@@ -2515,7 +2484,7 @@ var ObjectPattern = class ObjectPattern {
2515
2484
  toString() {
2516
2485
  switch (this._pattern.type) {
2517
2486
  case "Any": return "obj";
2518
- case "Pattern": return `obj(${this._pattern.pattern.toString()})`;
2487
+ case "Pattern": return `obj(${dispatchPatternToString(this._pattern.pattern)})`;
2519
2488
  }
2520
2489
  }
2521
2490
  /**
@@ -2536,9 +2505,13 @@ var ObjectPattern = class ObjectPattern {
2536
2505
  //#endregion
2537
2506
  //#region src/pattern/structure/assertions-pattern.ts
2538
2507
  let createStructureAssertionsPattern;
2508
+ let dispatchPatternToString$2;
2539
2509
  function registerAssertionsPatternFactory(factory) {
2540
2510
  createStructureAssertionsPattern = factory;
2541
2511
  }
2512
+ function registerAssertionsPatternToStringDispatch(fn) {
2513
+ dispatchPatternToString$2 = fn;
2514
+ }
2542
2515
  /**
2543
2516
  * Pattern for matching assertions in envelopes.
2544
2517
  *
@@ -2576,17 +2549,6 @@ var AssertionsPattern = class AssertionsPattern {
2576
2549
  });
2577
2550
  }
2578
2551
  /**
2579
- * Creates a new AssertionsPattern that matches assertions with both
2580
- * predicate and object patterns.
2581
- */
2582
- static withBoth(predicatePattern, objectPattern) {
2583
- return new AssertionsPattern({
2584
- type: "WithBoth",
2585
- predicatePattern,
2586
- objectPattern
2587
- });
2588
- }
2589
- /**
2590
2552
  * Gets the pattern type.
2591
2553
  */
2592
2554
  get patternType() {
@@ -2597,14 +2559,12 @@ var AssertionsPattern = class AssertionsPattern {
2597
2559
  */
2598
2560
  predicatePattern() {
2599
2561
  if (this._pattern.type === "WithPredicate") return this._pattern.pattern;
2600
- if (this._pattern.type === "WithBoth") return this._pattern.predicatePattern;
2601
2562
  }
2602
2563
  /**
2603
2564
  * Gets the object pattern if this has one, undefined otherwise.
2604
2565
  */
2605
2566
  objectPattern() {
2606
2567
  if (this._pattern.type === "WithObject") return this._pattern.pattern;
2607
- if (this._pattern.type === "WithBoth") return this._pattern.objectPattern;
2608
2568
  }
2609
2569
  pathsWithCaptures(haystack) {
2610
2570
  const paths = [];
@@ -2626,14 +2586,6 @@ var AssertionsPattern = class AssertionsPattern {
2626
2586
  }
2627
2587
  break;
2628
2588
  }
2629
- case "WithBoth": {
2630
- const predicate = assertion.asPredicate?.();
2631
- const object = assertion.asObject?.();
2632
- if (predicate !== void 0 && object !== void 0) {
2633
- if (matchPattern(this._pattern.predicatePattern, predicate) && matchPattern(this._pattern.objectPattern, object)) paths.push([assertion]);
2634
- }
2635
- break;
2636
- }
2637
2589
  }
2638
2590
  return [paths, /* @__PURE__ */ new Map()];
2639
2591
  }
@@ -2656,11 +2608,11 @@ var AssertionsPattern = class AssertionsPattern {
2656
2608
  return false;
2657
2609
  }
2658
2610
  toString() {
2611
+ const fmt = dispatchPatternToString$2;
2659
2612
  switch (this._pattern.type) {
2660
2613
  case "Any": return "assert";
2661
- case "WithPredicate": return `assertpred(${this._pattern.pattern.toString()})`;
2662
- case "WithObject": return `assertobj(${this._pattern.pattern.toString()})`;
2663
- case "WithBoth": return `assert(${this._pattern.predicatePattern.toString()}, ${this._pattern.objectPattern.toString()})`;
2614
+ case "WithPredicate": return `assertpred(${fmt !== void 0 ? fmt(this._pattern.pattern) : "?"})`;
2615
+ case "WithObject": return `assertobj(${fmt !== void 0 ? fmt(this._pattern.pattern) : "?"})`;
2664
2616
  }
2665
2617
  }
2666
2618
  /**
@@ -2672,10 +2624,6 @@ var AssertionsPattern = class AssertionsPattern {
2672
2624
  case "Any": return true;
2673
2625
  case "WithPredicate":
2674
2626
  case "WithObject": return this._pattern.pattern === other._pattern.pattern;
2675
- case "WithBoth": {
2676
- const otherBoth = other._pattern;
2677
- return this._pattern.predicatePattern === otherBoth.predicatePattern && this._pattern.objectPattern === otherBoth.objectPattern;
2678
- }
2679
2627
  }
2680
2628
  }
2681
2629
  /**
@@ -2686,7 +2634,6 @@ var AssertionsPattern = class AssertionsPattern {
2686
2634
  case "Any": return 0;
2687
2635
  case "WithPredicate": return 1;
2688
2636
  case "WithObject": return 2;
2689
- case "WithBoth": return 3;
2690
2637
  }
2691
2638
  }
2692
2639
  };
@@ -2715,12 +2662,6 @@ var DigestPattern = class DigestPattern {
2715
2662
  this._pattern = pattern;
2716
2663
  }
2717
2664
  /**
2718
- * Creates a new DigestPattern that matches any digest.
2719
- */
2720
- static any() {
2721
- return new DigestPattern({ type: "Any" });
2722
- }
2723
- /**
2724
2665
  * Creates a new DigestPattern that matches the exact digest.
2725
2666
  */
2726
2667
  static digest(digest) {
@@ -2758,9 +2699,6 @@ var DigestPattern = class DigestPattern {
2758
2699
  const digestData = digest.data();
2759
2700
  let isHit = false;
2760
2701
  switch (this._pattern.type) {
2761
- case "Any":
2762
- isHit = true;
2763
- break;
2764
2702
  case "Digest":
2765
2703
  isHit = digest.equals(this._pattern.digest);
2766
2704
  break;
@@ -2798,7 +2736,6 @@ var DigestPattern = class DigestPattern {
2798
2736
  }
2799
2737
  toString() {
2800
2738
  switch (this._pattern.type) {
2801
- case "Any": return "digest";
2802
2739
  case "Digest": return `digest(${this._pattern.digest.hex()})`;
2803
2740
  case "Prefix": return `digest(${(0, _bcts_dcbor.bytesToHex)(this._pattern.prefix)})`;
2804
2741
  case "BinaryRegex": return `digest(/${this._pattern.regex.source}/)`;
@@ -2806,17 +2743,26 @@ var DigestPattern = class DigestPattern {
2806
2743
  }
2807
2744
  /**
2808
2745
  * Equality comparison.
2746
+ *
2747
+ * `Prefix` comparison is case-insensitive on the *hex representation* to
2748
+ * mirror Rust's `eq_ignore_ascii_case` (which compares the underlying
2749
+ * `Vec<u8>` of hex bytes byte-for-byte modulo ASCII case). For raw byte
2750
+ * prefixes that happen to be ASCII, this is an ordinary byte compare.
2809
2751
  */
2810
2752
  equals(other) {
2811
2753
  if (this._pattern.type !== other._pattern.type) return false;
2812
2754
  switch (this._pattern.type) {
2813
- case "Any": return true;
2814
2755
  case "Digest": return this._pattern.digest.equals(other._pattern.digest);
2815
2756
  case "Prefix": {
2816
2757
  const thisPrefix = this._pattern.prefix;
2817
2758
  const otherPrefix = other._pattern.prefix;
2818
2759
  if (thisPrefix.length !== otherPrefix.length) return false;
2819
- for (let i = 0; i < thisPrefix.length; i++) if (thisPrefix[i] !== otherPrefix[i]) return false;
2760
+ for (let i = 0; i < thisPrefix.length; i++) {
2761
+ const a = thisPrefix[i];
2762
+ const b = otherPrefix[i];
2763
+ if (a === b) continue;
2764
+ if ((a >= 65 && a <= 90 ? a + 32 : a) !== (b >= 65 && b <= 90 ? b + 32 : b)) return false;
2765
+ }
2820
2766
  return true;
2821
2767
  }
2822
2768
  case "BinaryRegex": return this._pattern.regex.source === other._pattern.regex.source;
@@ -2827,7 +2773,6 @@ var DigestPattern = class DigestPattern {
2827
2773
  */
2828
2774
  hashCode() {
2829
2775
  switch (this._pattern.type) {
2830
- case "Any": return 0;
2831
2776
  case "Digest": {
2832
2777
  const data = this._pattern.digest.data().slice(0, 8);
2833
2778
  let hash = 0;
@@ -2836,7 +2781,10 @@ var DigestPattern = class DigestPattern {
2836
2781
  }
2837
2782
  case "Prefix": {
2838
2783
  let hash = 0;
2839
- for (const byte of this._pattern.prefix) hash = hash * 31 + byte | 0;
2784
+ for (const byte of this._pattern.prefix) {
2785
+ const folded = byte >= 65 && byte <= 90 ? byte + 32 : byte;
2786
+ hash = hash * 31 + folded | 0;
2787
+ }
2840
2788
  return hash;
2841
2789
  }
2842
2790
  case "BinaryRegex": {
@@ -2888,28 +2836,19 @@ var NodePattern = class NodePattern {
2888
2836
  });
2889
2837
  }
2890
2838
  /**
2891
- * Creates a new NodePattern with a subject pattern constraint.
2892
- */
2893
- static withSubject(subjectPattern) {
2894
- return new NodePattern({
2895
- type: "WithSubject",
2896
- subjectPattern
2897
- });
2898
- }
2899
- /**
2900
2839
  * Gets the pattern type.
2901
2840
  */
2902
2841
  get patternType() {
2903
2842
  return this._pattern;
2904
2843
  }
2905
2844
  /**
2906
- * Gets the subject pattern if this is a WithSubject type, undefined otherwise.
2845
+ * Returns the subject pattern, if any. Rust's `NodePattern` does not carry
2846
+ * subject patterns, so this always returns `undefined`.
2907
2847
  */
2908
- subjectPattern() {
2909
- return this._pattern.type === "WithSubject" ? this._pattern.subjectPattern : void 0;
2910
- }
2848
+ subjectPattern() {}
2911
2849
  /**
2912
- * Gets the assertion patterns (empty array if none).
2850
+ * Returns the assertion patterns. Rust's `NodePattern` does not carry
2851
+ * assertion sub-patterns, so this always returns an empty array.
2913
2852
  */
2914
2853
  assertionPatterns() {
2915
2854
  return [];
@@ -2924,9 +2863,6 @@ var NodePattern = class NodePattern {
2924
2863
  case "AssertionsInterval":
2925
2864
  isHit = this._pattern.interval.contains(haystack.assertions().length);
2926
2865
  break;
2927
- case "WithSubject":
2928
- isHit = true;
2929
- break;
2930
2866
  }
2931
2867
  return [isHit ? [[haystack]] : [], /* @__PURE__ */ new Map()];
2932
2868
  }
@@ -2947,7 +2883,6 @@ var NodePattern = class NodePattern {
2947
2883
  switch (this._pattern.type) {
2948
2884
  case "Any": return "node";
2949
2885
  case "AssertionsInterval": return `node(${this._pattern.interval.toString()})`;
2950
- case "WithSubject": return `node(${this._pattern.subjectPattern.toString()})`;
2951
2886
  }
2952
2887
  }
2953
2888
  /**
@@ -2958,7 +2893,6 @@ var NodePattern = class NodePattern {
2958
2893
  switch (this._pattern.type) {
2959
2894
  case "Any": return true;
2960
2895
  case "AssertionsInterval": return this._pattern.interval.equals(other._pattern.interval);
2961
- case "WithSubject": return this._pattern.subjectPattern === other._pattern.subjectPattern;
2962
2896
  }
2963
2897
  }
2964
2898
  /**
@@ -2968,7 +2902,6 @@ var NodePattern = class NodePattern {
2968
2902
  switch (this._pattern.type) {
2969
2903
  case "Any": return 0;
2970
2904
  case "AssertionsInterval": return this._pattern.interval.min() * 31 + (this._pattern.interval.max() ?? 0);
2971
- case "WithSubject": return 1;
2972
2905
  }
2973
2906
  }
2974
2907
  };
@@ -3078,12 +3011,16 @@ var ObscuredPattern = class ObscuredPattern {
3078
3011
  //#endregion
3079
3012
  //#region src/pattern/structure/wrapped-pattern.ts
3080
3013
  let createStructureWrappedPattern;
3014
+ let createAnyPattern;
3081
3015
  let dispatchPatternPathsWithCaptures;
3082
3016
  let dispatchPatternCompile;
3083
3017
  let dispatchPatternToString$1;
3084
3018
  function registerWrappedPatternFactory(factory) {
3085
3019
  createStructureWrappedPattern = factory;
3086
3020
  }
3021
+ function registerWrappedPatternAny(factory) {
3022
+ createAnyPattern = factory;
3023
+ }
3087
3024
  function registerWrappedPatternDispatch(dispatch) {
3088
3025
  dispatchPatternPathsWithCaptures = dispatch.pathsWithCaptures;
3089
3026
  dispatchPatternCompile = dispatch.compile;
@@ -3117,10 +3054,15 @@ var WrappedPattern = class WrappedPattern {
3117
3054
  }
3118
3055
  /**
3119
3056
  * Creates a new WrappedPattern that matches any wrapped envelope and descends into it.
3120
- * Note: This requires Pattern.any() to be available, so it's set up during registration.
3057
+ *
3058
+ * Mirrors Rust `WrappedPattern::unwrap()` which delegates to
3059
+ * `Self::unwrap_matching(Pattern::any())`. The `any` factory is wired in
3060
+ * during module-load registration to break the circular import on the
3061
+ * top-level `Pattern` type.
3121
3062
  */
3122
3063
  static unwrap() {
3123
- return new WrappedPattern({ type: "Any" });
3064
+ if (createAnyPattern === void 0) throw new Error("WrappedPattern.unwrap() requires Pattern.any factory; not registered");
3065
+ return WrappedPattern.unwrapMatching(createAnyPattern());
3124
3066
  }
3125
3067
  /**
3126
3068
  * Gets the pattern type.
@@ -3196,9 +3138,9 @@ var WrappedPattern = class WrappedPattern {
3196
3138
  switch (this._pattern.type) {
3197
3139
  case "Any": return "wrapped";
3198
3140
  case "Unwrap": {
3199
- const patternStr = dispatchPatternToString$1 !== void 0 ? dispatchPatternToString$1(this._pattern.pattern) : "*";
3200
- if (patternStr === "*") return "unwrap";
3201
- return `unwrap(${patternStr})`;
3141
+ const inner = this._pattern.pattern;
3142
+ if (inner.type === "Meta" && inner.pattern.type === "Any") return "unwrap";
3143
+ return `unwrap(${dispatchPatternToString$1 !== void 0 ? dispatchPatternToString$1(inner) : "?"})`;
3202
3144
  }
3203
3145
  }
3204
3146
  }
@@ -3422,10 +3364,7 @@ function axisChildren(axis, env) {
3422
3364
  case "Wrapped":
3423
3365
  if (envCase.type === "node") {
3424
3366
  const subject = envCase.subject;
3425
- if (subject.isWrapped()) {
3426
- const unwrapped = subject.unwrap();
3427
- if (unwrapped !== void 0) return [[unwrapped, "Content"]];
3428
- }
3367
+ if (subject.isWrapped()) return [[subject.tryUnwrap(), "Content"]];
3429
3368
  } else if (envCase.type === "wrapped") return [[envCase.envelope, "Content"]];
3430
3369
  return [];
3431
3370
  }
@@ -4423,30 +4362,20 @@ var SearchPattern = class SearchPattern {
4423
4362
  return [uniquePaths, /* @__PURE__ */ new Map()];
4424
4363
  }
4425
4364
  /**
4426
- * Walk the envelope tree recursively.
4365
+ * Walk the envelope tree using the canonical `Envelope.walk` traversal.
4366
+ *
4367
+ * Mirrors Rust `bc_envelope::Envelope::walk(false, vec![], visitor)`
4368
+ * which is what `SearchPattern::paths_with_captures` uses. The earlier
4369
+ * port hand-rolled a recursion that double-recursed assertions and
4370
+ * stepped through wrapped subjects manually, producing a different
4371
+ * path order (and extra duplicates that the digest-set deduplication
4372
+ * would partially mask).
4427
4373
  */
4428
4374
  _walkEnvelope(envelope, pathToCurrent, visitor) {
4429
- visitor(envelope, pathToCurrent);
4430
- const subject = envelope.subject();
4431
- const newPath = [...pathToCurrent, envelope];
4432
- if (!subject.digest().equals(envelope.digest())) this._walkEnvelope(subject, newPath, visitor);
4433
- for (const assertion of envelope.assertions()) {
4434
- this._walkEnvelope(assertion, newPath, visitor);
4435
- const predicate = assertion.asPredicate?.();
4436
- if (predicate !== void 0) {
4437
- const assertionPath = [...newPath, assertion];
4438
- this._walkEnvelope(predicate, assertionPath, visitor);
4439
- }
4440
- const object = assertion.asObject?.();
4441
- if (object !== void 0) {
4442
- const assertionPath = [...newPath, assertion];
4443
- this._walkEnvelope(object, assertionPath, visitor);
4444
- }
4445
- }
4446
- if (subject.isWrapped()) {
4447
- const unwrapped = subject.tryUnwrap?.();
4448
- if (unwrapped !== void 0) this._walkEnvelope(unwrapped, newPath, visitor);
4449
- }
4375
+ envelope.walk(false, pathToCurrent, (current, _level, _edge, state) => {
4376
+ visitor(current, state);
4377
+ return [[...state, current], false];
4378
+ });
4450
4379
  }
4451
4380
  paths(haystack) {
4452
4381
  return this.pathsWithCaptures(haystack)[0];
@@ -4575,7 +4504,7 @@ var TraversePattern = class TraversePattern {
4575
4504
  return _patternIsComplex(this._first) || this._rest !== void 0;
4576
4505
  }
4577
4506
  toString() {
4578
- return this.patterns().map((p) => p.toString()).join(" -> ");
4507
+ return this.patterns().map((p) => dispatchPatternToString(p)).join(" -> ");
4579
4508
  }
4580
4509
  /**
4581
4510
  * Equality comparison.
@@ -4924,17 +4853,44 @@ function convertValuePatternToEnvelopePattern(valuePattern) {
4924
4853
  */
4925
4854
  function convertStructurePatternToEnvelopePattern(structurePattern) {
4926
4855
  switch (structurePattern.type) {
4927
- case "Array": return ok({
4928
- type: "Leaf",
4929
- pattern: leafArray(ArrayPattern.fromDcborPattern({
4930
- kind: "Structure",
4931
- pattern: structurePattern
4932
- }))
4933
- });
4934
- case "Map": return ok({
4935
- type: "Leaf",
4936
- pattern: leafMap(MapPattern.any())
4937
- });
4856
+ case "Array": {
4857
+ const inner = structurePattern.pattern;
4858
+ let arrayPattern;
4859
+ switch (inner.variant) {
4860
+ case "Any":
4861
+ arrayPattern = ArrayPattern.any();
4862
+ break;
4863
+ case "Length":
4864
+ arrayPattern = ArrayPattern.fromInterval(inner.length);
4865
+ break;
4866
+ case "Elements":
4867
+ arrayPattern = ArrayPattern.fromDcborArrayPattern(inner);
4868
+ break;
4869
+ }
4870
+ return ok({
4871
+ type: "Leaf",
4872
+ pattern: leafArray(arrayPattern)
4873
+ });
4874
+ }
4875
+ case "Map": {
4876
+ const inner = structurePattern.pattern;
4877
+ let mapPattern;
4878
+ switch (inner.variant) {
4879
+ case "Any":
4880
+ mapPattern = MapPattern.any();
4881
+ break;
4882
+ case "Length":
4883
+ mapPattern = MapPattern.fromInterval(inner.length);
4884
+ break;
4885
+ case "Constraints":
4886
+ mapPattern = MapPattern.any();
4887
+ break;
4888
+ }
4889
+ return ok({
4890
+ type: "Leaf",
4891
+ pattern: leafMap(mapPattern)
4892
+ });
4893
+ }
4938
4894
  case "Tagged": return ok({
4939
4895
  type: "Leaf",
4940
4896
  pattern: leafTag(TaggedPattern.fromDcborPattern(structurePattern.pattern))
@@ -4992,6 +4948,169 @@ function convertMetaPatternToEnvelopePattern(metaPattern, originalPattern) {
4992
4948
  }
4993
4949
  }
4994
4950
  //#endregion
4951
+ //#region src/parse/utils.ts
4952
+ let createCborPattern;
4953
+ let createCborPatternFromDcbor;
4954
+ let createAnyArray;
4955
+ let createArrayWithCount;
4956
+ let createArrayWithRange;
4957
+ let createArrayFromDcborPattern;
4958
+ /**
4959
+ * Register pattern factory functions.
4960
+ * This is called by the pattern module to avoid circular dependencies.
4961
+ */
4962
+ function registerPatternFactories(factories) {
4963
+ createCborPattern = factories.cborPattern;
4964
+ createCborPatternFromDcbor = factories.cborPatternFromDcbor;
4965
+ createAnyArray = factories.anyArray;
4966
+ createArrayWithCount = factories.arrayWithCount;
4967
+ createArrayWithRange = factories.arrayWithRange;
4968
+ createArrayFromDcborPattern = factories.arrayFromDcborPattern;
4969
+ }
4970
+ /**
4971
+ * Skips whitespace in the source string.
4972
+ *
4973
+ * @param src - The source string
4974
+ * @param pos - The current position (modified in place)
4975
+ */
4976
+ function skipWs$1(src, pos) {
4977
+ while (pos.value < src.length) {
4978
+ const ch = src[pos.value];
4979
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f") pos.value++;
4980
+ else break;
4981
+ }
4982
+ }
4983
+ /**
4984
+ * Parses a CBOR value or dcbor-pattern expression.
4985
+ *
4986
+ * @param src - The source string
4987
+ * @returns The parsed pattern and consumed character count, or an error
4988
+ */
4989
+ function parseCborInner(src) {
4990
+ if (createCborPattern === void 0 || createCborPatternFromDcbor === void 0) return err(unknown());
4991
+ const pos = { value: 0 };
4992
+ skipWs$1(src, pos);
4993
+ if (src[pos.value] === "/") {
4994
+ pos.value++;
4995
+ const start = pos.value;
4996
+ let escape = false;
4997
+ while (pos.value < src.length) {
4998
+ const b = src[pos.value];
4999
+ pos.value++;
5000
+ if (escape) {
5001
+ escape = false;
5002
+ continue;
5003
+ }
5004
+ if (b === "\\") {
5005
+ escape = true;
5006
+ continue;
5007
+ }
5008
+ if (b === "/") {
5009
+ const parseResult = (0, _bcts_dcbor_pattern.parse)(src.slice(start, pos.value - 1));
5010
+ if (!parseResult.ok) return err(invalidPattern({
5011
+ start,
5012
+ end: pos.value - 1
5013
+ }));
5014
+ skipWs$1(src, pos);
5015
+ return ok([createCborPatternFromDcbor(parseResult.value), pos.value]);
5016
+ }
5017
+ }
5018
+ return err(unterminatedRegex({
5019
+ start: start - 1,
5020
+ end: pos.value
5021
+ }));
5022
+ }
5023
+ if (src.slice(pos.value, pos.value + 3) === "ur:") {
5024
+ const parseResult = (0, _bcts_dcbor_parse.parseDcborItemPartial)(src.slice(pos.value));
5025
+ if (!parseResult.ok) return err(unknown());
5026
+ const [cborValue, consumed] = parseResult.value;
5027
+ return ok([createCborPattern(cborValue), pos.value + consumed]);
5028
+ }
5029
+ const parseResult = (0, _bcts_dcbor_parse.parseDcborItemPartial)(src.slice(pos.value));
5030
+ if (!parseResult.ok) return err(unknown());
5031
+ const [cborValue, consumed] = parseResult.value;
5032
+ return ok([createCborPattern(cborValue), pos.value + consumed]);
5033
+ }
5034
+ /**
5035
+ * Parses an array pattern inner content.
5036
+ *
5037
+ * @param src - The source string (content between [ and ])
5038
+ * @returns The parsed pattern and consumed character count, or an error
5039
+ */
5040
+ function parseArrayInner(src) {
5041
+ if (createAnyArray === void 0 || createArrayWithCount === void 0 || createArrayWithRange === void 0 || createArrayFromDcborPattern === void 0) return err(unknown());
5042
+ const pos = { value: 0 };
5043
+ skipWs$1(src, pos);
5044
+ if (src[pos.value] === "*") {
5045
+ pos.value++;
5046
+ skipWs$1(src, pos);
5047
+ return ok([createAnyArray(), pos.value]);
5048
+ }
5049
+ if (src[pos.value] === "{") {
5050
+ pos.value++;
5051
+ skipWs$1(src, pos);
5052
+ const startPos = pos.value;
5053
+ while (pos.value < src.length && src[pos.value] !== void 0 && /\d/.test(src[pos.value])) pos.value++;
5054
+ if (startPos === pos.value) return err(invalidRange({
5055
+ start: pos.value,
5056
+ end: pos.value
5057
+ }));
5058
+ const firstNum = parseInt(src.slice(startPos, pos.value), 10);
5059
+ if (Number.isNaN(firstNum)) return err(invalidNumberFormat({
5060
+ start: startPos,
5061
+ end: pos.value
5062
+ }));
5063
+ skipWs$1(src, pos);
5064
+ if (pos.value >= src.length) return err(unexpectedEndOfInput());
5065
+ const ch = src[pos.value];
5066
+ if (ch === "}") {
5067
+ pos.value++;
5068
+ skipWs$1(src, pos);
5069
+ return ok([createArrayWithCount(firstNum), pos.value]);
5070
+ }
5071
+ if (ch === ",") {
5072
+ pos.value++;
5073
+ skipWs$1(src, pos);
5074
+ if (pos.value >= src.length) return err(unexpectedEndOfInput());
5075
+ const nextCh = src[pos.value];
5076
+ if (nextCh === "}") {
5077
+ pos.value++;
5078
+ skipWs$1(src, pos);
5079
+ return ok([createArrayWithRange(firstNum, void 0), pos.value]);
5080
+ }
5081
+ if (nextCh !== void 0 && /\d/.test(nextCh)) {
5082
+ const secondStart = pos.value;
5083
+ while (pos.value < src.length && src[pos.value] !== void 0 && /\d/.test(src[pos.value])) pos.value++;
5084
+ const secondNum = parseInt(src.slice(secondStart, pos.value), 10);
5085
+ if (Number.isNaN(secondNum)) return err(invalidNumberFormat({
5086
+ start: secondStart,
5087
+ end: pos.value
5088
+ }));
5089
+ skipWs$1(src, pos);
5090
+ if (pos.value >= src.length || src[pos.value] !== "}") return err(unexpectedEndOfInput());
5091
+ pos.value++;
5092
+ skipWs$1(src, pos);
5093
+ return ok([createArrayWithRange(firstNum, secondNum), pos.value]);
5094
+ }
5095
+ return err(invalidRange({
5096
+ start: pos.value,
5097
+ end: pos.value
5098
+ }));
5099
+ }
5100
+ return err(invalidRange({
5101
+ start: pos.value,
5102
+ end: pos.value
5103
+ }));
5104
+ }
5105
+ const parseResult = (0, _bcts_dcbor_pattern.parse)(`[${src.slice(pos.value)}]`);
5106
+ if (!parseResult.ok) return err(invalidPattern({
5107
+ start: pos.value,
5108
+ end: src.length
5109
+ }));
5110
+ const consumed = src.length - pos.value;
5111
+ return ok([createArrayFromDcborPattern(parseResult.value), consumed]);
5112
+ }
5113
+ //#endregion
4995
5114
  //#region src/pattern/index.ts
4996
5115
  /**
4997
5116
  * Creates a Leaf pattern.
@@ -5493,7 +5612,6 @@ function registerAllFactories() {
5493
5612
  registerTaggedPatternFactory((p) => patternLeaf(leafTag(p)));
5494
5613
  registerCBORPatternFactory((p) => patternLeaf(leafCbor(p)));
5495
5614
  registerLeafStructurePatternFactory((p) => patternStructure(structureLeaf(p)));
5496
- registerSubjectPatternFactory((p) => patternStructure(structureSubject(p)));
5497
5615
  registerPredicatePatternFactory((p) => patternStructure(structurePredicate(p)));
5498
5616
  registerObjectPatternFactory((p) => patternStructure(structureObject(p)));
5499
5617
  registerAssertionsPatternFactory((p) => patternStructure(structureAssertions(p)));
@@ -5506,17 +5624,24 @@ function registerAllFactories() {
5506
5624
  compile: patternCompile,
5507
5625
  toString: patternToString
5508
5626
  });
5627
+ registerWrappedPatternAny(any);
5628
+ registerAssertionsPatternToStringDispatch(patternToString);
5629
+ registerSubjectPatternDispatch({
5630
+ compile: patternCompile,
5631
+ toString: patternToString
5632
+ });
5509
5633
  registerAnyPatternFactory((p) => patternMeta(metaAny(p)));
5510
- registerAndPatternFactory((p) => patternMeta(metaAnd(p)));
5511
- registerOrPatternFactory((p) => patternMeta(metaOr(p)));
5512
- registerNotPatternFactory((p) => patternMeta(metaNot(p)));
5513
- registerCapturePatternFactory((p) => patternMeta(metaCapture(p)));
5514
- registerSearchPatternFactory((p) => patternMeta(metaSearch(p)));
5515
- registerTraversePatternFactory((p) => patternMeta(metaTraverse(p)));
5516
- registerGroupPatternFactory((p) => patternMeta(metaGroup(p)));
5517
5634
  }
5518
5635
  registerAllFactories();
5519
5636
  registerVMPatternFunctions(patternPathsWithCaptures, patternMatches, patternPaths);
5637
+ registerPatternFactories({
5638
+ cborPattern: (value) => cborValue(value),
5639
+ cborPatternFromDcbor: (pattern) => cborPattern(pattern),
5640
+ anyArray,
5641
+ arrayWithCount: (count) => patternLeaf(leafArray(ArrayPattern.count(count))),
5642
+ arrayWithRange: (min, max) => patternLeaf(leafArray(ArrayPattern.interval(min, max))),
5643
+ arrayFromDcborPattern: (pattern) => patternLeaf(leafArray(ArrayPattern.fromDcborPattern(pattern)))
5644
+ });
5520
5645
  registerPatternMatchFn(patternMatches);
5521
5646
  registerPatternDispatchFns({
5522
5647
  pathsWithCaptures: patternPathsWithCaptures,
@@ -6270,305 +6395,198 @@ var Lexer = class {
6270
6395
  }
6271
6396
  };
6272
6397
  //#endregion
6273
- //#region src/parse/index.ts
6398
+ //#region src/parse/leaf/array-parser.ts
6274
6399
  /**
6275
6400
  * Copyright © 2023-2026 Blockchain Commons, LLC
6276
6401
  * Copyright © 2025-2026 Parity Technologies
6277
6402
  *
6403
+ * Array parser — port of `bc-envelope-pattern-rust`
6404
+ * `parse/leaf/array_parser.rs`.
6278
6405
  *
6279
- * @bcts/envelope-pattern - Parser entry point
6406
+ * Mirrors Rust's flow exactly: after the `[` token has been consumed,
6407
+ * delegate to `utils::parseArrayInner` (which handles `*`, `{n}`, `{n,m}`,
6408
+ * `{n,}` directly and otherwise wraps the body in `[...]` and re-parses
6409
+ * via dcbor-pattern), then expect a closing `]`.
6410
+ *
6411
+ * @module envelope-pattern/parse/leaf/array-parser
6412
+ */
6413
+ function parseArray(lexer) {
6414
+ const inner = parseArrayInner(lexer.remainder());
6415
+ if (!inner.ok) return inner;
6416
+ const [pattern, consumed] = inner.value;
6417
+ lexer.bump(consumed);
6418
+ const close = lexer.next();
6419
+ if (close === void 0) return err(expectedCloseBracket(lexer.span()));
6420
+ if (close.token.type !== "BracketClose") return err(unexpectedToken(close.token, close.span));
6421
+ return ok(pattern);
6422
+ }
6423
+ //#endregion
6424
+ //#region src/parse/leaf/cbor-parser.ts
6425
+ /**
6426
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6427
+ * Copyright © 2025-2026 Parity Technologies
6280
6428
  *
6281
- * This is a 1:1 TypeScript port of bc-envelope-pattern-rust parse/mod.rs
6282
- * Recursive descent parser for Gordian Envelope pattern syntax.
6429
+ * CBOR pattern parser port of `bc-envelope-pattern-rust`
6430
+ * `parse/leaf/cbor_parser.rs`.
6283
6431
  *
6284
- * @module envelope-pattern/parse
6432
+ * Mirrors Rust's flow: lookahead for `(`. If absent, return `any_cbor()`.
6433
+ * Otherwise consume the `(`, delegate to `parseCborInner` (handles
6434
+ * `/regex/`, `ur:…`, and CBOR diagnostic notation), and expect a closing
6435
+ * `)`.
6436
+ *
6437
+ * @module envelope-pattern/parse/leaf/cbor-parser
6285
6438
  */
6439
+ function parseCbor(lexer) {
6440
+ if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyCbor());
6441
+ lexer.next();
6442
+ const innerResult = parseCborInner(lexer.remainder());
6443
+ if (!innerResult.ok) return innerResult;
6444
+ const [pattern, consumed] = innerResult.value;
6445
+ lexer.bump(consumed);
6446
+ const close = lexer.next();
6447
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6448
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6449
+ return ok(pattern);
6450
+ }
6451
+ //#endregion
6452
+ //#region src/parse/leaf/date-parser.ts
6286
6453
  /**
6287
- * Parse a pattern expression string into a Pattern.
6454
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6455
+ * Copyright © 2025-2026 Parity Technologies
6456
+ *
6457
+ * Date content parser — port of `bc-envelope-pattern-rust`
6458
+ * `parse/leaf/date_parser.rs`.
6459
+ *
6460
+ * Mirrors Rust's `Date::from_string`, which accepts a strict ISO-8601
6461
+ * subset, by deferring to dcbor's `CborDate.fromString`. Falls back to JS
6462
+ * `Date.parse` only as a defensive shim — that branch is unreachable for
6463
+ * conformant inputs.
6464
+ *
6465
+ * @module envelope-pattern/parse/leaf/date-parser
6288
6466
  */
6289
- function parse(input) {
6290
- const lexer = new Lexer(input);
6291
- const result = parseOr(lexer);
6292
- if (!result.ok) {
6293
- const dcborResult = (0, _bcts_dcbor_pattern.parse)(input);
6294
- if (dcborResult.ok) return convertDcborPatternToEnvelopePattern$1(dcborResult.value);
6295
- return result;
6467
+ /**
6468
+ * Parse a date pattern of one of the forms accepted by Rust:
6469
+ *
6470
+ * - `/regex/` (regex match against ISO-8601 string)
6471
+ * - `start...end` (inclusive range)
6472
+ * - `start...` (earliest)
6473
+ * - `...end` (latest)
6474
+ * - `iso-8601` (exact)
6475
+ *
6476
+ * Mirrors `parse_date_content` in Rust; uses `CborDate.fromString` so the
6477
+ * accepted formats match Rust's `bc_envelope::prelude::Date::from_string`
6478
+ * exactly rather than the looser JS `Date.parse`.
6479
+ */
6480
+ function parseDateContent(content, span) {
6481
+ if (content.startsWith("/") && content.endsWith("/") && content.length >= 2) {
6482
+ const regexStr = content.slice(1, -1);
6483
+ try {
6484
+ return ok(dateRegex(new RegExp(regexStr)));
6485
+ } catch {
6486
+ return err(invalidRegex(span));
6487
+ }
6488
+ }
6489
+ const ellipsisIdx = content.indexOf("...");
6490
+ if (ellipsisIdx !== -1) {
6491
+ const left = content.slice(0, ellipsisIdx);
6492
+ const right = content.slice(ellipsisIdx + 3);
6493
+ if (left.length === 0 && right.length > 0) {
6494
+ const parsed = parseIsoDateStrict(right);
6495
+ if (parsed === void 0) return err(invalidDateFormat(span));
6496
+ return ok(dateLatest(parsed));
6497
+ }
6498
+ if (left.length > 0 && right.length === 0) {
6499
+ const parsed = parseIsoDateStrict(left);
6500
+ if (parsed === void 0) return err(invalidDateFormat(span));
6501
+ return ok(dateEarliest(parsed));
6502
+ }
6503
+ if (left.length > 0 && right.length > 0) {
6504
+ const start = parseIsoDateStrict(left);
6505
+ const end = parseIsoDateStrict(right);
6506
+ if (start === void 0 || end === void 0) return err(invalidDateFormat(span));
6507
+ return ok(dateRange(start, end));
6508
+ }
6509
+ return err(invalidDateFormat(span));
6510
+ }
6511
+ const parsed = parseIsoDateStrict(content);
6512
+ if (parsed === void 0) return err(invalidDateFormat(span));
6513
+ return ok(date(parsed));
6514
+ }
6515
+ function parseIsoDateStrict(value) {
6516
+ try {
6517
+ return _bcts_dcbor.CborDate.fromString(value);
6518
+ } catch {
6519
+ return;
6296
6520
  }
6297
- const next = lexer.next();
6298
- if (next !== void 0) return err(extraData(next.span));
6299
- return result;
6300
6521
  }
6522
+ //#endregion
6523
+ //#region src/parse/leaf/known-value-parser.ts
6301
6524
  /**
6302
- * Parse a pattern, allowing extra data after the pattern.
6525
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6526
+ * Copyright © 2025-2026 Parity Technologies
6527
+ *
6528
+ * Helpers for parsing the body of a `'…'` (single-quoted) known-value
6529
+ * literal. Mirrors the inline body of Rust's `Token::SingleQuotedPattern`
6530
+ * branch in `parse_primary`:
6531
+ *
6532
+ * - If the contents are a valid `u64`, build `Pattern::known_value(...)`.
6533
+ * - Otherwise, build `Pattern::known_value_named(...)`.
6534
+ *
6535
+ * The earlier port duck-typed a fake `KnownValue`; this version uses the
6536
+ * real `KnownValue` constructor so all subsequent KnownValue methods work
6537
+ * (e.g., `taggedCbor()`, `name()`, etc.).
6538
+ *
6539
+ * @module envelope-pattern/parse/leaf/known-value-parser
6303
6540
  */
6304
- function parsePartial(input) {
6305
- const lexer = new Lexer(input);
6306
- const result = parseOr(lexer);
6307
- if (!result.ok) return result;
6308
- return ok([result.value, lexer.position]);
6309
- }
6310
6541
  /**
6311
- * Convert a dcbor-pattern Pattern to an envelope-pattern Pattern.
6542
+ * Maximum value of a Rust `u64`. Used to reject literals that would
6543
+ * silently wrap or lose precision when constructing a `KnownValue`.
6312
6544
  */
6313
- function convertDcborPatternToEnvelopePattern$1(_dcborPattern) {
6314
- return ok(any());
6315
- }
6545
+ const U64_MAX = 18446744073709551615n;
6316
6546
  /**
6317
- * Parse an Or expression: expr (| expr)*
6547
+ * Parse the inner contents of a `'…'` known-value pattern token.
6548
+ *
6549
+ * Mirrors the Rust dispatch
6550
+ * ```ignore
6551
+ * if let Ok(value) = content.parse::<u64>() {
6552
+ * Pattern::known_value(KnownValue::new(value))
6553
+ * } else {
6554
+ * Pattern::known_value_named(content)
6555
+ * }
6556
+ * ```
6557
+ * but uses BigInt parsing to preserve full `u64` range — the previous
6558
+ * `parseInt(...)` path silently truncated above `2^53-1`.
6318
6559
  */
6319
- function parseOr(lexer) {
6320
- const patterns = [];
6321
- const first = parseTraverse(lexer);
6322
- if (!first.ok) return first;
6323
- patterns.push(first.value);
6324
- while (true) {
6325
- if (lexer.peekToken()?.token.type !== "Or") break;
6326
- lexer.next();
6327
- const nextExpr = parseTraverse(lexer);
6328
- if (!nextExpr.ok) return nextExpr;
6329
- patterns.push(nextExpr.value);
6330
- }
6331
- if (patterns.length === 1) return ok(patterns[0]);
6332
- return ok(or(patterns));
6333
- }
6334
- /**
6335
- * Parse a Traverse expression: expr (-> expr)*
6336
- */
6337
- function parseTraverse(lexer) {
6338
- const patterns = [];
6339
- const first = parseAnd(lexer);
6340
- if (!first.ok) return first;
6341
- patterns.push(first.value);
6342
- while (true) {
6343
- if (lexer.peekToken()?.token.type !== "Traverse") break;
6344
- lexer.next();
6345
- const nextExpr = parseAnd(lexer);
6346
- if (!nextExpr.ok) return nextExpr;
6347
- patterns.push(nextExpr.value);
6348
- }
6349
- if (patterns.length === 1) return ok(patterns[0]);
6350
- return ok(traverse(patterns));
6351
- }
6352
- /**
6353
- * Parse an And expression: expr (& expr)*
6354
- */
6355
- function parseAnd(lexer) {
6356
- const patterns = [];
6357
- const first = parseNot(lexer);
6358
- if (!first.ok) return first;
6359
- patterns.push(first.value);
6360
- while (true) {
6361
- if (lexer.peekToken()?.token.type !== "And") break;
6362
- lexer.next();
6363
- const nextExpr = parseNot(lexer);
6364
- if (!nextExpr.ok) return nextExpr;
6365
- patterns.push(nextExpr.value);
6366
- }
6367
- if (patterns.length === 1) return ok(patterns[0]);
6368
- return ok(and(patterns));
6560
+ function parseKnownValueContent(content) {
6561
+ if (isU64Literal(content)) return ok(knownValue(new _bcts_known_values.KnownValue(BigInt(content))));
6562
+ return ok(patternLeaf(leafKnownValue(KnownValuePattern.named(content))));
6369
6563
  }
6370
- /**
6371
- * Parse a Not expression: !? group
6372
- */
6373
- function parseNot(lexer) {
6374
- if (lexer.peekToken()?.token.type === "Not") {
6375
- lexer.next();
6376
- const inner = parseGroup(lexer);
6377
- if (!inner.ok) return inner;
6378
- return ok(notMatching(inner.value));
6564
+ function isU64Literal(content) {
6565
+ if (content.length === 0) return false;
6566
+ for (let i = 0; i < content.length; i++) {
6567
+ const c = content.charCodeAt(i);
6568
+ if (c < 48 || c > 57) return false;
6379
6569
  }
6380
- return parseGroup(lexer);
6381
- }
6382
- /**
6383
- * Parse a Group expression: primary quantifier?
6384
- */
6385
- function parseGroup(lexer) {
6386
- const primary = parsePrimary(lexer);
6387
- if (!primary.ok) return primary;
6388
- const next = lexer.peekToken();
6389
- if (next === void 0) return primary;
6390
- const tokenType = next.token.type;
6391
- let quantifier;
6392
- if (tokenType === "RepeatZeroOrMore") {
6393
- lexer.next();
6394
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Greedy);
6395
- } else if (tokenType === "RepeatZeroOrMoreLazy") {
6396
- lexer.next();
6397
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Lazy);
6398
- } else if (tokenType === "RepeatZeroOrMorePossessive") {
6399
- lexer.next();
6400
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Possessive);
6401
- } else if (tokenType === "RepeatOneOrMore") {
6402
- lexer.next();
6403
- quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Greedy);
6404
- } else if (tokenType === "RepeatOneOrMoreLazy") {
6405
- lexer.next();
6406
- quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Lazy);
6407
- } else if (tokenType === "RepeatOneOrMorePossessive") {
6408
- lexer.next();
6409
- quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Possessive);
6410
- } else if (tokenType === "RepeatZeroOrOne") {
6411
- lexer.next();
6412
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Greedy);
6413
- } else if (tokenType === "RepeatZeroOrOneLazy") {
6414
- lexer.next();
6415
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Lazy);
6416
- } else if (tokenType === "RepeatZeroOrOnePossessive") {
6417
- lexer.next();
6418
- quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Possessive);
6419
- } else if (tokenType === "Range") {
6420
- lexer.next();
6421
- if (!next.token.value.ok) return err(next.token.value.error);
6422
- quantifier = next.token.value.value;
6423
- } else return primary;
6424
- return ok(repeat(primary.value, quantifier.min(), quantifier.max(), quantifier.reluctance()));
6425
- }
6426
- /**
6427
- * Parse a primary expression (atoms and structure keywords).
6428
- */
6429
- function parsePrimary(lexer) {
6430
- const tokenResult = lexer.next();
6431
- if (tokenResult === void 0) return err(unexpectedEndOfInput());
6432
- const { token, span } = tokenResult;
6433
- switch (token.type) {
6434
- case "Search": return parseSearch(lexer);
6435
- case "Node": return parseNode(lexer);
6436
- case "Assertion": return parseAssertion(lexer);
6437
- case "AssertionPred": return parseAssertionPred(lexer);
6438
- case "AssertionObj": return parseAssertionObj(lexer);
6439
- case "Digest": return parseDigest(lexer);
6440
- case "Obj": return parseObject(lexer);
6441
- case "Obscured": return ok(obscured());
6442
- case "Elided": return ok(elided());
6443
- case "Encrypted": return ok(encrypted());
6444
- case "Compressed": return ok(compressed());
6445
- case "Pred": return parsePredicate(lexer);
6446
- case "Subject": return parseSubject(lexer);
6447
- case "Wrapped": return ok(wrapped());
6448
- case "Unwrap": return parseUnwrap(lexer);
6449
- case "Leaf": return ok(leaf());
6450
- case "GroupName": return parseCapture(lexer, token.name);
6451
- case "ParenOpen": return parseParenGroup(lexer);
6452
- case "Cbor": return parseCbor(lexer);
6453
- case "RepeatZeroOrMore": return ok(any());
6454
- case "BoolKeyword": return ok(anyBool());
6455
- case "BoolTrue": return ok(bool(true));
6456
- case "BoolFalse": return ok(bool(false));
6457
- case "NumberKeyword": return ok(anyNumber());
6458
- case "TextKeyword": return ok(anyText());
6459
- case "StringLiteral":
6460
- if (!token.value.ok) return err(token.value.error);
6461
- return ok(text(token.value.value));
6462
- case "UnsignedInteger":
6463
- if (!token.value.ok) return err(token.value.error);
6464
- return parseNumberRangeOrComparison(lexer, token.value.value);
6465
- case "Integer":
6466
- if (!token.value.ok) return err(token.value.error);
6467
- return parseNumberRangeOrComparison(lexer, token.value.value);
6468
- case "Float":
6469
- if (!token.value.ok) return err(token.value.error);
6470
- return parseNumberRangeOrComparison(lexer, token.value.value);
6471
- case "GreaterThanOrEqual": return parseComparisonNumber(lexer, ">=");
6472
- case "LessThanOrEqual": return parseComparisonNumber(lexer, "<=");
6473
- case "GreaterThan": return parseComparisonNumber(lexer, ">");
6474
- case "LessThan": return parseComparisonNumber(lexer, "<");
6475
- case "NaN": return ok(patternLeaf(leafNumber(NumberPattern.nan())));
6476
- case "Infinity": return ok(number(Infinity));
6477
- case "NegativeInfinity": return ok(number(-Infinity));
6478
- case "Regex":
6479
- if (!token.value.ok) return err(token.value.error);
6480
- try {
6481
- return ok(textRegex(new RegExp(token.value.value)));
6482
- } catch {
6483
- return err(invalidRegex(span));
6484
- }
6485
- case "BracketOpen": return parseArray(lexer);
6486
- case "ByteString": return ok(anyByteString());
6487
- case "HexPattern":
6488
- if (!token.value.ok) return err(token.value.error);
6489
- return ok(byteString(token.value.value));
6490
- case "HexBinaryRegex":
6491
- if (!token.value.ok) return err(token.value.error);
6492
- try {
6493
- return ok(patternLeaf(leafByteString(ByteStringPattern.regex(new RegExp(token.value.value)))));
6494
- } catch {
6495
- return err(invalidRegex(span));
6496
- }
6497
- case "DateKeyword": return ok(anyDate());
6498
- case "DatePattern":
6499
- if (!token.value.ok) return err(token.value.error);
6500
- return parseDateContent(token.value.value, span);
6501
- case "Tagged": return parseTag(lexer);
6502
- case "Known": return ok(anyKnownValue());
6503
- case "SingleQuotedPattern":
6504
- if (!token.value.ok) return err(token.value.error);
6505
- return parseKnownValueContent(token.value.value);
6506
- case "SingleQuotedRegex":
6507
- if (!token.value.ok) return err(token.value.error);
6508
- try {
6509
- return ok(patternLeaf(leafKnownValue(KnownValuePattern.regex(new RegExp(token.value.value)))));
6510
- } catch {
6511
- return err(invalidRegex(span));
6512
- }
6513
- case "Null": return ok(nullPattern());
6514
- case "And":
6515
- case "Or":
6516
- case "Not":
6517
- case "Traverse":
6518
- case "RepeatZeroOrMoreLazy":
6519
- case "RepeatZeroOrMorePossessive":
6520
- case "RepeatOneOrMore":
6521
- case "RepeatOneOrMoreLazy":
6522
- case "RepeatOneOrMorePossessive":
6523
- case "RepeatZeroOrOne":
6524
- case "RepeatZeroOrOneLazy":
6525
- case "RepeatZeroOrOnePossessive":
6526
- case "ParenClose":
6527
- case "BracketClose":
6528
- case "Comma":
6529
- case "Ellipsis":
6530
- case "Range":
6531
- case "Identifier": return err(unexpectedToken(token, span));
6570
+ try {
6571
+ const value = BigInt(content);
6572
+ return value >= 0n && value <= U64_MAX;
6573
+ } catch {
6574
+ return false;
6532
6575
  }
6533
6576
  }
6577
+ //#endregion
6578
+ //#region src/parse/leaf/number-parser.ts
6534
6579
  /**
6535
- * Parse a parenthesized group expression.
6536
- */
6537
- function parseParenGroup(lexer) {
6538
- const inner = parseOr(lexer);
6539
- if (!inner.ok) return inner;
6540
- if (lexer.next()?.token.type !== "ParenClose") return err({
6541
- type: "ExpectedCloseParen",
6542
- span: lexer.span()
6543
- });
6544
- return ok(group(inner.value));
6545
- }
6546
- /**
6547
- * Parse a capture group: @name pattern
6548
- */
6549
- function parseCapture(lexer, name) {
6550
- const inner = parseGroup(lexer);
6551
- if (!inner.ok) return inner;
6552
- return ok(capture(name, inner.value));
6553
- }
6554
- /**
6555
- * Parse a search pattern: search(pattern)
6580
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6581
+ * Copyright © 2025-2026 Parity Technologies
6582
+ *
6583
+ * Number parsers — port of `bc-envelope-pattern-rust` `parse/leaf/number_parser.rs`.
6584
+ *
6585
+ * @module envelope-pattern/parse/leaf/number-parser
6556
6586
  */
6557
- function parseSearch(lexer) {
6558
- if (lexer.next()?.token.type !== "ParenOpen") return err({
6559
- type: "ExpectedOpenParen",
6560
- span: lexer.span()
6561
- });
6562
- const inner = parseOr(lexer);
6563
- if (!inner.ok) return inner;
6564
- if (lexer.next()?.token.type !== "ParenClose") return err({
6565
- type: "ExpectedCloseParen",
6566
- span: lexer.span()
6567
- });
6568
- return ok(search(inner.value));
6569
- }
6570
6587
  /**
6571
- * Parse number with possible range or comparison.
6588
+ * Parses an optional `...end` suffix following an already-consumed number,
6589
+ * mirroring Rust `parse_number_range_or_comparison`.
6572
6590
  */
6573
6591
  function parseNumberRangeOrComparison(lexer, firstValue) {
6574
6592
  const next = lexer.peekToken();
@@ -6578,10 +6596,7 @@ function parseNumberRangeOrComparison(lexer, firstValue) {
6578
6596
  const endToken = lexer.next();
6579
6597
  if (endToken === void 0) return err(unexpectedEndOfInput());
6580
6598
  let endValue;
6581
- if (endToken.token.type === "UnsignedInteger" || endToken.token.type === "Integer") {
6582
- if (!endToken.token.value.ok) return err(endToken.token.value.error);
6583
- endValue = endToken.token.value.value;
6584
- } else if (endToken.token.type === "Float") {
6599
+ if (endToken.token.type === "UnsignedInteger" || endToken.token.type === "Integer" || endToken.token.type === "Float") {
6585
6600
  if (!endToken.token.value.ok) return err(endToken.token.value.error);
6586
6601
  endValue = endToken.token.value.value;
6587
6602
  } else return err(unexpectedToken(endToken.token, endToken.span));
@@ -6590,16 +6605,14 @@ function parseNumberRangeOrComparison(lexer, firstValue) {
6590
6605
  return ok(number(firstValue));
6591
6606
  }
6592
6607
  /**
6593
- * Parse comparison number: >=n, <=n, >n, <n
6608
+ * Parses a number following a comparison operator, mirroring Rust
6609
+ * `parse_comparison_number`.
6594
6610
  */
6595
6611
  function parseComparisonNumber(lexer, op) {
6596
6612
  const numToken = lexer.next();
6597
6613
  if (numToken === void 0) return err(unexpectedEndOfInput());
6598
6614
  let value;
6599
- if (numToken.token.type === "UnsignedInteger" || numToken.token.type === "Integer") {
6600
- if (!numToken.token.value.ok) return err(numToken.token.value.error);
6601
- value = numToken.token.value.value;
6602
- } else if (numToken.token.type === "Float") {
6615
+ if (numToken.token.type === "UnsignedInteger" || numToken.token.type === "Integer" || numToken.token.type === "Float") {
6603
6616
  if (!numToken.token.value.ok) return err(numToken.token.value.error);
6604
6617
  value = numToken.token.value.value;
6605
6618
  } else return err(unexpectedToken(numToken.token, numToken.span));
@@ -6608,300 +6621,812 @@ function parseComparisonNumber(lexer, op) {
6608
6621
  case "<=": return ok(patternLeaf(leafNumber(NumberPattern.lessThanOrEqual(value))));
6609
6622
  case ">": return ok(numberGreaterThan(value));
6610
6623
  case "<": return ok(numberLessThan(value));
6611
- default: return ok(number(value));
6612
6624
  }
6613
6625
  }
6626
+ //#endregion
6627
+ //#region src/parse/leaf/tag-parser.ts
6614
6628
  /**
6615
- * Parse an array pattern.
6629
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6630
+ * Copyright © 2025-2026 Parity Technologies
6631
+ *
6632
+ * Tag parser — port of `bc-envelope-pattern-rust`
6633
+ * `parse/leaf/tag_parser.rs`.
6634
+ *
6635
+ * Mirrors the Rust dispatch exactly: lookahead for `(`; if absent, return
6636
+ * the bare `any_tag()`. Otherwise build a synthetic dcbor-pattern
6637
+ * expression `tagged(<inner>)`, parse it via `@bcts/dcbor-pattern`, and
6638
+ * extract the resulting `TaggedPattern` to wrap as an envelope-pattern
6639
+ * leaf. This keeps the **full** tag selector (number, name, regex)
6640
+ * intact — the previous port discarded the tag value entirely.
6641
+ *
6642
+ * @module envelope-pattern/parse/leaf/tag-parser
6616
6643
  */
6617
- function parseArray(lexer) {
6618
- const first = lexer.peekToken();
6619
- if (first === void 0) return err(unexpectedEndOfInput());
6620
- if (first.token.type === "BracketClose") {
6621
- lexer.next();
6622
- return ok(patternLeaf(leafArray(ArrayPattern.count(0))));
6623
- }
6624
- if (first.token.type === "RepeatZeroOrMore") {
6625
- lexer.next();
6626
- if (lexer.next()?.token.type !== "BracketClose") return err({
6627
- type: "ExpectedCloseBracket",
6628
- span: lexer.span()
6629
- });
6630
- return ok(anyArray());
6631
- }
6632
- const patterns = [];
6633
- while (true) {
6634
- const next = lexer.peekToken();
6635
- if (next === void 0) return err(unexpectedEndOfInput());
6636
- if (next.token.type === "BracketClose") {
6637
- lexer.next();
6638
- break;
6639
- }
6640
- const pattern = parseOr(lexer);
6641
- if (!pattern.ok) return pattern;
6642
- patterns.push(pattern.value);
6643
- const afterPattern = lexer.peekToken();
6644
- if (afterPattern === void 0) return err(unexpectedEndOfInput());
6645
- if (afterPattern.token.type === "Comma") lexer.next();
6646
- else if (afterPattern.token.type !== "BracketClose") return err(unexpectedToken(afterPattern.token, afterPattern.span));
6647
- }
6648
- if (patterns.length === 0) return ok(patternLeaf(leafArray(ArrayPattern.count(0))));
6649
- return ok(patternLeaf(leafArray(ArrayPattern.withPatterns(patterns))));
6650
- }
6651
6644
  /**
6652
- * Parse a tag pattern.
6645
+ * Parse `tagged` and `tagged(...)` patterns.
6653
6646
  */
6654
6647
  function parseTag(lexer) {
6655
- if (lexer.next()?.token.type !== "ParenOpen") return ok(anyTag());
6656
- const tagToken = lexer.next();
6657
- if (tagToken === void 0) return err(unexpectedEndOfInput());
6658
- if (tagToken.token.type !== "UnsignedInteger") return err(unexpectedToken(tagToken.token, tagToken.span));
6659
- if (!tagToken.token.value.ok) return err(tagToken.token.value.error);
6660
- if (lexer.next()?.token.type !== "ParenClose") return err({
6661
- type: "ExpectedCloseParen",
6662
- span: lexer.span()
6663
- });
6664
- return ok(anyTag());
6665
- }
6666
- /**
6667
- * Parse date content from date'...' pattern.
6668
- */
6669
- function parseDateContent(content, span) {
6670
- if (content.startsWith("/") && content.endsWith("/")) {
6671
- const regexStr = content.slice(1, -1);
6672
- try {
6673
- return ok(dateRegex(new RegExp(regexStr)));
6674
- } catch {
6675
- return err(invalidRegex(span));
6648
+ if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyTag());
6649
+ lexer.next();
6650
+ const remainder = lexer.remainder();
6651
+ const closeIdx = findMatchingCloseParen(remainder);
6652
+ if (closeIdx === void 0) return err(expectedCloseParen(lexer.span()));
6653
+ const innerContent = remainder.slice(0, closeIdx);
6654
+ const dcborResult = (0, _bcts_dcbor_pattern.parse)(`tagged(${innerContent})`);
6655
+ if (dcborResult.ok) {
6656
+ const dcborPattern = dcborResult.value;
6657
+ if (dcborPattern.kind === "Structure" && dcborPattern.pattern.type === "Tagged") {
6658
+ lexer.bump(closeIdx);
6659
+ const close = lexer.next();
6660
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6661
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6662
+ return ok(patternLeaf(leafTag(TaggedPattern.fromDcborPattern(dcborPattern.pattern.pattern))));
6676
6663
  }
6677
6664
  }
6678
- const rangeIdx = content.indexOf("...");
6679
- if (rangeIdx !== -1) {
6680
- const left = content.slice(0, rangeIdx).trim();
6681
- const right = content.slice(rangeIdx + 3).trim();
6682
- if (left.length === 0 && right.length > 0) {
6683
- const parsed = Date.parse(right);
6684
- if (isNaN(parsed)) return err({
6685
- type: "InvalidDateFormat",
6686
- span
6687
- });
6688
- return ok(dateLatest(_bcts_dcbor.CborDate.fromDatetime(new Date(parsed))));
6689
- }
6690
- if (left.length > 0 && right.length === 0) {
6691
- const parsed = Date.parse(left);
6692
- if (isNaN(parsed)) return err({
6693
- type: "InvalidDateFormat",
6694
- span
6695
- });
6696
- return ok(dateEarliest(_bcts_dcbor.CborDate.fromDatetime(new Date(parsed))));
6665
+ const fallback = parseTagInner(innerContent);
6666
+ if (!fallback.ok) return fallback;
6667
+ lexer.bump(closeIdx);
6668
+ const close = lexer.next();
6669
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6670
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6671
+ return ok(fallback.value);
6672
+ }
6673
+ /**
6674
+ * Locate the index of the closing `)` matching the `(` that has already
6675
+ * been consumed by `parseTag`. Mirrors Rust `find_matching_paren`.
6676
+ */
6677
+ function findMatchingCloseParen(src) {
6678
+ let depth = 0;
6679
+ for (let i = 0; i < src.length; i++) {
6680
+ const ch = src.charCodeAt(i);
6681
+ if (ch === 40) depth += 1;
6682
+ else if (ch === 41) {
6683
+ if (depth === 0) return i;
6684
+ depth -= 1;
6697
6685
  }
6698
- if (left.length > 0 && right.length > 0) {
6699
- const parsedStart = Date.parse(left);
6700
- const parsedEnd = Date.parse(right);
6701
- if (isNaN(parsedStart) || isNaN(parsedEnd)) return err({
6702
- type: "InvalidDateFormat",
6703
- span
6704
- });
6705
- return ok(dateRange(_bcts_dcbor.CborDate.fromDatetime(new Date(parsedStart)), _bcts_dcbor.CborDate.fromDatetime(new Date(parsedEnd))));
6706
- }
6707
- return err({
6708
- type: "InvalidDateFormat",
6709
- span
6710
- });
6711
6686
  }
6712
- const parsed = Date.parse(content);
6713
- if (isNaN(parsed)) return err({
6714
- type: "InvalidDateFormat",
6715
- span
6716
- });
6717
- return ok(date(_bcts_dcbor.CborDate.fromDatetime(new Date(parsed))));
6718
6687
  }
6719
6688
  /**
6720
- * Parse known value content from '...' pattern.
6689
+ * Fallback for `tagged(N)` and `tagged(name)` when the full delegation
6690
+ * to dcbor-pattern fails. Mirrors Rust `parse_tag_inner`.
6721
6691
  */
6722
- function parseKnownValueContent(content) {
6723
- const numValue = parseInt(content, 10);
6724
- if (!isNaN(numValue)) return ok(knownValue({ value: () => BigInt(numValue) }));
6725
- return ok(patternLeaf(leafKnownValue(KnownValuePattern.named(content))));
6692
+ function parseTagInner(src) {
6693
+ const trimmed = src.trim();
6694
+ if (trimmed.length === 0) return err(unexpectedEndOfInput());
6695
+ if (trimmed.startsWith("/")) return err(unexpectedEndOfInput());
6696
+ if (/^\d+$/.test(trimmed)) try {
6697
+ const dcborResult = (0, _bcts_dcbor_pattern.parse)(`tagged(${trimmed})`);
6698
+ if (dcborResult.ok && dcborResult.value.kind === "Structure" && dcborResult.value.pattern.type === "Tagged") return ok(patternLeaf(leafTag(TaggedPattern.fromDcborPattern(dcborResult.value.pattern.pattern))));
6699
+ } catch {}
6700
+ const dcborResult = (0, _bcts_dcbor_pattern.parse)(`tagged(${trimmed})`);
6701
+ if (dcborResult.ok && dcborResult.value.kind === "Structure" && dcborResult.value.pattern.type === "Tagged") return ok(patternLeaf(leafTag(TaggedPattern.fromDcborPattern(dcborResult.value.pattern.pattern))));
6702
+ return err(unexpectedEndOfInput());
6726
6703
  }
6704
+ //#endregion
6705
+ //#region src/parse/structure/assertion-parser.ts
6727
6706
  /**
6728
- * Parse CBOR pattern.
6707
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6708
+ * Copyright © 2025-2026 Parity Technologies
6729
6709
  *
6730
- * Matches Rust parse_cbor: tries dcbor-pattern regex first (/keyword/),
6731
- * then CBOR diagnostic notation via parseDcborItemPartial, then falls
6732
- * back to parseOr for envelope pattern expressions.
6710
+ * Assertion parser port of `bc-envelope-pattern-rust`
6711
+ * `parse/structure/assertion_parser.rs`.
6712
+ *
6713
+ * Note: Rust's `parse_assertion` ignores its lexer entirely and always
6714
+ * returns `Pattern::any_assertion()`. There is intentionally **no**
6715
+ * `assert(pred, obj)` syntax — predicate/object filters are written via
6716
+ * the dedicated `assertpred(...)` / `assertobj(...)` keywords.
6717
+ *
6718
+ * @module envelope-pattern/parse/structure/assertion-parser
6733
6719
  */
6734
- function parseCbor(lexer) {
6735
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyCbor());
6736
- lexer.next();
6737
- if (lexer.peek() === "/") {
6738
- const regexTokenResult = lexer.next();
6739
- if (regexTokenResult?.token.type === "Regex") {
6740
- const regexToken = regexTokenResult.token;
6741
- if (!regexToken.value.ok) return err(regexToken.value.error);
6742
- const keyword = regexToken.value.value;
6743
- const dcborResult = (0, _bcts_dcbor_pattern.parse)(keyword);
6744
- if (!dcborResult.ok) return err(unexpectedToken(regexToken, regexTokenResult.span));
6745
- if (lexer.next()?.token.type !== "ParenClose") return err({
6746
- type: "ExpectedCloseParen",
6747
- span: lexer.span()
6748
- });
6749
- return ok(cborPattern(dcborResult.value));
6750
- }
6751
- }
6752
- const cborResult = (0, _bcts_dcbor_parse.parseDcborItemPartial)(lexer.remainder());
6753
- if (cborResult.ok) {
6754
- const [cborData, consumed] = cborResult.value;
6755
- lexer.bump(consumed);
6756
- while (lexer.peek() === " " || lexer.peek() === " " || lexer.peek() === "\n") lexer.bump(1);
6757
- if (lexer.next()?.token.type !== "ParenClose") return err({
6758
- type: "ExpectedCloseParen",
6759
- span: lexer.span()
6760
- });
6761
- return ok(cborValue(cborData));
6762
- }
6763
- const inner = parseOr(lexer);
6764
- if (!inner.ok) return inner;
6765
- if (lexer.next()?.token.type !== "ParenClose") return err({
6766
- type: "ExpectedCloseParen",
6767
- span: lexer.span()
6768
- });
6769
- return inner;
6720
+ function parseAssertion(_lexer) {
6721
+ return ok(anyAssertion());
6770
6722
  }
6771
- function parseNode(lexer) {
6772
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyNode());
6773
- lexer.next();
6774
- const afterParen = lexer.peekToken();
6775
- if (afterParen?.token.type === "Range") {
6776
- lexer.next();
6777
- const rangeToken = afterParen.token;
6778
- if (!rangeToken.value.ok) return err(rangeToken.value.error);
6779
- const interval = rangeToken.value.value.interval();
6780
- if (lexer.next()?.token.type !== "ParenClose") return err({
6781
- type: "ExpectedCloseParen",
6782
- span: lexer.span()
6783
- });
6784
- return ok(patternStructure(structureNode(NodePattern.fromInterval(interval))));
6785
- }
6723
+ //#endregion
6724
+ //#region src/parse/structure/assertion-obj-parser.ts
6725
+ /**
6726
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6727
+ * Copyright © 2025-2026 Parity Technologies
6728
+ *
6729
+ * Assertion-object parser — port of
6730
+ * `bc-envelope-pattern-rust/src/parse/structure/assertion_obj_parser.rs`.
6731
+ *
6732
+ * Requires `assertobj(<pattern>)`. The bare `assertobj` keyword is a
6733
+ * syntax error in Rust; we now mirror that behaviour.
6734
+ *
6735
+ * @module envelope-pattern/parse/structure/assertion-obj-parser
6736
+ */
6737
+ function parseAssertionObj(lexer) {
6738
+ const open = lexer.next();
6739
+ if (open === void 0) return err(unexpectedEndOfInput());
6740
+ if (open.token.type !== "ParenOpen") return err(unexpectedToken(open.token, open.span));
6786
6741
  const inner = parseOr(lexer);
6787
6742
  if (!inner.ok) return inner;
6788
- if (lexer.next()?.token.type !== "ParenClose") return err({
6789
- type: "ExpectedCloseParen",
6790
- span: lexer.span()
6791
- });
6792
- return ok(patternStructure(structureNode(NodePattern.withSubject(inner.value))));
6793
- }
6794
- function parseAssertion(lexer) {
6795
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyAssertion());
6796
- lexer.next();
6797
- const pred = parseOr(lexer);
6798
- if (!pred.ok) return pred;
6799
- const comma = lexer.next();
6800
- if (comma?.token.type !== "Comma") return err(unexpectedToken(comma?.token ?? { type: "Null" }, comma?.span ?? lexer.span()));
6801
- const obj = parseOr(lexer);
6802
- if (!obj.ok) return obj;
6803
- if (lexer.next()?.token.type !== "ParenClose") return err({
6804
- type: "ExpectedCloseParen",
6805
- span: lexer.span()
6806
- });
6807
- return ok(patternStructure(structureAssertions(AssertionsPattern.withBoth(pred.value, obj.value))));
6743
+ const close = lexer.next();
6744
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6745
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6746
+ return ok(assertionWithObject(inner.value));
6808
6747
  }
6748
+ //#endregion
6749
+ //#region src/parse/structure/assertion-pred-parser.ts
6750
+ /**
6751
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6752
+ * Copyright © 2025-2026 Parity Technologies
6753
+ *
6754
+ * Assertion-predicate parser — port of
6755
+ * `bc-envelope-pattern-rust/src/parse/structure/assertion_pred_parser.rs`.
6756
+ *
6757
+ * Requires `assertpred(<pattern>)`. The bare `assertpred` keyword is a
6758
+ * syntax error in Rust (it errors on `UnexpectedEndOfInput` /
6759
+ * `UnexpectedToken`); we now mirror that behaviour.
6760
+ *
6761
+ * @module envelope-pattern/parse/structure/assertion-pred-parser
6762
+ */
6809
6763
  function parseAssertionPred(lexer) {
6810
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyAssertion());
6811
- lexer.next();
6764
+ const open = lexer.next();
6765
+ if (open === void 0) return err(unexpectedEndOfInput());
6766
+ if (open.token.type !== "ParenOpen") return err(unexpectedToken(open.token, open.span));
6812
6767
  const inner = parseOr(lexer);
6813
6768
  if (!inner.ok) return inner;
6814
- if (lexer.next()?.token.type !== "ParenClose") return err({
6815
- type: "ExpectedCloseParen",
6816
- span: lexer.span()
6817
- });
6769
+ const close = lexer.next();
6770
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6771
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6818
6772
  return ok(assertionWithPredicate(inner.value));
6819
6773
  }
6820
- function parseAssertionObj(lexer) {
6821
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyAssertion());
6822
- lexer.next();
6823
- const inner = parseOr(lexer);
6824
- if (!inner.ok) return inner;
6825
- if (lexer.next()?.token.type !== "ParenClose") return err({
6826
- type: "ExpectedCloseParen",
6827
- span: lexer.span()
6828
- });
6829
- return ok(assertionWithObject(inner.value));
6774
+ //#endregion
6775
+ //#region src/parse/structure/compressed-parser.ts
6776
+ /**
6777
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6778
+ * Copyright © 2025-2026 Parity Technologies
6779
+ *
6780
+ * Compressed parser — port of `bc-envelope-pattern-rust`
6781
+ * `parse/structure/compressed_parser.rs`.
6782
+ *
6783
+ * @module envelope-pattern/parse/structure/compressed-parser
6784
+ */
6785
+ function parseCompressed(_lexer) {
6786
+ return ok(compressed());
6830
6787
  }
6788
+ //#endregion
6789
+ //#region src/parse/structure/digest-parser.ts
6790
+ /**
6791
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6792
+ * Copyright © 2025-2026 Parity Technologies
6793
+ *
6794
+ * Digest parser — port of `bc-envelope-pattern-rust`
6795
+ * `parse/structure/digest_parser.rs`.
6796
+ *
6797
+ * Mirrors Rust exactly:
6798
+ *
6799
+ * - Requires `digest(...)` — bare `digest` is an `UnexpectedEndOfInput`
6800
+ * error (the previous TS port silently returned `digest(any)`).
6801
+ * - Inside the parens, accepts either a UR string (`ur:digest/...`,
6802
+ * parsed via `Digest.fromURString`) or a hex byte prefix.
6803
+ * - Hex prefixes must have even length and not exceed `Digest.DIGEST_SIZE`
6804
+ * bytes; otherwise the parser surfaces `InvalidHexString`.
6805
+ *
6806
+ * @module envelope-pattern/parse/structure/digest-parser
6807
+ */
6808
+ const DIGEST_SIZE = _bcts_envelope.Digest.DIGEST_SIZE;
6831
6809
  function parseDigest(lexer) {
6832
- if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(patternStructure(structureDigest(DigestPattern.any())));
6833
- lexer.next();
6834
- const digestToken = lexer.next();
6835
- if (digestToken === void 0) return err(unexpectedEndOfInput());
6836
- if (digestToken.token.type === "HexPattern") {
6837
- if (!digestToken.token.value.ok) return err(digestToken.token.value.error);
6838
- if (lexer.next()?.token.type !== "ParenClose") return err({
6839
- type: "ExpectedCloseParen",
6840
- span: lexer.span()
6841
- });
6842
- return ok(digestPrefix(digestToken.token.value.value));
6810
+ const open = lexer.next();
6811
+ if (open === void 0) return err(unexpectedEndOfInput());
6812
+ if (open.token.type !== "ParenOpen") return err(unexpectedToken(open.token, open.span));
6813
+ const innerResult = parseDigestInner(lexer.remainder(), lexer.position);
6814
+ if (!innerResult.ok) return innerResult;
6815
+ const [pattern, consumed] = innerResult.value;
6816
+ lexer.bump(consumed);
6817
+ const close = lexer.next();
6818
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6819
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6820
+ return ok(pattern);
6821
+ }
6822
+ function parseDigestInner(src, basePos) {
6823
+ let pos = 0;
6824
+ pos = skipWs(src, pos);
6825
+ if (src.startsWith("ur:", pos)) {
6826
+ const start = pos;
6827
+ while (pos < src.length && src[pos] !== ")") pos += 1;
6828
+ const ur = src.slice(start, pos).trimEnd();
6829
+ let parsed;
6830
+ try {
6831
+ parsed = _bcts_envelope.Digest.fromURString(ur);
6832
+ } catch {
6833
+ return err(invalidUr(ur, {
6834
+ start: basePos + start,
6835
+ end: basePos + pos
6836
+ }));
6837
+ }
6838
+ pos = skipWs(src, pos);
6839
+ return ok([digest(parsed), pos]);
6840
+ }
6841
+ const start = pos;
6842
+ while (pos < src.length && isAsciiHexDigit(src.charCodeAt(pos))) pos += 1;
6843
+ if (pos === start) return err(invalidHexString({
6844
+ start: basePos + pos,
6845
+ end: basePos + pos
6846
+ }));
6847
+ const hexStr = src.slice(start, pos);
6848
+ if (hexStr.length % 2 !== 0) return err(invalidHexString({
6849
+ start: basePos + pos,
6850
+ end: basePos + pos
6851
+ }));
6852
+ const bytes = decodeHex(hexStr);
6853
+ if (bytes === void 0) return err(invalidHexString({
6854
+ start: basePos + pos,
6855
+ end: basePos + pos
6856
+ }));
6857
+ if (bytes.length > DIGEST_SIZE) return err(invalidHexString({
6858
+ start: basePos + pos,
6859
+ end: basePos + pos
6860
+ }));
6861
+ pos = skipWs(src, pos);
6862
+ return ok([digestPrefix(bytes), pos]);
6863
+ }
6864
+ function skipWs(src, pos) {
6865
+ while (pos < src.length) {
6866
+ const ch = src[pos];
6867
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f") pos += 1;
6868
+ else break;
6843
6869
  }
6844
- if (digestToken.token.type === "Identifier") {
6845
- const hexStr = digestToken.token.value;
6846
- if (hexStr.length === 0 || hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) return err({
6847
- type: "InvalidHexString",
6848
- span: digestToken.span
6849
- });
6850
- const bytes = new Uint8Array(hexStr.length / 2);
6851
- for (let i = 0; i < hexStr.length; i += 2) bytes[i / 2] = Number.parseInt(hexStr.slice(i, i + 2), 16);
6852
- if (lexer.next()?.token.type !== "ParenClose") return err({
6853
- type: "ExpectedCloseParen",
6854
- span: lexer.span()
6855
- });
6856
- return ok(digestPrefix(bytes));
6870
+ return pos;
6871
+ }
6872
+ function isAsciiHexDigit(c) {
6873
+ return c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
6874
+ }
6875
+ function decodeHex(hex) {
6876
+ if (hex.length % 2 !== 0) return void 0;
6877
+ const out = new Uint8Array(hex.length / 2);
6878
+ for (let i = 0; i < hex.length; i += 2) {
6879
+ const value = Number.parseInt(hex.slice(i, i + 2), 16);
6880
+ if (Number.isNaN(value)) return void 0;
6881
+ out[i / 2] = value;
6857
6882
  }
6858
- return err(unexpectedToken(digestToken.token, digestToken.span));
6883
+ return out;
6884
+ }
6885
+ //#endregion
6886
+ //#region src/parse/structure/elided-parser.ts
6887
+ /**
6888
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6889
+ * Copyright © 2025-2026 Parity Technologies
6890
+ *
6891
+ * Elided parser — port of `bc-envelope-pattern-rust`
6892
+ * `parse/structure/elided_parser.rs`.
6893
+ *
6894
+ * @module envelope-pattern/parse/structure/elided-parser
6895
+ */
6896
+ function parseElided(_lexer) {
6897
+ return ok(elided());
6898
+ }
6899
+ //#endregion
6900
+ //#region src/parse/structure/encrypted-parser.ts
6901
+ /**
6902
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6903
+ * Copyright © 2025-2026 Parity Technologies
6904
+ *
6905
+ * Encrypted parser — port of `bc-envelope-pattern-rust`
6906
+ * `parse/structure/encrypted_parser.rs`.
6907
+ *
6908
+ * @module envelope-pattern/parse/structure/encrypted-parser
6909
+ */
6910
+ function parseEncrypted(_lexer) {
6911
+ return ok(encrypted());
6912
+ }
6913
+ //#endregion
6914
+ //#region src/parse/structure/node-parser.ts
6915
+ /**
6916
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6917
+ * Copyright © 2025-2026 Parity Technologies
6918
+ *
6919
+ * Node parser — port of `bc-envelope-pattern-rust`
6920
+ * `parse/structure/node_parser.rs`.
6921
+ *
6922
+ * Mirrors Rust:
6923
+ * - Bare `node` → `any_node()`.
6924
+ * - `node({n})` / `node({n,m})` / `node({n,})` → `node_with_assertions_range`.
6925
+ * - Anything else inside the parens (e.g. `node(text)`) is a syntax error
6926
+ * in Rust; the previous TS port silently produced an always-matching
6927
+ * `WithSubject` node, which we have removed.
6928
+ *
6929
+ * @module envelope-pattern/parse/structure/node-parser
6930
+ */
6931
+ function parseNode(lexer) {
6932
+ if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyNode());
6933
+ lexer.next();
6934
+ const inner = lexer.next();
6935
+ if (inner === void 0) return err(unexpectedEndOfInput());
6936
+ if (inner.token.type !== "Range") return err(unexpectedToken(inner.token, inner.span));
6937
+ if (!inner.token.value.ok) return err(inner.token.value.error);
6938
+ const interval = inner.token.value.value.interval();
6939
+ const close = lexer.next();
6940
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6941
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6942
+ return ok(patternStructure(structureNode(NodePattern.fromInterval(interval))));
6859
6943
  }
6944
+ //#endregion
6945
+ //#region src/parse/structure/object-parser.ts
6946
+ /**
6947
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6948
+ * Copyright © 2025-2026 Parity Technologies
6949
+ *
6950
+ * Object parser — port of `bc-envelope-pattern-rust`
6951
+ * `parse/structure/object_parser.rs`.
6952
+ *
6953
+ * @module envelope-pattern/parse/structure/object-parser
6954
+ */
6860
6955
  function parseObject(lexer) {
6861
6956
  if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyObject());
6862
6957
  lexer.next();
6863
6958
  const inner = parseOr(lexer);
6864
6959
  if (!inner.ok) return inner;
6865
- if (lexer.next()?.token.type !== "ParenClose") return err({
6866
- type: "ExpectedCloseParen",
6867
- span: lexer.span()
6868
- });
6960
+ const close = lexer.next();
6961
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6962
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6869
6963
  return ok(object(inner.value));
6870
6964
  }
6965
+ //#endregion
6966
+ //#region src/parse/structure/obscured-parser.ts
6967
+ /**
6968
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6969
+ * Copyright © 2025-2026 Parity Technologies
6970
+ *
6971
+ * Obscured parser — port of `bc-envelope-pattern-rust`
6972
+ * `parse/structure/obscured_parser.rs`.
6973
+ *
6974
+ * @module envelope-pattern/parse/structure/obscured-parser
6975
+ */
6976
+ function parseObscured(_lexer) {
6977
+ return ok(obscured());
6978
+ }
6979
+ //#endregion
6980
+ //#region src/parse/structure/predicate-parser.ts
6981
+ /**
6982
+ * Copyright © 2023-2026 Blockchain Commons, LLC
6983
+ * Copyright © 2025-2026 Parity Technologies
6984
+ *
6985
+ * Predicate parser — port of `bc-envelope-pattern-rust`
6986
+ * `parse/structure/predicate_parser.rs`.
6987
+ *
6988
+ * @module envelope-pattern/parse/structure/predicate-parser
6989
+ */
6871
6990
  function parsePredicate(lexer) {
6872
6991
  if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anyPredicate());
6873
6992
  lexer.next();
6874
6993
  const inner = parseOr(lexer);
6875
6994
  if (!inner.ok) return inner;
6876
- if (lexer.next()?.token.type !== "ParenClose") return err({
6877
- type: "ExpectedCloseParen",
6878
- span: lexer.span()
6879
- });
6995
+ const close = lexer.next();
6996
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
6997
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6880
6998
  return ok(predicate(inner.value));
6881
6999
  }
7000
+ //#endregion
7001
+ //#region src/parse/structure/subject-parser.ts
7002
+ /**
7003
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7004
+ * Copyright © 2025-2026 Parity Technologies
7005
+ *
7006
+ * Subject parser — port of `bc-envelope-pattern-rust`
7007
+ * `parse/structure/subject_parser.rs`.
7008
+ *
7009
+ * @module envelope-pattern/parse/structure/subject-parser
7010
+ */
6882
7011
  function parseSubject(lexer) {
6883
7012
  if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(anySubject());
6884
7013
  lexer.next();
6885
7014
  const inner = parseOr(lexer);
6886
7015
  if (!inner.ok) return inner;
6887
- if (lexer.next()?.token.type !== "ParenClose") return err({
6888
- type: "ExpectedCloseParen",
6889
- span: lexer.span()
6890
- });
7016
+ const close = lexer.next();
7017
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
7018
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6891
7019
  return ok(subject(inner.value));
6892
7020
  }
7021
+ //#endregion
7022
+ //#region src/parse/structure/wrapped-parser.ts
7023
+ /**
7024
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7025
+ * Copyright © 2025-2026 Parity Technologies
7026
+ *
7027
+ * Wrapped/unwrap parser — port of `bc-envelope-pattern-rust`
7028
+ * `parse/structure/wrapped_parser.rs`.
7029
+ *
7030
+ * @module envelope-pattern/parse/structure/wrapped-parser
7031
+ */
7032
+ function parseWrapped(_lexer) {
7033
+ return ok(wrapped());
7034
+ }
6893
7035
  function parseUnwrap(lexer) {
6894
7036
  if (lexer.peekToken()?.token.type !== "ParenOpen") return ok(unwrapEnvelope());
6895
7037
  lexer.next();
6896
7038
  const inner = parseOr(lexer);
6897
7039
  if (!inner.ok) return inner;
6898
- if (lexer.next()?.token.type !== "ParenClose") return err({
6899
- type: "ExpectedCloseParen",
6900
- span: lexer.span()
6901
- });
7040
+ const close = lexer.next();
7041
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
7042
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
6902
7043
  return ok(unwrapMatching(inner.value));
6903
7044
  }
6904
7045
  //#endregion
7046
+ //#region src/parse/meta/capture-parser.ts
7047
+ /**
7048
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7049
+ * Copyright © 2025-2026 Parity Technologies
7050
+ *
7051
+ * Capture parser — port of `bc-envelope-pattern-rust`
7052
+ * `parse/meta/capture_parser.rs`.
7053
+ *
7054
+ * The `@name(...)` form requires explicit parentheses. Mirrors Rust
7055
+ * exactly:
7056
+ * ```ignore
7057
+ * @name ( expr )
7058
+ * ```
7059
+ * The previous TS port called `parse_group` (primary + quantifier), which
7060
+ * wrapped the inner expression in a redundant `GroupPattern` and accepted
7061
+ * the bare `@name p` form that Rust rejects.
7062
+ *
7063
+ * @module envelope-pattern/parse/meta/capture-parser
7064
+ */
7065
+ function parseCapture(lexer, name) {
7066
+ const open = lexer.next();
7067
+ if (open === void 0) return err(unexpectedEndOfInput());
7068
+ if (open.token.type !== "ParenOpen") return err(unexpectedToken(open.token, open.span));
7069
+ const inner = parseOr(lexer);
7070
+ if (!inner.ok) return inner;
7071
+ const close = lexer.next();
7072
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
7073
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
7074
+ return ok(capture(name, inner.value));
7075
+ }
7076
+ //#endregion
7077
+ //#region src/parse/meta/group-parser.ts
7078
+ /**
7079
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7080
+ * Copyright © 2025-2026 Parity Technologies
7081
+ *
7082
+ * Group parser — port of `bc-envelope-pattern-rust`
7083
+ * `parse/meta/group_parser.rs`.
7084
+ *
7085
+ * Called from `parse_primary` after the opening `(` has been consumed.
7086
+ *
7087
+ * Mirrors Rust exactly:
7088
+ *
7089
+ * - Parse the inner expression with `parse_or`.
7090
+ * - Expect `)`. If missing, surface `ExpectedCloseParen`.
7091
+ * - Lookahead **once** for a quantifier suffix. If present, consume it
7092
+ * and wrap as `Pattern::repeat(inner, …)`. Otherwise return the inner
7093
+ * expression unchanged.
7094
+ *
7095
+ * The previous TS port wrapped every parenthesised expression in a
7096
+ * dedicated `GroupPattern` and applied quantifiers to bare primaries —
7097
+ * both broke `format(parse(s)) === s` round-tripping.
7098
+ *
7099
+ * @module envelope-pattern/parse/meta/group-parser
7100
+ */
7101
+ function parseGroup(lexer) {
7102
+ const inner = parseOr(lexer);
7103
+ if (!inner.ok) return inner;
7104
+ const close = lexer.next();
7105
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
7106
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
7107
+ const next = lexer.peekToken();
7108
+ if (next === void 0) return inner;
7109
+ let quantifier;
7110
+ const tokenType = next.token.type;
7111
+ if (tokenType === "RepeatZeroOrMore") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Greedy);
7112
+ else if (tokenType === "RepeatZeroOrMoreLazy") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Lazy);
7113
+ else if (tokenType === "RepeatZeroOrMorePossessive") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrMore(_bcts_dcbor_pattern.Reluctance.Possessive);
7114
+ else if (tokenType === "RepeatOneOrMore") quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Greedy);
7115
+ else if (tokenType === "RepeatOneOrMoreLazy") quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Lazy);
7116
+ else if (tokenType === "RepeatOneOrMorePossessive") quantifier = _bcts_dcbor_pattern.Quantifier.oneOrMore(_bcts_dcbor_pattern.Reluctance.Possessive);
7117
+ else if (tokenType === "RepeatZeroOrOne") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Greedy);
7118
+ else if (tokenType === "RepeatZeroOrOneLazy") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Lazy);
7119
+ else if (tokenType === "RepeatZeroOrOnePossessive") quantifier = _bcts_dcbor_pattern.Quantifier.zeroOrOne(_bcts_dcbor_pattern.Reluctance.Possessive);
7120
+ else if (tokenType === "Range") {
7121
+ if (!next.token.value.ok) return err(next.token.value.error);
7122
+ quantifier = next.token.value.value;
7123
+ } else return inner;
7124
+ lexer.next();
7125
+ return ok(repeat(inner.value, quantifier.min(), quantifier.max(), quantifier.reluctance()));
7126
+ }
7127
+ //#endregion
7128
+ //#region src/parse/meta/search-parser.ts
7129
+ /**
7130
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7131
+ * Copyright © 2025-2026 Parity Technologies
7132
+ *
7133
+ * Search parser — port of `bc-envelope-pattern-rust`
7134
+ * `parse/meta/search_parser.rs`.
7135
+ *
7136
+ * @module envelope-pattern/parse/meta/search-parser
7137
+ */
7138
+ function parseSearch(lexer) {
7139
+ const open = lexer.next();
7140
+ if (open === void 0) return err(unexpectedEndOfInput());
7141
+ if (open.token.type !== "ParenOpen") return err(unexpectedToken(open.token, open.span));
7142
+ const inner = parseOr(lexer);
7143
+ if (!inner.ok) return inner;
7144
+ const close = lexer.next();
7145
+ if (close === void 0) return err(expectedCloseParen(lexer.span()));
7146
+ if (close.token.type !== "ParenClose") return err(unexpectedToken(close.token, close.span));
7147
+ return ok(search(inner.value));
7148
+ }
7149
+ //#endregion
7150
+ //#region src/parse/meta/primary-parser.ts
7151
+ /**
7152
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7153
+ * Copyright © 2025-2026 Parity Technologies
7154
+ *
7155
+ * Primary parser — port of `bc-envelope-pattern-rust`
7156
+ * `parse/meta/primary_parser.rs`.
7157
+ *
7158
+ * Dispatches on the next token to the appropriate leaf/structure/meta
7159
+ * parser. When a `(` is encountered the open paren is consumed here and
7160
+ * `parse_group` handles the rest (paren'd expression + optional
7161
+ * quantifier suffix).
7162
+ *
7163
+ * @module envelope-pattern/parse/meta/primary-parser
7164
+ */
7165
+ function parsePrimary(lexer) {
7166
+ const tokenResult = lexer.next();
7167
+ if (tokenResult === void 0) return err(unexpectedEndOfInput());
7168
+ const { token, span } = tokenResult;
7169
+ switch (token.type) {
7170
+ case "Search": return parseSearch(lexer);
7171
+ case "Node": return parseNode(lexer);
7172
+ case "Assertion": return parseAssertion(lexer);
7173
+ case "AssertionPred": return parseAssertionPred(lexer);
7174
+ case "AssertionObj": return parseAssertionObj(lexer);
7175
+ case "Digest": return parseDigest(lexer);
7176
+ case "Obj": return parseObject(lexer);
7177
+ case "Obscured": return parseObscured(lexer);
7178
+ case "Elided": return parseElided(lexer);
7179
+ case "Encrypted": return parseEncrypted(lexer);
7180
+ case "Compressed": return parseCompressed(lexer);
7181
+ case "Pred": return parsePredicate(lexer);
7182
+ case "Subject": return parseSubject(lexer);
7183
+ case "Wrapped": return parseWrapped(lexer);
7184
+ case "Unwrap": return parseUnwrap(lexer);
7185
+ case "Leaf": return ok(leaf());
7186
+ case "GroupName": return parseCapture(lexer, token.name);
7187
+ case "ParenOpen": return parseGroup(lexer);
7188
+ case "Cbor": return parseCbor(lexer);
7189
+ case "RepeatZeroOrMore": return ok(any());
7190
+ case "BoolKeyword": return ok(anyBool());
7191
+ case "BoolTrue": return ok(bool(true));
7192
+ case "BoolFalse": return ok(bool(false));
7193
+ case "NumberKeyword": return ok(anyNumber());
7194
+ case "TextKeyword": return ok(anyText());
7195
+ case "StringLiteral":
7196
+ if (!token.value.ok) return err(token.value.error);
7197
+ return ok(text(token.value.value));
7198
+ case "UnsignedInteger":
7199
+ if (!token.value.ok) return err(token.value.error);
7200
+ return parseNumberRangeOrComparison(lexer, token.value.value);
7201
+ case "Integer":
7202
+ if (!token.value.ok) return err(token.value.error);
7203
+ return parseNumberRangeOrComparison(lexer, token.value.value);
7204
+ case "Float":
7205
+ if (!token.value.ok) return err(token.value.error);
7206
+ return parseNumberRangeOrComparison(lexer, token.value.value);
7207
+ case "GreaterThanOrEqual": return parseComparisonNumber(lexer, ">=");
7208
+ case "LessThanOrEqual": return parseComparisonNumber(lexer, "<=");
7209
+ case "GreaterThan": return parseComparisonNumber(lexer, ">");
7210
+ case "LessThan": return parseComparisonNumber(lexer, "<");
7211
+ case "NaN": return ok(patternLeaf(leafNumber(NumberPattern.nan())));
7212
+ case "Infinity": return ok(number(Infinity));
7213
+ case "NegativeInfinity": return ok(number(-Infinity));
7214
+ case "Regex":
7215
+ if (!token.value.ok) return err(token.value.error);
7216
+ try {
7217
+ return ok(textRegex(new RegExp(token.value.value)));
7218
+ } catch {
7219
+ return err(invalidRegex(span));
7220
+ }
7221
+ case "BracketOpen": return parseArray(lexer);
7222
+ case "ByteString": return ok(anyByteString());
7223
+ case "HexPattern":
7224
+ if (!token.value.ok) return err(token.value.error);
7225
+ return ok(byteString(token.value.value));
7226
+ case "HexBinaryRegex":
7227
+ if (!token.value.ok) return err(token.value.error);
7228
+ try {
7229
+ return ok(patternLeaf(leafByteString(ByteStringPattern.regex(new RegExp(token.value.value)))));
7230
+ } catch {
7231
+ return err(invalidRegex(span));
7232
+ }
7233
+ case "DateKeyword": return ok(anyDate());
7234
+ case "DatePattern":
7235
+ if (!token.value.ok) return err(token.value.error);
7236
+ return parseDateContent(token.value.value, span);
7237
+ case "Tagged": return parseTag(lexer);
7238
+ case "Known": return ok(anyKnownValue());
7239
+ case "SingleQuotedPattern":
7240
+ if (!token.value.ok) return err(token.value.error);
7241
+ return parseKnownValueContent(token.value.value);
7242
+ case "SingleQuotedRegex":
7243
+ if (!token.value.ok) return err(token.value.error);
7244
+ try {
7245
+ return ok(patternLeaf(leafKnownValue(KnownValuePattern.regex(new RegExp(token.value.value)))));
7246
+ } catch {
7247
+ return err(invalidRegex(span));
7248
+ }
7249
+ case "Null": return ok(nullPattern());
7250
+ case "Identifier": return err(unrecognizedToken(span));
7251
+ case "And":
7252
+ case "Or":
7253
+ case "Not":
7254
+ case "Traverse":
7255
+ case "RepeatZeroOrMoreLazy":
7256
+ case "RepeatZeroOrMorePossessive":
7257
+ case "RepeatOneOrMore":
7258
+ case "RepeatOneOrMoreLazy":
7259
+ case "RepeatOneOrMorePossessive":
7260
+ case "RepeatZeroOrOne":
7261
+ case "RepeatZeroOrOneLazy":
7262
+ case "RepeatZeroOrOnePossessive":
7263
+ case "ParenClose":
7264
+ case "BracketClose":
7265
+ case "Comma":
7266
+ case "Ellipsis":
7267
+ case "Range": return err(unexpectedToken(token, span));
7268
+ }
7269
+ }
7270
+ //#endregion
7271
+ //#region src/parse/meta/and-parser.ts
7272
+ /**
7273
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7274
+ * Copyright © 2025-2026 Parity Technologies
7275
+ *
7276
+ * And parser — port of `bc-envelope-pattern-rust`
7277
+ * `parse/meta/and_parser.rs`.
7278
+ *
7279
+ * Mirrors Rust: `parse_and` calls `parse_primary` (NOT `parse_not`); `!`
7280
+ * is handled at a higher precedence level by `parse_not`.
7281
+ *
7282
+ * @module envelope-pattern/parse/meta/and-parser
7283
+ */
7284
+ function parseAnd(lexer) {
7285
+ const patterns = [];
7286
+ const first = parsePrimary(lexer);
7287
+ if (!first.ok) return first;
7288
+ patterns.push(first.value);
7289
+ while (true) {
7290
+ if (lexer.peekToken()?.token.type !== "And") break;
7291
+ lexer.next();
7292
+ const nextExpr = parsePrimary(lexer);
7293
+ if (!nextExpr.ok) return nextExpr;
7294
+ patterns.push(nextExpr.value);
7295
+ }
7296
+ if (patterns.length === 1) return ok(patterns[0]);
7297
+ return ok(and(patterns));
7298
+ }
7299
+ //#endregion
7300
+ //#region src/parse/meta/not-parser.ts
7301
+ /**
7302
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7303
+ * Copyright © 2025-2026 Parity Technologies
7304
+ *
7305
+ * Not parser — port of `bc-envelope-pattern-rust`
7306
+ * `parse/meta/not_parser.rs`.
7307
+ *
7308
+ * Mirrors Rust:
7309
+ * - On `!`, recurse into `parse_not` so chained negation parses as
7310
+ * `not(not(x))` rather than `not(group(x))`.
7311
+ * - Otherwise descend into `parse_and`.
7312
+ *
7313
+ * @module envelope-pattern/parse/meta/not-parser
7314
+ */
7315
+ function parseNot(lexer) {
7316
+ if (lexer.peekToken()?.token.type === "Not") {
7317
+ lexer.next();
7318
+ const inner = parseNot(lexer);
7319
+ if (!inner.ok) return inner;
7320
+ return ok(notMatching(inner.value));
7321
+ }
7322
+ return parseAnd(lexer);
7323
+ }
7324
+ //#endregion
7325
+ //#region src/parse/meta/traverse-parser.ts
7326
+ /**
7327
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7328
+ * Copyright © 2025-2026 Parity Technologies
7329
+ *
7330
+ * Traverse parser — port of `bc-envelope-pattern-rust`
7331
+ * `parse/meta/traverse_parser.rs`.
7332
+ *
7333
+ * Note the precedence chain: `parse_or → parse_traverse → parse_not →
7334
+ * parse_and → parse_primary`. The earlier TS port had `parse_traverse`
7335
+ * call `parse_and` directly, which pushed `!` below `&` and
7336
+ * miscompiled `!a & b`.
7337
+ *
7338
+ * @module envelope-pattern/parse/meta/traverse-parser
7339
+ */
7340
+ function parseTraverse(lexer) {
7341
+ const patterns = [];
7342
+ const first = parseNot(lexer);
7343
+ if (!first.ok) return first;
7344
+ patterns.push(first.value);
7345
+ while (true) {
7346
+ if (lexer.peekToken()?.token.type !== "Traverse") break;
7347
+ lexer.next();
7348
+ const nextExpr = parseNot(lexer);
7349
+ if (!nextExpr.ok) return nextExpr;
7350
+ patterns.push(nextExpr.value);
7351
+ }
7352
+ if (patterns.length === 1) return ok(patterns[0]);
7353
+ return ok(traverse(patterns));
7354
+ }
7355
+ //#endregion
7356
+ //#region src/parse/meta/or-parser.ts
7357
+ /**
7358
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7359
+ * Copyright © 2025-2026 Parity Technologies
7360
+ *
7361
+ * Or parser — port of `bc-envelope-pattern-rust` `parse/meta/or_parser.rs`.
7362
+ *
7363
+ * @module envelope-pattern/parse/meta/or-parser
7364
+ */
7365
+ function parseOr(lexer) {
7366
+ const patterns = [];
7367
+ const first = parseTraverse(lexer);
7368
+ if (!first.ok) return first;
7369
+ patterns.push(first.value);
7370
+ while (true) {
7371
+ if (lexer.peekToken()?.token.type !== "Or") break;
7372
+ lexer.next();
7373
+ const nextExpr = parseTraverse(lexer);
7374
+ if (!nextExpr.ok) return nextExpr;
7375
+ patterns.push(nextExpr.value);
7376
+ }
7377
+ if (patterns.length === 1) return ok(patterns[0]);
7378
+ return ok(or(patterns));
7379
+ }
7380
+ //#endregion
7381
+ //#region src/parse/index.ts
7382
+ /**
7383
+ * Copyright © 2023-2026 Blockchain Commons, LLC
7384
+ * Copyright © 2025-2026 Parity Technologies
7385
+ *
7386
+ *
7387
+ * @bcts/envelope-pattern - Parser entry point
7388
+ *
7389
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust parse/mod.rs.
7390
+ *
7391
+ * Recursive-descent parser for the Gordian Envelope pattern syntax. The
7392
+ * parsing rules live under `parse/leaf/`, `parse/meta/`, and
7393
+ * `parse/structure/`, mirroring the Rust crate's module layout.
7394
+ *
7395
+ * @module envelope-pattern/parse
7396
+ */
7397
+ /**
7398
+ * Parse a pattern expression string into a Pattern.
7399
+ *
7400
+ * Mirrors Rust `Pattern::parse`: tries envelope-pattern parsing first;
7401
+ * on failure falls back to dcbor-pattern parsing and converts the
7402
+ * result into an envelope pattern via the
7403
+ * `dcbor_integration::convert_dcbor_pattern_to_envelope_pattern` bridge.
7404
+ */
7405
+ function parse(input) {
7406
+ const lexer = new Lexer(input);
7407
+ const result = parseOr(lexer);
7408
+ if (!result.ok) {
7409
+ const dcborResult = (0, _bcts_dcbor_pattern.parse)(input);
7410
+ if (dcborResult.ok) return convertDcborPatternToEnvelopePattern(dcborResult.value);
7411
+ return result;
7412
+ }
7413
+ const next = lexer.next();
7414
+ if (next !== void 0) return err(extraData(next.span));
7415
+ return result;
7416
+ }
7417
+ /**
7418
+ * Parse a pattern, allowing extra data after the pattern.
7419
+ *
7420
+ * Returns the parsed pattern and the byte offset at which parsing
7421
+ * stopped, mirroring `Pattern::parse_partial` in spirit.
7422
+ */
7423
+ function parsePartial(input) {
7424
+ const lexer = new Lexer(input);
7425
+ const result = parseOr(lexer);
7426
+ if (!result.ok) return result;
7427
+ return ok([result.value, lexer.position]);
7428
+ }
7429
+ //#endregion
6905
7430
  //#region src/index.ts
6906
7431
  /**
6907
7432
  * Package version.
@@ -7094,6 +7619,7 @@ exports.registerAndPatternFactory = registerAndPatternFactory;
7094
7619
  exports.registerAnyPatternFactory = registerAnyPatternFactory;
7095
7620
  exports.registerArrayPatternFactory = registerArrayPatternFactory;
7096
7621
  exports.registerAssertionsPatternFactory = registerAssertionsPatternFactory;
7622
+ exports.registerAssertionsPatternToStringDispatch = registerAssertionsPatternToStringDispatch;
7097
7623
  exports.registerBoolPatternFactory = registerBoolPatternFactory;
7098
7624
  exports.registerByteStringPatternFactory = registerByteStringPatternFactory;
7099
7625
  exports.registerCBORPatternFactory = registerCBORPatternFactory;
@@ -7115,11 +7641,13 @@ exports.registerPatternDispatchFns = registerPatternDispatchFns;
7115
7641
  exports.registerPatternMatchFn = registerPatternMatchFn;
7116
7642
  exports.registerPredicatePatternFactory = registerPredicatePatternFactory;
7117
7643
  exports.registerSearchPatternFactory = registerSearchPatternFactory;
7644
+ exports.registerSubjectPatternDispatch = registerSubjectPatternDispatch;
7118
7645
  exports.registerSubjectPatternFactory = registerSubjectPatternFactory;
7119
7646
  exports.registerTaggedPatternFactory = registerTaggedPatternFactory;
7120
7647
  exports.registerTextPatternFactory = registerTextPatternFactory;
7121
7648
  exports.registerTraversePatternFactory = registerTraversePatternFactory;
7122
7649
  exports.registerVMPatternFunctions = registerVMPatternFunctions;
7650
+ exports.registerWrappedPatternAny = registerWrappedPatternAny;
7123
7651
  exports.registerWrappedPatternDispatch = registerWrappedPatternDispatch;
7124
7652
  exports.registerWrappedPatternFactory = registerWrappedPatternFactory;
7125
7653
  exports.repeat = repeat;