@suss/extractor 0.22.3 → 0.23.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 +7 -1
- package/dist/index.cjs +116 -57
- package/dist/index.d.cts +70 -35
- package/dist/index.d.ts +70 -35
- package/dist/index.js +106 -45
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -75,11 +75,17 @@ What it does not read:
|
|
|
75
75
|
|
|
76
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
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.
|
|
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 as a transition. The contract comparison below is the one place that counts it anyway.
|
|
79
79
|
- **Registration order against route order.** A wrapper registered after a route still composes into it.
|
|
80
80
|
|
|
81
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
82
|
|
|
83
|
+
### The declared contract, compared again
|
|
84
|
+
|
|
85
|
+
`assembleSummary` compares a route's declared responses against its own body and records a gap for each status declared but never produced, or produced but never declared. That runs before anything around the route is read, so a contract that declares the 401 the auth middleware returns was reported as declaring a status nothing produces, once per route.
|
|
86
|
+
|
|
87
|
+
`composeWrappers` runs the same comparison over the composed transitions and replaces those gaps. A status a wrapper produces counts as produced, and one the contract leaves out is reported against the wrapper by name: "requireCaller, registered around this handler, produces status 429 which is not declared in the hono contract". An error handler's responses count as produced on every route it covers, whether or not a path through the route was seen to throw, since anything the route calls can throw at runtime. A run with `gapHandling: "silent"` gets no gaps from this step either.
|
|
88
|
+
|
|
83
89
|
## Which branch an effect belongs to
|
|
84
90
|
|
|
85
91
|
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.
|
package/dist/index.cjs
CHANGED
|
@@ -90,7 +90,68 @@ __export(index_exports, {
|
|
|
90
90
|
});
|
|
91
91
|
module.exports = __toCommonJS(index_exports);
|
|
92
92
|
var import_node_crypto3 = require("crypto");
|
|
93
|
-
var
|
|
93
|
+
var import_behavioral_ir5 = require("@suss/behavioral-ir");
|
|
94
|
+
|
|
95
|
+
// src/contractStatusGaps.ts
|
|
96
|
+
var import_behavioral_ir = require("@suss/behavioral-ir");
|
|
97
|
+
function contractStatusGaps(contract, transitions) {
|
|
98
|
+
const producers = producersByStatus(transitions);
|
|
99
|
+
const declared = new Set(contract.responses.map((r) => r.statusCode));
|
|
100
|
+
const gaps = [];
|
|
101
|
+
for (const status of declared) {
|
|
102
|
+
if (!producers.has(status)) {
|
|
103
|
+
gaps.push({
|
|
104
|
+
type: "unhandledCase",
|
|
105
|
+
conditions: [],
|
|
106
|
+
consequence: "frameworkDefault",
|
|
107
|
+
description: `Declared response ${status} is never produced by the handler`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const [status, wrappers] of producers) {
|
|
112
|
+
if (declared.has(status)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
gaps.push({
|
|
116
|
+
type: "unhandledCase",
|
|
117
|
+
conditions: [],
|
|
118
|
+
consequence: "unknown",
|
|
119
|
+
description: `${producerOf(wrappers)} produces status ${status} which is not declared in the ${contract.framework} contract`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return gaps;
|
|
123
|
+
}
|
|
124
|
+
function producersByStatus(transitions) {
|
|
125
|
+
const producers = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const transition of transitions) {
|
|
127
|
+
if (transition.output.type !== "response") {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const status = transition.output.statusCode;
|
|
131
|
+
if (status?.type !== "literal" || typeof status.value !== "number") {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const from = (0, import_behavioral_ir.readWrapperMetadata)(transition)?.from;
|
|
135
|
+
const seen = producers.get(status.value);
|
|
136
|
+
if (seen === void 0) {
|
|
137
|
+
producers.set(
|
|
138
|
+
status.value,
|
|
139
|
+
new Set(from === void 0 ? [] : [from.name])
|
|
140
|
+
);
|
|
141
|
+
} else if (from === void 0) {
|
|
142
|
+
seen.clear();
|
|
143
|
+
} else if (seen.size > 0) {
|
|
144
|
+
seen.add(from.name);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return producers;
|
|
148
|
+
}
|
|
149
|
+
function producerOf(wrappers) {
|
|
150
|
+
if (wrappers.size === 0) {
|
|
151
|
+
return "Handler";
|
|
152
|
+
}
|
|
153
|
+
return `${[...wrappers].sort().join(" and ")}, registered around this handler,`;
|
|
154
|
+
}
|
|
94
155
|
|
|
95
156
|
// src/inputReads.ts
|
|
96
157
|
function inputReadsOf(args) {
|
|
@@ -183,10 +244,10 @@ function mergeInputReads(derived, syntactic) {
|
|
|
183
244
|
}
|
|
184
245
|
|
|
185
246
|
// src/composeWrappers.ts
|
|
186
|
-
var
|
|
247
|
+
var import_behavioral_ir3 = require("@suss/behavioral-ir");
|
|
187
248
|
|
|
188
249
|
// src/paths/enumeratePaths.ts
|
|
189
|
-
var
|
|
250
|
+
var import_behavioral_ir2 = require("@suss/behavioral-ir");
|
|
190
251
|
var MAX_PATHS = 256;
|
|
191
252
|
var UnmodeledFlow = class extends Error {
|
|
192
253
|
};
|
|
@@ -230,7 +291,7 @@ var loopCompletedCond = (headerText, exit) => fixedCond(
|
|
|
230
291
|
exit === "throw" ? "earlyThrow" : "earlyReturn"
|
|
231
292
|
);
|
|
232
293
|
var catchEntryCond = () => fixedCond(
|
|
233
|
-
{ sourceText:
|
|
294
|
+
{ sourceText: import_behavioral_ir2.CATCH_ENTRY_TEXT, expression: null },
|
|
234
295
|
"positive",
|
|
235
296
|
"catchBlock"
|
|
236
297
|
);
|
|
@@ -607,7 +668,7 @@ var BUDGET_GAP = {
|
|
|
607
668
|
consequence: "unknown",
|
|
608
669
|
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`
|
|
609
670
|
};
|
|
610
|
-
function composeWrappers(summaries) {
|
|
671
|
+
function composeWrappers(summaries, options = {}) {
|
|
611
672
|
const byKey = /* @__PURE__ */ new Map();
|
|
612
673
|
for (const summary of summaries) {
|
|
613
674
|
const key = summaryKey(summary.location.file, summary.identity.name);
|
|
@@ -615,20 +676,21 @@ function composeWrappers(summaries) {
|
|
|
615
676
|
byKey.set(key, summary);
|
|
616
677
|
}
|
|
617
678
|
}
|
|
618
|
-
|
|
679
|
+
const keepGaps = options.gapHandling !== "silent";
|
|
680
|
+
return summaries.map((summary) => composeOne(summary, byKey, keepGaps));
|
|
619
681
|
}
|
|
620
682
|
function summaryKey(file, name) {
|
|
621
683
|
return `${file}::${name}`;
|
|
622
684
|
}
|
|
623
|
-
function composeOne(summary, byKey) {
|
|
624
|
-
const recorded = (0,
|
|
685
|
+
function composeOne(summary, byKey, keepGaps) {
|
|
686
|
+
const recorded = (0, import_behavioral_ir3.readWrapperMetadata)(summary);
|
|
625
687
|
const applied = recorded?.applied ?? [];
|
|
626
688
|
const covering = applied.filter(
|
|
627
689
|
(reference) => coversUnit(reference, summary)
|
|
628
690
|
);
|
|
629
691
|
const narrowed = covering.length === applied.length ? summary : {
|
|
630
692
|
...summary,
|
|
631
|
-
metadata: (0,
|
|
693
|
+
metadata: (0, import_behavioral_ir3.withWrapperMetadata)(summary.metadata, {
|
|
632
694
|
applied: covering
|
|
633
695
|
})
|
|
634
696
|
};
|
|
@@ -655,14 +717,45 @@ function composeOne(summary, byKey) {
|
|
|
655
717
|
}
|
|
656
718
|
composition = merge(composition, applyThrowWrapper(wrapper, composition));
|
|
657
719
|
}
|
|
658
|
-
|
|
720
|
+
const transitions = composition.transitions === summary.transitions ? summary.transitions : withDistinctIds(composition.transitions);
|
|
721
|
+
if (!keepGaps) {
|
|
722
|
+
return { ...narrowed, transitions };
|
|
723
|
+
}
|
|
724
|
+
const gaps = withContractStatusGaps(summary, transitions, wrappers);
|
|
725
|
+
if (transitions === summary.transitions && gaps === summary.gaps) {
|
|
659
726
|
return narrowed;
|
|
660
727
|
}
|
|
661
728
|
return {
|
|
662
729
|
...narrowed,
|
|
663
|
-
transitions
|
|
664
|
-
gaps: composition.degraded ? [...
|
|
730
|
+
transitions,
|
|
731
|
+
gaps: composition.degraded ? [...gaps, BUDGET_GAP] : gaps
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function withContractStatusGaps(summary, composed, wrappers) {
|
|
735
|
+
const contract = (0, import_behavioral_ir3.readHttpMetadata)(summary)?.declaredContract;
|
|
736
|
+
const binding = summary.identity.boundaryBinding;
|
|
737
|
+
if (contract === void 0 || binding !== null && !(0, import_behavioral_ir3.exchangesHttpResponses)(binding)) {
|
|
738
|
+
return summary.gaps;
|
|
739
|
+
}
|
|
740
|
+
const declared = {
|
|
741
|
+
framework: contract.framework ?? "declared",
|
|
742
|
+
responses: contract.responses
|
|
665
743
|
};
|
|
744
|
+
const stale = new Set(
|
|
745
|
+
contractStatusGaps(declared, summary.transitions).map(gapKey)
|
|
746
|
+
);
|
|
747
|
+
const onThrow = wrappers.filter((wrapper) => wrapper.reference.onThrow === true).flatMap(
|
|
748
|
+
(wrapper) => attribute(wrapper.summary.transitions, wrapper.reference)
|
|
749
|
+
);
|
|
750
|
+
const gaps = [
|
|
751
|
+
...summary.gaps.filter((gap) => !stale.has(gapKey(gap))),
|
|
752
|
+
...contractStatusGaps(declared, [...composed, ...onThrow])
|
|
753
|
+
];
|
|
754
|
+
const unchanged = gaps.length === summary.gaps.length && gaps.every((gap, i) => gapKey(gap) === gapKey(summary.gaps[i]));
|
|
755
|
+
return unchanged ? summary.gaps : gaps;
|
|
756
|
+
}
|
|
757
|
+
function gapKey(gap) {
|
|
758
|
+
return `${gap.type}|${gap.description}`;
|
|
666
759
|
}
|
|
667
760
|
function merge(before, step) {
|
|
668
761
|
return {
|
|
@@ -675,7 +768,7 @@ function coversUnit(reference, summary) {
|
|
|
675
768
|
return true;
|
|
676
769
|
}
|
|
677
770
|
const binding = summary.identity.boundaryBinding;
|
|
678
|
-
return binding !== null && (0,
|
|
771
|
+
return binding !== null && (0, import_behavioral_ir3.withinScope)(binding, reference.scope);
|
|
679
772
|
}
|
|
680
773
|
function applyWrapper(wrapper, inner) {
|
|
681
774
|
const shortCircuits = attribute(
|
|
@@ -732,7 +825,7 @@ function splice(outer, inner) {
|
|
|
732
825
|
function attribute(transitions, reference) {
|
|
733
826
|
return transitions.map((transition) => ({
|
|
734
827
|
...transition,
|
|
735
|
-
metadata: (0,
|
|
828
|
+
metadata: (0, import_behavioral_ir3.withWrapperMetadata)(transition.metadata, { from: reference })
|
|
736
829
|
}));
|
|
737
830
|
}
|
|
738
831
|
function withDistinctIds(transitions) {
|
|
@@ -1603,7 +1696,7 @@ function fileStampEquals(a, b) {
|
|
|
1603
1696
|
}
|
|
1604
1697
|
|
|
1605
1698
|
// src/extractionReport.ts
|
|
1606
|
-
var
|
|
1699
|
+
var import_behavioral_ir4 = require("@suss/behavioral-ir");
|
|
1607
1700
|
var emptyTally = () => ({
|
|
1608
1701
|
candidateFiles: 0,
|
|
1609
1702
|
unitsDiscovered: 0,
|
|
@@ -1654,7 +1747,7 @@ function summaryCountsByPack(summaries) {
|
|
|
1654
1747
|
}
|
|
1655
1748
|
const entry = counts.get(recognition) ?? emptyCounts();
|
|
1656
1749
|
entry.bound += 1;
|
|
1657
|
-
if (
|
|
1750
|
+
if (import_behavioral_ir4.BOUNDARY_ROLE[summary.kind] === "provider") {
|
|
1658
1751
|
entry.providers += 1;
|
|
1659
1752
|
}
|
|
1660
1753
|
if (summary.transitions.length > 0) {
|
|
@@ -2122,22 +2215,22 @@ function buildMetadata(raw) {
|
|
|
2122
2215
|
let metadata = {};
|
|
2123
2216
|
const http = buildHttpMetadataValue(raw);
|
|
2124
2217
|
if (http !== null) {
|
|
2125
|
-
metadata = (0,
|
|
2218
|
+
metadata = (0, import_behavioral_ir5.withHttpMetadata)(metadata, http);
|
|
2126
2219
|
}
|
|
2127
2220
|
const graphql = buildGraphqlMetadataValue(raw);
|
|
2128
2221
|
if (graphql !== null) {
|
|
2129
|
-
metadata = (0,
|
|
2222
|
+
metadata = (0, import_behavioral_ir5.withGraphqlMetadata)(metadata, graphql);
|
|
2130
2223
|
}
|
|
2131
2224
|
if (raw.sourceDocumentLabel !== void 0) {
|
|
2132
|
-
metadata = (0,
|
|
2225
|
+
metadata = (0, import_behavioral_ir5.withSourceDocumentMetadata)(metadata, {
|
|
2133
2226
|
label: raw.sourceDocumentLabel
|
|
2134
2227
|
});
|
|
2135
2228
|
}
|
|
2136
2229
|
if (raw.mount !== void 0) {
|
|
2137
|
-
metadata = (0,
|
|
2230
|
+
metadata = (0, import_behavioral_ir5.withMountMetadata)(metadata, raw.mount);
|
|
2138
2231
|
}
|
|
2139
2232
|
if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
|
|
2140
|
-
metadata = (0,
|
|
2233
|
+
metadata = (0, import_behavioral_ir5.withWrapperMetadata)(metadata, { applied: raw.wrappers });
|
|
2141
2234
|
}
|
|
2142
2235
|
return Object.keys(metadata).length > 0 ? metadata : null;
|
|
2143
2236
|
}
|
|
@@ -2187,7 +2280,7 @@ function answersWithHttpResponses(raw) {
|
|
|
2187
2280
|
if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
|
|
2188
2281
|
return true;
|
|
2189
2282
|
}
|
|
2190
|
-
return (0,
|
|
2283
|
+
return (0, import_behavioral_ir5.exchangesHttpResponses)(raw.boundaryBinding);
|
|
2191
2284
|
}
|
|
2192
2285
|
function detectGaps(raw, transitions, options) {
|
|
2193
2286
|
if (options.gapHandling === "silent") {
|
|
@@ -2221,41 +2314,7 @@ function detectGaps(raw, transitions, options) {
|
|
|
2221
2314
|
});
|
|
2222
2315
|
}
|
|
2223
2316
|
if (raw.declaredContract && answersWithHttpResponses(raw)) {
|
|
2224
|
-
|
|
2225
|
-
transitions.flatMap((t) => {
|
|
2226
|
-
if (t.output.type !== "response") {
|
|
2227
|
-
return [];
|
|
2228
|
-
}
|
|
2229
|
-
const sc = t.output.statusCode;
|
|
2230
|
-
if (sc?.type === "literal") {
|
|
2231
|
-
return [sc.value];
|
|
2232
|
-
}
|
|
2233
|
-
return [];
|
|
2234
|
-
})
|
|
2235
|
-
);
|
|
2236
|
-
const declaredStatuses = new Set(
|
|
2237
|
-
raw.declaredContract.responses.map((r) => r.statusCode)
|
|
2238
|
-
);
|
|
2239
|
-
for (const declared of declaredStatuses) {
|
|
2240
|
-
if (!producedStatuses.has(declared)) {
|
|
2241
|
-
gaps.push({
|
|
2242
|
-
type: "unhandledCase",
|
|
2243
|
-
conditions: [],
|
|
2244
|
-
consequence: "frameworkDefault",
|
|
2245
|
-
description: `Declared response ${declared} is never produced by the handler`
|
|
2246
|
-
});
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
for (const produced of producedStatuses) {
|
|
2250
|
-
if (!declaredStatuses.has(produced)) {
|
|
2251
|
-
gaps.push({
|
|
2252
|
-
type: "unhandledCase",
|
|
2253
|
-
conditions: [],
|
|
2254
|
-
consequence: "unknown",
|
|
2255
|
-
description: `Handler produces status ${produced} which is not declared in the ${raw.declaredContract.framework} contract`
|
|
2256
|
-
});
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2317
|
+
gaps.push(...contractStatusGaps(raw.declaredContract, transitions));
|
|
2259
2318
|
}
|
|
2260
2319
|
return gaps;
|
|
2261
2320
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -721,6 +721,66 @@ type DeclaredBinding = {
|
|
|
721
721
|
messageBus: MessageBusSemantics["messageBus"];
|
|
722
722
|
channel: ChannelSource;
|
|
723
723
|
};
|
|
724
|
+
/**
|
|
725
|
+
* How a framework takes a function that runs around a handler rather
|
|
726
|
+
* than as one. Middleware and an error handler are registered through
|
|
727
|
+
* a method on the routable, `app.use(fn)` or `app.onError(fn)`. A
|
|
728
|
+
* validation hook is handed to the routable's constructor instead,
|
|
729
|
+
* `new OpenAPIHono({ defaultHook })`, and runs for every route on it.
|
|
730
|
+
*/
|
|
731
|
+
type WrapperRegistration = WrapperMethodRegistration | WrapperOptionRegistration;
|
|
732
|
+
/** What every wrapper registration says about the function it registers. */
|
|
733
|
+
interface WrapperFunctionShape {
|
|
734
|
+
/**
|
|
735
|
+
* Parameter position of the continuation inside the wrapper, the one
|
|
736
|
+
* it calls to hand control on. Absent when the wrapper never
|
|
737
|
+
* continues, which is how an error handler that always responds is
|
|
738
|
+
* written.
|
|
739
|
+
*/
|
|
740
|
+
continuationParam?: number;
|
|
741
|
+
/**
|
|
742
|
+
* Parameter position that receives what the wrapped unit threw. A
|
|
743
|
+
* wrapper that declares one runs only when the wrapped unit's path
|
|
744
|
+
* ended by throwing, which is a fact about how the framework invokes
|
|
745
|
+
* it rather than anything its body says. Absent means the wrapper
|
|
746
|
+
* runs on the ordinary path.
|
|
747
|
+
*/
|
|
748
|
+
throwParam?: number;
|
|
749
|
+
/**
|
|
750
|
+
* Parameter position that receives a value the framework worked out
|
|
751
|
+
* before calling the wrapper, the outcome of validating the request
|
|
752
|
+
* for a hook. The pack's terminals read one parameter further along
|
|
753
|
+
* from there, as they do past `throwParam`, and nothing else changes:
|
|
754
|
+
* the wrapper still runs on the ordinary path.
|
|
755
|
+
*/
|
|
756
|
+
resultParam?: number;
|
|
757
|
+
}
|
|
758
|
+
/** A wrapper registered through a method on the routable. */
|
|
759
|
+
interface WrapperMethodRegistration extends WrapperFunctionShape {
|
|
760
|
+
/** Method name that registers a wrapper, e.g. "use" or "onError". */
|
|
761
|
+
method: string;
|
|
762
|
+
/** Argument position of the wrapper function. */
|
|
763
|
+
targetPosition: number;
|
|
764
|
+
/**
|
|
765
|
+
* Argument position of a path pattern narrowing which routes the
|
|
766
|
+
* wrapper runs for, when the method takes one. Absent means the
|
|
767
|
+
* wrapper runs for every route on the subject.
|
|
768
|
+
*/
|
|
769
|
+
scopePosition?: number;
|
|
770
|
+
/**
|
|
771
|
+
* Only match a registered function declared with exactly this many
|
|
772
|
+
* parameters. Express tells its error handlers apart from its
|
|
773
|
+
* middleware by arity alone, both being `app.use(fn)`.
|
|
774
|
+
*/
|
|
775
|
+
arity?: number;
|
|
776
|
+
}
|
|
777
|
+
/** A wrapper handed to the routable's constructor as an option. */
|
|
778
|
+
interface WrapperOptionRegistration extends WrapperFunctionShape {
|
|
779
|
+
/** The option the wrapper is under, e.g. "defaultHook". */
|
|
780
|
+
constructorOption: string;
|
|
781
|
+
/** Argument position of the options object in the constructor call. */
|
|
782
|
+
targetPosition: number;
|
|
783
|
+
}
|
|
724
784
|
interface DiscoveryPattern {
|
|
725
785
|
/** The kind of code unit this discovers: "handler", "loader", "action", "component", etc. */
|
|
726
786
|
kind: string;
|
|
@@ -767,39 +827,7 @@ interface DiscoveryPattern {
|
|
|
767
827
|
* routable records a reference to it. A registration whose function
|
|
768
828
|
* the resolution store cannot follow contributes nothing.
|
|
769
829
|
*/
|
|
770
|
-
wraps?:
|
|
771
|
-
/** Method name that registers a wrapper, e.g. "use" or "onError". */
|
|
772
|
-
method: string;
|
|
773
|
-
/** Argument position of the wrapper function. */
|
|
774
|
-
targetPosition: number;
|
|
775
|
-
/**
|
|
776
|
-
* Argument position of a path pattern narrowing which routes the
|
|
777
|
-
* wrapper runs for, when the method takes one. Absent means the
|
|
778
|
-
* wrapper runs for every route on the subject.
|
|
779
|
-
*/
|
|
780
|
-
scopePosition?: number;
|
|
781
|
-
/**
|
|
782
|
-
* Parameter position of the continuation inside the wrapper, the one
|
|
783
|
-
* it calls to hand control on. Absent when the wrapper never
|
|
784
|
-
* continues, which is how an error handler that always responds is
|
|
785
|
-
* written.
|
|
786
|
-
*/
|
|
787
|
-
continuationParam?: number;
|
|
788
|
-
/**
|
|
789
|
-
* Parameter position that receives what the wrapped unit threw. A
|
|
790
|
-
* wrapper that declares one runs only when the wrapped unit's path
|
|
791
|
-
* ended by throwing, which is a fact about how the framework invokes
|
|
792
|
-
* it rather than anything its body says. Absent means the wrapper
|
|
793
|
-
* runs on the ordinary path.
|
|
794
|
-
*/
|
|
795
|
-
throwParam?: number;
|
|
796
|
-
/**
|
|
797
|
-
* Only match a registered function declared with exactly this many
|
|
798
|
-
* parameters. Express tells its error handlers apart from its
|
|
799
|
-
* middleware by arity alone, both being `app.use(fn)`.
|
|
800
|
-
*/
|
|
801
|
-
arity?: number;
|
|
802
|
-
};
|
|
830
|
+
wraps?: WrapperRegistration;
|
|
803
831
|
/**
|
|
804
832
|
* This pattern only runs against files that import one of these module
|
|
805
833
|
* specifiers, or a sub-path of one. An empty array means no gate at all
|
|
@@ -1863,12 +1891,19 @@ declare function valueToReadFurtherFrom<T>(reading: Reading<T>): T | null;
|
|
|
1863
1891
|
* what composition does not read.
|
|
1864
1892
|
*/
|
|
1865
1893
|
|
|
1894
|
+
/**
|
|
1895
|
+
* Whether to keep gaps at all. A run that asked for none has none on
|
|
1896
|
+
* the summaries coming in, and composition adds none either.
|
|
1897
|
+
*/
|
|
1898
|
+
interface ComposeOptions {
|
|
1899
|
+
gapHandling?: "strict" | "permissive" | "silent";
|
|
1900
|
+
}
|
|
1866
1901
|
/**
|
|
1867
1902
|
* Every summary, with the ones that record wrappers replaced by their
|
|
1868
1903
|
* composition. A summary with no wrappers, and one whose wrappers this
|
|
1869
1904
|
* run has no summaries for, comes back untouched.
|
|
1870
1905
|
*/
|
|
1871
|
-
declare function composeWrappers(summaries: readonly BehavioralSummary[]): BehavioralSummary[];
|
|
1906
|
+
declare function composeWrappers(summaries: readonly BehavioralSummary[], options?: ComposeOptions): BehavioralSummary[];
|
|
1872
1907
|
|
|
1873
1908
|
/**
|
|
1874
1909
|
* Which branch an effect belongs to.
|
|
@@ -3407,4 +3442,4 @@ declare function rawConditionToPredicate(c: RawCondition): Predicate;
|
|
|
3407
3442
|
declare function effectToIR(effect: RawEffect): Effect;
|
|
3408
3443
|
declare function paramToInput(param: RawParameter): Input;
|
|
3409
3444
|
|
|
3410
|
-
export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
|
|
3445
|
+
export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
|
package/dist/index.d.ts
CHANGED
|
@@ -721,6 +721,66 @@ type DeclaredBinding = {
|
|
|
721
721
|
messageBus: MessageBusSemantics["messageBus"];
|
|
722
722
|
channel: ChannelSource;
|
|
723
723
|
};
|
|
724
|
+
/**
|
|
725
|
+
* How a framework takes a function that runs around a handler rather
|
|
726
|
+
* than as one. Middleware and an error handler are registered through
|
|
727
|
+
* a method on the routable, `app.use(fn)` or `app.onError(fn)`. A
|
|
728
|
+
* validation hook is handed to the routable's constructor instead,
|
|
729
|
+
* `new OpenAPIHono({ defaultHook })`, and runs for every route on it.
|
|
730
|
+
*/
|
|
731
|
+
type WrapperRegistration = WrapperMethodRegistration | WrapperOptionRegistration;
|
|
732
|
+
/** What every wrapper registration says about the function it registers. */
|
|
733
|
+
interface WrapperFunctionShape {
|
|
734
|
+
/**
|
|
735
|
+
* Parameter position of the continuation inside the wrapper, the one
|
|
736
|
+
* it calls to hand control on. Absent when the wrapper never
|
|
737
|
+
* continues, which is how an error handler that always responds is
|
|
738
|
+
* written.
|
|
739
|
+
*/
|
|
740
|
+
continuationParam?: number;
|
|
741
|
+
/**
|
|
742
|
+
* Parameter position that receives what the wrapped unit threw. A
|
|
743
|
+
* wrapper that declares one runs only when the wrapped unit's path
|
|
744
|
+
* ended by throwing, which is a fact about how the framework invokes
|
|
745
|
+
* it rather than anything its body says. Absent means the wrapper
|
|
746
|
+
* runs on the ordinary path.
|
|
747
|
+
*/
|
|
748
|
+
throwParam?: number;
|
|
749
|
+
/**
|
|
750
|
+
* Parameter position that receives a value the framework worked out
|
|
751
|
+
* before calling the wrapper, the outcome of validating the request
|
|
752
|
+
* for a hook. The pack's terminals read one parameter further along
|
|
753
|
+
* from there, as they do past `throwParam`, and nothing else changes:
|
|
754
|
+
* the wrapper still runs on the ordinary path.
|
|
755
|
+
*/
|
|
756
|
+
resultParam?: number;
|
|
757
|
+
}
|
|
758
|
+
/** A wrapper registered through a method on the routable. */
|
|
759
|
+
interface WrapperMethodRegistration extends WrapperFunctionShape {
|
|
760
|
+
/** Method name that registers a wrapper, e.g. "use" or "onError". */
|
|
761
|
+
method: string;
|
|
762
|
+
/** Argument position of the wrapper function. */
|
|
763
|
+
targetPosition: number;
|
|
764
|
+
/**
|
|
765
|
+
* Argument position of a path pattern narrowing which routes the
|
|
766
|
+
* wrapper runs for, when the method takes one. Absent means the
|
|
767
|
+
* wrapper runs for every route on the subject.
|
|
768
|
+
*/
|
|
769
|
+
scopePosition?: number;
|
|
770
|
+
/**
|
|
771
|
+
* Only match a registered function declared with exactly this many
|
|
772
|
+
* parameters. Express tells its error handlers apart from its
|
|
773
|
+
* middleware by arity alone, both being `app.use(fn)`.
|
|
774
|
+
*/
|
|
775
|
+
arity?: number;
|
|
776
|
+
}
|
|
777
|
+
/** A wrapper handed to the routable's constructor as an option. */
|
|
778
|
+
interface WrapperOptionRegistration extends WrapperFunctionShape {
|
|
779
|
+
/** The option the wrapper is under, e.g. "defaultHook". */
|
|
780
|
+
constructorOption: string;
|
|
781
|
+
/** Argument position of the options object in the constructor call. */
|
|
782
|
+
targetPosition: number;
|
|
783
|
+
}
|
|
724
784
|
interface DiscoveryPattern {
|
|
725
785
|
/** The kind of code unit this discovers: "handler", "loader", "action", "component", etc. */
|
|
726
786
|
kind: string;
|
|
@@ -767,39 +827,7 @@ interface DiscoveryPattern {
|
|
|
767
827
|
* routable records a reference to it. A registration whose function
|
|
768
828
|
* the resolution store cannot follow contributes nothing.
|
|
769
829
|
*/
|
|
770
|
-
wraps?:
|
|
771
|
-
/** Method name that registers a wrapper, e.g. "use" or "onError". */
|
|
772
|
-
method: string;
|
|
773
|
-
/** Argument position of the wrapper function. */
|
|
774
|
-
targetPosition: number;
|
|
775
|
-
/**
|
|
776
|
-
* Argument position of a path pattern narrowing which routes the
|
|
777
|
-
* wrapper runs for, when the method takes one. Absent means the
|
|
778
|
-
* wrapper runs for every route on the subject.
|
|
779
|
-
*/
|
|
780
|
-
scopePosition?: number;
|
|
781
|
-
/**
|
|
782
|
-
* Parameter position of the continuation inside the wrapper, the one
|
|
783
|
-
* it calls to hand control on. Absent when the wrapper never
|
|
784
|
-
* continues, which is how an error handler that always responds is
|
|
785
|
-
* written.
|
|
786
|
-
*/
|
|
787
|
-
continuationParam?: number;
|
|
788
|
-
/**
|
|
789
|
-
* Parameter position that receives what the wrapped unit threw. A
|
|
790
|
-
* wrapper that declares one runs only when the wrapped unit's path
|
|
791
|
-
* ended by throwing, which is a fact about how the framework invokes
|
|
792
|
-
* it rather than anything its body says. Absent means the wrapper
|
|
793
|
-
* runs on the ordinary path.
|
|
794
|
-
*/
|
|
795
|
-
throwParam?: number;
|
|
796
|
-
/**
|
|
797
|
-
* Only match a registered function declared with exactly this many
|
|
798
|
-
* parameters. Express tells its error handlers apart from its
|
|
799
|
-
* middleware by arity alone, both being `app.use(fn)`.
|
|
800
|
-
*/
|
|
801
|
-
arity?: number;
|
|
802
|
-
};
|
|
830
|
+
wraps?: WrapperRegistration;
|
|
803
831
|
/**
|
|
804
832
|
* This pattern only runs against files that import one of these module
|
|
805
833
|
* specifiers, or a sub-path of one. An empty array means no gate at all
|
|
@@ -1863,12 +1891,19 @@ declare function valueToReadFurtherFrom<T>(reading: Reading<T>): T | null;
|
|
|
1863
1891
|
* what composition does not read.
|
|
1864
1892
|
*/
|
|
1865
1893
|
|
|
1894
|
+
/**
|
|
1895
|
+
* Whether to keep gaps at all. A run that asked for none has none on
|
|
1896
|
+
* the summaries coming in, and composition adds none either.
|
|
1897
|
+
*/
|
|
1898
|
+
interface ComposeOptions {
|
|
1899
|
+
gapHandling?: "strict" | "permissive" | "silent";
|
|
1900
|
+
}
|
|
1866
1901
|
/**
|
|
1867
1902
|
* Every summary, with the ones that record wrappers replaced by their
|
|
1868
1903
|
* composition. A summary with no wrappers, and one whose wrappers this
|
|
1869
1904
|
* run has no summaries for, comes back untouched.
|
|
1870
1905
|
*/
|
|
1871
|
-
declare function composeWrappers(summaries: readonly BehavioralSummary[]): BehavioralSummary[];
|
|
1906
|
+
declare function composeWrappers(summaries: readonly BehavioralSummary[], options?: ComposeOptions): BehavioralSummary[];
|
|
1872
1907
|
|
|
1873
1908
|
/**
|
|
1874
1909
|
* Which branch an effect belongs to.
|
|
@@ -3407,4 +3442,4 @@ declare function rawConditionToPredicate(c: RawCondition): Predicate;
|
|
|
3407
3442
|
declare function effectToIR(effect: RawEffect): Effect;
|
|
3408
3443
|
declare function paramToInput(param: RawParameter): Input;
|
|
3409
3444
|
|
|
3410
|
-
export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
|
|
3445
|
+
export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { createHash as createHash3 } from "crypto";
|
|
3
3
|
import {
|
|
4
|
-
exchangesHttpResponses,
|
|
4
|
+
exchangesHttpResponses as exchangesHttpResponses2,
|
|
5
5
|
withGraphqlMetadata,
|
|
6
6
|
withHttpMetadata,
|
|
7
7
|
withMountMetadata,
|
|
@@ -9,6 +9,67 @@ import {
|
|
|
9
9
|
withWrapperMetadata as withWrapperMetadata2
|
|
10
10
|
} from "@suss/behavioral-ir";
|
|
11
11
|
|
|
12
|
+
// src/contractStatusGaps.ts
|
|
13
|
+
import { readWrapperMetadata } from "@suss/behavioral-ir";
|
|
14
|
+
function contractStatusGaps(contract, transitions) {
|
|
15
|
+
const producers = producersByStatus(transitions);
|
|
16
|
+
const declared = new Set(contract.responses.map((r) => r.statusCode));
|
|
17
|
+
const gaps = [];
|
|
18
|
+
for (const status of declared) {
|
|
19
|
+
if (!producers.has(status)) {
|
|
20
|
+
gaps.push({
|
|
21
|
+
type: "unhandledCase",
|
|
22
|
+
conditions: [],
|
|
23
|
+
consequence: "frameworkDefault",
|
|
24
|
+
description: `Declared response ${status} is never produced by the handler`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [status, wrappers] of producers) {
|
|
29
|
+
if (declared.has(status)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
gaps.push({
|
|
33
|
+
type: "unhandledCase",
|
|
34
|
+
conditions: [],
|
|
35
|
+
consequence: "unknown",
|
|
36
|
+
description: `${producerOf(wrappers)} produces status ${status} which is not declared in the ${contract.framework} contract`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return gaps;
|
|
40
|
+
}
|
|
41
|
+
function producersByStatus(transitions) {
|
|
42
|
+
const producers = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const transition of transitions) {
|
|
44
|
+
if (transition.output.type !== "response") {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const status = transition.output.statusCode;
|
|
48
|
+
if (status?.type !== "literal" || typeof status.value !== "number") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const from = readWrapperMetadata(transition)?.from;
|
|
52
|
+
const seen = producers.get(status.value);
|
|
53
|
+
if (seen === void 0) {
|
|
54
|
+
producers.set(
|
|
55
|
+
status.value,
|
|
56
|
+
new Set(from === void 0 ? [] : [from.name])
|
|
57
|
+
);
|
|
58
|
+
} else if (from === void 0) {
|
|
59
|
+
seen.clear();
|
|
60
|
+
} else if (seen.size > 0) {
|
|
61
|
+
seen.add(from.name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return producers;
|
|
65
|
+
}
|
|
66
|
+
function producerOf(wrappers) {
|
|
67
|
+
if (wrappers.size === 0) {
|
|
68
|
+
return "Handler";
|
|
69
|
+
}
|
|
70
|
+
return `${[...wrappers].sort().join(" and ")}, registered around this handler,`;
|
|
71
|
+
}
|
|
72
|
+
|
|
12
73
|
// src/inputReads.ts
|
|
13
74
|
function inputReadsOf(args) {
|
|
14
75
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -101,7 +162,9 @@ function mergeInputReads(derived, syntactic) {
|
|
|
101
162
|
|
|
102
163
|
// src/composeWrappers.ts
|
|
103
164
|
import {
|
|
104
|
-
|
|
165
|
+
exchangesHttpResponses,
|
|
166
|
+
readHttpMetadata,
|
|
167
|
+
readWrapperMetadata as readWrapperMetadata2,
|
|
105
168
|
withinScope,
|
|
106
169
|
withWrapperMetadata
|
|
107
170
|
} from "@suss/behavioral-ir";
|
|
@@ -528,7 +591,7 @@ var BUDGET_GAP = {
|
|
|
528
591
|
consequence: "unknown",
|
|
529
592
|
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`
|
|
530
593
|
};
|
|
531
|
-
function composeWrappers(summaries) {
|
|
594
|
+
function composeWrappers(summaries, options = {}) {
|
|
532
595
|
const byKey = /* @__PURE__ */ new Map();
|
|
533
596
|
for (const summary of summaries) {
|
|
534
597
|
const key = summaryKey(summary.location.file, summary.identity.name);
|
|
@@ -536,13 +599,14 @@ function composeWrappers(summaries) {
|
|
|
536
599
|
byKey.set(key, summary);
|
|
537
600
|
}
|
|
538
601
|
}
|
|
539
|
-
|
|
602
|
+
const keepGaps = options.gapHandling !== "silent";
|
|
603
|
+
return summaries.map((summary) => composeOne(summary, byKey, keepGaps));
|
|
540
604
|
}
|
|
541
605
|
function summaryKey(file, name) {
|
|
542
606
|
return `${file}::${name}`;
|
|
543
607
|
}
|
|
544
|
-
function composeOne(summary, byKey) {
|
|
545
|
-
const recorded =
|
|
608
|
+
function composeOne(summary, byKey, keepGaps) {
|
|
609
|
+
const recorded = readWrapperMetadata2(summary);
|
|
546
610
|
const applied = recorded?.applied ?? [];
|
|
547
611
|
const covering = applied.filter(
|
|
548
612
|
(reference) => coversUnit(reference, summary)
|
|
@@ -576,14 +640,45 @@ function composeOne(summary, byKey) {
|
|
|
576
640
|
}
|
|
577
641
|
composition = merge(composition, applyThrowWrapper(wrapper, composition));
|
|
578
642
|
}
|
|
579
|
-
|
|
643
|
+
const transitions = composition.transitions === summary.transitions ? summary.transitions : withDistinctIds(composition.transitions);
|
|
644
|
+
if (!keepGaps) {
|
|
645
|
+
return { ...narrowed, transitions };
|
|
646
|
+
}
|
|
647
|
+
const gaps = withContractStatusGaps(summary, transitions, wrappers);
|
|
648
|
+
if (transitions === summary.transitions && gaps === summary.gaps) {
|
|
580
649
|
return narrowed;
|
|
581
650
|
}
|
|
582
651
|
return {
|
|
583
652
|
...narrowed,
|
|
584
|
-
transitions
|
|
585
|
-
gaps: composition.degraded ? [...
|
|
653
|
+
transitions,
|
|
654
|
+
gaps: composition.degraded ? [...gaps, BUDGET_GAP] : gaps
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function withContractStatusGaps(summary, composed, wrappers) {
|
|
658
|
+
const contract = readHttpMetadata(summary)?.declaredContract;
|
|
659
|
+
const binding = summary.identity.boundaryBinding;
|
|
660
|
+
if (contract === void 0 || binding !== null && !exchangesHttpResponses(binding)) {
|
|
661
|
+
return summary.gaps;
|
|
662
|
+
}
|
|
663
|
+
const declared = {
|
|
664
|
+
framework: contract.framework ?? "declared",
|
|
665
|
+
responses: contract.responses
|
|
586
666
|
};
|
|
667
|
+
const stale = new Set(
|
|
668
|
+
contractStatusGaps(declared, summary.transitions).map(gapKey)
|
|
669
|
+
);
|
|
670
|
+
const onThrow = wrappers.filter((wrapper) => wrapper.reference.onThrow === true).flatMap(
|
|
671
|
+
(wrapper) => attribute(wrapper.summary.transitions, wrapper.reference)
|
|
672
|
+
);
|
|
673
|
+
const gaps = [
|
|
674
|
+
...summary.gaps.filter((gap) => !stale.has(gapKey(gap))),
|
|
675
|
+
...contractStatusGaps(declared, [...composed, ...onThrow])
|
|
676
|
+
];
|
|
677
|
+
const unchanged = gaps.length === summary.gaps.length && gaps.every((gap, i) => gapKey(gap) === gapKey(summary.gaps[i]));
|
|
678
|
+
return unchanged ? summary.gaps : gaps;
|
|
679
|
+
}
|
|
680
|
+
function gapKey(gap) {
|
|
681
|
+
return `${gap.type}|${gap.description}`;
|
|
587
682
|
}
|
|
588
683
|
function merge(before, step) {
|
|
589
684
|
return {
|
|
@@ -2107,7 +2202,7 @@ function answersWithHttpResponses(raw) {
|
|
|
2107
2202
|
if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
|
|
2108
2203
|
return true;
|
|
2109
2204
|
}
|
|
2110
|
-
return
|
|
2205
|
+
return exchangesHttpResponses2(raw.boundaryBinding);
|
|
2111
2206
|
}
|
|
2112
2207
|
function detectGaps(raw, transitions, options) {
|
|
2113
2208
|
if (options.gapHandling === "silent") {
|
|
@@ -2141,41 +2236,7 @@ function detectGaps(raw, transitions, options) {
|
|
|
2141
2236
|
});
|
|
2142
2237
|
}
|
|
2143
2238
|
if (raw.declaredContract && answersWithHttpResponses(raw)) {
|
|
2144
|
-
|
|
2145
|
-
transitions.flatMap((t) => {
|
|
2146
|
-
if (t.output.type !== "response") {
|
|
2147
|
-
return [];
|
|
2148
|
-
}
|
|
2149
|
-
const sc = t.output.statusCode;
|
|
2150
|
-
if (sc?.type === "literal") {
|
|
2151
|
-
return [sc.value];
|
|
2152
|
-
}
|
|
2153
|
-
return [];
|
|
2154
|
-
})
|
|
2155
|
-
);
|
|
2156
|
-
const declaredStatuses = new Set(
|
|
2157
|
-
raw.declaredContract.responses.map((r) => r.statusCode)
|
|
2158
|
-
);
|
|
2159
|
-
for (const declared of declaredStatuses) {
|
|
2160
|
-
if (!producedStatuses.has(declared)) {
|
|
2161
|
-
gaps.push({
|
|
2162
|
-
type: "unhandledCase",
|
|
2163
|
-
conditions: [],
|
|
2164
|
-
consequence: "frameworkDefault",
|
|
2165
|
-
description: `Declared response ${declared} is never produced by the handler`
|
|
2166
|
-
});
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
for (const produced of producedStatuses) {
|
|
2170
|
-
if (!declaredStatuses.has(produced)) {
|
|
2171
|
-
gaps.push({
|
|
2172
|
-
type: "unhandledCase",
|
|
2173
|
-
conditions: [],
|
|
2174
|
-
consequence: "unknown",
|
|
2175
|
-
description: `Handler produces status ${produced} which is not declared in the ${raw.declaredContract.framework} contract`
|
|
2176
|
-
});
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2239
|
+
gaps.push(...contractStatusGaps(raw.declaredContract, transitions));
|
|
2179
2240
|
}
|
|
2180
2241
|
return gaps;
|
|
2181
2242
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/extractor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The suss extraction pipeline, which turns what a language adapter read into the summaries the checker pairs across a boundary.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"suss",
|
|
8
|
-
"behavioral-summary",
|
|
9
8
|
"static-analysis",
|
|
9
|
+
"behavioral-summary",
|
|
10
10
|
"extraction",
|
|
11
|
-
"typescript"
|
|
11
|
+
"typescript",
|
|
12
|
+
"api-contract"
|
|
12
13
|
],
|
|
13
14
|
"license": "Apache-2.0",
|
|
14
15
|
"author": "Nimbus Cloud AI LLC",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"typecheck": "tsc --noEmit"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@suss/behavioral-ir": "0.
|
|
49
|
+
"@suss/behavioral-ir": "0.23.0",
|
|
49
50
|
"zod": "^4.0.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|