@suss/ir-core 0.33.1 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/dist/{chunk-N564FH5U.js → chunk-A46EUIKC.js} +546 -237
- package/dist/index.cjs +659 -284
- package/dist/index.d.cts +452 -360
- package/dist/index.d.ts +452 -360
- package/dist/index.js +94 -31
- package/dist/{schemas-CIns0fqF.d.cts → schemas-lfg8kvak.d.cts} +94 -81
- package/dist/{schemas-CIns0fqF.d.ts → schemas-lfg8kvak.d.ts} +94 -81
- package/dist/schemas.cjs +541 -239
- package/dist/schemas.d.cts +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/schemas.ts
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z13 } from "zod";
|
|
3
3
|
|
|
4
4
|
// src/semantics/registry.ts
|
|
5
|
-
import { z as
|
|
5
|
+
import { z as z12 } from "zod";
|
|
6
6
|
|
|
7
7
|
// src/semantics/functionCall.ts
|
|
8
8
|
import { z } from "zod";
|
|
@@ -23,6 +23,9 @@ function unitIdentityKey(deploymentTarget, instanceName) {
|
|
|
23
23
|
function metricIdentityKey(metricSystem, metricType) {
|
|
24
24
|
return `metric:${metricSystem} ${metricType}`;
|
|
25
25
|
}
|
|
26
|
+
function ioIdentityKey(target) {
|
|
27
|
+
return `io:${target}`;
|
|
28
|
+
}
|
|
26
29
|
|
|
27
30
|
// src/semantics/definition.ts
|
|
28
31
|
function defineBoundarySemantics(definition) {
|
|
@@ -33,10 +36,11 @@ function defineBoundarySemantics(definition) {
|
|
|
33
36
|
var FunctionCallSemanticsSchema = z.object({
|
|
34
37
|
name: z.literal("function-call"),
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* module path
|
|
39
|
-
*
|
|
39
|
+
* Where the function lives inside this repository. It is either the
|
|
40
|
+
* name of a module the project lists in `suss.json`, such as
|
|
41
|
+
* `"billing"`, or a module path, such as the file a server action is
|
|
42
|
+
* written in. A name has no separator in it and a path always does.
|
|
43
|
+
* Packs that don't do cross-module pairing can leave it unset.
|
|
40
44
|
*/
|
|
41
45
|
module: z.string().optional(),
|
|
42
46
|
/** Named export within the module, when applicable. */
|
|
@@ -61,35 +65,38 @@ var FunctionCallSemanticsSchema = z.object({
|
|
|
61
65
|
*/
|
|
62
66
|
exportPath: z.array(z.string()).optional()
|
|
63
67
|
});
|
|
68
|
+
function isModuleName(module) {
|
|
69
|
+
return !module.includes("/") && !module.includes("\\");
|
|
70
|
+
}
|
|
64
71
|
var functionCallSemantics = defineBoundarySemantics({
|
|
65
72
|
name: "function-call",
|
|
66
73
|
schema: FunctionCallSemanticsSchema,
|
|
67
|
-
// A call
|
|
68
|
-
//
|
|
74
|
+
// A call inside the process does not produce a span, and the
|
|
75
|
+
// conventions have no attributes for it.
|
|
69
76
|
semconv: {},
|
|
70
77
|
behavior: {
|
|
71
|
-
/** A call returns a value,
|
|
78
|
+
/** A call returns a value, with no status. */
|
|
72
79
|
exchangesHttpResponses: false,
|
|
73
80
|
leavesTheProcess: false,
|
|
74
81
|
reportsUnpairedItself: false,
|
|
75
82
|
/**
|
|
76
|
-
* In-repo units
|
|
77
|
-
*
|
|
78
|
-
*
|
|
83
|
+
* In-repo units such as components have no key on purpose. They
|
|
84
|
+
* pair through call edges instead of the keyed pass, so a missing
|
|
85
|
+
* key does not make them unreachable.
|
|
79
86
|
*/
|
|
80
87
|
canPair: () => true,
|
|
81
|
-
/**
|
|
82
|
-
* `"fn:<package>::<exportPath>"` when both are set, and
|
|
83
|
-
* `"fn:<module>::<exportName>"` for an in-repo unit that states its
|
|
84
|
-
* module, the way a server action does. Components and bare
|
|
85
|
-
* handlers state neither and have no key at all.
|
|
86
|
-
*/
|
|
87
88
|
rewritePaths(semantics, rewrite) {
|
|
88
|
-
if (semantics.module === void 0) {
|
|
89
|
+
if (semantics.module === void 0 || isModuleName(semantics.module)) {
|
|
89
90
|
return semantics;
|
|
90
91
|
}
|
|
91
92
|
return { ...semantics, module: rewrite(semantics.module) };
|
|
92
93
|
},
|
|
94
|
+
/**
|
|
95
|
+
* `"fn:<package>::<exportPath>"` when both are set, and
|
|
96
|
+
* `"fn:<module>::<exportName>"` for an in-repo unit that gives its
|
|
97
|
+
* module, as a server action does. Components and plain handlers
|
|
98
|
+
* give neither and have no key.
|
|
99
|
+
*/
|
|
93
100
|
identityKey(semantics) {
|
|
94
101
|
if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
|
|
95
102
|
return fnIdentityKey(semantics.package, semantics.exportPath);
|
|
@@ -120,15 +127,15 @@ var graphqlOperationSemantics = defineBoundarySemantics({
|
|
|
120
127
|
behavior: {
|
|
121
128
|
/**
|
|
122
129
|
* An operation gets a data-and-errors document back, which the
|
|
123
|
-
* GraphQL contract checker reads. Status codes
|
|
130
|
+
* GraphQL contract checker reads. Status codes do not apply.
|
|
124
131
|
*/
|
|
125
132
|
exchangesHttpResponses: false,
|
|
126
133
|
leavesTheProcess: true,
|
|
127
134
|
reportsUnpairedItself: false,
|
|
128
135
|
identityKey: () => null,
|
|
129
|
-
/**
|
|
136
|
+
/** No key on purpose. The dedicated pass pairs an operation by its document. */
|
|
130
137
|
canPair: () => true,
|
|
131
|
-
/**
|
|
138
|
+
/** `query GetUser`. Only for display, since the contract checker does the pairing. */
|
|
132
139
|
displayLabel(semantics) {
|
|
133
140
|
return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
|
|
134
141
|
}
|
|
@@ -151,14 +158,14 @@ var GraphqlResolverSemanticsSchema = z3.object({
|
|
|
151
158
|
var graphqlResolverSemantics = defineBoundarySemantics({
|
|
152
159
|
name: "graphql-resolver",
|
|
153
160
|
schema: GraphqlResolverSemanticsSchema,
|
|
154
|
-
// The conventions
|
|
155
|
-
// resolver the server ran for one field of it.
|
|
161
|
+
// The conventions describe the operation a client sent. They have no
|
|
162
|
+
// attribute for the resolver the server ran for one field of it.
|
|
156
163
|
semconv: {},
|
|
157
164
|
behavior: {
|
|
158
|
-
/** A resolver returns a field value,
|
|
165
|
+
/** A resolver returns a field value, with no status. */
|
|
159
166
|
exchangesHttpResponses: false,
|
|
160
|
-
// A
|
|
161
|
-
// server calls
|
|
167
|
+
// A resolver runs because a client outside the process sent a query,
|
|
168
|
+
// even though the server calls it in its own process.
|
|
162
169
|
leavesTheProcess: true,
|
|
163
170
|
reportsUnpairedItself: false,
|
|
164
171
|
/**
|
|
@@ -175,8 +182,32 @@ var graphqlResolverSemantics = defineBoundarySemantics({
|
|
|
175
182
|
}
|
|
176
183
|
});
|
|
177
184
|
|
|
178
|
-
// src/semantics/
|
|
185
|
+
// src/semantics/io.ts
|
|
179
186
|
import { z as z4 } from "zod";
|
|
187
|
+
var IoSemanticsSchema = z4.object({
|
|
188
|
+
name: z4.literal("io"),
|
|
189
|
+
/** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
|
|
190
|
+
target: z4.string().nullable()
|
|
191
|
+
});
|
|
192
|
+
var ioSemantics = defineBoundarySemantics({
|
|
193
|
+
name: "io",
|
|
194
|
+
schema: IoSemanticsSchema,
|
|
195
|
+
// The semantic conventions have no attribute for a process's own streams.
|
|
196
|
+
semconv: {},
|
|
197
|
+
behavior: {
|
|
198
|
+
exchangesHttpResponses: false,
|
|
199
|
+
leavesTheProcess: true,
|
|
200
|
+
reportsUnpairedItself: false,
|
|
201
|
+
identityKey(semantics) {
|
|
202
|
+
return semantics.target === null ? null : ioIdentityKey(semantics.target);
|
|
203
|
+
},
|
|
204
|
+
// Nothing declares the reading side, so a write has nothing to meet.
|
|
205
|
+
canPair: () => false
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// src/semantics/messageBus.ts
|
|
210
|
+
import { z as z5 } from "zod";
|
|
180
211
|
|
|
181
212
|
// src/channel.ts
|
|
182
213
|
function parseChannel(channel) {
|
|
@@ -202,12 +233,11 @@ function channelsPair(a, b) {
|
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
// src/semantics/messageBus.ts
|
|
205
|
-
var MessageBusSemanticsSchema =
|
|
206
|
-
name:
|
|
207
|
-
messageBus:
|
|
208
|
-
// The conventions
|
|
209
|
-
// underscore and one with a dot
|
|
210
|
-
// not a typo.
|
|
236
|
+
var MessageBusSemanticsSchema = z5.object({
|
|
237
|
+
name: z5.literal("message-bus"),
|
|
238
|
+
messageBus: z5.enum([
|
|
239
|
+
// The conventions write these two AWS buses differently, one with an
|
|
240
|
+
// underscore and one with a dot. Both are as the conventions give them.
|
|
211
241
|
"aws_sqs",
|
|
212
242
|
"aws.sns",
|
|
213
243
|
"s3",
|
|
@@ -226,12 +256,13 @@ var MessageBusSemanticsSchema = z4.object({
|
|
|
226
256
|
"cloudflare-tail"
|
|
227
257
|
]),
|
|
228
258
|
/**
|
|
229
|
-
*
|
|
230
|
-
* a subject pattern, or `bus#detailType`. Null when this
|
|
231
|
-
* not say which channel, as with a send whose queue the
|
|
232
|
-
*
|
|
259
|
+
* A stable channel identifier: a CloudFormation logical id, a queue or
|
|
260
|
+
* topic name, a subject pattern, or `bus#detailType`. Null when this
|
|
261
|
+
* source does not say which channel, as with a send whose queue the
|
|
262
|
+
* code picks at run time, or a receive whose queue is set in the
|
|
263
|
+
* event-source mapping.
|
|
233
264
|
*/
|
|
234
|
-
channel:
|
|
265
|
+
channel: z5.string().min(1).nullable()
|
|
235
266
|
});
|
|
236
267
|
var messageBusSemantics = defineBoundarySemantics({
|
|
237
268
|
name: "message-bus",
|
|
@@ -245,25 +276,26 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
245
276
|
exchangesHttpResponses: false,
|
|
246
277
|
leavesTheProcess: true,
|
|
247
278
|
/**
|
|
248
|
-
* `checkMessageBus`
|
|
249
|
-
*
|
|
279
|
+
* `checkMessageBus` reports every channel, unused ones included, so
|
|
280
|
+
* the generic unmatched list leaves them out.
|
|
250
281
|
*/
|
|
251
282
|
reportsUnpairedItself: true,
|
|
252
283
|
/**
|
|
253
|
-
* A channel
|
|
254
|
-
*
|
|
255
|
-
* channel
|
|
284
|
+
* A channel that is null here can still pair. The dedicated pass
|
|
285
|
+
* follows environment variable chains before comparing, so a
|
|
286
|
+
* consumer whose channel is set at run time meets the template that
|
|
287
|
+
* declares it.
|
|
256
288
|
*/
|
|
257
289
|
canPair: () => true,
|
|
258
290
|
/**
|
|
259
291
|
* `"bus:<messageBus> <subject>"`, or null when the channel is null
|
|
260
292
|
* or its subject is empty.
|
|
261
293
|
*
|
|
262
|
-
* The key has the subject
|
|
263
|
-
*
|
|
294
|
+
* The key has the subject and leaves out the bus, so a template
|
|
295
|
+
* writing `default#order.placed` and a handler writing
|
|
264
296
|
* `order.placed` land in one bucket, where `sidesAgree` compares the
|
|
265
|
-
* buses.
|
|
266
|
-
*
|
|
297
|
+
* buses. Keying on the bus would separate a side that cannot know
|
|
298
|
+
* its bus from the side that can.
|
|
267
299
|
*
|
|
268
300
|
* The bus technology stays in, because a queue and an event router
|
|
269
301
|
* are different destinations even with the same subject. Case stays
|
|
@@ -286,9 +318,9 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
286
318
|
return channelsPair(a.channel, b.channel);
|
|
287
319
|
},
|
|
288
320
|
/**
|
|
289
|
-
* The whole channel, bus included,
|
|
290
|
-
*
|
|
291
|
-
*
|
|
321
|
+
* The whole channel, bus included, where the identity key has only
|
|
322
|
+
* the subject. A reader looking at unmatched channels needs to see
|
|
323
|
+
* which bus each one was on.
|
|
292
324
|
*/
|
|
293
325
|
displayLabel(semantics) {
|
|
294
326
|
if (semantics.channel === null) {
|
|
@@ -300,25 +332,25 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
300
332
|
});
|
|
301
333
|
|
|
302
334
|
// src/semantics/metric.ts
|
|
303
|
-
import { z as
|
|
304
|
-
var MetricSemanticsSchema =
|
|
305
|
-
name:
|
|
306
|
-
/**
|
|
307
|
-
metricSystem:
|
|
335
|
+
import { z as z6 } from "zod";
|
|
336
|
+
var MetricSemanticsSchema = z6.object({
|
|
337
|
+
name: z6.literal("metric"),
|
|
338
|
+
/** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
|
|
339
|
+
metricSystem: z6.string(),
|
|
308
340
|
/**
|
|
309
|
-
* The type string the system
|
|
310
|
-
* both sides
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
341
|
+
* The type string the system uses for the metric, written the same
|
|
342
|
+
* way on both sides. Null when the reader could not work the string
|
|
343
|
+
* out, as with a query it could not parse. A null type pairs with
|
|
344
|
+
* nothing, so it cannot pair with an unrelated metric that happens to
|
|
345
|
+
* share its source text.
|
|
314
346
|
*/
|
|
315
|
-
metricType:
|
|
347
|
+
metricType: z6.string().nullable()
|
|
316
348
|
});
|
|
317
349
|
var metricSemantics = defineBoundarySemantics({
|
|
318
350
|
name: "metric",
|
|
319
351
|
schema: MetricSemanticsSchema,
|
|
320
|
-
// OpenTelemetry
|
|
321
|
-
// attribute for the system
|
|
352
|
+
// OpenTelemetry identifies a metric by its instrument name. It has no
|
|
353
|
+
// attribute for the metric's system or its type string.
|
|
322
354
|
semconv: {},
|
|
323
355
|
behavior: {
|
|
324
356
|
exchangesHttpResponses: false,
|
|
@@ -334,7 +366,7 @@ var metricSemantics = defineBoundarySemantics({
|
|
|
334
366
|
});
|
|
335
367
|
|
|
336
368
|
// src/semantics/rest.ts
|
|
337
|
-
import { z as
|
|
369
|
+
import { z as z7 } from "zod";
|
|
338
370
|
|
|
339
371
|
// src/dispatch.ts
|
|
340
372
|
function dispatchByType(table, value) {
|
|
@@ -482,6 +514,38 @@ function pathAfterOrigin(text) {
|
|
|
482
514
|
function statesAnOrigin(text) {
|
|
483
515
|
return SCHEME_ORIGIN.test(text) || PROTOCOL_RELATIVE_ORIGIN.test(text);
|
|
484
516
|
}
|
|
517
|
+
var PRIVATE_SUFFIXES = [
|
|
518
|
+
"local",
|
|
519
|
+
"internal",
|
|
520
|
+
"localhost",
|
|
521
|
+
"lan",
|
|
522
|
+
"home.arpa",
|
|
523
|
+
"svc",
|
|
524
|
+
"test",
|
|
525
|
+
"example",
|
|
526
|
+
"invalid",
|
|
527
|
+
"example.com",
|
|
528
|
+
"example.net",
|
|
529
|
+
"example.org"
|
|
530
|
+
];
|
|
531
|
+
var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
|
|
532
|
+
function hostnameOf(host) {
|
|
533
|
+
const lower = host.toLowerCase();
|
|
534
|
+
const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
|
|
535
|
+
return name.replace(/\.$/, "");
|
|
536
|
+
}
|
|
537
|
+
function hostCanBeOwn(host) {
|
|
538
|
+
const name = hostnameOf(host);
|
|
539
|
+
if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
if (!name.includes(".")) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
return PRIVATE_SUFFIXES.some(
|
|
546
|
+
(suffix) => name === suffix || name.endsWith(`.${suffix}`)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
485
549
|
|
|
486
550
|
// src/semantics/pathPattern.ts
|
|
487
551
|
var HOLE_RANGE_SUFFIX = {
|
|
@@ -548,14 +612,18 @@ function shapedItem(segment) {
|
|
|
548
612
|
return ".*";
|
|
549
613
|
}
|
|
550
614
|
if (part.startsWith("{")) {
|
|
551
|
-
return "[^/]+";
|
|
615
|
+
return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
|
|
552
616
|
}
|
|
553
617
|
return escapedForRegex(part);
|
|
554
618
|
}).join("");
|
|
619
|
+
const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
|
|
620
|
+
const text = segment.replace(/\{[^}]*\}|\*/g, "");
|
|
555
621
|
return {
|
|
556
622
|
kind: "shaped",
|
|
557
623
|
shape: segment.replace(/\{[^}]*\}/g, "{}"),
|
|
558
|
-
test: new RegExp(`^${source}$`)
|
|
624
|
+
test: new RegExp(`^${source}$`),
|
|
625
|
+
bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
|
|
626
|
+
textLength: text.length
|
|
559
627
|
};
|
|
560
628
|
}
|
|
561
629
|
function itemsOf(segment) {
|
|
@@ -575,10 +643,35 @@ function segmentsOf(alternative) {
|
|
|
575
643
|
const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
|
|
576
644
|
return trimmed === "" ? [] : trimmed.split("/");
|
|
577
645
|
}
|
|
646
|
+
function patternSegmentsOf(alternative) {
|
|
647
|
+
const segments = segmentsOf(alternative);
|
|
648
|
+
const first = segments[0];
|
|
649
|
+
const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
|
|
650
|
+
return opensWithBaseUrl ? segments.slice(1) : segments;
|
|
651
|
+
}
|
|
652
|
+
function pathAfterBaseUrl(path) {
|
|
653
|
+
const segments = segmentsOf(path);
|
|
654
|
+
const first = segments[0];
|
|
655
|
+
if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
|
|
656
|
+
return path;
|
|
657
|
+
}
|
|
658
|
+
return `/${segments.slice(1).join("/")}`;
|
|
659
|
+
}
|
|
660
|
+
var PATTERNS = /* @__PURE__ */ new Map();
|
|
661
|
+
var PATTERN_CACHE_LIMIT = 5e4;
|
|
578
662
|
function patternOf(path) {
|
|
579
|
-
|
|
580
|
-
|
|
663
|
+
const known = PATTERNS.get(path);
|
|
664
|
+
if (known !== void 0) {
|
|
665
|
+
return known;
|
|
666
|
+
}
|
|
667
|
+
const pattern = alternativesOf(path).map(
|
|
668
|
+
(alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
|
|
581
669
|
);
|
|
670
|
+
if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
|
|
671
|
+
PATTERNS.clear();
|
|
672
|
+
}
|
|
673
|
+
PATTERNS.set(path, pattern);
|
|
674
|
+
return pattern;
|
|
582
675
|
}
|
|
583
676
|
function literalItemsOf(path) {
|
|
584
677
|
return segmentsOf(path).map((text) => ({ kind: "text", text }));
|
|
@@ -657,17 +750,104 @@ function pathSpansShapes(path) {
|
|
|
657
750
|
function countOf(items, kinds) {
|
|
658
751
|
return items.filter((item) => kinds.includes(item.kind)).length;
|
|
659
752
|
}
|
|
660
|
-
function
|
|
661
|
-
|
|
662
|
-
const ranks = alternatives.map((items) => [
|
|
753
|
+
function readingSpecificity(items) {
|
|
754
|
+
return [
|
|
663
755
|
countOf(items, ["text"]),
|
|
664
756
|
countOf(items, ["shaped"]),
|
|
665
757
|
-countOf(items, ["optional", "star"])
|
|
666
|
-
]
|
|
667
|
-
|
|
668
|
-
|
|
758
|
+
];
|
|
759
|
+
}
|
|
760
|
+
function segmentFit(route, request) {
|
|
761
|
+
if (!meetOnOneSegment(route, request)) {
|
|
762
|
+
return "apart";
|
|
763
|
+
}
|
|
764
|
+
if (request.kind === "text") {
|
|
765
|
+
if (route.kind === "text") {
|
|
766
|
+
return "stated";
|
|
767
|
+
}
|
|
768
|
+
return route.kind === "shaped" ? "shaped" : "free";
|
|
769
|
+
}
|
|
770
|
+
if (absorbsAnything(route)) {
|
|
771
|
+
return "free";
|
|
772
|
+
}
|
|
773
|
+
if (request.kind !== "shaped") {
|
|
774
|
+
return "guess";
|
|
775
|
+
}
|
|
776
|
+
if (route.kind === "shaped") {
|
|
777
|
+
return "free";
|
|
778
|
+
}
|
|
779
|
+
return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
|
|
780
|
+
}
|
|
781
|
+
function shapedText(item) {
|
|
782
|
+
return item.kind === "shaped" ? item.textLength : 0;
|
|
783
|
+
}
|
|
784
|
+
function betterFit(a, b) {
|
|
785
|
+
return a === void 0 || compareRanks(b, a) > 0;
|
|
786
|
+
}
|
|
787
|
+
function bestFit(route, request) {
|
|
788
|
+
const width = request.length + 1;
|
|
789
|
+
const best = new Array((route.length + 1) * width);
|
|
790
|
+
best[0] = [0, 0];
|
|
791
|
+
const offer = (i, j, fit) => {
|
|
792
|
+
const at = i * width + j;
|
|
793
|
+
if (betterFit(best[at], fit)) {
|
|
794
|
+
best[at] = fit;
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
for (let reached = 0; reached <= route.length + request.length; reached += 1) {
|
|
798
|
+
for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
|
|
799
|
+
const j = reached - i;
|
|
800
|
+
const fit = best[i * width + j];
|
|
801
|
+
if (fit === void 0) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const left = route[i];
|
|
805
|
+
const right = request[j];
|
|
806
|
+
if (left !== void 0 && skippable(left)) {
|
|
807
|
+
offer(i + 1, j, fit);
|
|
808
|
+
}
|
|
809
|
+
if (right !== void 0 && skippable(right)) {
|
|
810
|
+
offer(i, j + 1, fit);
|
|
811
|
+
}
|
|
812
|
+
if (left === void 0 || right === void 0) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (left.kind === "star" && right.kind === "star") {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const kind = segmentFit(left, right);
|
|
819
|
+
if (kind === "apart" || kind === "guess") {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
offer(
|
|
823
|
+
left.kind === "star" ? i : i + 1,
|
|
824
|
+
right.kind === "star" ? j : j + 1,
|
|
825
|
+
[
|
|
826
|
+
fit[0] + (kind === "stated" ? 1 : 0),
|
|
827
|
+
fit[1] + (kind === "shaped" ? shapedText(left) : 0)
|
|
828
|
+
]
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return best[route.length * width + request.length] ?? null;
|
|
833
|
+
}
|
|
834
|
+
function requestRank(route, request) {
|
|
835
|
+
let found = null;
|
|
836
|
+
for (const routeItems of patternOf(route)) {
|
|
837
|
+
for (const requestItems of patternOf(request)) {
|
|
838
|
+
const fit = bestFit(routeItems, requestItems);
|
|
839
|
+
const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
|
|
840
|
+
if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
|
|
841
|
+
found = rank;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return found;
|
|
846
|
+
}
|
|
847
|
+
function isCatchAll(route) {
|
|
848
|
+
return patternOf(route).some(
|
|
849
|
+
(items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
|
|
669
850
|
);
|
|
670
|
-
return [...loosest, 1 - alternatives.length];
|
|
671
851
|
}
|
|
672
852
|
function compareRanks(a, b) {
|
|
673
853
|
const length = Math.max(a.length, b.length);
|
|
@@ -681,23 +861,34 @@ function compareRanks(a, b) {
|
|
|
681
861
|
}
|
|
682
862
|
|
|
683
863
|
// src/semantics/rest.ts
|
|
684
|
-
var RestSemanticsSchema =
|
|
685
|
-
name:
|
|
864
|
+
var RestSemanticsSchema = z7.object({
|
|
865
|
+
name: z7.literal("rest"),
|
|
686
866
|
/**
|
|
687
867
|
* Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
|
|
688
868
|
* responds to every method, or null when this source does not say
|
|
689
869
|
* which method.
|
|
690
870
|
*/
|
|
691
|
-
method:
|
|
871
|
+
method: z7.string().min(1).nullable(),
|
|
692
872
|
/** Normalized route path ("/users/{id}"), or null when this source does not give one. */
|
|
693
|
-
path:
|
|
873
|
+
path: z7.string().min(1).nullable(),
|
|
874
|
+
/**
|
|
875
|
+
* Status codes the producing source declared explicitly, such as
|
|
876
|
+
* OpenAPI responses or CloudFormation MethodResponses. They are kept
|
|
877
|
+
* here so the pairing layer can see them without unwrapping any
|
|
878
|
+
* metadata. Absent or empty for an inferred source.
|
|
879
|
+
*/
|
|
880
|
+
declaredResponses: z7.array(z7.number()).optional(),
|
|
694
881
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* metadata. Absent, or empty, for a source that was inferred.
|
|
882
|
+
* The host a client wrote in an absolute URL, with its port
|
|
883
|
+
* (`api.example.com:8443`). A piece suss could not read is written
|
|
884
|
+
* `{name}`. Absent for a relative URL and for a route.
|
|
699
885
|
*/
|
|
700
|
-
|
|
886
|
+
host: z7.string().min(1).optional(),
|
|
887
|
+
/**
|
|
888
|
+
* True for a route the framework also serves with `.{format}` after
|
|
889
|
+
* its path, the way Rails serves `/search.json` from `get "search"`.
|
|
890
|
+
*/
|
|
891
|
+
optionalFormat: z7.boolean().optional()
|
|
701
892
|
});
|
|
702
893
|
var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
|
|
703
894
|
function baseUrlReference(semantics) {
|
|
@@ -705,7 +896,10 @@ function baseUrlReference(semantics) {
|
|
|
705
896
|
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
706
897
|
}
|
|
707
898
|
function normalizePath(path) {
|
|
708
|
-
let normalized = path.replace(
|
|
899
|
+
let normalized = withoutParameterConstraints(path).replace(
|
|
900
|
+
/:([a-zA-Z_]\w*)([?+*]?)/g,
|
|
901
|
+
"{$1$2}"
|
|
902
|
+
);
|
|
709
903
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
710
904
|
normalized = normalized.slice(0, -1);
|
|
711
905
|
}
|
|
@@ -715,6 +909,46 @@ function normalizePath(path) {
|
|
|
715
909
|
);
|
|
716
910
|
return normalized;
|
|
717
911
|
}
|
|
912
|
+
function withoutParameterConstraints(path) {
|
|
913
|
+
let out = "";
|
|
914
|
+
let i = 0;
|
|
915
|
+
while (i < path.length) {
|
|
916
|
+
const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
|
|
917
|
+
if (name === null) {
|
|
918
|
+
out += path[i];
|
|
919
|
+
i += 1;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const open = i + name[0].length - 1;
|
|
923
|
+
const close = closingParenthesis(path, open);
|
|
924
|
+
if (close === -1) {
|
|
925
|
+
return out + path.slice(i);
|
|
926
|
+
}
|
|
927
|
+
out += path.slice(i, open);
|
|
928
|
+
i = close + 1;
|
|
929
|
+
}
|
|
930
|
+
return out;
|
|
931
|
+
}
|
|
932
|
+
function closingParenthesis(text, open) {
|
|
933
|
+
let depth = 0;
|
|
934
|
+
for (let i = open; i < text.length; i += 1) {
|
|
935
|
+
const char = text[i];
|
|
936
|
+
if (char === "\\") {
|
|
937
|
+
i += 1;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (char === "(") {
|
|
941
|
+
depth += 1;
|
|
942
|
+
}
|
|
943
|
+
if (char === ")") {
|
|
944
|
+
depth -= 1;
|
|
945
|
+
if (depth === 0) {
|
|
946
|
+
return i;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return -1;
|
|
951
|
+
}
|
|
718
952
|
function pathShape(path) {
|
|
719
953
|
return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
|
|
720
954
|
}
|
|
@@ -730,6 +964,40 @@ function routePathAdmits(declaredPath, requestPath) {
|
|
|
730
964
|
function routePathsMeet(a, b) {
|
|
731
965
|
return pathsMeet(normalizePath(a), normalizePath(b));
|
|
732
966
|
}
|
|
967
|
+
function readHost(semantics) {
|
|
968
|
+
const host = semantics.host;
|
|
969
|
+
return host === void 0 || /[{(]/.test(host) ? void 0 : host;
|
|
970
|
+
}
|
|
971
|
+
function hostsAgree(a, b) {
|
|
972
|
+
return readHost(a) === readHost(b);
|
|
973
|
+
}
|
|
974
|
+
function hostUnread(semantics) {
|
|
975
|
+
if (semantics.host !== void 0) {
|
|
976
|
+
return readHost(semantics) === void 0;
|
|
977
|
+
}
|
|
978
|
+
return baseUrlReference(semantics) !== null;
|
|
979
|
+
}
|
|
980
|
+
function servedPath(semantics, path) {
|
|
981
|
+
const normalized = normalizePath(path);
|
|
982
|
+
return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
|
|
983
|
+
}
|
|
984
|
+
function withoutHost(semantics) {
|
|
985
|
+
const { host: _host, ...rest } = semantics;
|
|
986
|
+
return rest;
|
|
987
|
+
}
|
|
988
|
+
function groundedBaseUrl(semantics, deployment) {
|
|
989
|
+
const reference = baseUrlReference(semantics);
|
|
990
|
+
if (reference === null) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
const base = deployment.setTo(reference);
|
|
994
|
+
if (base === null) {
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
|
|
998
|
+
const grounded = pathAfterOrigin(`${base}${rest}`);
|
|
999
|
+
return { ...semantics, path: grounded === "" ? "/" : grounded };
|
|
1000
|
+
}
|
|
733
1001
|
var restSemantics = defineBoundarySemantics({
|
|
734
1002
|
name: "rest",
|
|
735
1003
|
schema: RestSemanticsSchema,
|
|
@@ -754,39 +1022,65 @@ var restSemantics = defineBoundarySemantics({
|
|
|
754
1022
|
return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
|
|
755
1023
|
},
|
|
756
1024
|
/**
|
|
757
|
-
* The bucket has only the path
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* sees and a suppression targets ("GET /users", "* /users")
|
|
761
|
-
* this key groups what a reader still tells apart.
|
|
1025
|
+
* The bucket has only the path, so a `"*"` route lands with the
|
|
1026
|
+
* consumers that each give a method, and `methodsAgree` compares the
|
|
1027
|
+
* methods inside the bucket. The identity key is still what a reader
|
|
1028
|
+
* sees and what a suppression targets ("GET /users", "* /users").
|
|
762
1029
|
*
|
|
763
|
-
*
|
|
764
|
-
* `{userId}` where the Express route that serves it
|
|
765
|
-
* both match the same requests
|
|
766
|
-
* boundaries that never
|
|
1030
|
+
* Parameter names are dropped too. A hand-written spec writes
|
|
1031
|
+
* `{userId}` where the Express route that serves it writes `:id`,
|
|
1032
|
+
* and both match the same requests. Pairing on the name would leave
|
|
1033
|
+
* two boundaries that never meet.
|
|
767
1034
|
*/
|
|
768
1035
|
pairingKey(semantics) {
|
|
769
1036
|
if (semantics.method === null || semantics.path === null) {
|
|
770
1037
|
return null;
|
|
771
1038
|
}
|
|
772
|
-
|
|
1039
|
+
const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
|
|
1040
|
+
const host = readHost(semantics);
|
|
1041
|
+
return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
|
|
1042
|
+
},
|
|
1043
|
+
/**
|
|
1044
|
+
* A spec writes `/teams/{team_id}` where the route that serves it
|
|
1045
|
+
* writes `/teams/:id`. The method stays in, so a `"*"` handler is
|
|
1046
|
+
* never compared with the document for one method.
|
|
1047
|
+
*/
|
|
1048
|
+
operationKey(semantics) {
|
|
1049
|
+
if (semantics.method === null || semantics.path === null) {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
|
|
773
1053
|
},
|
|
774
1054
|
sidesAgree(a, b) {
|
|
775
|
-
return methodsAgree(a.method, b.method);
|
|
1055
|
+
return methodsAgree(a.method, b.method) && hostsAgree(a, b);
|
|
776
1056
|
},
|
|
777
1057
|
spansBuckets(semantics) {
|
|
778
|
-
return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
|
|
1058
|
+
return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
|
|
779
1059
|
},
|
|
780
1060
|
bucketsMeet(a, b) {
|
|
781
|
-
return a.path !== null && b.path !== null &&
|
|
1061
|
+
return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
|
|
782
1062
|
},
|
|
783
|
-
|
|
784
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* A catch-all route never takes a call whose host suss could not
|
|
1065
|
+
* read, since nothing else ties that call to this app.
|
|
1066
|
+
*/
|
|
1067
|
+
pairRank(provider, consumer) {
|
|
1068
|
+
if (provider.path === null || consumer.path === null) {
|
|
1069
|
+
return [];
|
|
1070
|
+
}
|
|
1071
|
+
const route = normalizePath(provider.path);
|
|
1072
|
+
if (hostUnread(consumer) && isCatchAll(route)) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
return requestRank(
|
|
1076
|
+
servedPath(provider, provider.path),
|
|
1077
|
+
normalizePath(consumer.path)
|
|
1078
|
+
);
|
|
785
1079
|
},
|
|
786
1080
|
/**
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
1081
|
+
* The identity key, with a missing half still readable: `ANY` when
|
|
1082
|
+
* the source gave no method, and `?` when it gave no path. Null when
|
|
1083
|
+
* both are missing, since then there is nothing to show.
|
|
790
1084
|
*/
|
|
791
1085
|
displayLabel(semantics) {
|
|
792
1086
|
if (semantics.method === null && semantics.path === null) {
|
|
@@ -797,11 +1091,11 @@ var restSemantics = defineBoundarySemantics({
|
|
|
797
1091
|
return `${method} ${path}`;
|
|
798
1092
|
},
|
|
799
1093
|
/**
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
1094
|
+
* With both halves present, the method goes through `methodsAgree`
|
|
1095
|
+
* (so a `"*"` route matches every method) and the path through
|
|
1096
|
+
* `routePathAdmits`. A route missing either half might still handle
|
|
1097
|
+
* the request, and this declaration cannot settle that, so the
|
|
1098
|
+
* result is `"unknown"`.
|
|
805
1099
|
*/
|
|
806
1100
|
servesRequest(semantics, method, path) {
|
|
807
1101
|
if (semantics.method === null || semantics.path === null) {
|
|
@@ -814,43 +1108,41 @@ var restSemantics = defineBoundarySemantics({
|
|
|
814
1108
|
},
|
|
815
1109
|
/**
|
|
816
1110
|
* A route the registration pattern admits is inside it. A route
|
|
817
|
-
*
|
|
818
|
-
*
|
|
1111
|
+
* without a path counts as outside, because nothing shows that the
|
|
1112
|
+
* pattern covers it.
|
|
819
1113
|
*/
|
|
820
1114
|
withinScope(semantics, scope) {
|
|
821
1115
|
return semantics.path !== null && routePathAdmits(scope, semantics.path);
|
|
822
1116
|
},
|
|
823
1117
|
/**
|
|
824
1118
|
* A call whose base URL the deployment fills in, resolved to the
|
|
825
|
-
* path it reaches
|
|
826
|
-
*
|
|
827
|
-
* The source cannot settle this on its own, which is why the
|
|
828
|
-
* adapter leaves the hole in. `API_BASE` could be
|
|
829
|
-
* `http://backend.internal`, and then the path is `/orders`. It
|
|
830
|
-
* could equally be `/api/v2`, and then the path is
|
|
831
|
-
* `/api/v2/orders`. Putting the deployed value in and reading the
|
|
832
|
-
* path back out gets the right one either way.
|
|
1119
|
+
* path it reaches, and a call to a host that can be the project's
|
|
1120
|
+
* own, with the host left out so it meets the route.
|
|
833
1121
|
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
1122
|
+
* The source cannot settle a base URL on its own, so the adapter
|
|
1123
|
+
* leaves the hole in. `API_BASE` could be `http://backend.internal`,
|
|
1124
|
+
* and then the path is `/orders`. It could equally be `/api/v2`, and
|
|
1125
|
+
* then the path is `/api/v2/orders`. Filling in the deployed value
|
|
1126
|
+
* and reading the path back out gets the right one either way, and
|
|
1127
|
+
* a base the deployment sets counts as the app's own.
|
|
837
1128
|
*/
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
const base = deployment.setTo(reference);
|
|
844
|
-
if (base === null) {
|
|
1129
|
+
/** A listed host matches with or without the port the call gives. */
|
|
1130
|
+
withoutOwnHost(semantics, ownHosts) {
|
|
1131
|
+
const host = semantics.host?.toLowerCase();
|
|
1132
|
+
if (host === void 0) {
|
|
845
1133
|
return null;
|
|
846
1134
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1135
|
+
return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
|
|
1136
|
+
},
|
|
1137
|
+
groundName(semantics, deployment) {
|
|
1138
|
+
const based = groundedBaseUrl(semantics, deployment);
|
|
1139
|
+
const grounded = based ?? semantics;
|
|
1140
|
+
const host = readHost(grounded);
|
|
1141
|
+
return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
|
|
850
1142
|
},
|
|
851
1143
|
nameReference: baseUrlReference,
|
|
852
1144
|
ruleBoundary: {
|
|
853
|
-
// "METHOD /path": one
|
|
1145
|
+
// "METHOD /path": one token, a space, then an absolute path.
|
|
854
1146
|
claims(raw) {
|
|
855
1147
|
return /^\S+ +\//.test(raw.trim());
|
|
856
1148
|
},
|
|
@@ -865,12 +1157,12 @@ var restSemantics = defineBoundarySemantics({
|
|
|
865
1157
|
});
|
|
866
1158
|
|
|
867
1159
|
// src/semantics/runtimeConfig.ts
|
|
868
|
-
import { z as
|
|
1160
|
+
import { z as z9 } from "zod";
|
|
869
1161
|
|
|
870
1162
|
// src/deployableUnit.ts
|
|
871
|
-
import { z as
|
|
872
|
-
var DeployableUnitSchema =
|
|
873
|
-
deploymentTarget:
|
|
1163
|
+
import { z as z8 } from "zod";
|
|
1164
|
+
var DeployableUnitSchema = z8.object({
|
|
1165
|
+
deploymentTarget: z8.enum([
|
|
874
1166
|
"lambda",
|
|
875
1167
|
"ecs-task",
|
|
876
1168
|
"container",
|
|
@@ -879,7 +1171,7 @@ var DeployableUnitSchema = z7.object({
|
|
|
879
1171
|
]),
|
|
880
1172
|
// An empty name would agree with every other empty name, so a unit
|
|
881
1173
|
// with no name has to leave the field off instead.
|
|
882
|
-
instanceName:
|
|
1174
|
+
instanceName: z8.string().min(1)
|
|
883
1175
|
});
|
|
884
1176
|
function ecsContainerInstanceName(taskDefinitionLogicalId, containerName) {
|
|
885
1177
|
return `${taskDefinitionLogicalId}/${containerName}`;
|
|
@@ -890,14 +1182,14 @@ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
|
|
|
890
1182
|
deploymentTarget: true,
|
|
891
1183
|
instanceName: true
|
|
892
1184
|
}).extend({
|
|
893
|
-
name:
|
|
1185
|
+
name: z9.literal("runtime-config")
|
|
894
1186
|
});
|
|
895
1187
|
var runtimeConfigSemantics = defineBoundarySemantics({
|
|
896
1188
|
name: "runtime-config",
|
|
897
1189
|
schema: RuntimeConfigSemanticsSchema,
|
|
898
|
-
// service.name and cloud.resource_id
|
|
899
|
-
// instanceName is the
|
|
900
|
-
//
|
|
1190
|
+
// service.name and cloud.resource_id identify the same deployable, but
|
|
1191
|
+
// instanceName is the template's logical id, a different string from
|
|
1192
|
+
// both.
|
|
901
1193
|
semconv: {},
|
|
902
1194
|
behavior: {
|
|
903
1195
|
/** A process reads its config at startup, and nothing replies. */
|
|
@@ -906,46 +1198,53 @@ var runtimeConfigSemantics = defineBoundarySemantics({
|
|
|
906
1198
|
// comes from outside the code.
|
|
907
1199
|
leavesTheProcess: true,
|
|
908
1200
|
reportsUnpairedItself: false,
|
|
909
|
-
identityKey: () => null
|
|
1201
|
+
identityKey: () => null,
|
|
1202
|
+
// A reader sees which deployable's environment this is. Code that
|
|
1203
|
+
// reads a variable does not say which deployable runs it, so a read
|
|
1204
|
+
// has no name.
|
|
1205
|
+
displayLabel(semantics) {
|
|
1206
|
+
const instance = semantics.instanceName;
|
|
1207
|
+
return instance === void 0 ? null : `runtime-config:${instance}`;
|
|
1208
|
+
}
|
|
910
1209
|
}
|
|
911
1210
|
});
|
|
912
1211
|
|
|
913
1212
|
// src/semantics/storage.ts
|
|
914
|
-
import { z as
|
|
915
|
-
var StorageSemanticsSchema =
|
|
916
|
-
name:
|
|
1213
|
+
import { z as z10 } from "zod";
|
|
1214
|
+
var StorageSemanticsSchema = z10.object({
|
|
1215
|
+
name: z10.literal("storage"),
|
|
917
1216
|
/**
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
* containers can share a name,
|
|
922
|
-
*
|
|
923
|
-
*
|
|
1217
|
+
* The storage system: an OpenTelemetry value such as `postgresql` or
|
|
1218
|
+
* `aws.dynamodb`, or suss's own word for one the conventions do not
|
|
1219
|
+
* cover, such as `s3`. Null when the reader could not work out which
|
|
1220
|
+
* system the source uses. Two products' containers can share a name,
|
|
1221
|
+
* and this field keeps them apart. The pairing pass decides how to
|
|
1222
|
+
* treat a null.
|
|
924
1223
|
*/
|
|
925
|
-
storageSystem:
|
|
1224
|
+
storageSystem: z10.string().nullable(),
|
|
926
1225
|
/**
|
|
927
1226
|
* The ORM, schema, or deployment scope. A single-database setup uses
|
|
928
1227
|
* `"default"`, and a monorepo with several schemas gives each one its
|
|
929
1228
|
* own value.
|
|
930
1229
|
*/
|
|
931
|
-
scope:
|
|
1230
|
+
scope: z10.string(),
|
|
932
1231
|
/**
|
|
933
|
-
* The table, bucket, collection
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
1232
|
+
* The table, bucket, collection or index, as the source declares it.
|
|
1233
|
+
* Null when the reader could not work out the container, and a null
|
|
1234
|
+
* container pairs with nothing, so it cannot match by accident of
|
|
1235
|
+
* shared source text. The string uses the boundary-name syntax (a
|
|
1236
|
+
* literal, a pattern with `{}` holes, or a reference), and only
|
|
1237
|
+
* `parseBoundaryName` should read it.
|
|
939
1238
|
*/
|
|
940
|
-
container:
|
|
1239
|
+
container: z10.string().nullable(),
|
|
941
1240
|
/**
|
|
942
|
-
* A secondary way into the container, a DynamoDB global
|
|
943
|
-
* index or an Elasticsearch alias, each with its own key
|
|
944
|
-
* Null means the container's
|
|
1241
|
+
* A secondary way into the container, such as a DynamoDB global
|
|
1242
|
+
* secondary index or an Elasticsearch alias, each with its own key
|
|
1243
|
+
* fields. Null means the container's primary key. A query through an
|
|
945
1244
|
* index and a query through the table are different accesses, so
|
|
946
1245
|
* they pair separately.
|
|
947
1246
|
*/
|
|
948
|
-
accessPath:
|
|
1247
|
+
accessPath: z10.string().nullable()
|
|
949
1248
|
});
|
|
950
1249
|
function containerReference(semantics) {
|
|
951
1250
|
return semantics.container === null ? null : referenceFromName(semantics.container);
|
|
@@ -955,21 +1254,21 @@ var storageSemantics = defineBoundarySemantics({
|
|
|
955
1254
|
schema: StorageSemanticsSchema,
|
|
956
1255
|
semconv: {
|
|
957
1256
|
storageSystem: { name: "db.system.name" },
|
|
958
|
-
// "default"
|
|
1257
|
+
// suss writes "default" when the source did not say which database.
|
|
959
1258
|
scope: { name: "db.namespace", placeholderValues: ["default"] },
|
|
960
1259
|
container: { name: "db.collection.name" }
|
|
961
|
-
//
|
|
962
|
-
//
|
|
1260
|
+
// The conventions have no attribute for a secondary index, so
|
|
1261
|
+
// accessPath is left out.
|
|
963
1262
|
},
|
|
964
1263
|
behavior: {
|
|
965
|
-
/** A query returns rows or items,
|
|
1264
|
+
/** A query returns rows or items, with no status. */
|
|
966
1265
|
exchangesHttpResponses: false,
|
|
967
1266
|
leavesTheProcess: true,
|
|
968
1267
|
reportsUnpairedItself: false,
|
|
969
1268
|
identityKey: () => null,
|
|
970
1269
|
displayLabel: storageLabel,
|
|
971
1270
|
/**
|
|
972
|
-
* A container the source
|
|
1271
|
+
* A container the source gives as a variable is whatever the
|
|
973
1272
|
* deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
|
|
974
1273
|
* code and `prod-subscribers-v1` in the manifest are one table.
|
|
975
1274
|
*/
|
|
@@ -999,9 +1298,9 @@ function storageContainerLabel(semantics) {
|
|
|
999
1298
|
}
|
|
1000
1299
|
|
|
1001
1300
|
// src/semantics/unitInvocation.ts
|
|
1002
|
-
import { z as
|
|
1301
|
+
import { z as z11 } from "zod";
|
|
1003
1302
|
var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
|
|
1004
|
-
name:
|
|
1303
|
+
name: z11.literal("unit-invocation"),
|
|
1005
1304
|
instanceName: DeployableUnitSchema.shape.instanceName.nullable()
|
|
1006
1305
|
});
|
|
1007
1306
|
function calleeReference(semantics) {
|
|
@@ -1031,9 +1330,9 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1031
1330
|
*/
|
|
1032
1331
|
reportsUnpairedItself: false,
|
|
1033
1332
|
/**
|
|
1034
|
-
* A callee the source
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1333
|
+
* A callee the source gives only as a variable has no name until the
|
|
1334
|
+
* deployment fills it in. It gets no key, since a key built from the
|
|
1335
|
+
* variable would pair only with itself.
|
|
1037
1336
|
*/
|
|
1038
1337
|
identityKey(semantics) {
|
|
1039
1338
|
if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
|
|
@@ -1054,11 +1353,10 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1054
1353
|
);
|
|
1055
1354
|
},
|
|
1056
1355
|
/**
|
|
1057
|
-
* A call that reads its callee's name
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
* id and neither side ever writes the other's string.
|
|
1356
|
+
* A call that reads its callee's name from the environment reaches
|
|
1357
|
+
* whichever resource the template wires that variable to. The name
|
|
1358
|
+
* is grounded to the logical id and not the deployed name, because
|
|
1359
|
+
* the invoked unit's own summary is keyed by the logical id.
|
|
1062
1360
|
*/
|
|
1063
1361
|
groundName(semantics, deployment) {
|
|
1064
1362
|
const reference = calleeReference(semantics);
|
|
@@ -1073,7 +1371,7 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1073
1371
|
});
|
|
1074
1372
|
|
|
1075
1373
|
// src/semantics/registry.ts
|
|
1076
|
-
var SemanticsSchema =
|
|
1374
|
+
var SemanticsSchema = z12.discriminatedUnion("name", [
|
|
1077
1375
|
restSemantics.schema,
|
|
1078
1376
|
functionCallSemantics.schema,
|
|
1079
1377
|
graphqlResolverSemantics.schema,
|
|
@@ -1082,7 +1380,8 @@ var SemanticsSchema = z11.discriminatedUnion("name", [
|
|
|
1082
1380
|
storageSemantics.schema,
|
|
1083
1381
|
messageBusSemantics.schema,
|
|
1084
1382
|
metricSemantics.schema,
|
|
1085
|
-
unitInvocationSemantics.schema
|
|
1383
|
+
unitInvocationSemantics.schema,
|
|
1384
|
+
ioSemantics.schema
|
|
1086
1385
|
]);
|
|
1087
1386
|
var DEFINITIONS = [
|
|
1088
1387
|
restSemantics,
|
|
@@ -1093,7 +1392,8 @@ var DEFINITIONS = [
|
|
|
1093
1392
|
storageSemantics,
|
|
1094
1393
|
messageBusSemantics,
|
|
1095
1394
|
metricSemantics,
|
|
1096
|
-
unitInvocationSemantics
|
|
1395
|
+
unitInvocationSemantics,
|
|
1396
|
+
ioSemantics
|
|
1097
1397
|
];
|
|
1098
1398
|
var BY_NAME = new Map(
|
|
1099
1399
|
DEFINITIONS.map((d) => [d.name, d])
|
|
@@ -1116,90 +1416,95 @@ function allBehaviors() {
|
|
|
1116
1416
|
}
|
|
1117
1417
|
|
|
1118
1418
|
// src/schemas.ts
|
|
1119
|
-
var ConfidenceSourceSchema =
|
|
1419
|
+
var ConfidenceSourceSchema = z13.enum([
|
|
1120
1420
|
"inferred_static",
|
|
1121
1421
|
"inferred_ai",
|
|
1122
1422
|
"declared",
|
|
1123
1423
|
"derived"
|
|
1124
1424
|
]);
|
|
1125
|
-
var ConfidenceLevelSchema =
|
|
1126
|
-
var CorroborationSchema =
|
|
1127
|
-
outcome:
|
|
1425
|
+
var ConfidenceLevelSchema = z13.enum(["high", "medium", "low"]);
|
|
1426
|
+
var CorroborationSchema = z13.object({
|
|
1427
|
+
outcome: z13.enum(["observed", "refuted", "untested"]),
|
|
1128
1428
|
/** Executions that produced a verdict for this claim. */
|
|
1129
|
-
runs:
|
|
1429
|
+
runs: z13.number(),
|
|
1130
1430
|
/** Present when refuted: the input and observation that disagreed. */
|
|
1131
|
-
counterexample:
|
|
1431
|
+
counterexample: z13.unknown().optional(),
|
|
1132
1432
|
/** Present when untested: why no verdict was reachable. */
|
|
1133
|
-
reason:
|
|
1433
|
+
reason: z13.string().optional()
|
|
1134
1434
|
});
|
|
1135
|
-
var ConfidenceInfoSchema =
|
|
1435
|
+
var ConfidenceInfoSchema = z13.object({
|
|
1136
1436
|
source: ConfidenceSourceSchema,
|
|
1137
1437
|
level: ConfidenceLevelSchema,
|
|
1138
1438
|
corroboration: CorroborationSchema.optional()
|
|
1139
1439
|
});
|
|
1140
|
-
var SourceLocationSchema =
|
|
1141
|
-
file:
|
|
1440
|
+
var SourceLocationSchema = z13.object({
|
|
1441
|
+
file: z13.string(),
|
|
1142
1442
|
/** Line numbers, for a person reading the summary or an editor link. */
|
|
1143
|
-
range:
|
|
1443
|
+
range: z13.object({ start: z13.number(), end: z13.number() }),
|
|
1144
1444
|
/**
|
|
1145
|
-
* Character offsets of the unit in its file. Identity and joins
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
* contract artifact's say.
|
|
1445
|
+
* Character offsets of the unit in its file. Identity and joins use
|
|
1446
|
+
* these, because two functions can share a line but never an offset
|
|
1447
|
+
* range. `range` gives the same place in lines. Absent on a summary
|
|
1448
|
+
* with no source position, such as one read from a contract.
|
|
1150
1449
|
*/
|
|
1151
|
-
span:
|
|
1152
|
-
exportName:
|
|
1450
|
+
span: z13.object({ start: z13.number(), end: z13.number() }).optional(),
|
|
1451
|
+
exportName: z13.string().nullable(),
|
|
1153
1452
|
/**
|
|
1154
|
-
* The project the extract
|
|
1155
|
-
*
|
|
1453
|
+
* The project the extract ran on, by the name the project gives
|
|
1454
|
+
* itself.
|
|
1156
1455
|
*
|
|
1157
1456
|
* Paths are relative to wherever the extract ran, so two services in
|
|
1158
|
-
* one repository both report `src/handlers.ts`, and merging their
|
|
1159
|
-
* summaries
|
|
1160
|
-
*
|
|
1457
|
+
* one repository can both report `src/handlers.ts`, and merging their
|
|
1458
|
+
* summaries would mix them up. This field tells them apart, and a
|
|
1459
|
+
* reader groups by it.
|
|
1460
|
+
*/
|
|
1461
|
+
workspace: z13.string().optional(),
|
|
1462
|
+
/**
|
|
1463
|
+
* The module of the application the unit belongs to, by the name the
|
|
1464
|
+
* project's `suss.json` gives it. Set only when the project lists its
|
|
1465
|
+
* modules, and then on every unit whose file is under a module's root.
|
|
1161
1466
|
*/
|
|
1162
|
-
|
|
1467
|
+
module: z13.string().optional()
|
|
1163
1468
|
});
|
|
1164
|
-
var BoundaryBindingSchema =
|
|
1165
|
-
transport:
|
|
1469
|
+
var BoundaryBindingSchema = z13.object({
|
|
1470
|
+
transport: z13.string(),
|
|
1166
1471
|
semantics: SemanticsSchema,
|
|
1167
|
-
recognition:
|
|
1472
|
+
recognition: z13.string()
|
|
1168
1473
|
});
|
|
1169
1474
|
function typeDefinitionKey(ref) {
|
|
1170
1475
|
return ref.def ?? null;
|
|
1171
1476
|
}
|
|
1172
|
-
var TypeShapeSchema =
|
|
1173
|
-
() =>
|
|
1174
|
-
|
|
1175
|
-
type:
|
|
1176
|
-
properties:
|
|
1177
|
-
spreads:
|
|
1477
|
+
var TypeShapeSchema = z13.lazy(
|
|
1478
|
+
() => z13.discriminatedUnion("type", [
|
|
1479
|
+
z13.object({
|
|
1480
|
+
type: z13.literal("record"),
|
|
1481
|
+
properties: z13.record(z13.string(), TypeShapeSchema),
|
|
1482
|
+
spreads: z13.array(z13.object({ sourceText: z13.string() })).optional()
|
|
1178
1483
|
}),
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
type:
|
|
1183
|
-
value:
|
|
1184
|
-
raw:
|
|
1484
|
+
z13.object({ type: z13.literal("dictionary"), values: TypeShapeSchema }),
|
|
1485
|
+
z13.object({ type: z13.literal("array"), items: TypeShapeSchema }),
|
|
1486
|
+
z13.object({
|
|
1487
|
+
type: z13.literal("literal"),
|
|
1488
|
+
value: z13.union([z13.string(), z13.number(), z13.boolean()]),
|
|
1489
|
+
raw: z13.string().optional()
|
|
1185
1490
|
}),
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
type:
|
|
1194
|
-
variants:
|
|
1491
|
+
z13.object({ type: z13.literal("text") }),
|
|
1492
|
+
z13.object({ type: z13.literal("integer") }),
|
|
1493
|
+
z13.object({ type: z13.literal("number") }),
|
|
1494
|
+
z13.object({ type: z13.literal("boolean") }),
|
|
1495
|
+
z13.object({ type: z13.literal("null") }),
|
|
1496
|
+
z13.object({ type: z13.literal("undefined") }),
|
|
1497
|
+
z13.object({
|
|
1498
|
+
type: z13.literal("union"),
|
|
1499
|
+
variants: z13.array(TypeShapeSchema)
|
|
1195
1500
|
}),
|
|
1196
|
-
|
|
1197
|
-
type:
|
|
1198
|
-
name:
|
|
1199
|
-
from:
|
|
1200
|
-
def:
|
|
1501
|
+
z13.object({
|
|
1502
|
+
type: z13.literal("ref"),
|
|
1503
|
+
name: z13.string(),
|
|
1504
|
+
from: z13.string().optional(),
|
|
1505
|
+
def: z13.string().optional()
|
|
1201
1506
|
}),
|
|
1202
|
-
|
|
1507
|
+
z13.object({ type: z13.literal("unknown") })
|
|
1203
1508
|
])
|
|
1204
1509
|
);
|
|
1205
1510
|
var MAX_DEFINITION_DEPTH = 6;
|
|
@@ -1250,8 +1555,11 @@ export {
|
|
|
1250
1555
|
busIdentityKey,
|
|
1251
1556
|
unitIdentityKey,
|
|
1252
1557
|
metricIdentityKey,
|
|
1558
|
+
ioIdentityKey,
|
|
1253
1559
|
FunctionCallSemanticsSchema,
|
|
1560
|
+
isModuleName,
|
|
1254
1561
|
GraphqlResolverSemanticsSchema,
|
|
1562
|
+
IoSemanticsSchema,
|
|
1255
1563
|
parseChannel,
|
|
1256
1564
|
formatChannel,
|
|
1257
1565
|
busesAgree,
|
|
@@ -1273,6 +1581,7 @@ export {
|
|
|
1273
1581
|
referenceFromName,
|
|
1274
1582
|
pathAfterOrigin,
|
|
1275
1583
|
statesAnOrigin,
|
|
1584
|
+
hostCanBeOwn,
|
|
1276
1585
|
rangedHole,
|
|
1277
1586
|
setPiece,
|
|
1278
1587
|
compareRanks,
|