@bcts/envelope-pattern 1.0.0-alpha.17 → 1.0.0-alpha.18

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 (42) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1679 -1401
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +54 -2
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +54 -2
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.iife.js +1680 -1403
  9. package/dist/index.iife.js.map +1 -1
  10. package/dist/index.mjs +1670 -1402
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +6 -6
  13. package/src/format.ts +23 -4
  14. package/src/parse/index.ts +138 -5
  15. package/src/parse/token.ts +4 -3
  16. package/src/pattern/index.ts +110 -2
  17. package/src/pattern/leaf/array-pattern.ts +1 -1
  18. package/src/pattern/leaf/bool-pattern.ts +1 -1
  19. package/src/pattern/leaf/byte-string-pattern.ts +1 -1
  20. package/src/pattern/leaf/cbor-pattern.ts +2 -2
  21. package/src/pattern/leaf/date-pattern.ts +1 -1
  22. package/src/pattern/leaf/index.ts +1 -2
  23. package/src/pattern/leaf/known-value-pattern.ts +4 -3
  24. package/src/pattern/leaf/map-pattern.ts +1 -1
  25. package/src/pattern/leaf/null-pattern.ts +1 -1
  26. package/src/pattern/leaf/number-pattern.ts +1 -1
  27. package/src/pattern/leaf/text-pattern.ts +1 -1
  28. package/src/pattern/matcher.ts +88 -3
  29. package/src/pattern/meta/and-pattern.ts +10 -9
  30. package/src/pattern/meta/capture-pattern.ts +5 -6
  31. package/src/pattern/meta/group-pattern.ts +9 -6
  32. package/src/pattern/meta/not-pattern.ts +3 -2
  33. package/src/pattern/meta/or-pattern.ts +18 -13
  34. package/src/pattern/meta/search-pattern.ts +4 -4
  35. package/src/pattern/meta/traverse-pattern.ts +31 -7
  36. package/src/pattern/structure/assertions-pattern.ts +7 -8
  37. package/src/pattern/structure/index.ts +1 -0
  38. package/src/pattern/structure/object-pattern.ts +2 -3
  39. package/src/pattern/structure/predicate-pattern.ts +2 -3
  40. package/src/pattern/structure/subject-pattern.ts +2 -3
  41. package/src/pattern/structure/wrapped-pattern.ts +28 -7
  42. package/src/pattern/vm.ts +12 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bcts/envelope-pattern",
3
- "version": "1.0.0-alpha.17",
3
+ "version": "1.0.0-alpha.18",
4
4
  "type": "module",
5
5
  "description": "Pattern matching for Gordian Envelope structures",
6
6
  "license": "BSD-2-Clause-Patent",
@@ -66,10 +66,10 @@
66
66
  "vitest": "^4.0.18"
67
67
  },
68
68
  "dependencies": {
69
- "@bcts/dcbor": "^1.0.0-alpha.17",
70
- "@bcts/dcbor-parse": "^1.0.0-alpha.17",
71
- "@bcts/dcbor-pattern": "^1.0.0-alpha.17",
72
- "@bcts/envelope": "^1.0.0-alpha.17",
73
- "@bcts/known-values": "^1.0.0-alpha.17"
69
+ "@bcts/dcbor": "^1.0.0-alpha.18",
70
+ "@bcts/dcbor-parse": "^1.0.0-alpha.18",
71
+ "@bcts/dcbor-pattern": "^1.0.0-alpha.18",
72
+ "@bcts/envelope": "^1.0.0-alpha.18",
73
+ "@bcts/known-values": "^1.0.0-alpha.18"
74
74
  }
75
75
  }
