@suss/extractor 0.19.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 };
@@ -866,6 +1170,7 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
866
1170
  location: {
867
1171
  file: raw.identity.file,
868
1172
  range: raw.identity.range,
1173
+ ...raw.identity.span !== void 0 ? { span: raw.identity.span } : {},
869
1174
  exportName: raw.identity.exportName
870
1175
  },
871
1176
  identity: {
@@ -888,17 +1193,23 @@ function buildMetadata(raw) {
888
1193
  let metadata = {};
889
1194
  const http = buildHttpMetadataValue(raw);
890
1195
  if (http !== null) {
891
- metadata = (0, import_behavioral_ir2.withHttpMetadata)(metadata, http);
1196
+ metadata = (0, import_behavioral_ir3.withHttpMetadata)(metadata, http);
892
1197
  }
893
1198
  const graphql = buildGraphqlMetadataValue(raw);
894
1199
  if (graphql !== null) {
895
- metadata = (0, import_behavioral_ir2.withGraphqlMetadata)(metadata, graphql);
1200
+ metadata = (0, import_behavioral_ir3.withGraphqlMetadata)(metadata, graphql);
896
1201
  }
897
1202
  if (raw.sourceDocumentLabel !== void 0) {
898
- metadata = (0, import_behavioral_ir2.withSourceDocumentMetadata)(metadata, {
1203
+ metadata = (0, import_behavioral_ir3.withSourceDocumentMetadata)(metadata, {
899
1204
  label: raw.sourceDocumentLabel
900
1205
  });
901
1206
  }
1207
+ if (raw.mount !== void 0) {
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 });
1212
+ }
902
1213
  return Object.keys(metadata).length > 0 ? metadata : null;
903
1214
  }
904
1215
  function buildHttpMetadataValue(raw) {
@@ -947,7 +1258,7 @@ function answersWithHttpResponses(raw) {
947
1258
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
948
1259
  return true;
949
1260
  }
950
- return (0, import_behavioral_ir2.exchangesHttpResponses)(raw.boundaryBinding);
1261
+ return (0, import_behavioral_ir3.exchangesHttpResponses)(raw.boundaryBinding);
951
1262
  }
952
1263
  function detectGaps(raw, transitions, options) {
953
1264
  if (options.gapHandling === "silent") {
@@ -1162,27 +1473,39 @@ function paramToInput(param) {
1162
1473
  0 && (module.exports = {
1163
1474
  IdMap,
1164
1475
  IdSet,
1476
+ MAX_PATHS,
1165
1477
  PathBudgetExceeded,
1478
+ SKIP_CHILDREN,
1166
1479
  UnmodeledFlow,
1167
1480
  absentReading,
1168
1481
  ambiguousReading,
1169
1482
  andThenReading,
1170
1483
  assembleSummary,
1171
1484
  assessConfidence,
1485
+ composeWrappers,
1486
+ configuredCallOption,
1172
1487
  detectGaps,
1173
1488
  effectToIR,
1174
1489
  enumerateOrDegrade,
1175
1490
  enumerateStructuredPaths,
1176
1491
  firstWrittenReading,
1492
+ guardsHoldOn,
1177
1493
  httpRouteDiscovery,
1178
1494
  makeTransitionId,
1179
1495
  mapReading,
1180
1496
  paramToInput,
1497
+ rawConditionToPredicate,
1181
1498
  registrationHelperDiscovery,
1499
+ routeHelperIndex,
1500
+ runsBefore,
1501
+ scopeOption,
1182
1502
  sharedGatingConditions,
1503
+ storageSystemOption,
1183
1504
  terminalToOutput,
1184
1505
  unreadableReading,
1185
1506
  unwrapJsonStringify,
1186
1507
  valueToReadFurtherFrom,
1508
+ walkDescendants,
1509
+ wrapperDiscovery,
1187
1510
  writtenReading
1188
1511
  });