@telorun/analyzer 0.12.1 → 0.14.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/adapters/http-adapter.d.ts +10 -0
- package/dist/adapters/http-adapter.d.ts.map +1 -0
- package/dist/adapters/http-adapter.js +18 -0
- package/dist/adapters/node-adapter.d.ts +17 -0
- package/dist/adapters/node-adapter.d.ts.map +1 -0
- package/dist/adapters/node-adapter.js +71 -0
- package/dist/adapters/registry-adapter.d.ts +15 -0
- package/dist/adapters/registry-adapter.d.ts.map +1 -0
- package/dist/adapters/registry-adapter.js +53 -0
- package/dist/alias-resolver.d.ts +6 -0
- package/dist/alias-resolver.d.ts.map +1 -1
- package/dist/alias-resolver.js +8 -0
- package/dist/analysis-registry.d.ts +13 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +15 -0
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +176 -88
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +86 -0
- package/dist/cel-environment.d.ts +1 -1
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +40 -2
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +41 -62
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/kernel-globals.d.ts +1 -1
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +19 -1
- package/dist/manifest-visitor.d.ts +124 -0
- package/dist/manifest-visitor.d.ts.map +1 -0
- package/dist/manifest-visitor.js +181 -0
- package/dist/reference-field-map.js +16 -0
- package/dist/resolve-ref-sentinels.d.ts +15 -17
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +94 -40
- package/dist/resolve-throws-union.d.ts +10 -0
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +35 -7
- package/dist/schema-compat.d.ts +10 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +32 -0
- package/dist/validate-cel-context.d.ts +14 -0
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +38 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +215 -160
- package/dist/validate-unused-declarations.d.ts +25 -0
- package/dist/validate-unused-declarations.d.ts.map +1 -0
- package/dist/validate-unused-declarations.js +91 -0
- package/package.json +3 -3
- package/src/analysis-registry.ts +20 -0
- package/src/analyzer.ts +256 -168
- package/src/builtins.ts +85 -0
- package/src/cel-environment.ts +42 -1
- package/src/dependency-graph.ts +37 -52
- package/src/index.ts +11 -0
- package/src/kernel-globals.ts +22 -1
- package/src/manifest-visitor.ts +340 -0
- package/src/reference-field-map.ts +14 -0
- package/src/resolve-throws-union.ts +36 -8
- package/src/schema-compat.ts +32 -0
- package/src/validate-cel-context.ts +50 -0
- package/src/validate-references.ts +175 -211
- package/src/validate-unused-declarations.ts +95 -0
|
@@ -1,38 +1,99 @@
|
|
|
1
1
|
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
+
import { isModuleKind } from "./module-kinds.js";
|
|
2
3
|
import { isRefEntry } from "./reference-field-map.js";
|
|
3
4
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
4
5
|
/**
|
|
5
6
|
* Walks every `x-telo-ref` slot in every non-system resource and rewrites
|
|
6
|
-
* `!ref <name>` sentinels in-place to `{kind
|
|
7
|
+
* `!ref <name>` sentinels in-place to `{kind, name}` (local) or
|
|
8
|
+
* `{kind, name, alias}` (cross-module).
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Reference grammar — the tag's source string is split on the FIRST dot:
|
|
11
|
+
* - `!ref writeLine` → local resource `writeLine`
|
|
12
|
+
* - `!ref Self.writeLine` → local resource `writeLine` (explicit self-qualifier)
|
|
13
|
+
* - `!ref Console.writeLine` → instance `writeLine` exported by the import aliased
|
|
14
|
+
* `Console`, resolved against the forwarded foreign set
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Aliases are PascalCase identifiers without dots and resource names carry no dots
|
|
17
|
+
* (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
|
|
18
|
+
* name doesn't resolve, the sentinel is left in place so `validateReferences` emits the
|
|
19
|
+
* `UNRESOLVED_REFERENCE` diagnostic with full context.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* to the path-encoding rules of `resolveFieldEntries` — any new path
|
|
23
|
-
* marker would silently break this writer. Descending directly avoids
|
|
24
|
-
* that coupling.
|
|
21
|
+
* Forwarded foreign resources (an imported library's exported instances, carrying a
|
|
22
|
+
* `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
|
|
23
|
+
* re-walked as sources here, since their own ref slots belong to their own module scope.
|
|
25
24
|
*/
|
|
26
|
-
export function resolveRefSentinels(resources, registry, aliases, aliasesByModule
|
|
25
|
+
export function resolveRefSentinels(resources, registry, aliases, aliasesByModule,
|
|
26
|
+
// Extra foreign resources used only as cross-module resolution TARGETS (not mutated, not
|
|
27
|
+
// walked as sources). The kernel passes the analyzer-flattened set here so the runtime
|
|
28
|
+
// pass — which loads the entry module only — can still resolve `!ref Alias.name` against
|
|
29
|
+
// imported libraries' exported instances.
|
|
30
|
+
crossModuleTargets = []) {
|
|
31
|
+
const rootModules = new Set();
|
|
32
|
+
for (const r of resources) {
|
|
33
|
+
if (isModuleKind(r.kind) && typeof r.metadata?.name === "string") {
|
|
34
|
+
rootModules.add(r.metadata.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const moduleOf = (r) => r.metadata?.module;
|
|
38
|
+
const isForeign = (r) => {
|
|
39
|
+
const mod = moduleOf(r);
|
|
40
|
+
return typeof mod === "string" && !rootModules.has(mod);
|
|
41
|
+
};
|
|
42
|
+
// Local resources resolve a bare / `Self.`-qualified name; forwarded foreign exports
|
|
43
|
+
// resolve an `Alias.`-qualified name keyed by (module, name).
|
|
27
44
|
const byName = new Map();
|
|
45
|
+
const byModuleName = new Map();
|
|
28
46
|
for (const r of resources) {
|
|
29
|
-
if (r.metadata?.name
|
|
30
|
-
|
|
47
|
+
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind))
|
|
48
|
+
continue;
|
|
49
|
+
const name = r.metadata.name;
|
|
50
|
+
if (isForeign(r)) {
|
|
51
|
+
byModuleName.set(`${moduleOf(r)}\0${name}`, r);
|
|
31
52
|
}
|
|
53
|
+
else {
|
|
54
|
+
byName.set(name, r);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const r of crossModuleTargets) {
|
|
58
|
+
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForeign(r))
|
|
59
|
+
continue;
|
|
60
|
+
byModuleName.set(`${moduleOf(r)}\0${r.metadata.name}`, r);
|
|
32
61
|
}
|
|
62
|
+
const resolveTarget = (source) => {
|
|
63
|
+
const dot = source.indexOf(".");
|
|
64
|
+
if (dot === -1) {
|
|
65
|
+
const t = byName.get(source);
|
|
66
|
+
return t ? { kind: t.kind, name: source } : undefined;
|
|
67
|
+
}
|
|
68
|
+
const alias = source.slice(0, dot);
|
|
69
|
+
const name = source.slice(dot + 1);
|
|
70
|
+
if (alias === "Self") {
|
|
71
|
+
const t = byName.get(name);
|
|
72
|
+
return t ? { kind: t.kind, name } : undefined;
|
|
73
|
+
}
|
|
74
|
+
const module = aliases?.moduleForAlias(alias);
|
|
75
|
+
if (module) {
|
|
76
|
+
const t = byModuleName.get(`${module}\0${name}`);
|
|
77
|
+
if (t) {
|
|
78
|
+
// The foreign instance's `kind` is authored in ITS module's scope (e.g.
|
|
79
|
+
// `Self.WriteLine`); canonicalize to a scope-independent `<module>.<Kind>` for the
|
|
80
|
+
// consumer's kind check. `Self.` maps to the owning module directly — the forwarded
|
|
81
|
+
// library's Library doc (hence its `Self` alias) isn't in the consumer's manifest
|
|
82
|
+
// set — while other alias prefixes resolve via that module's forwarded import scope.
|
|
83
|
+
const rawKind = t.kind;
|
|
84
|
+
const foreignKind = rawKind.startsWith("Self.")
|
|
85
|
+
? `${module}.${rawKind.slice("Self.".length)}`
|
|
86
|
+
: aliasesByModule?.get(module)?.resolveKind(rawKind) ?? rawKind;
|
|
87
|
+
return { kind: foreignKind, name, alias };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
};
|
|
33
92
|
for (const r of resources) {
|
|
34
93
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
35
94
|
continue;
|
|
95
|
+
if (isForeign(r))
|
|
96
|
+
continue;
|
|
36
97
|
const fieldMap = aliases && aliasesByModule
|
|
37
98
|
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
38
99
|
: registry.getFieldMapForKind(r.kind, aliases);
|
|
@@ -41,37 +102,30 @@ export function resolveRefSentinels(resources, registry, aliases, aliasesByModul
|
|
|
41
102
|
for (const [fieldPath, entry] of fieldMap) {
|
|
42
103
|
if (!isRefEntry(entry))
|
|
43
104
|
continue;
|
|
44
|
-
|
|
105
|
+
descend(r, fieldPath.split("."), resolveTarget);
|
|
45
106
|
}
|
|
46
107
|
}
|
|
47
108
|
}
|
|
48
|
-
/** Walks `obj` along `fieldPath` (dot notation with `[]` for arrays and
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
* round-trip. */
|
|
53
|
-
function replaceSentinelsAtPath(obj, fieldPath, byName) {
|
|
54
|
-
const parts = fieldPath.split(".");
|
|
55
|
-
descend(obj, parts, byName);
|
|
56
|
-
}
|
|
57
|
-
function descend(obj, parts, byName) {
|
|
109
|
+
/** Walks `obj` along `fieldPath` parts (dot notation with `[]` for arrays and `{}` for
|
|
110
|
+
* additionalProperties-typed maps) and replaces any `!ref` sentinel at the terminal slot
|
|
111
|
+
* with its resolved `{kind, name, alias?}`. Mutates the parent container in place. */
|
|
112
|
+
function descend(obj, parts, resolve) {
|
|
58
113
|
if (obj == null || typeof obj !== "object" || parts.length === 0)
|
|
59
114
|
return;
|
|
60
115
|
const [head, ...rest] = parts;
|
|
61
|
-
// Map iteration: descend into every value of the current object.
|
|
62
116
|
if (head === "{}") {
|
|
63
117
|
const container = obj;
|
|
64
118
|
for (const key of Object.keys(container)) {
|
|
65
119
|
const child = container[key];
|
|
66
120
|
if (rest.length === 0) {
|
|
67
121
|
if (isRefSentinel(child)) {
|
|
68
|
-
const target =
|
|
122
|
+
const target = resolve(child.source);
|
|
69
123
|
if (target)
|
|
70
|
-
container[key] =
|
|
124
|
+
container[key] = target;
|
|
71
125
|
}
|
|
72
126
|
}
|
|
73
127
|
else {
|
|
74
|
-
descend(child, rest,
|
|
128
|
+
descend(child, rest, resolve);
|
|
75
129
|
}
|
|
76
130
|
}
|
|
77
131
|
return;
|
|
@@ -89,26 +143,26 @@ function descend(obj, parts, byName) {
|
|
|
89
143
|
if (rest.length === 0) {
|
|
90
144
|
const elem = val[i];
|
|
91
145
|
if (isRefSentinel(elem)) {
|
|
92
|
-
const target =
|
|
146
|
+
const target = resolve(elem.source);
|
|
93
147
|
if (target)
|
|
94
|
-
val[i] =
|
|
148
|
+
val[i] = target;
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
else {
|
|
98
|
-
descend(val[i], rest,
|
|
152
|
+
descend(val[i], rest, resolve);
|
|
99
153
|
}
|
|
100
154
|
}
|
|
101
155
|
}
|
|
102
156
|
else {
|
|
103
157
|
if (rest.length === 0) {
|
|
104
158
|
if (isRefSentinel(val)) {
|
|
105
|
-
const target =
|
|
159
|
+
const target = resolve(val.source);
|
|
106
160
|
if (target)
|
|
107
|
-
container[key] =
|
|
161
|
+
container[key] = target;
|
|
108
162
|
}
|
|
109
163
|
}
|
|
110
164
|
else {
|
|
111
|
-
descend(val, rest,
|
|
165
|
+
descend(val, rest, resolve);
|
|
112
166
|
}
|
|
113
167
|
}
|
|
114
168
|
}
|
|
@@ -4,6 +4,10 @@ import type { DefinitionRegistry } from "./definition-registry.js";
|
|
|
4
4
|
export interface ThrowsCodeMeta {
|
|
5
5
|
data?: Record<string, any>;
|
|
6
6
|
}
|
|
7
|
+
/** Code a non-`InvokeError` failure surfaces as inside a `catch` block. Mirrors
|
|
8
|
+
* `PLAIN_ERROR_CODE` in `@telorun/run`'s `toSequenceError`: any invoke can throw
|
|
9
|
+
* a plain error, which the catch sees as `error.code === "INTERNAL_ERROR"`. */
|
|
10
|
+
export declare const PLAIN_ERROR_CODE = "INTERNAL_ERROR";
|
|
7
11
|
export interface ThrowsUnion {
|
|
8
12
|
/** Code → per-code metadata (data schema, etc). Keys are the declared codes. */
|
|
9
13
|
codes: Map<string, ThrowsCodeMeta>;
|
|
@@ -12,6 +16,12 @@ export interface ThrowsUnion {
|
|
|
12
16
|
* an unknown kind was encountered, or a cycle short-circuited resolution.
|
|
13
17
|
* Callers must treat unbounded unions as requiring a catch-all entry. */
|
|
14
18
|
unbounded: boolean;
|
|
19
|
+
/** True when the block can fail with a non-`InvokeError` (any `invoke:` step).
|
|
20
|
+
* Such a failure surfaces inside an enclosing `catch` as `PLAIN_ERROR_CODE`,
|
|
21
|
+
* so a `throw: { code: "${{ error.code }}" }` rethrow can propagate it. Not
|
|
22
|
+
* injected into `codes` — only seeds `enclosingTryCodes` at a try/catch site,
|
|
23
|
+
* leaving non-rethrow unions untouched. */
|
|
24
|
+
canThrowPlain?: boolean;
|
|
15
25
|
}
|
|
16
26
|
export interface ResolveCtx {
|
|
17
27
|
allManifests: ResourceManifest[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,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,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,GACrB,UAAU,CAQZ;AAgCD;;;;8CAI8C;AAC9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,UAAU,GACd,WAAW,CA+Cb"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
|
+
/** Code a non-`InvokeError` failure surfaces as inside a `catch` block. Mirrors
|
|
3
|
+
* `PLAIN_ERROR_CODE` in `@telorun/run`'s `toSequenceError`: any invoke can throw
|
|
4
|
+
* a plain error, which the catch sees as `error.code === "INTERNAL_ERROR"`. */
|
|
5
|
+
export const PLAIN_ERROR_CODE = "INTERNAL_ERROR";
|
|
1
6
|
export function createResolveCtx(allManifests, defs, aliases) {
|
|
2
7
|
return {
|
|
3
8
|
allManifests,
|
|
@@ -17,6 +22,8 @@ function unionInto(target, src) {
|
|
|
17
22
|
}
|
|
18
23
|
if (src.unbounded)
|
|
19
24
|
target.unbounded = true;
|
|
25
|
+
if (src.canThrowPlain)
|
|
26
|
+
target.canThrowPlain = true;
|
|
20
27
|
}
|
|
21
28
|
function definitionFor(kind, defs, aliases) {
|
|
22
29
|
const resolved = aliases.resolveKind(kind);
|
|
@@ -115,7 +122,11 @@ function collectStepArrayThrows(steps, invokeField, enclosingTryCodes, ctx) {
|
|
|
115
122
|
* composers that reuse those shape conventions work without changes here. */
|
|
116
123
|
function collectStepThrows(step, invokeField, enclosingTryCodes, ctx) {
|
|
117
124
|
if (step[invokeField]) {
|
|
118
|
-
|
|
125
|
+
// Any invoked resource can throw a non-InvokeError at runtime, which an
|
|
126
|
+
// enclosing catch surfaces as PLAIN_ERROR_CODE — record that possibility.
|
|
127
|
+
const u = cloneUnion(resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx));
|
|
128
|
+
u.canThrowPlain = true;
|
|
129
|
+
return u;
|
|
119
130
|
}
|
|
120
131
|
if (step.throw && typeof step.throw === "object") {
|
|
121
132
|
return resolveThrowStepCode(step.throw, enclosingTryCodes);
|
|
@@ -128,6 +139,11 @@ function collectStepThrows(step, invokeField, enclosingTryCodes, ctx) {
|
|
|
128
139
|
// out instead. Sequence-specific subtraction — the plan explicitly
|
|
129
140
|
// anchors this to Run.Sequence's try/catch schema shape.
|
|
130
141
|
const tryCodes = new Set(tryUnion.codes.keys());
|
|
142
|
+
// A plain (non-InvokeError) failure in the try block reaches the catch as
|
|
143
|
+
// `error.code === PLAIN_ERROR_CODE`, so a `throw: { code: error.code }`
|
|
144
|
+
// rethrow can propagate it — seed the set the catch resolves against.
|
|
145
|
+
if (tryUnion.canThrowPlain)
|
|
146
|
+
tryCodes.add(PLAIN_ERROR_CODE);
|
|
131
147
|
propagated = collectStepArrayThrows(step.catch, invokeField, tryCodes, ctx);
|
|
132
148
|
// Unbounded in the try block still signals the caller to expect
|
|
133
149
|
// arbitrary codes to flow through the catch (e.g. via passthrough).
|
|
@@ -179,6 +195,8 @@ function cloneUnion(u) {
|
|
|
179
195
|
for (const [c, m] of u.codes)
|
|
180
196
|
out.codes.set(c, m);
|
|
181
197
|
out.unbounded = u.unbounded;
|
|
198
|
+
if (u.canThrowPlain)
|
|
199
|
+
out.canThrowPlain = true;
|
|
182
200
|
return out;
|
|
183
201
|
}
|
|
184
202
|
function resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx) {
|
|
@@ -226,14 +244,24 @@ function resolveThrowStepCode(throwSpec, enclosingTryCodes) {
|
|
|
226
244
|
return resolveCodeExpression(throwSpec.code, enclosingTryCodes);
|
|
227
245
|
}
|
|
228
246
|
function resolveCodeExpression(codeInput, enclosingTryCodes) {
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
// A `!cel`-tagged sentinel and a `${{ … }}` string must resolve identically —
|
|
248
|
+
// normalize both to the inner CEL expression (or a bare literal code).
|
|
249
|
+
let expr;
|
|
250
|
+
if (isTaggedSentinel(codeInput)) {
|
|
251
|
+
if (codeInput.engine !== "cel")
|
|
252
|
+
return { codes: new Map(), unbounded: true };
|
|
253
|
+
expr = codeInput.source.trim();
|
|
231
254
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
255
|
+
else if (typeof codeInput === "string" && codeInput.length > 0) {
|
|
256
|
+
const match = codeInput.match(/^\s*\$\{\{\s*([\s\S]+?)\s*\}\}\s*$/);
|
|
257
|
+
if (!match) {
|
|
258
|
+
return { codes: new Map([[codeInput, {}]]), unbounded: false };
|
|
259
|
+
}
|
|
260
|
+
expr = match[1].trim();
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
return { codes: new Map(), unbounded: true };
|
|
235
264
|
}
|
|
236
|
-
const expr = match[1].trim();
|
|
237
265
|
const litMatch = expr.match(/^'([^']+)'$|^"([^"]+)"$/);
|
|
238
266
|
if (litMatch) {
|
|
239
267
|
const code = litMatch[1] ?? litMatch[2];
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -35,6 +35,16 @@ export declare function navigateJsonPointer(schema: unknown, pointer: string): u
|
|
|
35
35
|
* Stops and returns the current node when a union type (`anyOf`/`oneOf`) is reached.
|
|
36
36
|
* Returns `undefined` if any segment cannot be resolved. */
|
|
37
37
|
export declare function navigateSchemaToExprPath(schema: Record<string, any>, path: string): Record<string, any> | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Recognized `x-telo-type` value brands and the CEL primitive each refines.
|
|
40
|
+
* A brand is a nominal type the analyzer registers (see cel-environment.ts) so
|
|
41
|
+
* structurally-identical values (a `TcpPort` and a `UdpPort` are both integers)
|
|
42
|
+
* stay distinct for static wiring checks. Brands carry no runtime effect — the
|
|
43
|
+
* value flows as its base type. Add new brands here (e.g. `Url: "string"`).
|
|
44
|
+
*/
|
|
45
|
+
export declare const VALUE_BRAND_BASE: Record<string, string>;
|
|
46
|
+
/** Read a recognized `x-telo-type` brand off a schema, or undefined. */
|
|
47
|
+
export declare function brandOfSchema(schema: Record<string, any> | undefined): string | undefined;
|
|
38
48
|
/** Map a JSON Schema type annotation to a CEL type string. */
|
|
39
49
|
export declare function jsonSchemaToCelType(schema: Record<string, any> | undefined): string;
|
|
40
50
|
/** Check whether a CEL return type is compatible with a JSON Schema type constraint. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGnD,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAyBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiChG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AAID,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -172,10 +172,29 @@ export function navigateSchemaToExprPath(schema, path) {
|
|
|
172
172
|
}
|
|
173
173
|
return current;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Recognized `x-telo-type` value brands and the CEL primitive each refines.
|
|
177
|
+
* A brand is a nominal type the analyzer registers (see cel-environment.ts) so
|
|
178
|
+
* structurally-identical values (a `TcpPort` and a `UdpPort` are both integers)
|
|
179
|
+
* stay distinct for static wiring checks. Brands carry no runtime effect — the
|
|
180
|
+
* value flows as its base type. Add new brands here (e.g. `Url: "string"`).
|
|
181
|
+
*/
|
|
182
|
+
export const VALUE_BRAND_BASE = {
|
|
183
|
+
TcpPort: "int",
|
|
184
|
+
UdpPort: "int",
|
|
185
|
+
};
|
|
186
|
+
/** Read a recognized `x-telo-type` brand off a schema, or undefined. */
|
|
187
|
+
export function brandOfSchema(schema) {
|
|
188
|
+
const brand = schema?.["x-telo-type"];
|
|
189
|
+
return typeof brand === "string" && brand in VALUE_BRAND_BASE ? brand : undefined;
|
|
190
|
+
}
|
|
175
191
|
/** Map a JSON Schema type annotation to a CEL type string. */
|
|
176
192
|
export function jsonSchemaToCelType(schema) {
|
|
177
193
|
if (!schema || typeof schema !== "object")
|
|
178
194
|
return "dyn";
|
|
195
|
+
const brand = brandOfSchema(schema);
|
|
196
|
+
if (brand)
|
|
197
|
+
return brand;
|
|
179
198
|
if (schema.anyOf || schema.oneOf || schema.allOf)
|
|
180
199
|
return "dyn";
|
|
181
200
|
if (Array.isArray(schema.type))
|
|
@@ -206,6 +225,19 @@ export function jsonSchemaToCelType(schema) {
|
|
|
206
225
|
export function celTypeSatisfiesJsonSchema(celType, schema) {
|
|
207
226
|
if (celType === "dyn")
|
|
208
227
|
return true;
|
|
228
|
+
// Nominal value brands: when the expression's type is a recognized brand,
|
|
229
|
+
// a branded consuming field must match exactly (a UdpPort wired into a
|
|
230
|
+
// TcpPort-branded field is the error we want). An unbranded field accepts
|
|
231
|
+
// the brand as its base type — gradual typing, so a TcpPort flows freely
|
|
232
|
+
// into a plain integer field. (A plain integer into a branded field is also
|
|
233
|
+
// allowed: only a *conflicting* brand is rejected.)
|
|
234
|
+
const sourceBase = VALUE_BRAND_BASE[celType];
|
|
235
|
+
if (sourceBase) {
|
|
236
|
+
const fieldBrand = brandOfSchema(schema);
|
|
237
|
+
if (fieldBrand)
|
|
238
|
+
return fieldBrand === celType;
|
|
239
|
+
celType = sourceBase;
|
|
240
|
+
}
|
|
209
241
|
if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf)
|
|
210
242
|
return true;
|
|
211
243
|
if (schema.anyOf || schema.oneOf || schema.allOf)
|
|
@@ -83,4 +83,18 @@ export declare function resolveContextAnnotations(schema: Record<string, any>, m
|
|
|
83
83
|
* e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
|
|
84
84
|
*/
|
|
85
85
|
export declare function getManifestItem(exprPath: string, scope: string, manifest: Record<string, any>): Record<string, any>;
|
|
86
|
+
/**
|
|
87
|
+
* Walk a JSON Schema tree and collect all `x-telo-context` annotations,
|
|
88
|
+
* returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
|
|
89
|
+
* the same format the analyzer uses for CEL context validation.
|
|
90
|
+
*
|
|
91
|
+
* Result is sorted by scope specificity (longer scope first) so that the
|
|
92
|
+
* per-expression resolver's first-match-wins logic picks the most-specific
|
|
93
|
+
* context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
|
|
94
|
+
* could shadow a narrower descendant scope whose activation differs.
|
|
95
|
+
*/
|
|
96
|
+
export declare function extractContextsFromSchema(schema: Record<string, any>, path?: string): Array<{
|
|
97
|
+
scope: string;
|
|
98
|
+
schema: Record<string, any>;
|
|
99
|
+
}>;
|
|
86
100
|
//# sourceMappingURL=validate-cel-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqGrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB"}
|
|
1
|
+
{"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqGrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD"}
|
|
@@ -219,3 +219,41 @@ function navigatePath(obj, segments) {
|
|
|
219
219
|
}
|
|
220
220
|
return cur;
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Walk a JSON Schema tree and collect all `x-telo-context` annotations,
|
|
224
|
+
* returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
|
|
225
|
+
* the same format the analyzer uses for CEL context validation.
|
|
226
|
+
*
|
|
227
|
+
* Result is sorted by scope specificity (longer scope first) so that the
|
|
228
|
+
* per-expression resolver's first-match-wins logic picks the most-specific
|
|
229
|
+
* context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
|
|
230
|
+
* could shadow a narrower descendant scope whose activation differs.
|
|
231
|
+
*/
|
|
232
|
+
export function extractContextsFromSchema(schema, path = "$") {
|
|
233
|
+
const all = collectContexts(schema, path);
|
|
234
|
+
return all.sort((a, b) => b.scope.length - a.scope.length);
|
|
235
|
+
}
|
|
236
|
+
function collectContexts(schema, path) {
|
|
237
|
+
if (!schema || typeof schema !== "object")
|
|
238
|
+
return [];
|
|
239
|
+
const results = [];
|
|
240
|
+
if (schema["x-telo-context"]) {
|
|
241
|
+
results.push({ scope: path, schema: schema["x-telo-context"] });
|
|
242
|
+
}
|
|
243
|
+
if (schema.properties) {
|
|
244
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
245
|
+
results.push(...collectContexts(value, `${path}.${key}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (schema.items && typeof schema.items === "object") {
|
|
249
|
+
results.push(...collectContexts(schema.items, `${path}[*]`));
|
|
250
|
+
}
|
|
251
|
+
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
252
|
+
if (Array.isArray(schema[key])) {
|
|
253
|
+
for (const subschema of schema[key]) {
|
|
254
|
+
results.push(...collectContexts(subschema, path));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA6C/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2etB"}
|