@suss/extractor 0.20.0 → 0.21.0

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.
package/README.md CHANGED
@@ -58,6 +58,53 @@ const summary = assembleSummary(raw);
58
58
  // summary.transitions[0].output.type === "response"
59
59
  ```
60
60
 
61
+ ## Composing the wrappers around a unit
62
+
63
+ A route's wire behaviour is not only what its own body does. Middleware, error handlers and validation hooks produce responses for it without appearing in it, which is why a service whose auth middleware returns 401 used to look like every route disagreed with its contract.
64
+
65
+ A wrapper is a meta-function: it takes a unit and returns a unit. Its own body says what it does once you know which call is the continuation, so the adapter reads the call to that parameter as a `delegate` terminal. A path through the wrapper that reaches it hands control to the wrapped unit; a path that ends first responds on its own.
66
+
67
+ ```
68
+ composed = the wrapper's short circuits
69
+ + (its pass-throughs x the wrapped unit's transitions)
70
+ ```
71
+
72
+ `composeWrappers` runs that over a whole run's summaries. It reads the `wrappers` metadata a unit records, finds each wrapper's summary by file and name, and folds them innermost first, the way `mw1(mw2(handler))` reads. A wrapper the framework only calls on a throw goes on last and replaces the paths that ended by throwing with its own response. Every transition a wrapper contributed records which one, under `wrappers.from`, so a reader asking why a route returns 401 lands in the middleware.
73
+
74
+ What it does not read:
75
+
76
+ - **Anything after the continuation returns.** A middleware that inspects the response on the way back out is read up to the `next()` call and no further.
77
+ - **A wrapper whose continuation the pack does not declare, or whose call the walk cannot see.** Nothing says where control passes on, so the wrapper's outcomes are reported beside the unit's own rather than around them.
78
+ - **A throw the walk never saw.** An error handler composes over the paths that end by throwing, so a route whose 500 comes from a call the walk could not follow still does not report 500.
79
+ - **Registration order against route order.** A wrapper registered after a route still composes into it.
80
+
81
+ Composition multiplies paths, so it is capped by the same `MAX_PATHS` budget path enumeration uses. Past it the two sides are reported side by side and the unit gets a gap saying so.
82
+
83
+ ## Which branch an effect belongs to
84
+
85
+ An adapter finds the calls a body makes once, and it finds the branches separately. Something then has to say which branch each call runs on. `guardsHoldOn` and `runsBefore` in `effectGuards.ts` are that decision, and both adapters that make it call these rather than keeping a copy.
86
+
87
+ A call belongs to a branch when two things are true of it.
88
+
89
+ - **No guard the call is written under was recorded the other way around on the branch.** A call inside `if (flag)` stays off every branch that recorded `!flag`. A call written above every guard has none, so it reaches all of them.
90
+ - **The call is written no later than the terminal's last line.** A call written after an early return did not run on that early return's path. The terminal's last line and not its first, because `return new Promise((resolve) => resolve(read()))` runs the call inside its own expression.
91
+
92
+ Neither test is enough on its own, and the two ways to get it wrong are both ordinary code.
93
+
94
+ ```ts
95
+ const found = await dynamo.send(new GetItemCommand({ TableName: "Invoices" }));
96
+ if (!found.Item) {
97
+ return { statusCode: 404, body: JSON.stringify({ error: "no invoice" }) };
98
+ }
99
+ return { statusCode: 200, body: JSON.stringify({ invoice: found.Item }) };
100
+ ```
101
+
102
+ The read is under no guard, so guards alone put it on both branches, which is right. Source order alone would too. But `JSON.stringify` inside the 404 return is under `!found.Item`, and its line comes before the 200 return, so source order alone would put it on the 200 branch, where it never ran. Guards catch that. Turn the example around, put the read after the 404 return, and guards alone put it on the 404 branch, where it also never ran. Source order catches that.
103
+
104
+ The first test asks whether the branch contradicts the guard, not whether the branch repeats it. That matters because a branch often cannot speak about a guard at all. The path engine opacifies a loop, so a terminal after the loop records nothing about an `if` inside the loop body, and the same goes for a call inside a `catch` or inside a callback the walk descended into. The commonest shape in this repo is an accumulator: a loop that pushes into an array under a condition, then returns the array. Requiring the branch to repeat the guard takes those pushes off the only branch there is, and the unit comes out calling nothing at all. So silence on the branch leaves the effect in place, and only a guard written down the other way around takes it off.
105
+
106
+ Comparison is by the condition's polarity and the text it was written as, the same key a transition id is built from. An adapter therefore has to spell a guard on an effect the way it spells the same guard on a branch, or the two never meet and the guard is treated as silence.
107
+
61
108
  ## Coverage
62
109
 
63
110
  ![coverage](../../.github/badges/coverage-extractor.svg)
package/dist/index.cjs CHANGED
@@ -22,33 +22,45 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  IdMap: () => IdMap,
24
24
  IdSet: () => IdSet,
25
+ MAX_PATHS: () => MAX_PATHS,
25
26
  PathBudgetExceeded: () => PathBudgetExceeded,
27
+ SKIP_CHILDREN: () => SKIP_CHILDREN,
26
28
  UnmodeledFlow: () => UnmodeledFlow,
27
29
  absentReading: () => absentReading,
28
30
  ambiguousReading: () => ambiguousReading,
29
31
  andThenReading: () => andThenReading,
30
32
  assembleSummary: () => assembleSummary,
31
33
  assessConfidence: () => assessConfidence,
34
+ composeWrappers: () => composeWrappers,
35
+ configuredCallOption: () => configuredCallOption,
32
36
  detectGaps: () => detectGaps,
33
37
  effectToIR: () => effectToIR,
34
38
  enumerateOrDegrade: () => enumerateOrDegrade,
35
39
  enumerateStructuredPaths: () => enumerateStructuredPaths,
36
40
  firstWrittenReading: () => firstWrittenReading,
41
+ guardsHoldOn: () => guardsHoldOn,
37
42
  httpRouteDiscovery: () => httpRouteDiscovery,
38
43
  makeTransitionId: () => makeTransitionId,
39
44
  mapReading: () => mapReading,
40
45
  paramToInput: () => paramToInput,
46
+ rawConditionToPredicate: () => rawConditionToPredicate,
41
47
  registrationHelperDiscovery: () => registrationHelperDiscovery,
48
+ routeHelperIndex: () => routeHelperIndex,
49
+ runsBefore: () => runsBefore,
50
+ scopeOption: () => scopeOption,
42
51
  sharedGatingConditions: () => sharedGatingConditions,
52
+ storageSystemOption: () => storageSystemOption,
43
53
  terminalToOutput: () => terminalToOutput,
44
54
  unreadableReading: () => unreadableReading,
45
55
  unwrapJsonStringify: () => unwrapJsonStringify,
46
56
  valueToReadFurtherFrom: () => valueToReadFurtherFrom,
57
+ walkDescendants: () => walkDescendants,
58
+ wrapperDiscovery: () => wrapperDiscovery,
47
59
  writtenReading: () => writtenReading
48
60
  });
49
61
  module.exports = __toCommonJS(index_exports);
50
62
  var import_node_crypto = require("crypto");
51
- var import_behavioral_ir2 = require("@suss/behavioral-ir");
63
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
52
64
 
53
65
  // src/inputReads.ts
54
66
  function inputReadsOf(args) {
@@ -140,80 +152,8 @@ function mergeInputReads(derived, syntactic) {
140
152
  return [...found.values()];
141
153
  }
142
154
 
143
- // src/packHelpers.ts
144
- function httpRouteDiscovery(opts) {
145
- const kind = opts.kind ?? "handler";
146
- const calls = opts.importNames.map((importName) => ({
147
- kind,
148
- match: {
149
- type: "registrationCall",
150
- importModule: opts.importModule,
151
- importName,
152
- registrationChain: [...opts.methods]
153
- },
154
- bindingExtraction: {
155
- // `.all` registers every method, so it records "*", which the pairing
156
- // engine treats as agreeing with any method at all.
157
- method: {
158
- type: "fromRegistration",
159
- position: "methodName",
160
- nameMap: { all: "*" }
161
- },
162
- path: { type: "fromArgument", position: 0 }
163
- },
164
- ...opts.mount !== void 0 ? { mount: opts.mount } : {},
165
- requiresImport: [opts.importModule]
166
- }));
167
- if (opts.importNames.length === 0) {
168
- return calls;
169
- }
170
- return [
171
- ...calls,
172
- // Routes registered in a loop over an array of specs, a shape
173
- // registration-call discovery cannot see. The receiver comes from
174
- // the same import declaration as the calls above, so a loop that
175
- // never touches this library's routable is left alone.
176
- {
177
- kind,
178
- match: {
179
- type: "registrationLoop",
180
- elementShape: LOOP_ELEMENT_SHAPE,
181
- receiver: {
182
- importModule: opts.importModule,
183
- importNames: [...opts.importNames]
184
- }
185
- },
186
- requiresImport: [opts.importModule]
187
- }
188
- ];
189
- }
190
- var LOOP_ELEMENT_SHAPE = {
191
- methodKey: "method",
192
- pathKey: "path",
193
- handlerKey: "handler"
194
- };
195
- function registrationHelperDiscovery(helpers, kind = "handler") {
196
- return helpers.map((helper) => ({
197
- kind,
198
- match: {
199
- type: "registrationTemplate",
200
- helperName: helper.helperName,
201
- ...helper.importModule !== void 0 ? { importModule: helper.importModule } : {},
202
- registrations: helper.registrations.map((one) => ({ ...one }))
203
- }
204
- }));
205
- }
206
- function unwrapJsonStringify(body) {
207
- if (body === null || typeof body !== "object") {
208
- return body;
209
- }
210
- const candidate = body;
211
- if (candidate.kind !== "call" || candidate.callee !== "JSON.stringify") {
212
- return body;
213
- }
214
- const inner = candidate.args?.[0];
215
- return inner ?? body;
216
- }
155
+ // src/composeWrappers.ts
156
+ var import_behavioral_ir2 = require("@suss/behavioral-ir");
217
157
 
218
158
  // src/paths/enumeratePaths.ts
219
159
  var import_behavioral_ir = require("@suss/behavioral-ir");
@@ -630,23 +570,372 @@ function reasonToDegrade(error) {
630
570
  return null;
631
571
  }
632
572
 
633
- // src/paths/gatingConditions.ts
573
+ // src/composeWrappers.ts
574
+ var BUDGET_GAP = {
575
+ type: "unreadOutcome",
576
+ conditions: [],
577
+ consequence: "unknown",
578
+ description: `Composing the wrappers registered around this unit would have gone past the path budget of ${MAX_PATHS}, so what each of them produces is reported beside this unit's own outcomes rather than under the conditions that reach it`
579
+ };
580
+ function composeWrappers(summaries) {
581
+ const byKey = /* @__PURE__ */ new Map();
582
+ for (const summary of summaries) {
583
+ const key = summaryKey(summary.location.file, summary.identity.name);
584
+ if (!byKey.has(key)) {
585
+ byKey.set(key, summary);
586
+ }
587
+ }
588
+ return summaries.map((summary) => composeOne(summary, byKey));
589
+ }
590
+ function summaryKey(file, name) {
591
+ return `${file}::${name}`;
592
+ }
593
+ function composeOne(summary, byKey) {
594
+ const recorded = (0, import_behavioral_ir2.readWrapperMetadata)(summary);
595
+ const applied = recorded?.applied ?? [];
596
+ const covering = applied.filter(
597
+ (reference) => coversUnit(reference, summary)
598
+ );
599
+ const narrowed = covering.length === applied.length ? summary : {
600
+ ...summary,
601
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(summary.metadata, {
602
+ applied: covering
603
+ })
604
+ };
605
+ const wrappers = covering.flatMap((reference) => {
606
+ const found = byKey.get(summaryKey(reference.file, reference.name));
607
+ return found === void 0 ? [] : [{ reference, summary: found }];
608
+ });
609
+ if (wrappers.length === 0) {
610
+ return narrowed;
611
+ }
612
+ let composition = {
613
+ transitions: summary.transitions,
614
+ degraded: false
615
+ };
616
+ for (const wrapper of [...wrappers].reverse()) {
617
+ if (wrapper.reference.onThrow === true) {
618
+ continue;
619
+ }
620
+ composition = merge(composition, applyWrapper(wrapper, composition));
621
+ }
622
+ for (const wrapper of wrappers) {
623
+ if (wrapper.reference.onThrow !== true) {
624
+ continue;
625
+ }
626
+ composition = merge(composition, applyThrowWrapper(wrapper, composition));
627
+ }
628
+ if (composition.transitions === summary.transitions) {
629
+ return narrowed;
630
+ }
631
+ return {
632
+ ...narrowed,
633
+ transitions: withDistinctIds(composition.transitions),
634
+ gaps: composition.degraded ? [...summary.gaps, BUDGET_GAP] : summary.gaps
635
+ };
636
+ }
637
+ function merge(before, step) {
638
+ return {
639
+ transitions: step.transitions,
640
+ degraded: before.degraded || step.degraded
641
+ };
642
+ }
643
+ function coversUnit(reference, summary) {
644
+ if (reference.scope === void 0) {
645
+ return true;
646
+ }
647
+ const binding = summary.identity.boundaryBinding;
648
+ return binding !== null && (0, import_behavioral_ir2.withinScope)(binding, reference.scope);
649
+ }
650
+ function applyWrapper(wrapper, inner) {
651
+ const shortCircuits = attribute(
652
+ wrapper.summary.transitions.filter((t) => t.output.type !== "delegate"),
653
+ wrapper.reference
654
+ );
655
+ const passThroughs = wrapper.summary.transitions.filter(
656
+ (t) => t.output.type === "delegate"
657
+ );
658
+ if (passThroughs.length === 0 || shortCircuits.length + passThroughs.length * inner.transitions.length > MAX_PATHS) {
659
+ return {
660
+ transitions: [...shortCircuits, ...inner.transitions],
661
+ degraded: passThroughs.length > 0
662
+ };
663
+ }
664
+ const continued = passThroughs.flatMap(
665
+ (passThrough) => inner.transitions.map((transition) => splice(passThrough, transition))
666
+ );
667
+ return { transitions: [...shortCircuits, ...continued], degraded: false };
668
+ }
669
+ function applyThrowWrapper(wrapper, inner) {
670
+ const thrown = inner.transitions.filter((t) => t.output.type === "throw");
671
+ if (thrown.length === 0) {
672
+ return inner;
673
+ }
674
+ const rest = inner.transitions.filter((t) => t.output.type !== "throw");
675
+ const handled = wrapper.summary.transitions;
676
+ if (rest.length + thrown.length * handled.length > MAX_PATHS) {
677
+ return {
678
+ transitions: [
679
+ ...inner.transitions,
680
+ ...attribute(handled, wrapper.reference)
681
+ ],
682
+ degraded: true
683
+ };
684
+ }
685
+ const composed = thrown.flatMap(
686
+ (transition) => attribute(
687
+ handled.map((handler) => splice(transition, handler)),
688
+ wrapper.reference
689
+ )
690
+ );
691
+ return { transitions: [...rest, ...composed], degraded: false };
692
+ }
693
+ function splice(outer, inner) {
694
+ return {
695
+ ...inner,
696
+ id: `${inner.id}:via:${outer.id}`,
697
+ conditions: [...outer.conditions, ...inner.conditions],
698
+ effects: [...outer.effects, ...inner.effects],
699
+ isDefault: outer.isDefault && inner.isDefault
700
+ };
701
+ }
702
+ function attribute(transitions, reference) {
703
+ return transitions.map((transition) => ({
704
+ ...transition,
705
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, { from: reference })
706
+ }));
707
+ }
708
+ function withDistinctIds(transitions) {
709
+ const seen = /* @__PURE__ */ new Map();
710
+ return transitions.map((transition) => {
711
+ const taken = seen.get(transition.id);
712
+ seen.set(transition.id, (taken ?? 0) + 1);
713
+ return taken === void 0 ? transition : { ...transition, id: `${transition.id}#${taken + 1}` };
714
+ });
715
+ }
716
+
717
+ // src/effectGuards.ts
634
718
  var keyOf = (condition) => `${condition.polarity}:${condition.sourceText}`;
