@telorun/analyzer 0.70.0 → 0.72.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/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +4 -5
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +92 -10
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +10 -4
- package/dist/catch-scope.d.ts +72 -0
- package/dist/catch-scope.d.ts.map +1 -0
- package/dist/catch-scope.js +102 -0
- package/dist/cel-scope-query.d.ts +14 -0
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +36 -6
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +3 -4
- package/dist/deprecation.d.ts +21 -0
- package/dist/deprecation.d.ts.map +1 -0
- package/dist/deprecation.js +26 -0
- package/dist/flatten-for-analyzer.d.ts +2 -2
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/manifest-visitor.d.ts +17 -1
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +16 -4
- package/dist/migrations/report.d.ts +1 -1
- package/dist/migrations/report.d.ts.map +1 -1
- package/dist/migrations/report.js +5 -0
- package/dist/module-alias-scope.d.ts +65 -0
- package/dist/module-alias-scope.d.ts.map +1 -0
- package/dist/module-alias-scope.js +25 -0
- package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
- package/dist/module-documents.d.ts.map +1 -0
- package/dist/ref-sentinel-target.d.ts +38 -0
- package/dist/ref-sentinel-target.d.ts.map +1 -0
- package/dist/ref-sentinel-target.js +13 -0
- package/dist/ref-slot.d.ts +15 -0
- package/dist/ref-slot.d.ts.map +1 -1
- package/dist/ref-slot.js +7 -0
- package/dist/resolve-schema-type-refs.d.ts.map +1 -1
- package/dist/resolve-schema-type-refs.js +2 -1
- package/dist/resolve-throws-union.d.ts +47 -2
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +199 -20
- package/dist/resolve-zone-requirements.d.ts +3 -3
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +12 -12
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +13 -1
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +48 -4
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +3 -1
- package/dist/schema-walk.d.ts +27 -0
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +44 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +2 -4
- package/dist/types.d.ts +20 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/validate-identifier-names.d.ts +2 -2
- package/dist/validate-identifier-names.d.ts.map +1 -1
- package/dist/validate-identifier-names.js +22 -7
- package/dist/validate-invocation-contract.d.ts +5 -0
- package/dist/validate-invocation-contract.d.ts.map +1 -1
- package/dist/validate-invocation-contract.js +114 -3
- package/dist/validate-logging.d.ts.map +1 -1
- package/dist/validate-logging.js +2 -2
- package/dist/validate-ref-slots.d.ts +1 -1
- package/dist/validate-ref-slots.d.ts.map +1 -1
- package/dist/validate-ref-slots.js +34 -0
- package/dist/validate-references.d.ts +16 -5
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +57 -15
- package/dist/validate-resource-inputs.d.ts +1 -26
- package/dist/validate-resource-inputs.d.ts.map +1 -1
- package/dist/validate-resource-inputs.js +12 -2
- package/dist/validate-schema-type-refs.d.ts.map +1 -1
- package/dist/validate-schema-type-refs.js +2 -1
- package/dist/validate-throws-coverage.d.ts +5 -1
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +241 -86
- package/package.json +3 -3
- package/src/analysis-registry.ts +4 -5
- package/src/analyzer.ts +115 -11
- package/src/call-graph.ts +9 -3
- package/src/catch-scope.ts +157 -0
- package/src/cel-scope-query.ts +45 -8
- package/src/definition-registry.ts +3 -6
- package/src/deprecation.ts +36 -0
- package/src/flatten-for-analyzer.ts +3 -3
- package/src/index.ts +10 -3
- package/src/manifest-visitor.ts +30 -5
- package/src/migrations/report.ts +5 -1
- package/src/module-alias-scope.ts +94 -0
- package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
- package/src/ref-sentinel-target.ts +46 -0
- package/src/ref-slot.ts +19 -0
- package/src/resolve-schema-type-refs.ts +2 -1
- package/src/resolve-throws-union.ts +253 -20
- package/src/resolve-zone-requirements.ts +14 -14
- package/src/schema-compat.ts +13 -0
- package/src/schema-error-report.ts +50 -6
- package/src/schema-keywords.ts +4 -1
- package/src/schema-walk.ts +56 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +2 -5
- package/src/types.ts +21 -3
- package/src/validate-identifier-names.ts +28 -9
- package/src/validate-invocation-contract.ts +128 -2
- package/src/validate-logging.ts +2 -3
- package/src/validate-ref-slots.ts +41 -1
- package/src/validate-references.ts +74 -14
- package/src/validate-resource-inputs.ts +26 -3
- package/src/validate-schema-type-refs.ts +2 -1
- package/src/validate-throws-coverage.ts +344 -92
- package/dist/zone-module-documents.d.ts.map +0 -1
- /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
package/dist/ref-slot.js
CHANGED
|
@@ -166,6 +166,7 @@ export function readRefSlot(node) {
|
|
|
166
166
|
const uses = new Set();
|
|
167
167
|
let useCases;
|
|
168
168
|
let inputs;
|
|
169
|
+
let throwsThrough = false;
|
|
169
170
|
for (const carrier of nodes) {
|
|
170
171
|
const annotation = carrier["x-telo-ref"];
|
|
171
172
|
for (const kind of readKinds(annotation)) {
|
|
@@ -180,6 +181,8 @@ export function readRefSlot(node) {
|
|
|
180
181
|
useCases ??= readUseCases(obj.use);
|
|
181
182
|
if (typeof obj.inputs === "string")
|
|
182
183
|
inputs ??= obj.inputs;
|
|
184
|
+
if (obj.throwsThrough === true)
|
|
185
|
+
throwsThrough = true;
|
|
183
186
|
}
|
|
184
187
|
const slot = {
|
|
185
188
|
kinds,
|
|
@@ -191,6 +194,8 @@ export function readRefSlot(node) {
|
|
|
191
194
|
slot.useCases = useCases;
|
|
192
195
|
if (inputs !== undefined)
|
|
193
196
|
slot.inputs = inputs;
|
|
197
|
+
if (throwsThrough)
|
|
198
|
+
slot.throwsThrough = true;
|
|
194
199
|
return slot;
|
|
195
200
|
}
|
|
196
201
|
/** True when the node declares a reference slot in any accepted shape. The
|
|
@@ -218,6 +223,8 @@ export function refSlotAnnotation(slot) {
|
|
|
218
223
|
annotation.use = slot.uses;
|
|
219
224
|
if (slot.inputs !== undefined)
|
|
220
225
|
annotation.inputs = slot.inputs;
|
|
226
|
+
if (slot.throwsThrough)
|
|
227
|
+
annotation.throwsThrough = true;
|
|
221
228
|
return annotation;
|
|
222
229
|
}
|
|
223
230
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/resolve-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/resolve-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,IAAI,CA6DN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
|
|
2
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
2
3
|
/** Schema-bearing fields on a Telo.Definition / Telo.Type resource. */
|
|
3
4
|
const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
|
|
4
5
|
/**
|
|
@@ -88,7 +89,7 @@ export function resolveSchemaTypeRefs(resources, aliases, aliasesByModule) {
|
|
|
88
89
|
};
|
|
89
90
|
for (const r of resources) {
|
|
90
91
|
const ownModule = r.metadata?.module;
|
|
91
|
-
const resolver = (
|
|
92
|
+
const resolver = moduleAliasScope(r.metadata, aliases, aliasesByModule);
|
|
92
93
|
const resolveAuthority = (authority) => authority === "Self" ? ownModule : resolver?.moduleForAlias(authority);
|
|
93
94
|
for (const field of SCHEMA_FIELDS) {
|
|
94
95
|
walk(r[field], resolveAuthority, ownModule, true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResourceManifest } from "@telorun/sdk";
|
|
1
|
+
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { type AliasResolver } from "./alias-resolver.js";
|
|
3
3
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
4
|
export interface ThrowsCodeMeta {
|
|
@@ -34,14 +34,59 @@ export interface ResolveCtx {
|
|
|
34
34
|
aliasesByModule: Map<string, AliasResolver>;
|
|
35
35
|
/** The consumer/root module names; resources owned by these resolve against `aliases`. */
|
|
36
36
|
rootModules: Set<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Every imported library's FULL manifest list, keyed by module name.
|
|
39
|
+
*
|
|
40
|
+
* A consumer's flat set holds a library's EXPORTED instances and nothing else,
|
|
41
|
+
* so the siblings an exported entry point invokes are not in it. Without this
|
|
42
|
+
* the walk stops at the first such hop, and the difference is not academic: a
|
|
43
|
+
* library whose entry point raises its own code through an internal guard
|
|
44
|
+
* presented an empty union to its consumer, which then had its `catches:`
|
|
45
|
+
* rejected for the very code the entry point documents.
|
|
46
|
+
*
|
|
47
|
+
* Consulted only as a FALLBACK, after the flat set — the flat set is what the
|
|
48
|
+
* consumer's own resources resolve against, and a library-internal name must
|
|
49
|
+
* never shadow one of them.
|
|
50
|
+
*/
|
|
51
|
+
moduleManifests: Map<string, ResourceManifest[]>;
|
|
52
|
+
/** Keyed `<module>\0<name>`: resource names are module-scoped, so two
|
|
53
|
+
* libraries each declaring a `query` are two different unions. */
|
|
37
54
|
memo: Map<string, ThrowsUnion>;
|
|
38
55
|
inProgress: Set<string>;
|
|
39
56
|
}
|
|
40
|
-
export declare function createResolveCtx(allManifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>): ResolveCtx;
|
|
57
|
+
export declare function createResolveCtx(allManifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>, moduleManifests?: Map<string, ResourceManifest[]>): ResolveCtx;
|
|
41
58
|
/** Resolve the effective throw union for a named manifest. The result combines
|
|
42
59
|
* explicit `throws.codes`, `throws.inherit: true` dataflow (step-context
|
|
43
60
|
* traversal with try/catch subtraction), and unbounded markers for
|
|
44
61
|
* unresolvable passthrough call sites. Cycles short-circuit to an empty
|
|
45
62
|
* result so resolution always terminates. */
|
|
46
63
|
export declare function resolveThrowsUnion(manifest: ResourceManifest, ctx: ResolveCtx): ThrowsUnion;
|
|
64
|
+
/**
|
|
65
|
+
* The union a SCOPE-LEVEL `catches:` list can be asked to render — everything
|
|
66
|
+
* the resource it is written on drives, transitively.
|
|
67
|
+
*
|
|
68
|
+
* `x-telo-catches-for: ""` is itself the claim that this is the denominator, so
|
|
69
|
+
* nothing is inferred from a kind that did not opt in, and no definition has to
|
|
70
|
+
* declare a `throws:` block to carry a catch scope (the kernel forbids one on a
|
|
71
|
+
* `Telo.Service` and a `Telo.Mount`, and rightly: what a router renders is not
|
|
72
|
+
* what a router THROWS).
|
|
73
|
+
*
|
|
74
|
+
* Three edges, three answers. A **step body** contributes its own traversal,
|
|
75
|
+
* subtraction included. A **control-transferring ref** contributes the target's
|
|
76
|
+
* own declared union — a route handler is a leaf here, and asking what IT drives
|
|
77
|
+
* would credit this scope with codes the handler catches internally. A
|
|
78
|
+
* **`throwsThrough` ref** recurses, because the target is another scope on the
|
|
79
|
+
* same ladder: a server renders what its mounts' routes throw, not what the
|
|
80
|
+
* mounts themselves declare.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveScopeUnion(manifest: ResourceManifest, definition: ResourceDefinition, ctx: ResolveCtx, seen?: Set<ResourceManifest>): ThrowsUnion;
|
|
83
|
+
/**
|
|
84
|
+
* The manifest a resolved reference value names, in either shape it arrives in —
|
|
85
|
+
* a `{kind, name}` pair, or a `!ref` still carrying its parse-time sentinel.
|
|
86
|
+
*
|
|
87
|
+
* Exported because the catch-scope enclosure walk asks the same question about
|
|
88
|
+
* the same values; resolving a name twice by two rules is how two passes end up
|
|
89
|
+
* disagreeing about which resource a slot points at.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveRefManifest(refValue: unknown, ctx: ResolveCtx, ownerModule: string | undefined): ResourceManifest | undefined;
|
|
47
92
|
//# sourceMappingURL=resolve-throws-union.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKnE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B;AAED;;gFAEgF;AAChF,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACnC;;;8EAG0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;gDAI4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB;;;wFAGoF;IACpF,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,0FAA0F;IAC1F,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACjD;uEACmE;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,gBAAgB,EAAE,EAChC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,EACpC,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAa,GAC3D,UAAU,CAWZ;AA2HD;;;;8CAI8C;AAC9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,UAAU,GACd,WAAW,CAiDb;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,GAAG,CAAC,gBAAgB,CAAa,GACtC,WAAW,CAmCb;AAkMD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,GAAG,EAAE,UAAU,EACf,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,gBAAgB,GAAG,SAAS,CAsB9B"}
|
|
@@ -1,21 +1,93 @@
|
|
|
1
1
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
2
|
import { scopeResolverForModule } from "./alias-resolver.js";
|
|
3
|
+
import { resolveScopedName } from "./call-graph.js";
|
|
4
|
+
import { refSentinelTarget } from "./ref-sentinel-target.js";
|
|
5
|
+
import { possibleUses, transfersControl } from "./ref-slot.js";
|
|
3
6
|
import { readStepSlot } from "./step-slot.js";
|
|
7
|
+
import { forEachDrivenSlot } from "./schema-walk.js";
|
|
4
8
|
/** Code a non-`InvokeError` failure surfaces as inside a `catch` block. Mirrors
|
|
5
9
|
* `PLAIN_ERROR_CODE` in `@telorun/run`'s `toSequenceError`: any invoke can throw
|
|
6
10
|
* a plain error, which the catch sees as `error.code === "INTERNAL_ERROR"`. */
|
|
7
11
|
export const PLAIN_ERROR_CODE = "INTERNAL_ERROR";
|
|
8
|
-
export function createResolveCtx(allManifests, defs, aliases, aliasesByModule = new Map(), rootModules = new Set()) {
|
|
12
|
+
export function createResolveCtx(allManifests, defs, aliases, aliasesByModule = new Map(), rootModules = new Set(), moduleManifests = new Map()) {
|
|
9
13
|
return {
|
|
10
14
|
allManifests,
|
|
11
15
|
defs,
|
|
12
16
|
aliases,
|
|
13
17
|
aliasesByModule,
|
|
14
18
|
rootModules,
|
|
19
|
+
moduleManifests,
|
|
15
20
|
memo: new Map(),
|
|
16
21
|
inProgress: new Set(),
|
|
17
22
|
};
|
|
18
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A dispatch target named by a resolved `{kind, name}` ref.
|
|
26
|
+
*
|
|
27
|
+
* The flat set is the scope such a ref was resolved in, so it is asked first;
|
|
28
|
+
* the owning library's own documents are the fallback for a name the flattened
|
|
29
|
+
* view dropped. `kindMatches` applies to BOTH — one function, one rule, or the
|
|
30
|
+
* next caller inherits whichever half it happened to hit.
|
|
31
|
+
*/
|
|
32
|
+
function findTarget(ctx, name, ownerModule, kindMatches) {
|
|
33
|
+
const flat = ctx.allManifests.find((m) => m.metadata?.name === name && kindMatches(m));
|
|
34
|
+
if (flat)
|
|
35
|
+
return flat;
|
|
36
|
+
if (!ownerModule)
|
|
37
|
+
return undefined;
|
|
38
|
+
return ctx.moduleManifests
|
|
39
|
+
.get(ownerModule)
|
|
40
|
+
?.find((m) => m.metadata?.name === name && kindMatches(m));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The target of a `!ref` that still carries its sentinel — a library-internal
|
|
44
|
+
* reference inside a manifest forwarded into a consumer's flat set, where Phase
|
|
45
|
+
* 2.5 had nothing to resolve it against.
|
|
46
|
+
*
|
|
47
|
+
* **The declaring library is asked FIRST**, and that ordering is the whole rule:
|
|
48
|
+
* a bare name in a library manifest is unambiguously library-internal, so
|
|
49
|
+
* searching the consumer's flat set first let any consumer resource that
|
|
50
|
+
* happened to share the name supply another library's throw union — the same
|
|
51
|
+
* false `{∅}` this branch exists to remove, arrived at from the other side.
|
|
52
|
+
*
|
|
53
|
+
* An ALIAS-qualified source is the cross-module case and is resolved through the
|
|
54
|
+
* declaring module's own alias table, never by dropping the alias and matching
|
|
55
|
+
* the bare name anywhere.
|
|
56
|
+
*
|
|
57
|
+
* Ambiguity resolves to NOTHING rather than to a guess (`resolveScopedName`'s
|
|
58
|
+
* rule), and the caller reads that as unbounded — the safe direction here, since
|
|
59
|
+
* a union that cannot be enumerated must not read as empty.
|
|
60
|
+
*/
|
|
61
|
+
function findSentinelTarget(ctx, target, ownerModule) {
|
|
62
|
+
const named = (pool) => (pool ?? []).filter((m) => m.metadata?.name === target.name);
|
|
63
|
+
if (target.alias !== undefined && target.alias !== "Self") {
|
|
64
|
+
// A forwarded export: its module is whatever the DECLARING module aliases
|
|
65
|
+
// that prefix to, and it keeps its export name in the flat set.
|
|
66
|
+
// A root-owned manifest resolves aliases against the global table, which is
|
|
67
|
+
// what `scopeResolverForModule` returns undefined for — so fall back to it
|
|
68
|
+
// rather than reading a root's own alias as unresolvable.
|
|
69
|
+
const resolver = scopeResolverFor(ctx, ownerModule) ?? ctx.aliases;
|
|
70
|
+
const module = resolver.moduleForAlias(target.alias);
|
|
71
|
+
if (!module)
|
|
72
|
+
return undefined;
|
|
73
|
+
return named(ctx.allManifests).find((m) => declaringModuleOf(m) === module);
|
|
74
|
+
}
|
|
75
|
+
const own = named(ctx.moduleManifests.get(ownerModule ?? ""));
|
|
76
|
+
if (own.length === 1)
|
|
77
|
+
return own[0];
|
|
78
|
+
if (own.length > 1)
|
|
79
|
+
return undefined;
|
|
80
|
+
return resolveScopedName(named(ctx.allManifests), declaringModuleOf, ownerModule);
|
|
81
|
+
}
|
|
82
|
+
const declaringModuleOf = (m) => m.metadata?.module;
|
|
83
|
+
/** Memo key. Module-scoped, because resource names are. */
|
|
84
|
+
function memoKey(manifest) {
|
|
85
|
+
const name = manifest.metadata?.name;
|
|
86
|
+
if (!name)
|
|
87
|
+
return undefined;
|
|
88
|
+
const mod = manifest.metadata?.module ?? "";
|
|
89
|
+
return `${mod}\0${name}`;
|
|
90
|
+
}
|
|
19
91
|
function emptyUnion() {
|
|
20
92
|
return { codes: new Map(), unbounded: false };
|
|
21
93
|
}
|
|
@@ -60,7 +132,7 @@ function codesFromDefinition(definition) {
|
|
|
60
132
|
* unresolvable passthrough call sites. Cycles short-circuit to an empty
|
|
61
133
|
* result so resolution always terminates. */
|
|
62
134
|
export function resolveThrowsUnion(manifest, ctx) {
|
|
63
|
-
const name = manifest
|
|
135
|
+
const name = memoKey(manifest);
|
|
64
136
|
if (name) {
|
|
65
137
|
const cached = ctx.memo.get(name);
|
|
66
138
|
if (cached)
|
|
@@ -109,6 +181,16 @@ export function resolveThrowsUnion(manifest, ctx) {
|
|
|
109
181
|
ctx.inProgress.delete(name);
|
|
110
182
|
}
|
|
111
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* `throws.inherit: true` — the union a composer's own STEP BODIES reach.
|
|
186
|
+
*
|
|
187
|
+
* Deliberately steps only, and not every slot the resource drives: `inherit` is
|
|
188
|
+
* a DECLARATION that a kind's union is the union of what it dispatches, and a
|
|
189
|
+
* kind that does not make that claim must not have it inferred — a kind holding
|
|
190
|
+
* a `call` ref it catches internally would silently gain codes it never lets
|
|
191
|
+
* escape. What a CATCH SCOPE needs is a different question with a different
|
|
192
|
+
* answer, and it has its own resolver below.
|
|
193
|
+
*/
|
|
112
194
|
function resolveInherited(manifest, definition, ctx, ownerModule) {
|
|
113
195
|
const result = { codes: new Map(), unbounded: false };
|
|
114
196
|
const props = definition.schema?.properties;
|
|
@@ -125,6 +207,54 @@ function resolveInherited(manifest, definition, ctx, ownerModule) {
|
|
|
125
207
|
}
|
|
126
208
|
return result;
|
|
127
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* The union a SCOPE-LEVEL `catches:` list can be asked to render — everything
|
|
212
|
+
* the resource it is written on drives, transitively.
|
|
213
|
+
*
|
|
214
|
+
* `x-telo-catches-for: ""` is itself the claim that this is the denominator, so
|
|
215
|
+
* nothing is inferred from a kind that did not opt in, and no definition has to
|
|
216
|
+
* declare a `throws:` block to carry a catch scope (the kernel forbids one on a
|
|
217
|
+
* `Telo.Service` and a `Telo.Mount`, and rightly: what a router renders is not
|
|
218
|
+
* what a router THROWS).
|
|
219
|
+
*
|
|
220
|
+
* Three edges, three answers. A **step body** contributes its own traversal,
|
|
221
|
+
* subtraction included. A **control-transferring ref** contributes the target's
|
|
222
|
+
* own declared union — a route handler is a leaf here, and asking what IT drives
|
|
223
|
+
* would credit this scope with codes the handler catches internally. A
|
|
224
|
+
* **`throwsThrough` ref** recurses, because the target is another scope on the
|
|
225
|
+
* same ladder: a server renders what its mounts' routes throw, not what the
|
|
226
|
+
* mounts themselves declare.
|
|
227
|
+
*/
|
|
228
|
+
export function resolveScopeUnion(manifest, definition, ctx, seen = new Set()) {
|
|
229
|
+
const result = { codes: new Map(), unbounded: false };
|
|
230
|
+
if (seen.has(manifest))
|
|
231
|
+
return result;
|
|
232
|
+
seen.add(manifest);
|
|
233
|
+
const ownerModule = manifest.metadata?.module;
|
|
234
|
+
forEachDrivenSlot(definition.schema, manifest, (driven) => {
|
|
235
|
+
if (driven.kind === "step") {
|
|
236
|
+
unionInto(result, collectStepArrayThrows(driven.data, driven.slot.invoke, undefined, ctx, ownerModule));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (driven.slot.throwsThrough) {
|
|
240
|
+
const target = resolveRefManifest(driven.data, ctx, ownerModule);
|
|
241
|
+
const targetDef = target
|
|
242
|
+
? definitionFor(target.kind, ctx.defs, ctx.aliases, scopeResolverFor(ctx, target.metadata?.module))
|
|
243
|
+
: undefined;
|
|
244
|
+
if (target && targetDef)
|
|
245
|
+
unionInto(result, resolveScopeUnion(target, targetDef, ctx, seen));
|
|
246
|
+
// A target that cannot be resolved says nothing about what it throws, so
|
|
247
|
+
// the scope's union is no longer enumerable.
|
|
248
|
+
else
|
|
249
|
+
result.unbounded = true;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!possibleUses(driven.slot).some(transfersControl))
|
|
253
|
+
return;
|
|
254
|
+
unionInto(result, resolveRefTargetThrows(driven.data, ctx, ownerModule));
|
|
255
|
+
});
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
128
258
|
function collectStepArrayThrows(steps, invokeField, enclosingTryCodes, ctx, ownerModule) {
|
|
129
259
|
const result = emptyUnion();
|
|
130
260
|
for (const step of steps) {
|
|
@@ -219,34 +349,56 @@ function cloneUnion(u) {
|
|
|
219
349
|
return out;
|
|
220
350
|
}
|
|
221
351
|
function resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx, ownerModule) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
352
|
+
return resolveRefTargetThrows(step[invokeField], ctx, ownerModule, () => resolvePassthroughAtCallSite(step, enclosingTryCodes));
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The effective throw union behind a resolved reference value.
|
|
356
|
+
*
|
|
357
|
+
* Shared by both ways a resource drives another: a step's `invoke:` and a
|
|
358
|
+
* reference slot that carries throws. The two used to differ only in where the
|
|
359
|
+
* ref value was read from, and keeping one copy is what stops a router's
|
|
360
|
+
* denominator and a sequence's from disagreeing about what a name resolves to.
|
|
361
|
+
*
|
|
362
|
+
* `onPassthrough` is the one genuine difference: a passthrough kind's union is a
|
|
363
|
+
* property of the CALL SITE (`inputs.code`), which only a step has. A reference
|
|
364
|
+
* slot has no such site, so the union is unbounded there rather than guessed.
|
|
365
|
+
*/
|
|
366
|
+
function resolveRefTargetThrows(refValue, ctx, ownerModule, onPassthrough) {
|
|
367
|
+
if (!refValue || typeof refValue !== "object" || Array.isArray(refValue))
|
|
227
368
|
return emptyUnion();
|
|
369
|
+
const ref = refValue;
|
|
370
|
+
const invokedKind = ref.kind;
|
|
371
|
+
// A reference that still carries its parse-time sentinel — a library-internal
|
|
372
|
+
// `!ref` inside a manifest forwarded into a consumer's flat set, where Phase
|
|
373
|
+
// 2.5 had nothing to resolve it against. The target is in the declaring
|
|
374
|
+
// library's own documents, so it is looked up there; only a name that is not
|
|
375
|
+
// there either is UNKNOWN, which is unbounded rather than empty. Reading it as
|
|
376
|
+
// empty is what made a library's exported entry point present a `{∅}` union to
|
|
377
|
+
// its consumer and get the consumer's `catches:` rejected for the code the
|
|
378
|
+
// entry point documents.
|
|
379
|
+
if (!invokedKind) {
|
|
380
|
+
const sentinel = refSentinelTarget(ref);
|
|
381
|
+
if (!sentinel)
|
|
382
|
+
return emptyUnion();
|
|
383
|
+
const target = findSentinelTarget(ctx, sentinel, ownerModule);
|
|
384
|
+
if (target)
|
|
385
|
+
return resolveThrowsUnion(target, ctx);
|
|
386
|
+
return { codes: new Map(), unbounded: true };
|
|
387
|
+
}
|
|
228
388
|
// The invoked kind's alias resolves in the OWNER manifest's lexical scope (the
|
|
229
|
-
//
|
|
389
|
+
// resource that declares the slot), so a library's step referencing its own
|
|
230
390
|
// import resolves against that library, not the consumer.
|
|
231
391
|
const scopeResolver = scopeResolverFor(ctx, ownerModule);
|
|
232
392
|
const definition = definitionFor(invokedKind, ctx.defs, ctx.aliases, scopeResolver);
|
|
233
393
|
if (!definition)
|
|
234
394
|
return { codes: new Map(), unbounded: true };
|
|
235
395
|
if (definition.throws?.passthrough) {
|
|
236
|
-
return
|
|
396
|
+
return onPassthrough ? onPassthrough() : { codes: new Map(), unbounded: true };
|
|
237
397
|
}
|
|
238
398
|
// Named manifest: resolve the full chain (covers transitive inherit).
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
const target = ctx.allManifests.find((m) => m.metadata?.name === invokeName &&
|
|
243
|
-
(m.kind === invokedKind ||
|
|
244
|
-
ctx.aliases.resolveKind(m.kind) === invokedKind ||
|
|
245
|
-
m.kind === ctx.aliases.resolveKind(invokedKind) ||
|
|
246
|
-
(scopedInvokedKind !== undefined && m.kind === scopedInvokedKind)));
|
|
247
|
-
if (target)
|
|
248
|
-
return resolveThrowsUnion(target, ctx);
|
|
249
|
-
}
|
|
399
|
+
const target = resolveRefManifest(ref, ctx, ownerModule);
|
|
400
|
+
if (target)
|
|
401
|
+
return resolveThrowsUnion(target, ctx);
|
|
250
402
|
// Fall back to the definition's own explicit codes. Mark unbounded when the
|
|
251
403
|
// definition depends on call-site or transitive resolution we couldn't
|
|
252
404
|
// perform (no specific target manifest to recurse into).
|
|
@@ -254,6 +406,33 @@ function resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx, owne
|
|
|
254
406
|
const unbounded = definition.throws?.inherit === true || definition.throws?.passthrough === true;
|
|
255
407
|
return { codes, unbounded };
|
|
256
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* The manifest a resolved reference value names, in either shape it arrives in —
|
|
411
|
+
* a `{kind, name}` pair, or a `!ref` still carrying its parse-time sentinel.
|
|
412
|
+
*
|
|
413
|
+
* Exported because the catch-scope enclosure walk asks the same question about
|
|
414
|
+
* the same values; resolving a name twice by two rules is how two passes end up
|
|
415
|
+
* disagreeing about which resource a slot points at.
|
|
416
|
+
*/
|
|
417
|
+
export function resolveRefManifest(refValue, ctx, ownerModule) {
|
|
418
|
+
if (!refValue || typeof refValue !== "object" || Array.isArray(refValue))
|
|
419
|
+
return undefined;
|
|
420
|
+
const ref = refValue;
|
|
421
|
+
const kind = ref.kind;
|
|
422
|
+
if (!kind) {
|
|
423
|
+
const sentinel = refSentinelTarget(ref);
|
|
424
|
+
return sentinel ? findSentinelTarget(ctx, sentinel, ownerModule) : undefined;
|
|
425
|
+
}
|
|
426
|
+
const name = ref.name;
|
|
427
|
+
if (!name)
|
|
428
|
+
return undefined;
|
|
429
|
+
const scopeResolver = scopeResolverFor(ctx, ownerModule);
|
|
430
|
+
const scopedKind = scopeResolver?.resolveKind(kind);
|
|
431
|
+
return findTarget(ctx, name, ownerModule, (m) => m.kind === kind ||
|
|
432
|
+
ctx.aliases.resolveKind(m.kind) === kind ||
|
|
433
|
+
m.kind === ctx.aliases.resolveKind(kind) ||
|
|
434
|
+
(scopedKind !== undefined && m.kind === scopedKind));
|
|
435
|
+
}
|
|
257
436
|
/** Resolve a passthrough-style invocable at a specific call site. Recognised forms
|
|
258
437
|
* (see "passthrough: true" in the plan):
|
|
259
438
|
* - constant literal (no template) → `{ <literal> }`
|
|
@@ -17,7 +17,7 @@ import type { ResourceManifest } from "@telorun/sdk";
|
|
|
17
17
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
18
18
|
import { type CallGraph, type ResourceGraphNode } from "./call-graph.js";
|
|
19
19
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
20
|
-
import type {
|
|
20
|
+
import type { ModuleDocuments } from "./module-documents.js";
|
|
21
21
|
import { type AnalysisDiagnostic } from "./types.js";
|
|
22
22
|
/** One open requirement on a library's exported resource — the contract an
|
|
23
23
|
* importer must satisfy. Plain data, so it caches and crosses the
|
|
@@ -92,7 +92,7 @@ export declare function zoneDocumentsSignature(manifests: readonly ResourceManif
|
|
|
92
92
|
* its full documents (which the flattened analysis view no longer holds), run
|
|
93
93
|
* the projection derive-only, and keep what reaches an exported instance.
|
|
94
94
|
*/
|
|
95
|
-
export declare function deriveLibraryExportRequirements(docs:
|
|
95
|
+
export declare function deriveLibraryExportRequirements(docs: ModuleDocuments, defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, cache?: ZoneExportCache): ZoneExportRequirements;
|
|
96
96
|
export interface ZoneAnalysisArgs {
|
|
97
97
|
/** The entry analysis set (post inline-normalization + sentinel resolution). */
|
|
98
98
|
manifests: ResourceManifest[];
|
|
@@ -101,7 +101,7 @@ export interface ZoneAnalysisArgs {
|
|
|
101
101
|
aliases: AliasResolver;
|
|
102
102
|
aliasesByModule: Map<string, AliasResolver>;
|
|
103
103
|
rootModules: ReadonlySet<string>;
|
|
104
|
-
moduleDocuments?: readonly
|
|
104
|
+
moduleDocuments?: readonly ModuleDocuments[];
|
|
105
105
|
cache?: ZoneExportCache;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMnE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { refSentinelTarget } from "./ref-sentinel-target.js";
|
|
2
2
|
import { buildCallGraph, } from "./call-graph.js";
|
|
3
3
|
import { enclosingOf, propertySchemas, resolveLocalRef, } from "./manifest-navigation.js";
|
|
4
4
|
import { readProvidesZone, readRequiresZone } from "./zone-slot.js";
|
|
5
5
|
import { DiagnosticSeverity } from "./types.js";
|
|
6
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
6
7
|
const SOURCE = "telo-analyzer";
|
|
7
8
|
/** Resolve a possibly alias-form kind to its definition in the DECLARING
|
|
8
9
|
* module's scope — the same layering `AnalysisRegistry.resolveDefinitionIn`
|
|
9
10
|
* uses. */
|
|
10
11
|
function definitionResolver(defs, aliases, aliasesByModule) {
|
|
11
12
|
return (kind, module) => {
|
|
12
|
-
const scope = (module
|
|
13
|
+
const scope = moduleAliasScope({ module }, aliases, aliasesByModule);
|
|
13
14
|
const canonical = scope.resolveKind(kind);
|
|
14
15
|
return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
|
|
15
16
|
};
|
|
@@ -67,18 +68,17 @@ function schemaNodeAt(rootSchema, slotPath) {
|
|
|
67
68
|
* under-approximating direction the whole pass leans on. `Self.` is a local
|
|
68
69
|
* name written the long way and does resolve.
|
|
69
70
|
*
|
|
70
|
-
* This
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* This is this pass's REDUCTION over the shared `refSentinelTarget` parse, not a
|
|
72
|
+
* second reading of the tag: `call-graph`'s answers a different question (what
|
|
73
|
+
* an EDGE points at, cross-module included, for a graph whose consumers tolerate
|
|
74
|
+
* an unresolved target) and states its own reduction the same way.
|
|
74
75
|
*/
|
|
75
76
|
function refName(value) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
|
|
77
|
+
const sentinel = refSentinelTarget(value);
|
|
78
|
+
if (sentinel) {
|
|
79
|
+
if (sentinel.alias === undefined)
|
|
80
|
+
return sentinel.name;
|
|
81
|
+
return sentinel.alias === "Self" ? sentinel.name : undefined;
|
|
82
82
|
}
|
|
83
83
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
84
84
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AjvModule from "ajv";
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
3
|
import { isRefSentinel, isTaggedSentinel, producedTypeOf, } from "@telorun/templating";
|
|
4
|
-
import { celBaseOfValueType, celTypeOfValueType, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
|
|
4
|
+
import { celBaseOfValueType, celTypeOfValueType, isCompiledValue, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
|
|
5
5
|
import { ManifestRootSchema } from "./manifest-schemas.js";
|
|
6
6
|
import { schemaIssues } from "./schema-error-report.js";
|
|
7
7
|
import { registerTeloKeywords } from "./value-type-keyword.js";
|
|
@@ -709,6 +709,18 @@ export function substituteCelFields(data, schema, rootSchema, options = {}) {
|
|
|
709
709
|
mark();
|
|
710
710
|
return celPlaceholderForSchema(resolved);
|
|
711
711
|
}
|
|
712
|
+
// The same fact in its third spelling. An expression reaches this walk as a
|
|
713
|
+
// `${{ … }}` string or a `!cel` sentinel BEFORE `precompileDoc`, and as a
|
|
714
|
+
// CompiledValue after — so a caller running under `compile: true` (every
|
|
715
|
+
// `telo run`, unlike `telo check`) handed one to AJV as a plain object, and a
|
|
716
|
+
// slot typed `boolean` rejected a `when:` the author wrote correctly. The
|
|
717
|
+
// kernel's `stripCompiledValues` has always substituted here; missing it on
|
|
718
|
+
// this side made one manifest mean two things depending on which command read
|
|
719
|
+
// it.
|
|
720
|
+
if (isCompiledValue(data)) {
|
|
721
|
+
mark();
|
|
722
|
+
return celPlaceholderForSchema(resolved);
|
|
723
|
+
}
|
|
712
724
|
if (Array.isArray(data)) {
|
|
713
725
|
const item = resolveRefIn((resolved.items ?? {}), root, external);
|
|
714
726
|
return data.map((element, i) => substituteCelFields(element, item.schema, item.root, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAuCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA6B3D;AAoGD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,CAsF5F;AAqHD;;sFAEsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAaxD;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAKrF;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIjF"}
|
|
@@ -32,9 +32,38 @@ const UNION_KEYWORDS = new Set(["anyOf", "oneOf"]);
|
|
|
32
32
|
* not of that branch's shape at all — as opposed to being that shape and wrong
|
|
33
33
|
* further in. These are what make a branch implausible. */
|
|
34
34
|
const SHAPE_KEYWORDS = new Set(["required", "type", "additionalProperties", "enum", "const"]);
|
|
35
|
+
/** Keywords that exist to DISCRIMINATE, so a mismatch is positive evidence that
|
|
36
|
+
* the value is not this branch — at any depth, not only at the union node.
|
|
37
|
+
*
|
|
38
|
+
* Depth is otherwise the tiebreak, and it inverts exactly here: a branch that
|
|
39
|
+
* agreed on the discriminator and failed one constraint reported at the union
|
|
40
|
+
* node loses to every branch that disagreed about the discriminator one level
|
|
41
|
+
* in. That is how a `capability: Telo.Service` document declaring a forbidden
|
|
42
|
+
* key was reported as `/capability must be equal to constant` — naming neither
|
|
43
|
+
* the key at fault nor a branch the value could ever have been. */
|
|
44
|
+
const DISCRIMINATOR_KEYWORDS = new Set(["const", "enum"]);
|
|
45
|
+
/** RFC 6901 escapes: `~1` is a literal `/` in the key, `~0` a literal `~`.
|
|
46
|
+
* Decoded wherever a segment is shown or matched, because encoded it is not the
|
|
47
|
+
* key the manifest holds — a content map (`application/json`) anchors nowhere
|
|
48
|
+
* and reads wrong in the sentence. `~0` is expanded LAST, or `~01` would decode
|
|
49
|
+
* to `/` instead of the literal `~1` it encodes. */
|
|
50
|
+
function unescapeSegment(part) {
|
|
51
|
+
return part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
52
|
+
}
|
|
53
|
+
/** The whole pointer, segment by segment, for prose that quotes it verbatim. */
|
|
54
|
+
function unescapePointer(pointer) {
|
|
55
|
+
return pointer
|
|
56
|
+
.split("/")
|
|
57
|
+
.map((segment, i) => (i === 0 ? segment : unescapeSegment(segment)))
|
|
58
|
+
.join("/");
|
|
59
|
+
}
|
|
35
60
|
/* ------------------------------------------------------------------ prose */
|
|
36
61
|
export function formatSingleError(err) {
|
|
37
|
-
|
|
62
|
+
// Unescaped for the same reason the path is: a reader acting on this sentence
|
|
63
|
+
// needs the key the manifest holds (`application/json`), not its RFC 6901
|
|
64
|
+
// encoding. Reporting one form in `path` and the other in `message` made one
|
|
65
|
+
// diagnostic disagree with itself.
|
|
66
|
+
const p = unescapePointer(err.instancePath || "") || "/";
|
|
38
67
|
const params = err.params ?? {};
|
|
39
68
|
switch (err.keyword) {
|
|
40
69
|
case "additionalProperties":
|
|
@@ -45,6 +74,16 @@ export function formatSingleError(err) {
|
|
|
45
74
|
return `${p} ${err.message ?? "is invalid"} (${params.allowedValues?.join(" | ")})`;
|
|
46
75
|
case "type":
|
|
47
76
|
return `${p} must be ${params.type}${describeActual(err)}`;
|
|
77
|
+
// A `false` schema at a property is how a branch forbids a key it otherwise
|
|
78
|
+
// declares. AJV's own text ("boolean schema is false") describes the schema
|
|
79
|
+
// rather than the value, and the path is the only part a reader can act on.
|
|
80
|
+
case "false schema":
|
|
81
|
+
return `${p} is not allowed here`;
|
|
82
|
+
// `not:` says a forbidden shape matched, and AJV reports nothing about the
|
|
83
|
+
// inner schema — so the honest message says where, and no more. A branch
|
|
84
|
+
// that wants to name the key writes `properties: { <key>: false }` instead.
|
|
85
|
+
case "not":
|
|
86
|
+
return `${p} matches a shape that is not allowed here`;
|
|
48
87
|
default:
|
|
49
88
|
return `${p} ${err.message ?? "is invalid"}`;
|
|
50
89
|
}
|
|
@@ -109,9 +148,14 @@ function describeAlternatives(errors, unionInstancePath) {
|
|
|
109
148
|
return phrases.length > 0 ? phrases : ["another shape"];
|
|
110
149
|
}
|
|
111
150
|
/** Is this branch a plausible reading of the value — does it accept the value's
|
|
112
|
-
* shape at the union node itself, and
|
|
151
|
+
* shape at the union node itself, and agree with every discriminator it pins? */
|
|
113
152
|
function isPlausible(errors, unionInstancePath) {
|
|
114
|
-
return !errors.some((e) =>
|
|
153
|
+
return !errors.some((e) => {
|
|
154
|
+
const keyword = e.keyword ?? "";
|
|
155
|
+
if (DISCRIMINATOR_KEYWORDS.has(keyword))
|
|
156
|
+
return true;
|
|
157
|
+
return (e.instancePath || "") === unionInstancePath && SHAPE_KEYWORDS.has(keyword);
|
|
158
|
+
});
|
|
115
159
|
}
|
|
116
160
|
function isUnder(child, parent) {
|
|
117
161
|
return parent === "" ? child !== "" : child.startsWith(parent + "/");
|
|
@@ -332,7 +376,7 @@ export function ajvErrorToPath(err) {
|
|
|
332
376
|
if (/^\d+$/.test(part))
|
|
333
377
|
result += `[${part}]`;
|
|
334
378
|
else
|
|
335
|
-
result += result ? `.${part}` : part;
|
|
379
|
+
result += result ? `.${unescapeSegment(part)}` : unescapeSegment(part);
|
|
336
380
|
}
|
|
337
381
|
if (err.keyword === "required" && err.params?.missingProperty) {
|
|
338
382
|
const missing = err.params.missingProperty;
|