package/src/format.ts CHANGED
@@ -156,18 +156,37 @@ export function envelopeSummary(env: Envelope): string {
156
156
 
157
157
  let summary: string;
158
158
  switch (c.type) {
159
- case "node":
160
- summary = `NODE ${env.summary(Number.MAX_SAFE_INTEGER)}`;
159
+ case "node": {
160
+ const subjectSummary = env.subject().summary(Number.MAX_SAFE_INTEGER);
161
+ const assertions = env.assertions();
162
+ if (assertions.length > 0) {
163
+ const assertionSummaries = assertions.map((a) => {
164
+ const ac = a.case();
165
+ if (ac.type === "assertion") {
166
+ const pred = ac.assertion.predicate().summary(Number.MAX_SAFE_INTEGER);
167
+ const obj = ac.assertion.object().summary(Number.MAX_SAFE_INTEGER);
168
+ return `${pred}: ${obj}`;
169
+ }
170
+ return a.summary(Number.MAX_SAFE_INTEGER);
171
+ });
172
+ summary = `NODE ${subjectSummary} [ ${assertionSummaries.join(", ")} ]`;
173
+ } else {
174
+ summary = `NODE ${subjectSummary}`;
175
+ }
161
176
  break;
177
+ }
162
178
  case "leaf":
163
179
  summary = `LEAF ${env.summary(Number.MAX_SAFE_INTEGER)}`;
164
180
  break;
165
181
  case "wrapped":
166
182
  summary = `WRAPPED ${env.summary(Number.MAX_SAFE_INTEGER)}`;
167
183
  break;
168
- case "assertion":
169
- summary = `ASSERTION ${env.summary(Number.MAX_SAFE_INTEGER)}`;
184
+ case "assertion": {
185
+ const pred = c.assertion.predicate().summary(Number.MAX_SAFE_INTEGER);
186
+ const obj = c.assertion.object().summary(Number.MAX_SAFE_INTEGER);
187
+ summary = `ASSERTION ${pred}: ${obj}`;
170
188
  break;
189
+ }
171
190
  case "elided":
172
191
  summary = "ELIDED";
173
192
  break;
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { parse as parseDcborPattern } from "@bcts/dcbor-pattern";
11
+ import { parseDcborItemPartial } from "@bcts/dcbor-parse";
11
12
  import { Lexer } from "./token";
12
13
  import {
13
14
  type Result,
@@ -37,10 +38,17 @@ import {
37
38
  byteString,
38
39
  anyDate,
39
40
  date,
41
+ dateRange,
42
+ dateEarliest,
43
+ dateLatest,
44
+ dateRegex,
40
45
  anyKnownValue,
41
46
  knownValue,
42
47
  anyArray,
43
48
  anyTag,
49
+ anyCbor,
50
+ cborValue,
51
+ cborPattern,
44
52
  nullPattern,
45
53
  // Structure pattern constructors
46
54
  leaf,
@@ -79,7 +87,6 @@ import {
79
87
  ByteStringPattern,
80
88
  KnownValuePattern,
81
89
  ArrayPattern,
82
- TaggedPattern,
83
90
  DigestPattern,
84
91
  NodePattern,
85
92
  AssertionsPattern,
@@ -87,7 +94,6 @@ import {
87
94
  leafByteString,
88
95
  leafKnownValue,
89
96
  leafArray,
90
- leafTag,
91
97
  structureDigest,
92
98
  structureNode,
93
99
  structureAssertions,
@@ -469,6 +475,7 @@ function parsePrimary(lexer: Lexer): Result<Pattern> {
469
475
  case "Comma":
470
476
  case "Ellipsis":
471
477
  case "Range":
478
+ case "Identifier":
472
479
  return err(unexpectedToken(token, span));
473
480
  }
474
481
  }
@@ -685,7 +692,50 @@ function parseTag(lexer: Lexer): Result<Pattern> {
685
692
  * Parse date content from date'...' pattern.
686
693
  */
687
694
  function parseDateContent(content: string, span: Span): Result<Pattern> {
688
- // Try to parse as ISO date
695
+ // Check for regex syntax: /pattern/
696
+ if (content.startsWith("/") && content.endsWith("/")) {
697
+ const regexStr = content.slice(1, -1);
698
+ try {
699
+ return ok(dateRegex(new RegExp(regexStr)));
700
+ } catch {
701
+ return err(invalidRegex(span));
702
+ }
703
+ }
704
+
705
+ // Check for range syntax: date1...date2, date1..., ...date2
706
+ const rangeIdx = content.indexOf("...");
707
+ if (rangeIdx !== -1) {
708
+ const left = content.slice(0, rangeIdx).trim();
709
+ const right = content.slice(rangeIdx + 3).trim();
710
+
711
+ if (left.length === 0 && right.length > 0) {
712
+ // ...date2 → latest
713
+ const parsed = Date.parse(right);
714
+ if (isNaN(parsed)) return err({ type: "InvalidDateFormat", span });
715
+ return ok(dateLatest(CborDate.fromDatetime(new Date(parsed))));
716
+ }
717
+ if (left.length > 0 && right.length === 0) {
718
+ // date1... → earliest
719
+ const parsed = Date.parse(left);
720
+ if (isNaN(parsed)) return err({ type: "InvalidDateFormat", span });
721
+ return ok(dateEarliest(CborDate.fromDatetime(new Date(parsed))));
722
+ }
723
+ if (left.length > 0 && right.length > 0) {
724
+ // date1...date2 → range
725
+ const parsedStart = Date.parse(left);
726
+ const parsedEnd = Date.parse(right);
727
+ if (isNaN(parsedStart) || isNaN(parsedEnd)) return err({ type: "InvalidDateFormat", span });
728
+ return ok(
729
+ dateRange(
730
+ CborDate.fromDatetime(new Date(parsedStart)),
731
+ CborDate.fromDatetime(new Date(parsedEnd)),
732
+ ),
733
+ );
734
+ }
735
+ return err({ type: "InvalidDateFormat", span });
736
+ }
737
+
738
+ // Simple exact date
689
739
  const parsed = Date.parse(content);
690
740
  if (isNaN(parsed)) {
691
741
  return err({ type: "InvalidDateFormat", span });
@@ -713,17 +763,64 @@ function parseKnownValueContent(content: string): Result<Pattern> {
713
763
 
714
764
  /**
715
765
  * Parse CBOR pattern.
766
+ *
767
+ * Matches Rust parse_cbor: tries dcbor-pattern regex first (/keyword/),
768
+ * then CBOR diagnostic notation via parseDcborItemPartial, then falls
769
+ * back to parseOr for envelope pattern expressions.
716
770
  */
717
771
  function parseCbor(lexer: Lexer): Result<Pattern> {
718
772
  // Check for optional content in parentheses
719
773
  const next = lexer.peekToken();
720
774
  if (next?.token.type !== "ParenOpen") {
721
- return ok(patternLeaf(leafTag(TaggedPattern.any()))); // cbor matches any CBOR
775
+ return ok(anyCbor()); // cbor matches any CBOR value
722
776
  }
723
777
 
724
778
  lexer.next(); // consume (
725
779
 
726
- // Parse inner content - this is a dcbor-pattern expression
780
+ // Check for dcbor-pattern regex syntax: cbor(/keyword/)
781
+ // Use peek() (character-level, non-destructive) instead of peekToken()
782
+ // to avoid the lexer advancing past the CBOR content.
783
+ if (lexer.peek() === "/") {
784
+ const regexTokenResult = lexer.next(); // tokenize /pattern/
785
+ if (regexTokenResult?.token.type === "Regex") {
786
+ const regexToken = regexTokenResult.token;
787
+ if (!regexToken.value.ok) return err(regexToken.value.error);
788
+ const keyword = regexToken.value.value;
789
+
790
+ // Parse the keyword as a dcbor-pattern expression
791
+ const dcborResult = parseDcborPattern(keyword);
792
+ if (!dcborResult.ok) {
793
+ return err(unexpectedToken(regexToken, regexTokenResult.span));
794
+ }
795
+
796
+ const close = lexer.next();
797
+ if (close?.token.type !== "ParenClose") {
798
+ return err({ type: "ExpectedCloseParen", span: lexer.span() });
799
+ }
800
+
801
+ return ok(cborPattern(dcborResult.value));
802
+ }
803
+ }
804
+
805
+ // Try to parse inner content as CBOR diagnostic notation
806
+ // (matching Rust utils::parse_cbor_inner which calls parse_dcbor_item_partial)
807
+ const remaining = lexer.remainder();
808
+ const cborResult = parseDcborItemPartial(remaining);
809
+ if (cborResult.ok) {
810
+ const [cborData, consumed] = cborResult.value;
811
+ lexer.bump(consumed);
812
+ // Skip whitespace before closing paren
813
+ while (lexer.peek() === " " || lexer.peek() === "\t" || lexer.peek() === "\n") {
814
+ lexer.bump(1);
815
+ }
816
+ const close = lexer.next();
817
+ if (close?.token.type !== "ParenClose") {
818
+ return err({ type: "ExpectedCloseParen", span: lexer.span() });
819
+ }
820
+ return ok(cborValue(cborData));
821
+ }
822
+
823
+ // Fallback: try parsing as a regular pattern expression
727
824
  const inner = parseOr(lexer);
728
825
  if (!inner.ok) return inner;
729
826
 
@@ -746,6 +843,24 @@ function parseNode(lexer: Lexer): Result<Pattern> {
746
843
  }
747
844
 
748
845
  lexer.next(); // consume (
846
+
847
+ // Check for assertion count range: node({n,m}), node({n}), node({n,})
848
+ const afterParen = lexer.peekToken();
849
+ if (afterParen?.token.type === "Range") {
850
+ lexer.next(); // consume Range token
851
+ const rangeToken = afterParen.token;
852
+ if (!rangeToken.value.ok) return err(rangeToken.value.error);
853
+ const quantifier = rangeToken.value.value;
854
+ const interval = quantifier.interval();
855
+
856
+ const close = lexer.next();
857
+ if (close?.token.type !== "ParenClose") {
858
+ return err({ type: "ExpectedCloseParen", span: lexer.span() });
859
+ }
860
+
861
+ return ok(patternStructure(structureNode(NodePattern.fromInterval(interval))));
862
+ }
863
+
749
864
  const inner = parseOr(lexer);
750
865
  if (!inner.ok) return inner;
751
866
 
@@ -847,6 +962,24 @@ function parseDigest(lexer: Lexer): Result<Pattern> {
847
962
  return ok(digestPrefix(digestToken.token.value.value));
848
963
  }
849
964
 
965
+ // Accept raw hex string identifiers: digest(a1b2c3)
966
+ if (digestToken.token.type === "Identifier") {
967
+ const hexStr = digestToken.token.value;
968
+ // Validate hex string: must be even length and all hex digits
969
+ if (hexStr.length === 0 || hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) {
970
+ return err({ type: "InvalidHexString", span: digestToken.span });
971
+ }
972
+ const bytes = new Uint8Array(hexStr.length / 2);
973
+ for (let i = 0; i < hexStr.length; i += 2) {
974
+ bytes[i / 2] = Number.parseInt(hexStr.slice(i, i + 2), 16);
975
+ }
976
+ const close = lexer.next();
977
+ if (close?.token.type !== "ParenClose") {
978
+ return err({ type: "ExpectedCloseParen", span: lexer.span() });
979
+ }
980
+ return ok(digestPrefix(bytes));
981
+ }
982
+
850
983
  return err(unexpectedToken(digestToken.token, digestToken.span));
851
984
  }
852
985
 
@@ -99,7 +99,8 @@ export type Token =
99
99
  | { readonly type: "DatePattern"; readonly value: Result<string> }
100
100
  | { readonly type: "Range"; readonly value: Result<Quantifier> }
101
101
  | { readonly type: "SingleQuotedPattern"; readonly value: Result<string> }
102
- | { readonly type: "SingleQuotedRegex"; readonly value: Result<string> };
102
+ | { readonly type: "SingleQuotedRegex"; readonly value: Result<string> }
103
+ | { readonly type: "Identifier"; readonly value: string };
103
104
 
104
105
  /**
105
106
  * Keyword to token type mapping.
@@ -875,8 +876,8 @@ export class Lexer {
875
876
  return { token: keyword, span: this.span() };
876
877
  }
877
878
 
878
- // Unknown identifier - treat as error
879
- return undefined;
879
+ // Unknown identifier - return as Identifier token
880
+ return { token: { type: "Identifier", value: ident }, span: this.span() };
880
881
  }
881
882
 
882
883
  // Unknown character
@@ -19,6 +19,7 @@ import {
19
19
 
20
20
  import type { Path } from "../format";
21
21
  import type { Instr } from "./vm";
22
+ import { compile, run } from "./vm";
22
23
 
23
24
  // Re-export sub-modules
24
25
  export * from "./leaf";
@@ -104,6 +105,7 @@ import {
104
105
  registerNodePatternFactory,
105
106
  registerObscuredPatternFactory,
106
107
  registerWrappedPatternFactory,
108
+ registerWrappedPatternDispatch,
107
109
  } from "./structure";
108
110
 
109
111
  // Import meta patterns
@@ -176,13 +178,57 @@ export function patternMeta(meta: MetaPattern): Pattern {
176
178
  // Pattern Matcher Implementation
177
179
  // ============================================================================
178
180
 
181
+ /**
182
+ * Check if a pattern requires the VM for execution.
183
+ * Patterns containing Group (repeat), Traverse, or Search require compilation.
184
+ */
185
+ function patternNeedsVM(pattern: Pattern): boolean {
186
+ switch (pattern.type) {
187
+ case "Leaf":
188
+ case "Structure":
189
+ return false;
190
+ case "Meta":
191
+ return metaPatternNeedsVM(pattern.pattern);
192
+ }
193
+ }
194
+
195
+ function metaPatternNeedsVM(pattern: MetaPattern): boolean {
196
+ switch (pattern.type) {
197
+ case "Any":
198
+ return false;
199
+ case "And":
200
+ return pattern.pattern.patterns().some(patternNeedsVM);
201
+ case "Or":
202
+ return pattern.pattern.patterns().some(patternNeedsVM);
203
+ case "Not":
204
+ return patternNeedsVM(pattern.pattern.pattern());
205
+ case "Capture":
206
+ return patternNeedsVM(pattern.pattern.pattern());
207
+ case "Search":
208
+ case "Traverse":
209
+ return true;
210
+ case "Group": {
211
+ // Group with exactly(1) is simple pass-through, doesn't need VM
212
+ const q = pattern.pattern.quantifier();
213
+ if (q.min() === 1 && q.max() === 1) {
214
+ return patternNeedsVM(pattern.pattern.pattern());
215
+ }
216
+ return true;
217
+ }
218
+ }
219
+ }
220
+
179
221
  /**
180
222
  * Gets paths with captures for a pattern.
223
+ * Routes through the VM for complex patterns that require compilation.
181
224
  */
182
225
  export function patternPathsWithCaptures(
183
226
  pattern: Pattern,
184
227
  haystack: Envelope,
185
228
  ): [Path[], Map<string, Path[]>] {
229
+ if (patternNeedsVM(pattern)) {
230
+ return patternPathsWithCapturesViaVM(pattern, haystack);
231
+ }
186
232
  switch (pattern.type) {
187
233
  case "Leaf":
188
234
  return leafPatternPathsWithCaptures(pattern.pattern, haystack);
@@ -193,6 +239,31 @@ export function patternPathsWithCaptures(
193
239
  }
194
240
  }
195
241
 
242
+ /**
243
+ * Execute a pattern through the VM, merging results.
244
+ */
245
+ function patternPathsWithCapturesViaVM(
246
+ pattern: Pattern,
247
+ haystack: Envelope,
248
+ ): [Path[], Map<string, Path[]>] {
249
+ const prog = compile(pattern);
250
+ const results = run(prog, haystack);
251
+
252
+ const allPaths: Path[] = [];
253
+ const mergedCaptures = new Map<string, Path[]>();
254
+
255
+ for (const [path, captures] of results) {
256
+ allPaths.push(path);
257
+ for (const [name, paths] of captures) {
258
+ const existing = mergedCaptures.get(name) ?? [];
259
+ existing.push(...paths);
260
+ mergedCaptures.set(name, existing);
261
+ }
262
+ }
263
+
264
+ return [allPaths, mergedCaptures];
265
+ }
266
+
196
267
  /**
197
268
  * Gets paths for a pattern.
198
269
  */
@@ -347,6 +418,27 @@ export function dateRange(earliest: CborDate, latest: CborDate): Pattern {
347
418
  return patternLeaf(leafDate(DatePattern.range(earliest, latest)));
348
419
  }
349
420
 
421
+ /**
422
+ * Creates a new Pattern that matches Date values on or after the specified date.
423
+ */
424
+ export function dateEarliest(d: CborDate): Pattern {
425
+ return patternLeaf(leafDate(DatePattern.earliest(d)));
426
+ }
427
+
428
+ /**
429
+ * Creates a new Pattern that matches Date values on or before the specified date.
430
+ */
431
+ export function dateLatest(d: CborDate): Pattern {
432
+ return patternLeaf(leafDate(DatePattern.latest(d)));
433
+ }
434
+
435
+ /**
436
+ * Creates a new Pattern that matches Date values whose ISO-8601 string matches the regex.
437
+ */
438
+ export function dateRegex(pattern: RegExp): Pattern {
439
+ return patternLeaf(leafDate(DatePattern.regex(pattern)));
440
+ }
441
+
350
442
  /**
351
443
  * Creates a new Pattern that matches any number value.
352
444
  */
@@ -700,6 +792,11 @@ function registerAllFactories(): void {
700
792
  registerNodePatternFactory((p) => patternStructure(structureNode(p)));
701
793
  registerObscuredPatternFactory((p) => patternStructure(structureObscured(p)));
702
794
  registerWrappedPatternFactory((p) => patternStructure(structureWrapped(p)));
795
+ registerWrappedPatternDispatch({
796
+ pathsWithCaptures: patternPathsWithCaptures,
797
+ compile: patternCompile,
798
+ toString: patternToString,
799
+ });
703
800
 
704
801
  // Meta pattern factories
705
802
  registerAnyPatternFactory((p) => patternMeta(metaAny(p)));
@@ -719,6 +816,17 @@ registerAllFactories();
719
816
  import { registerVMPatternFunctions } from "./vm";
720
817
  registerVMPatternFunctions(patternPathsWithCaptures, patternMatches, patternPaths);
721
818
 
722
- // Register pattern match function for meta patterns
723
- import { registerPatternMatchFn } from "./matcher";
819
+ // Register pattern dispatch functions for meta patterns
820
+ import { registerPatternMatchFn, registerPatternDispatchFns } from "./matcher";
724
821
  registerPatternMatchFn(patternMatches);
822
+ registerPatternDispatchFns({
823
+ pathsWithCaptures: patternPathsWithCaptures,
824
+ paths: patternPaths,
825
+ compile: patternCompile,
826
+ isComplex: patternIsComplex,
827
+ toString: patternToString,
828
+ });
829
+
830
+ // Register traverse dispatch functions to resolve circular dependencies
831
+ import { registerTraverseDispatchFunctions } from "./meta/traverse-pattern";
832
+ registerTraverseDispatchFunctions(patternPathsWithCaptures, patternCompile, patternIsComplex);
@@ -98,7 +98,7 @@ export class ArrayPattern implements Matcher {
98
98
 
99
99
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
100
100
  // Try to extract CBOR from the envelope
101
- const cbor = haystack.asLeaf();
101
+ const cbor = haystack.subject().asLeaf();
102
102
  if (cbor === undefined) {
103
103
  return [[], new Map<string, Path[]>()];
104
104
  }
@@ -72,7 +72,7 @@ export class BoolPattern implements Matcher {
72
72
 
73
73
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
74
74
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
75
- const cbor = haystack.asLeaf();
75
+ const cbor = haystack.subject().asLeaf();
76
76
  if (cbor !== undefined) {
77
77
  // Delegate to dcbor-pattern for CBOR matching
78
78
  const dcborPaths = dcborBoolPatternPaths(this._inner, cbor);
@@ -82,7 +82,7 @@ export class ByteStringPattern implements Matcher {
82
82
 
83
83
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
84
84
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
85
- const cbor = haystack.asLeaf();
85
+ const cbor = haystack.subject().asLeaf();
86
86
  if (cbor !== undefined) {
87
87
  // Delegate to dcbor-pattern for CBOR matching
88
88
  const dcborPaths = dcborByteStringPatternPaths(this._inner, cbor);
@@ -156,7 +156,7 @@ export class CBORPattern implements Matcher {
156
156
  }
157
157
 
158
158
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
159
- const envCase = haystack.case();
159
+ const envCase = haystack.subject().case();
160
160
 
161
161
  // Special case for KnownValue envelope
162
162
  if (envCase.type === "knownValue") {
@@ -206,7 +206,7 @@ export class CBORPattern implements Matcher {
206
206
  }
207
207
 
208
208
  // Standard case for CBOR leaf
209
- const leafCbor = haystack.asLeaf();
209
+ const leafCbor = haystack.subject().asLeaf();
210
210
  if (leafCbor === undefined) {
211
211
  return [[], new Map<string, Path[]>()];
212
212
  }
@@ -114,7 +114,7 @@ export class DatePattern implements Matcher {
114
114
 
115
115
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
116
116
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
117
- const cbor = haystack.asLeaf();
117
+ const cbor = haystack.subject().asLeaf();
118
118
  if (cbor !== undefined) {
119
119
  // Delegate to dcbor-pattern for CBOR matching
120
120
  const dcborPaths = dcborDatePatternPaths(this._inner, cbor);
@@ -141,8 +141,7 @@ export function leafPatternPathsWithCaptures(
141
141
  ): [Path[], Map<string, Path[]>] {
142
142
  switch (pattern.type) {
143
143
  case "Cbor":
144
- // CBOR patterns from dcbor-pattern need special handling
145
- return [[], new Map<string, Path[]>()];
144
+ return pattern.pattern.pathsWithCaptures(haystack);
146
145
  case "Number":
147
146
  return pattern.pattern.pathsWithCaptures(haystack);
148
147
  case "Text":
@@ -90,8 +90,9 @@ export class KnownValuePattern implements Matcher {
90
90
  }
91
91
 
92
92
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
93
- // Check if the envelope is a known value via case()
94
- const envCase = haystack.case();
93
+ // Check if the envelope subject is a known value via case()
94
+ const subject = haystack.subject();
95
+ const envCase = subject.case();
95
96
 
96
97
  if (envCase.type === "knownValue") {
97
98
  // Get the KnownValue and create CBOR for pattern matching
@@ -102,7 +103,7 @@ export class KnownValuePattern implements Matcher {
102
103
  }
103
104
 
104
105
  // Also try matching as a leaf (for tagged CBOR containing known values)
105
- const leafCbor = haystack.asLeaf();
106
+ const leafCbor = subject.asLeaf();
106
107
  if (leafCbor !== undefined) {
107
108
  if (knownValuePatternMatches(this._inner, leafCbor)) {
108
109
  return [[[haystack]], new Map<string, Path[]>()];
@@ -67,7 +67,7 @@ export class MapPattern implements Matcher {
67
67
 
68
68
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
69
69
  // Try to extract CBOR from the envelope
70
- const cbor = haystack.asLeaf();
70
+ const cbor = haystack.subject().asLeaf();
71
71
  if (cbor === undefined) {
72
72
  return [[], new Map<string, Path[]>()];
73
73
  }
@@ -58,7 +58,7 @@ export class NullPattern implements Matcher {
58
58
 
59
59
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
60
60
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
61
- const cbor = haystack.asLeaf();
61
+ const cbor = haystack.subject().asLeaf();
62
62
  if (cbor !== undefined) {
63
63
  // Delegate to dcbor-pattern for CBOR matching
64
64
  const dcborPaths = dcborNullPatternPaths(this._inner, cbor);
@@ -136,7 +136,7 @@ export class NumberPattern implements Matcher {
136
136
 
137
137
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
138
138
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
139
- const cbor = haystack.asLeaf();
139
+ const cbor = haystack.subject().asLeaf();
140
140
  if (cbor !== undefined) {
141
141
  // Delegate to dcbor-pattern for CBOR matching
142
142
  const dcborPaths = dcborNumberPatternPaths(this._inner, cbor);
@@ -80,7 +80,7 @@ export class TextPattern implements Matcher {
80
80
 
81
81
  pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
82
82
  // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
83
- const cbor = haystack.asLeaf();
83
+ const cbor = haystack.subject().asLeaf();
84
84
  if (cbor !== undefined) {
85
85
  // Delegate to dcbor-pattern for CBOR matching
86
86
  const dcborPaths = dcborTextPatternPaths(this._inner, cbor);