719
+ var opposite = (condition) => `${condition.polarity === "positive" ? "negative" : "positive"}:${condition.sourceText}`;
720
+ function guardsHoldOn(preconditions, conditions) {
721
+ if (preconditions === void 0 || preconditions.length === 0) {
722
+ return true;
723
+ }
724
+ const onBranch = new Set(conditions.map(keyOf));
725
+ return preconditions.every(
726
+ (precondition) => !onBranch.has(opposite(precondition))
727
+ );
728
+ }
729
+ function runsBefore(effectLine, terminalEndLine) {
730
+ return effectLine <= terminalEndLine;
731
+ }
732
+
733
+ // src/packHelpers.ts
734
+ function httpRouteDiscovery(opts) {
735
+ const kind = opts.kind ?? "handler";
736
+ const calls = opts.importNames.map((importName) => ({
737
+ kind,
738
+ match: {
739
+ type: "registrationCall",
740
+ importModule: opts.importModule,
741
+ importName,
742
+ registrationChain: [...opts.methods]
743
+ },
744
+ bindingExtraction: {
745
+ // `.all` registers every method, so it records "*", which the pairing
746
+ // engine treats as agreeing with any method at all.
747
+ method: {
748
+ type: "fromRegistration",
749
+ position: "methodName",
750
+ nameMap: { all: "*" }
751
+ },
752
+ path: { type: "fromArgument", position: 0 }
753
+ },
754
+ ...opts.mount !== void 0 ? { mount: opts.mount } : {},
755
+ requiresImport: [opts.importModule]
756
+ }));
757
+ if (opts.importNames.length === 0) {
758
+ return calls;
759
+ }
760
+ return [
761
+ ...calls,
762
+ // Routes registered in a loop over an array of specs, a shape
763
+ // registration-call discovery cannot see. The receiver comes from
764
+ // the same import declaration as the calls above, so a loop that
765
+ // never touches this library's routable is left alone.
766
+ {
767
+ kind,
768
+ match: {
769
+ type: "registrationLoop",
770
+ elementShape: LOOP_ELEMENT_SHAPE,
771
+ receiver: {
772
+ importModule: opts.importModule,
773
+ importNames: [...opts.importNames]
774
+ }
775
+ },
776
+ requiresImport: [opts.importModule]
777
+ }
778
+ ];
779
+ }
780
+ function wrapperDiscovery(opts) {
781
+ return opts.importNames.flatMap(
782
+ (importName) => opts.wraps.map((wraps) => ({
783
+ kind: opts.kind ?? "middleware",
784
+ match: {
785
+ type: "registrationCall",
786
+ importModule: opts.importModule,
787
+ importName,
788
+ registrationChain: []
789
+ },
790
+ wraps: { ...wraps },
791
+ requiresImport: [opts.importModule]
792
+ }))
793
+ );
794
+ }
795
+ var LOOP_ELEMENT_SHAPE = {
796
+ methodKey: "method",
797
+ pathKey: "path",
798
+ handlerKey: "handler"
799
+ };
800
+ function registrationHelperDiscovery(helpers, kind = "handler") {
801
+ return helpers.map((helper) => ({
802
+ kind,
803
+ match: {
804
+ type: "registrationTemplate",
805
+ helperName: helper.helperName,
806
+ ...helper.importModule !== void 0 ? { importModule: helper.importModule } : {},
807
+ ...helper.subject !== void 0 ? { subject: helper.subject } : {},
808
+ registrations: helper.registrations.map((one) => ({ ...one }))
809
+ }
810
+ }));
811
+ }
812
+ function routeHelperIndex(opts) {
813
+ const methods = new Set(
814
+ opts.methods.map(
815
+ (method) => method.startsWith(".") ? method.slice(1) : method
816
+ )
817
+ );
818
+ return {
819
+ find: { by: "subject" },
820
+ declare: (helpers) => ({
821
+ discovery: registrationHelperDiscovery(
822
+ helpers.flatMap((helper) => routesRegisteredBy(helper, methods, opts)),
823
+ opts.kind ?? "handler"
824
+ )
825
+ })
826
+ };
827
+ }
828
+ function routesRegisteredBy(helper, methods, opts) {
829
+ const registrations = [];
830
+ let subjectArgument;
831
+ for (const sink of helper.sinks) {
832
+ const registration = routeRegisteredBy(helper, sink, methods);
833
+ if (registration === null) {
834
+ continue;
835
+ }
836
+ registrations.push(registration);
837
+ if (sink.receiver.as === "parameter") {
838
+ subjectArgument = sink.receiver.position;
839
+ }
840
+ }
841
+ if (registrations.length === 0 || subjectArgument === void 0) {
842
+ return [];
843
+ }
844
+ return [
845
+ {
846
+ helperName: helper.name,
847
+ importModule: helper.file,
848
+ subject: {
849
+ argument: subjectArgument,
850
+ importModule: opts.importModule,
851
+ importNames: [...opts.importNames]
852
+ },
853
+ registrations
854
+ }
855
+ ];
856
+ }
857
+ function routeRegisteredBy(helper, sink, methods) {
858
+ if (sink.method === null || !methods.has(sink.method)) {
859
+ return null;
860
+ }
861
+ const { receiver } = sink;
862
+ if (receiver.as !== "parameter" || receiver.property !== void 0 || !helper.subjectParameters.includes(receiver.position)) {
863
+ return null;
864
+ }
865
+ const pathTemplate = sink.arguments[0];
866
+ const handler = sink.arguments[sink.arguments.length - 1];
867
+ if (sink.arguments.length < 2 || pathTemplate === void 0 || pathTemplate.as !== "text" || handler === void 0) {
868
+ return null;
869
+ }
870
+ const handlerArg = handlerSlot(handler);
871
+ return handlerArg === null ? null : {
872
+ // `.all` registers every method, which the pairing engine
873
+ // spells as a wildcard.
874
+ method: sink.method === "all" ? "*" : sink.method.toUpperCase(),
875
+ pathTemplate: pathTemplate.text,
876
+ handlerArg
877
+ };
878
+ }
879
+ function handlerSlot(value) {
880
+ if (value.as !== "parameter") {
881
+ return null;
882
+ }
883
+ return value.property === void 0 ? `{${value.position}}` : `{${value.position}}.${value.property}`;
884
+ }
885
+ function unwrapJsonStringify(body) {
886
+ if (body === null || typeof body !== "object") {
887
+ return body;
888
+ }
889
+ const candidate = body;
890
+ if (candidate.kind !== "call" || candidate.callee !== "JSON.stringify") {
891
+ return body;
892
+ }
893
+ const inner = candidate.args?.[0];
894
+ return inner ?? body;
895
+ }
896
+
897
+ // src/packOptions.ts
898
+ var import_zod = require("zod");
899
+ var storageSystemOption = import_zod.z.enum([
900
+ "postgresql",
901
+ "mysql",
902
+ "sqlite"
903
+ ]);
904
+ var scopeOption = import_zod.z.string();
905
+ var configuredCallOption = import_zod.z.object({
906
+ /** Module that declares the receiver's type. */
907
+ module: import_zod.z.string(),
908
+ /** Type name of the receiver, as exported from that module. */
909
+ receiver: import_zod.z.string(),
910
+ /** Method that performs the send. */
911
+ method: import_zod.z.string(),
912
+ /** Argument index carrying the subject. */
913
+ subjectArg: import_zod.z.number(),
914
+ /**
915
+ * Which argument the message body is. Left out when the method has no
916
+ * single body argument, as a batch method taking a list of entries
917
+ * does, and then no body is reported.
918
+ */
919
+ bodyArg: import_zod.z.number().optional()
920
+ }).strict();
921
+
922
+ // src/paths/gatingConditions.ts
923
+ var keyOf2 = (condition) => `${condition.polarity}:${condition.sourceText}`;
635
924
  function sharedGatingConditions(paths, readPredicate) {
636
925
  const [first, ...rest] = paths ?? [];
637
926
  if (first === void 0) {
638
927
  return [];
639
928
  }
640
- const shared = new Set(first.map(keyOf));
929
+ const shared = new Set(first.map(keyOf2));
641
930
  for (const path of rest) {
642
- const here = new Set(path.map(keyOf));
931
+ const here = new Set(path.map(keyOf2));
643
932
  for (const value of shared) {
644
933
  if (!here.has(value)) {
645
934
  shared.delete(value);
646
935
  }
647
936
  }
648
937
  }
649
- return first.filter((condition) => shared.has(keyOf(condition))).map((condition) => ({
938
+ return first.filter((condition) => shared.has(keyOf2(condition))).map((condition) => ({
650
939
  sourceText: condition.sourceText,
651
940
  structured: readPredicate === void 0 || condition.expression === null ? null : readPredicate(condition.expression),
652
941
  polarity: condition.polarity,
@@ -700,6 +989,21 @@ var IdMap = class {
700
989
  }
701
990
  };
702
991
 
992
+ // src/paths/nodeWalk.ts
993
+ var SKIP_CHILDREN = /* @__PURE__ */ Symbol("skipChildren");
994
+ function walkDescendants(root, carried, visitor) {
995
+ for (const child of root.namedChildren) {
996
+ if (child === null) {
997
+ continue;
998
+ }
999
+ visitor.at(child, carried);
1000
+ const inner = visitor.into(child, carried);
1001
+ if (inner !== SKIP_CHILDREN) {
1002
+ walkDescendants(child, inner, visitor);
1003
+ }
1004
+ }
1005
+ }
1006
+
703
1007
  // src/reading.ts
704
1008
  function writtenReading(value, range) {
705
1009
  return { kind: "written", value, range };
@@ -889,19 +1193,22 @@ function buildMetadata(raw) {
889
1193
  let metadata = {};
890
1194
  const http = buildHttpMetadataValue(raw);
891
1195
  if (http !== null) {
892
- metadata = (0, import_behavioral_ir2.withHttpMetadata)(metadata, http);
1196
+ metadata = (0, import_behavioral_ir3.withHttpMetadata)(metadata, http);
893
1197
  }
894
1198
  const graphql = buildGraphqlMetadataValue(raw);
895
1199
  if (graphql !== null) {
896
- metadata = (0, import_behavioral_ir2.withGraphqlMetadata)(metadata, graphql);
1200
+ metadata = (0, import_behavioral_ir3.withGraphqlMetadata)(metadata, graphql);
897
1201
  }
898
1202
  if (raw.sourceDocumentLabel !== void 0) {
899
- metadata = (0, import_behavioral_ir2.withSourceDocumentMetadata)(metadata, {
1203
+ metadata = (0, import_behavioral_ir3.withSourceDocumentMetadata)(metadata, {
900
1204
  label: raw.sourceDocumentLabel
901
1205
  });
902
1206
  }
903
1207
  if (raw.mount !== void 0) {
904
- metadata = (0, import_behavioral_ir2.withMountMetadata)(metadata, raw.mount);
1208
+ metadata = (0, import_behavioral_ir3.withMountMetadata)(metadata, raw.mount);
1209
+ }
1210
+ if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
1211
+ metadata = (0, import_behavioral_ir3.withWrapperMetadata)(metadata, { applied: raw.wrappers });
905
1212
  }
906
1213
  return Object.keys(metadata).length > 0 ? metadata : null;
907
1214
  }
@@ -951,7 +1258,7 @@ function answersWithHttpResponses(raw) {
951
1258
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
952
1259
  return true;
953
1260
  }
954
- return (0, import_behavioral_ir2.exchangesHttpResponses)(raw.boundaryBinding);
1261
+ return (0, import_behavioral_ir3.exchangesHttpResponses)(raw.boundaryBinding);
955
1262
  }
956
1263
  function detectGaps(raw, transitions, options) {
957
1264
  if (options.gapHandling === "silent") {
@@ -1166,27 +1473,39 @@ function paramToInput(param) {
1166
1473
  0 && (module.exports = {
1167
1474
  IdMap,
1168
1475
  IdSet,
1476
+ MAX_PATHS,
1169
1477
  PathBudgetExceeded,
1478
+ SKIP_CHILDREN,
1170
1479
  UnmodeledFlow,
1171
1480
  absentReading,
1172
1481
  ambiguousReading,
1173
1482
  andThenReading,
1174
1483
  assembleSummary,
1175
1484
  assessConfidence,
1485
+ composeWrappers,
1486
+ configuredCallOption,
1176
1487
  detectGaps,
1177
1488
  effectToIR,
1178
1489
  enumerateOrDegrade,
1179
1490
  enumerateStructuredPaths,
1180
1491
  firstWrittenReading,
1492
+ guardsHoldOn,
1181
1493
  httpRouteDiscovery,
1182
1494
  makeTransitionId,
1183
1495
  mapReading,
1184
1496
  paramToInput,
1497
+ rawConditionToPredicate,
1185
1498
  registrationHelperDiscovery,
1499
+ routeHelperIndex,
1500
+ runsBefore,
1501
+ scopeOption,
1186
1502
  sharedGatingConditions,
1503
+ storageSystemOption,
1187
1504
  terminalToOutput,
1188
1505
  unreadableReading,
1189
1506
  unwrapJsonStringify,
1190
1507
  valueToReadFurtherFrom,
1508
+ walkDescendants,
1509
+ wrapperDiscovery,
1191
1510
  writtenReading
1192
1511
  });