@telorun/analyzer 0.10.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/analysis-registry.d.ts +7 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +38 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +198 -6
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +158 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +9 -11
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +26 -14
- package/dist/position-metadata.d.ts +5 -1
- package/dist/position-metadata.d.ts.map +1 -1
- package/dist/position-metadata.js +8 -1
- package/dist/reference-field-map.d.ts +21 -4
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +35 -19
- package/dist/residual-schema.d.ts +23 -0
- package/dist/residual-schema.d.ts.map +1 -0
- package/dist/residual-schema.js +45 -0
- package/dist/rewrite-synthetic-origins.d.ts +10 -0
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -0
- package/dist/rewrite-synthetic-origins.js +55 -0
- package/dist/validate-cel-context.d.ts +61 -7
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +90 -8
- package/dist/validate-provider-coherence.d.ts +23 -0
- package/dist/validate-provider-coherence.d.ts.map +1 -0
- package/dist/validate-provider-coherence.js +148 -0
- package/dist/validate-references.js +24 -24
- package/package.json +5 -3
- package/src/analysis-registry.ts +37 -0
- package/src/analyzer.ts +240 -8
- package/src/builtins.ts +158 -1
- package/src/index.ts +1 -0
- package/src/kernel-globals.ts +9 -11
- package/src/normalize-inline-resources.ts +48 -13
- package/src/position-metadata.ts +8 -1
- package/src/reference-field-map.ts +46 -18
- package/src/residual-schema.ts +49 -0
- package/src/rewrite-synthetic-origins.ts +75 -0
- package/src/validate-cel-context.ts +111 -8
- package/src/validate-provider-coherence.ts +166 -0
- package/src/validate-references.ts +24 -24
|
@@ -60,7 +60,7 @@ export function normalizeInlineResources(resources, registry, aliases, aliasesBy
|
|
|
60
60
|
fieldPath.startsWith(prefix + ".") ||
|
|
61
61
|
fieldPath.startsWith(prefix + "["));
|
|
62
62
|
const invocationContext = isRefEntry(entry) ? entry.context : undefined;
|
|
63
|
-
const extracted = extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext);
|
|
63
|
+
const extracted = extractInlinesAtPath(resource, fieldPath, parentName, resource.kind, parentModule, invocationContext);
|
|
64
64
|
for (const manifest of extracted) {
|
|
65
65
|
result.push(manifest);
|
|
66
66
|
queue.push(manifest);
|
|
@@ -75,11 +75,22 @@ export function normalizeInlineResources(resources, registry, aliases, aliasesBy
|
|
|
75
75
|
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
76
76
|
* Mutates the resource in-place: replaces each inline value with `{kind, name}`.
|
|
77
77
|
* Returns the extracted manifests.
|
|
78
|
+
*
|
|
79
|
+
* Each extracted manifest carries `metadata.xTeloOrigin` so downstream
|
|
80
|
+
* diagnostics can be rerouted back to the parent doc's YAML position:
|
|
81
|
+
* - `parentKind` / `parentName` — the resource that owned the inline slot
|
|
82
|
+
* - `pathFromParent` — concrete dotted path with `[N]` indices, matching
|
|
83
|
+
* `buildPositionIndex` keys (e.g. `routes[0].handler`)
|
|
78
84
|
*/
|
|
79
|
-
function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext) {
|
|
85
|
+
function extractInlinesAtPath(resource, fieldPath, parentName, parentKind, parentModule, invocationContext) {
|
|
80
86
|
const extracted = [];
|
|
81
87
|
const parts = fieldPath.split(".");
|
|
82
|
-
function
|
|
88
|
+
function emit(inline, nameSegments, concretePath) {
|
|
89
|
+
const name = sanitizeName([parentName, ...nameSegments].join("_"));
|
|
90
|
+
extracted.push(buildManifest(inline, name, parentKind, parentName, concretePath, parentModule, invocationContext));
|
|
91
|
+
return name;
|
|
92
|
+
}
|
|
93
|
+
function traverse(obj, partsLeft, nameParts, pathSoFar) {
|
|
83
94
|
if (!obj || typeof obj !== "object" || partsLeft.length === 0)
|
|
84
95
|
return;
|
|
85
96
|
const [head, ...rest] = partsLeft;
|
|
@@ -92,15 +103,15 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
|
|
|
92
103
|
if (!elem || typeof elem !== "object")
|
|
93
104
|
continue;
|
|
94
105
|
const sanitizedKey = sanitizeName(mapKey);
|
|
106
|
+
const childPath = pathSoFar ? `${pathSoFar}.${mapKey}` : mapKey;
|
|
95
107
|
if (rest.length === 0) {
|
|
96
108
|
if (isInlineResource(elem)) {
|
|
97
|
-
const name =
|
|
98
|
-
extracted.push(buildManifest(elem, name, parentModule, invocationContext));
|
|
109
|
+
const name = emit(elem, [...nameParts, sanitizedKey], childPath);
|
|
99
110
|
container[mapKey] = { kind: elem.kind, name };
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
else {
|
|
103
|
-
traverse(elem, rest, [...nameParts, sanitizedKey]);
|
|
114
|
+
traverse(elem, rest, [...nameParts, sanitizedKey], childPath);
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
return;
|
|
@@ -111,6 +122,7 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
|
|
|
111
122
|
const val = container[key];
|
|
112
123
|
if (val == null)
|
|
113
124
|
return;
|
|
125
|
+
const keyPath = pathSoFar ? `${pathSoFar}.${key}` : key;
|
|
114
126
|
if (isArr) {
|
|
115
127
|
if (!Array.isArray(val))
|
|
116
128
|
return;
|
|
@@ -121,16 +133,16 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
|
|
|
121
133
|
const elemId = typeof elem.name === "string"
|
|
122
134
|
? elem.name
|
|
123
135
|
: String(idx);
|
|
136
|
+
const childPath = `${keyPath}[${idx}]`;
|
|
124
137
|
if (rest.length === 0) {
|
|
125
138
|
// Array element itself is the ref slot
|
|
126
139
|
if (isInlineResource(elem)) {
|
|
127
|
-
const name =
|
|
128
|
-
extracted.push(buildManifest(elem, name, parentModule, invocationContext));
|
|
140
|
+
const name = emit(elem, [...nameParts, key, elemId], childPath);
|
|
129
141
|
val[idx] = { kind: elem.kind, name };
|
|
130
142
|
}
|
|
131
143
|
}
|
|
132
144
|
else {
|
|
133
|
-
traverse(elem, rest, [...nameParts, key, elemId]);
|
|
145
|
+
traverse(elem, rest, [...nameParts, key, elemId], childPath);
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
}
|
|
@@ -138,20 +150,19 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
|
|
|
138
150
|
if (rest.length === 0) {
|
|
139
151
|
// val is the ref slot
|
|
140
152
|
if (val && typeof val === "object" && !Array.isArray(val) && isInlineResource(val)) {
|
|
141
|
-
const name =
|
|
142
|
-
extracted.push(buildManifest(val, name, parentModule, invocationContext));
|
|
153
|
+
const name = emit(val, [...nameParts, key], keyPath);
|
|
143
154
|
container[key] = { kind: val.kind, name };
|
|
144
155
|
}
|
|
145
156
|
}
|
|
146
157
|
else {
|
|
147
|
-
traverse(val, rest, [...nameParts, key]);
|
|
158
|
+
traverse(val, rest, [...nameParts, key], keyPath);
|
|
148
159
|
}
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
|
-
traverse(resource, parts, []);
|
|
162
|
+
traverse(resource, parts, [], "");
|
|
152
163
|
return extracted;
|
|
153
164
|
}
|
|
154
|
-
function buildManifest(inline, name, parentModule, invocationContext) {
|
|
165
|
+
function buildManifest(inline, name, parentKind, parentName, pathFromParent, parentModule, invocationContext) {
|
|
155
166
|
const existingMeta = inline.metadata && typeof inline.metadata === "object"
|
|
156
167
|
? inline.metadata
|
|
157
168
|
: {};
|
|
@@ -163,6 +174,7 @@ function buildManifest(inline, name, parentModule, invocationContext) {
|
|
|
163
174
|
// Inherit parent module only if the inline doesn't already declare one
|
|
164
175
|
...(parentModule && !existingMeta.module ? { module: parentModule } : {}),
|
|
165
176
|
...(invocationContext ? { xTeloInvocationContext: invocationContext } : {}),
|
|
177
|
+
xTeloOrigin: { parentKind, parentName, pathFromParent },
|
|
166
178
|
},
|
|
167
179
|
};
|
|
168
180
|
}
|
|
@@ -22,6 +22,10 @@ export declare function documentLineOffsets(text: string): number[];
|
|
|
22
22
|
* yaml-AST node range into Range coordinates. */
|
|
23
23
|
export declare function buildLineOffsets(text: string): number[];
|
|
24
24
|
/** Walks the YAML AST and records source ranges for every field value, keyed
|
|
25
|
-
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
|
|
25
|
+
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
|
|
26
|
+
* Map keys are also recorded under the `@key:<path>` namespace so diagnostic
|
|
27
|
+
* resolvers can squiggle just the key identifier instead of the full value
|
|
28
|
+
* block — used when a diagnostic targets a missing child property and the
|
|
29
|
+
* resolver has to fall back to the parent. */
|
|
26
30
|
export declare function buildPositionIndex(doc: Document, lineOffsets: number[]): PositionIndex;
|
|
27
31
|
//# sourceMappingURL=position-metadata.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD
|
|
1
|
+
{"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;;;;;+CAK+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CA0CtF"}
|
|
@@ -45,7 +45,11 @@ function offsetToPosition(offset, lineOffsets) {
|
|
|
45
45
|
return { line: lo, character: offset - lineOffsets[lo] };
|
|
46
46
|
}
|
|
47
47
|
/** Walks the YAML AST and records source ranges for every field value, keyed
|
|
48
|
-
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
|
|
48
|
+
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
|
|
49
|
+
* Map keys are also recorded under the `@key:<path>` namespace so diagnostic
|
|
50
|
+
* resolvers can squiggle just the key identifier instead of the full value
|
|
51
|
+
* block — used when a diagnostic targets a missing child property and the
|
|
52
|
+
* resolver has to fall back to the parent. */
|
|
49
53
|
export function buildPositionIndex(doc, lineOffsets) {
|
|
50
54
|
const index = new Map();
|
|
51
55
|
function recordNode(node, path) {
|
|
@@ -66,6 +70,9 @@ export function buildPositionIndex(doc, lineOffsets) {
|
|
|
66
70
|
if (key == null)
|
|
67
71
|
continue;
|
|
68
72
|
const childPath = path ? `${path}.${key}` : key;
|
|
73
|
+
if (pair.key && pair.key.range) {
|
|
74
|
+
recordNode(pair.key, `@key:${childPath}`);
|
|
75
|
+
}
|
|
69
76
|
if (pair.value != null) {
|
|
70
77
|
recordNode(pair.value, childPath);
|
|
71
78
|
walk(pair.value, childPath);
|
|
@@ -43,12 +43,29 @@ export declare const REFERENCE_KEYS: Set<string>;
|
|
|
43
43
|
* A named reference (has string `name`) may carry extra keys (e.g. `inputs`)
|
|
44
44
|
* that are runtime call parameters — those are never inline resources. */
|
|
45
45
|
export declare function isInlineResource(val: Record<string, unknown>): boolean;
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
/** A value found at a field-map path, paired with the concrete path that
|
|
47
|
+
* produced it. `path` has every `[]` substituted with `[N]` and every `{}`
|
|
48
|
+
* substituted with the actual map key, matching the format produced by
|
|
49
|
+
* `buildPositionIndex`. Used so diagnostics emitted against a specific
|
|
50
|
+
* array element / map entry can be resolved back to a YAML range. */
|
|
51
|
+
export interface ResolvedFieldEntry {
|
|
52
|
+
value: unknown;
|
|
53
|
+
path: string;
|
|
54
|
+
}
|
|
55
|
+
/** Resolves all `{value, path}` entries at a field map path in a resource
|
|
56
|
+
* config. The returned `path` is the concrete dotted path produced by the
|
|
57
|
+
* substitutions below, matching the format `buildPositionIndex` keys on.
|
|
58
|
+
* Path-segment markers accepted in the input `path`:
|
|
59
|
+
* - `[]` iterate array values at this key, substituting `[N]` per item
|
|
60
|
+
* (e.g. `routes[]` → `routes[0]`, `routes[1]`, …).
|
|
49
61
|
* - `{}` iterate map values (every value in an `additionalProperties`-typed
|
|
50
62
|
* object — used for fields like `content[mime]` whose schema declares
|
|
51
|
-
* a key-as-MIME map).
|
|
63
|
+
* a key-as-MIME map). Substituted with the literal map key joined by
|
|
64
|
+
* a dot, so the input `content.{}.encoder` yields concrete paths
|
|
65
|
+
* like `content.application/json.encoder`. */
|
|
66
|
+
export declare function resolveFieldEntries(obj: unknown, path: string): ResolvedFieldEntry[];
|
|
67
|
+
/** Backwards-compat wrapper that drops the concrete path. Prefer
|
|
68
|
+
* `resolveFieldEntries` for new code that wants positions. */
|
|
52
69
|
export declare function resolveFieldValues(obj: unknown, path: string): unknown[];
|
|
53
70
|
/**
|
|
54
71
|
* Traverses a definition's JSON Schema once and returns a field map recording every
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED
|
|
1
|
+
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;wDAUwD;AACxD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAoCpF;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;AAiBD;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
|
|
@@ -26,25 +26,30 @@ export function isInlineResource(val) {
|
|
|
26
26
|
return false;
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
|
-
/** Resolves all
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
/** Resolves all `{value, path}` entries at a field map path in a resource
|
|
30
|
+
* config. The returned `path` is the concrete dotted path produced by the
|
|
31
|
+
* substitutions below, matching the format `buildPositionIndex` keys on.
|
|
32
|
+
* Path-segment markers accepted in the input `path`:
|
|
33
|
+
* - `[]` iterate array values at this key, substituting `[N]` per item
|
|
34
|
+
* (e.g. `routes[]` → `routes[0]`, `routes[1]`, …).
|
|
32
35
|
* - `{}` iterate map values (every value in an `additionalProperties`-typed
|
|
33
36
|
* object — used for fields like `content[mime]` whose schema declares
|
|
34
|
-
* a key-as-MIME map).
|
|
35
|
-
|
|
37
|
+
* a key-as-MIME map). Substituted with the literal map key joined by
|
|
38
|
+
* a dot, so the input `content.{}.encoder` yields concrete paths
|
|
39
|
+
* like `content.application/json.encoder`. */
|
|
40
|
+
export function resolveFieldEntries(obj, path) {
|
|
36
41
|
const parts = path.split(".");
|
|
37
|
-
let current = [obj];
|
|
42
|
+
let current = [{ value: obj, path: "" }];
|
|
38
43
|
for (const part of parts) {
|
|
39
44
|
if (part === "{}") {
|
|
40
|
-
// Iterate the values of every map currently in `current`.
|
|
41
45
|
const next = [];
|
|
42
|
-
for (const
|
|
43
|
-
if (!
|
|
46
|
+
for (const entry of current) {
|
|
47
|
+
if (!entry.value || typeof entry.value !== "object")
|
|
44
48
|
continue;
|
|
45
|
-
for (const v of Object.
|
|
46
|
-
if (v != null)
|
|
47
|
-
next.push(v);
|
|
49
|
+
for (const [k, v] of Object.entries(entry.value)) {
|
|
50
|
+
if (v != null) {
|
|
51
|
+
next.push({ value: v, path: entry.path ? `${entry.path}.${k}` : k });
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
current = next;
|
|
@@ -53,21 +58,32 @@ export function resolveFieldValues(obj, path) {
|
|
|
53
58
|
const isArray = part.endsWith("[]");
|
|
54
59
|
const key = isArray ? part.slice(0, -2) : part;
|
|
55
60
|
const next = [];
|
|
56
|
-
for (const
|
|
57
|
-
if (!
|
|
61
|
+
for (const entry of current) {
|
|
62
|
+
if (!entry.value || typeof entry.value !== "object")
|
|
58
63
|
continue;
|
|
59
|
-
const val =
|
|
64
|
+
const val = entry.value[key];
|
|
60
65
|
if (val == null)
|
|
61
66
|
continue;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
const basePath = entry.path ? `${entry.path}.${key}` : key;
|
|
68
|
+
if (isArray && Array.isArray(val)) {
|
|
69
|
+
for (let i = 0; i < val.length; i++) {
|
|
70
|
+
if (val[i] != null)
|
|
71
|
+
next.push({ value: val[i], path: `${basePath}[${i}]` });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (!isArray) {
|
|
75
|
+
next.push({ value: val, path: basePath });
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
78
|
current = next;
|
|
68
79
|
}
|
|
69
80
|
return current;
|
|
70
81
|
}
|
|
82
|
+
/** Backwards-compat wrapper that drops the concrete path. Prefer
|
|
83
|
+
* `resolveFieldEntries` for new code that wants positions. */
|
|
84
|
+
export function resolveFieldValues(obj, path) {
|
|
85
|
+
return resolveFieldEntries(obj, path).map((e) => e.value);
|
|
86
|
+
}
|
|
71
87
|
/**
|
|
72
88
|
* Traverses a definition's JSON Schema once and returns a field map recording every
|
|
73
89
|
* x-telo-ref slot and every x-telo-scope slot.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the residual JSON Schema for a `variables` / `secrets` entry.
|
|
3
|
+
*
|
|
4
|
+
* For Telo.Application env-binding entries (those with an `env:` key), strips
|
|
5
|
+
* the kernel-specific wrapper keys `env` and `default` — `default` here is
|
|
6
|
+
* the *fallback host value* the kernel coerces when the env var is unset, not
|
|
7
|
+
* a JSON Schema annotation, so it must not leak into the validator.
|
|
8
|
+
*
|
|
9
|
+
* For Telo.Library entries (no `env:`), passes the entry through unchanged.
|
|
10
|
+
* Library `default:` is a standard JSON Schema annotation and stays.
|
|
11
|
+
*
|
|
12
|
+
* Single source of truth for "residual schema" referenced by both the
|
|
13
|
+
* analyzer's CEL globals normalization and the kernel's runtime env-var
|
|
14
|
+
* resolver — keeping them aligned prevents the two surfaces from drifting.
|
|
15
|
+
*/
|
|
16
|
+
export declare function residualEntrySchema(entry: Record<string, unknown> | null | undefined): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Apply `residualEntrySchema` to every entry in a `variables` / `secrets` map.
|
|
19
|
+
* Returns a property-map suitable for use as the inner schema of CEL's
|
|
20
|
+
* `variables` / `secrets` namespaces.
|
|
21
|
+
*/
|
|
22
|
+
export declare function residualEntrySchemaMap(entries: Record<string, unknown> | null | undefined): Record<string, Record<string, unknown>>;
|
|
23
|
+
//# sourceMappingURL=residual-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"residual-schema.d.ts","sourceRoot":"","sources":["../src/residual-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAChD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAClD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAWzC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the residual JSON Schema for a `variables` / `secrets` entry.
|
|
3
|
+
*
|
|
4
|
+
* For Telo.Application env-binding entries (those with an `env:` key), strips
|
|
5
|
+
* the kernel-specific wrapper keys `env` and `default` — `default` here is
|
|
6
|
+
* the *fallback host value* the kernel coerces when the env var is unset, not
|
|
7
|
+
* a JSON Schema annotation, so it must not leak into the validator.
|
|
8
|
+
*
|
|
9
|
+
* For Telo.Library entries (no `env:`), passes the entry through unchanged.
|
|
10
|
+
* Library `default:` is a standard JSON Schema annotation and stays.
|
|
11
|
+
*
|
|
12
|
+
* Single source of truth for "residual schema" referenced by both the
|
|
13
|
+
* analyzer's CEL globals normalization and the kernel's runtime env-var
|
|
14
|
+
* resolver — keeping them aligned prevents the two surfaces from drifting.
|
|
15
|
+
*/
|
|
16
|
+
export function residualEntrySchema(entry) {
|
|
17
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
18
|
+
return { type: "object", additionalProperties: true };
|
|
19
|
+
}
|
|
20
|
+
const isAppEnvBinding = "env" in entry;
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
23
|
+
if (isAppEnvBinding && (key === "env" || key === "default"))
|
|
24
|
+
continue;
|
|
25
|
+
out[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply `residualEntrySchema` to every entry in a `variables` / `secrets` map.
|
|
31
|
+
* Returns a property-map suitable for use as the inner schema of CEL's
|
|
32
|
+
* `variables` / `secrets` namespaces.
|
|
33
|
+
*/
|
|
34
|
+
export function residualEntrySchemaMap(entries) {
|
|
35
|
+
const out = {};
|
|
36
|
+
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
for (const [name, value] of Object.entries(entries)) {
|
|
40
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
41
|
+
out[name] = residualEntrySchema(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/** Diagnostics emitted on synthetic manifests (resources extracted by
|
|
4
|
+
* `normalizeInlineResources`) carry the synthetic's identity in
|
|
5
|
+
* `data.resource`, which has no YAML source. Rewrite each such diagnostic
|
|
6
|
+
* back to the chain root: walk up `metadata.xTeloOrigin` until a manifest
|
|
7
|
+
* with no origin is reached, and prepend each hop's `pathFromParent` to
|
|
8
|
+
* `data.path` so position-index lookups against the root doc resolve. */
|
|
9
|
+
export declare function rewriteSyntheticOrigins(diagnostics: AnalysisDiagnostic[], manifests: ResourceManifest[]): AnalysisDiagnostic[];
|
|
10
|
+
//# sourceMappingURL=rewrite-synthetic-origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewrite-synthetic-origins.d.ts","sourceRoot":"","sources":["../src/rewrite-synthetic-origins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsBrD;;;;;0EAK0E;AAC1E,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,kBAAkB,EAAE,EACjC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,kBAAkB,EAAE,CA0CtB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function readOrigin(manifest) {
|
|
2
|
+
if (!manifest)
|
|
3
|
+
return undefined;
|
|
4
|
+
const origin = manifest.metadata?.xTeloOrigin;
|
|
5
|
+
if (!origin ||
|
|
6
|
+
typeof origin.parentKind !== "string" ||
|
|
7
|
+
typeof origin.parentName !== "string" ||
|
|
8
|
+
typeof origin.pathFromParent !== "string") {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return origin;
|
|
12
|
+
}
|
|
13
|
+
/** Diagnostics emitted on synthetic manifests (resources extracted by
|
|
14
|
+
* `normalizeInlineResources`) carry the synthetic's identity in
|
|
15
|
+
* `data.resource`, which has no YAML source. Rewrite each such diagnostic
|
|
16
|
+
* back to the chain root: walk up `metadata.xTeloOrigin` until a manifest
|
|
17
|
+
* with no origin is reached, and prepend each hop's `pathFromParent` to
|
|
18
|
+
* `data.path` so position-index lookups against the root doc resolve. */
|
|
19
|
+
export function rewriteSyntheticOrigins(diagnostics, manifests) {
|
|
20
|
+
const byName = new Map();
|
|
21
|
+
for (const m of manifests) {
|
|
22
|
+
const name = m.metadata?.name;
|
|
23
|
+
if (typeof name === "string")
|
|
24
|
+
byName.set(name, m);
|
|
25
|
+
}
|
|
26
|
+
return diagnostics.map((d) => {
|
|
27
|
+
const data = d.data;
|
|
28
|
+
if (!data?.resource?.name)
|
|
29
|
+
return d;
|
|
30
|
+
let current = byName.get(data.resource.name);
|
|
31
|
+
let origin = readOrigin(current);
|
|
32
|
+
if (!origin)
|
|
33
|
+
return d;
|
|
34
|
+
let accumPath = typeof data.path === "string" ? data.path : "";
|
|
35
|
+
let rootKind = origin.parentKind;
|
|
36
|
+
let rootName = origin.parentName;
|
|
37
|
+
while (origin) {
|
|
38
|
+
accumPath = accumPath ? `${origin.pathFromParent}.${accumPath}` : origin.pathFromParent;
|
|
39
|
+
rootKind = origin.parentKind;
|
|
40
|
+
rootName = origin.parentName;
|
|
41
|
+
current = byName.get(origin.parentName);
|
|
42
|
+
origin = readOrigin(current);
|
|
43
|
+
}
|
|
44
|
+
const rootFilePath = current?.metadata?.source ?? data.filePath;
|
|
45
|
+
return {
|
|
46
|
+
...d,
|
|
47
|
+
data: {
|
|
48
|
+
...data,
|
|
49
|
+
resource: { kind: rootKind, name: rootName },
|
|
50
|
+
filePath: rootFilePath,
|
|
51
|
+
path: accumPath,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
|
|
2
|
+
export interface ContextResolveOpts {
|
|
3
|
+
/** When provided, used to resolve `x-telo-context-from-root` annotations against the
|
|
4
|
+
* root manifest. When omitted, defaults to `manifestItem`. */
|
|
5
|
+
manifestRoot?: Record<string, any>;
|
|
6
|
+
/** When provided alongside `aliases`, used to resolve `x-telo-context-from-ref-kind`
|
|
7
|
+
* annotations: read a kind name from a path on `manifestRoot` and return the
|
|
8
|
+
* declared definition's `<field>` schema. */
|
|
9
|
+
defs?: {
|
|
10
|
+
resolve(kind: string): Record<string, any> | undefined;
|
|
11
|
+
};
|
|
12
|
+
aliases?: {
|
|
13
|
+
resolveKind(kind: string): string | undefined;
|
|
14
|
+
};
|
|
15
|
+
allManifests?: Record<string, any>[];
|
|
16
|
+
}
|
|
2
17
|
/**
|
|
3
18
|
* Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
|
|
4
19
|
* - String: look up the named type in allManifests (Type.JsonSchema resources)
|
|
@@ -15,15 +30,54 @@ export declare function resolveTypeFieldToSchema(value: unknown, allManifests: R
|
|
|
15
30
|
*/
|
|
16
31
|
export declare function pathMatchesScope(exprPath: string, scope: string): boolean;
|
|
17
32
|
/**
|
|
18
|
-
* Resolves `x-telo-context
|
|
19
|
-
* manifest item
|
|
20
|
-
* the result as named properties into the annotated node (locking additionalProperties: false).
|
|
33
|
+
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
34
|
+
* manifest item (per-scope) and the manifest root.
|
|
21
35
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
36
|
+
* Annotation forms:
|
|
37
|
+
*
|
|
38
|
+
* - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
|
|
39
|
+
* value as a **property map** (keys → sub-schemas) that is merged into the
|
|
40
|
+
* annotated node's properties. Used for HTTP-style scopes where the navigated
|
|
41
|
+
* value is itself a map of variable names.
|
|
42
|
+
*
|
|
43
|
+
* Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
|
|
44
|
+
* (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
|
|
45
|
+
* of the context node.
|
|
46
|
+
*
|
|
47
|
+
* - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
|
|
48
|
+
* annotated node's schema with the resolved value. Used on individual property
|
|
49
|
+
* schemas (e.g. `properties.self`) where the resolved value is a single variable's
|
|
50
|
+
* full schema, not a property map.
|
|
51
|
+
*
|
|
52
|
+
* Example: `properties.self.x-telo-context-from-root: "schema"` reads
|
|
53
|
+
* `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
|
|
54
|
+
*
|
|
55
|
+
* - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
|
|
56
|
+
* resolves it via the definition registry, and returns that kind's `<field>` schema
|
|
57
|
+
* (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
|
|
58
|
+
* target's declared output shape.
|
|
59
|
+
*
|
|
60
|
+
* Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
|
|
61
|
+
*
|
|
62
|
+
* Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
|
|
63
|
+
* `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
|
|
64
|
+
* and returns the `outputType` schema.
|
|
65
|
+
*
|
|
66
|
+
* Accepts either a single string or an array of strings. With an array, paths
|
|
67
|
+
* are tried in order and the first one that resolves to a usable schema wins —
|
|
68
|
+
* used by `result:` to find its dispatch target under whichever entry-point
|
|
69
|
+
* field (`provide:` or `invoke:`) the definition declares.
|
|
70
|
+
*
|
|
71
|
+
* - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
|
|
72
|
+
* `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
|
|
73
|
+
*
|
|
74
|
+
* **Fallback chain.** When both `x-telo-context-from-root` and
|
|
75
|
+
* `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
|
|
76
|
+
* `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
|
|
77
|
+
* This lets a definition declare typing from its own field with a sibling-kind fallback
|
|
78
|
+
* (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
|
|
25
79
|
*/
|
|
26
|
-
export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>,
|
|
80
|
+
export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>, opts?: ContextResolveOpts | Record<string, any>[]): Record<string, any>;
|
|
27
81
|
/**
|
|
28
82
|
* Extracts the concrete manifest array item for a given expression path + scope.
|
|
29
83
|
* e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
|
|
@@ -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;;;;;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
|
|
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"}
|
|
@@ -61,17 +61,61 @@ export function pathMatchesScope(exprPath, scope) {
|
|
|
61
61
|
return remaining === "" || remaining[0] === "." || remaining[0] === "[";
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Resolves `x-telo-context
|
|
65
|
-
* manifest item
|
|
66
|
-
* the result as named properties into the annotated node (locking additionalProperties: false).
|
|
64
|
+
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
65
|
+
* manifest item (per-scope) and the manifest root.
|
|
67
66
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `
|
|
67
|
+
* Annotation forms:
|
|
68
|
+
*
|
|
69
|
+
* - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
|
|
70
|
+
* value as a **property map** (keys → sub-schemas) that is merged into the
|
|
71
|
+
* annotated node's properties. Used for HTTP-style scopes where the navigated
|
|
72
|
+
* value is itself a map of variable names.
|
|
73
|
+
*
|
|
74
|
+
* Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
|
|
75
|
+
* (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
|
|
76
|
+
* of the context node.
|
|
77
|
+
*
|
|
78
|
+
* - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
|
|
79
|
+
* annotated node's schema with the resolved value. Used on individual property
|
|
80
|
+
* schemas (e.g. `properties.self`) where the resolved value is a single variable's
|
|
81
|
+
* full schema, not a property map.
|
|
82
|
+
*
|
|
83
|
+
* Example: `properties.self.x-telo-context-from-root: "schema"` reads
|
|
84
|
+
* `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
|
|
85
|
+
*
|
|
86
|
+
* - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
|
|
87
|
+
* resolves it via the definition registry, and returns that kind's `<field>` schema
|
|
88
|
+
* (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
|
|
89
|
+
* target's declared output shape.
|
|
90
|
+
*
|
|
91
|
+
* Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
|
|
92
|
+
*
|
|
93
|
+
* Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
|
|
94
|
+
* `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
|
|
95
|
+
* and returns the `outputType` schema.
|
|
96
|
+
*
|
|
97
|
+
* Accepts either a single string or an array of strings. With an array, paths
|
|
98
|
+
* are tried in order and the first one that resolves to a usable schema wins —
|
|
99
|
+
* used by `result:` to find its dispatch target under whichever entry-point
|
|
100
|
+
* field (`provide:` or `invoke:`) the definition declares.
|
|
101
|
+
*
|
|
102
|
+
* - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
|
|
103
|
+
* `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
|
|
104
|
+
*
|
|
105
|
+
* **Fallback chain.** When both `x-telo-context-from-root` and
|
|
106
|
+
* `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
|
|
107
|
+
* `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
|
|
108
|
+
* This lets a definition declare typing from its own field with a sibling-kind fallback
|
|
109
|
+
* (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
|
|
71
110
|
*/
|
|
72
|
-
export function resolveContextAnnotations(schema, manifestItem,
|
|
111
|
+
export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
73
112
|
if (!schema || typeof schema !== "object")
|
|
74
113
|
return schema;
|
|
114
|
+
// Back-compat: third positional arg used to be `allManifests: Record<string, any>[]`.
|
|
115
|
+
const normalizedOpts = Array.isArray(opts)
|
|
116
|
+
? { allManifests: opts }
|
|
117
|
+
: (opts ?? {});
|
|
118
|
+
const { manifestRoot = manifestItem, defs, aliases, allManifests } = normalizedOpts;
|
|
75
119
|
const from = schema["x-telo-context-from"];
|
|
76
120
|
if (from) {
|
|
77
121
|
const resolved = navigatePath(manifestItem, from.split("/"));
|
|
@@ -82,6 +126,44 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
|
|
|
82
126
|
additionalProperties: false,
|
|
83
127
|
};
|
|
84
128
|
}
|
|
129
|
+
const fromRoot = schema["x-telo-context-from-root"];
|
|
130
|
+
const fromRefKindRaw = schema["x-telo-context-from-ref-kind"];
|
|
131
|
+
const fromRefKinds = fromRefKindRaw == null
|
|
132
|
+
? []
|
|
133
|
+
: Array.isArray(fromRefKindRaw)
|
|
134
|
+
? fromRefKindRaw
|
|
135
|
+
: [fromRefKindRaw];
|
|
136
|
+
if (fromRoot || fromRefKinds.length > 0) {
|
|
137
|
+
if (fromRoot) {
|
|
138
|
+
const resolved = navigatePath(manifestRoot, fromRoot.split("/"));
|
|
139
|
+
if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
140
|
+
return resolved;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (defs) {
|
|
144
|
+
for (const fromRefKind of fromRefKinds) {
|
|
145
|
+
const hashIdx = fromRefKind.indexOf("#");
|
|
146
|
+
if (hashIdx <= 0)
|
|
147
|
+
continue;
|
|
148
|
+
const refPath = fromRefKind.slice(0, hashIdx);
|
|
149
|
+
const field = fromRefKind.slice(hashIdx + 1);
|
|
150
|
+
const kindValue = navigatePath(manifestRoot, refPath.split("/"));
|
|
151
|
+
if (typeof kindValue !== "string" || kindValue.length === 0)
|
|
152
|
+
continue;
|
|
153
|
+
const canonical = aliases?.resolveKind(kindValue) ?? kindValue;
|
|
154
|
+
const def = defs.resolve(canonical);
|
|
155
|
+
const typeField = def
|
|
156
|
+
? def[field]
|
|
157
|
+
: undefined;
|
|
158
|
+
const resolved = resolveTypeFieldToSchema(typeField, allManifests ?? []);
|
|
159
|
+
if (resolved && typeof resolved === "object") {
|
|
160
|
+
return resolved;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Open fallback so unresolved types never produce false-positive CEL diagnostics.
|
|
165
|
+
return { type: "object", additionalProperties: true };
|
|
166
|
+
}
|
|
85
167
|
const refFrom = schema["x-telo-context-ref-from"];
|
|
86
168
|
if (refFrom && allManifests) {
|
|
87
169
|
const slashIdx = refFrom.indexOf("/");
|
|
@@ -107,7 +189,7 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
|
|
|
107
189
|
if (schema.properties) {
|
|
108
190
|
const props = {};
|
|
109
191
|
for (const [k, v] of Object.entries(schema.properties)) {
|
|
110
|
-
props[k] = resolveContextAnnotations(v, manifestItem,
|
|
192
|
+
props[k] = resolveContextAnnotations(v, manifestItem, normalizedOpts);
|
|
111
193
|
}
|
|
112
194
|
return { ...schema, properties: props };
|
|
113
195
|
}
|