@telorun/analyzer 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -17
- package/dist/alias-resolver.d.ts +7 -0
- package/dist/alias-resolver.d.ts.map +1 -1
- package/dist/alias-resolver.js +14 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +59 -15
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +10 -9
- package/dist/cel-environment.d.ts +8 -0
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +48 -0
- package/dist/flatten-for-analyzer.d.ts +52 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +192 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/resolve-ref-sentinels.d.ts +22 -7
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +46 -136
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +28 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +5 -0
- package/dist/validate-reference-forms.d.ts +28 -0
- package/dist/validate-reference-forms.d.ts.map +1 -0
- package/dist/validate-reference-forms.js +91 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +4 -37
- package/package.json +2 -2
- package/src/alias-resolver.ts +14 -0
- package/src/analyzer.ts +69 -19
- package/src/builtins.ts +10 -9
- package/src/cel-environment.ts +57 -0
- package/src/flatten-for-analyzer.ts +217 -4
- package/src/index.ts +7 -0
- package/src/resolve-ref-sentinels.ts +39 -133
- package/src/schema-compat.ts +27 -8
- package/src/validate-cel-context.ts +5 -0
- package/src/validate-reference-forms.ts +110 -0
- package/src/validate-references.ts +4 -39
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { isModuleKind } from "./module-kinds.js";
|
|
2
|
+
/** Parse a single dotted export entry: `Alias.Name` → `{name: "Name", alias: "Alias"}`,
|
|
3
|
+
* bare `Name` → `{name: "Name"}`. The single grammar for `exports.resources` and
|
|
4
|
+
* `exports.kinds`, shared by the kernel's import controller and the analyzer/editor so
|
|
5
|
+
* the dotted-name split can't drift. A leading dot (`.Name`) has no alias by design —
|
|
6
|
+
* the empty prefix isn't a valid alias. */
|
|
7
|
+
export function parseExportEntry(entry) {
|
|
8
|
+
const dot = entry.indexOf(".");
|
|
9
|
+
return dot > 0 ? { name: entry.slice(dot + 1), alias: entry.slice(0, dot) } : { name: entry };
|
|
10
|
+
}
|
|
2
11
|
/** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
|
|
3
12
|
* CLI / kernel loader path) and the telo-editor's workspace projection so the
|
|
4
13
|
* two cannot drift. Given one module's stamped manifests and whether that
|
|
@@ -23,7 +32,15 @@ export function selectModuleManifestsForAnalysis(moduleManifests, isRoot) {
|
|
|
23
32
|
if (isRoot)
|
|
24
33
|
return moduleManifests;
|
|
25
34
|
const libDoc = moduleManifests.find((m) => isModuleKind(m.kind));
|
|
26
|
-
|
|
35
|
+
// An `exports.resources` entry is a bare name or a dotted `Alias.Name` (re-export). Only the
|
|
36
|
+
// export NAME matches a local instance below; re-exports are forwarded by `forwardReExports`.
|
|
37
|
+
const exportedResources = new Set();
|
|
38
|
+
for (const entry of libDoc?.exports
|
|
39
|
+
?.resources ?? []) {
|
|
40
|
+
if (typeof entry !== "string")
|
|
41
|
+
continue;
|
|
42
|
+
exportedResources.add(parseExportEntry(entry).name);
|
|
43
|
+
}
|
|
27
44
|
const out = [];
|
|
28
45
|
for (const m of moduleManifests) {
|
|
29
46
|
if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract" || m.kind === "Telo.Import") {
|
|
@@ -81,6 +98,7 @@ export function flattenForAnalyzer(graph) {
|
|
|
81
98
|
result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(targetModule), false));
|
|
82
99
|
}
|
|
83
100
|
}
|
|
101
|
+
forwardReExports(graph, result);
|
|
84
102
|
// Stamp resolved import identity on every Telo.Import in the result by
|
|
85
103
|
// reading the edge's pre-resolved name/namespace — no re-derivation from
|
|
86
104
|
// manifest metadata. The edge is keyed by (owner-file, alias) which is
|
|
@@ -105,6 +123,179 @@ export function flattenForAnalyzer(graph) {
|
|
|
105
123
|
}
|
|
106
124
|
return result;
|
|
107
125
|
}
|
|
126
|
+
/** Extract re-export specs from a library doc's `exports.resources` — the dotted `Alias.Name`
|
|
127
|
+
* entries (bare-name locals are forwarded by the BFS instead). Shared by the CLI graph path
|
|
128
|
+
* and the editor's workspace projection so the two cannot drift. */
|
|
129
|
+
export function reExportSpecsFromExports(moduleName, exportsResources) {
|
|
130
|
+
const specs = [];
|
|
131
|
+
for (const entry of exportsResources ?? []) {
|
|
132
|
+
if (typeof entry !== "string")
|
|
133
|
+
continue;
|
|
134
|
+
const { name, alias } = parseExportEntry(entry);
|
|
135
|
+
if (!alias || alias === "Self")
|
|
136
|
+
continue;
|
|
137
|
+
specs.push({ module: moduleName, alias, name });
|
|
138
|
+
}
|
|
139
|
+
return specs;
|
|
140
|
+
}
|
|
141
|
+
/** Forward re-exported instances (`exports.resources: [!ref Alias.name]`) transitively so a
|
|
142
|
+
* consumer's `!ref Consumer.name` resolves in `resolveRefSentinels` (keyed by the RE-EXPORTING
|
|
143
|
+
* module). The owning instance is already forwarded under its own module; here we emit an
|
|
144
|
+
* additional copy stamped under each re-exporting module, with an already-canonical kind. A
|
|
145
|
+
* fixpoint loop forwards chains of arbitrary depth (`app → api → domain → …`): each pass can
|
|
146
|
+
* resolve a re-export whose source was emitted in a prior pass. Graph-agnostic: `aliasToModule`
|
|
147
|
+
* maps `(module, alias)` to the imported module's name. Mutates `result` in place. */
|
|
148
|
+
export function forwardReExportManifests(result, specs, aliasToModule) {
|
|
149
|
+
// Index forwarded instances by `module\0name` (only re-export TARGETS are forwarded).
|
|
150
|
+
const forwarded = new Map();
|
|
151
|
+
for (const m of result) {
|
|
152
|
+
const meta = m.metadata;
|
|
153
|
+
if (meta?.forwardedExport && meta.module && meta.name) {
|
|
154
|
+
forwarded.set(`${meta.module}\0${meta.name}`, m);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Canonicalize an authored/forwarded kind to a scope-independent `<module>.<Kind>` using the
|
|
158
|
+
// owning module's own import aliases. Idempotent: an already-canonical kind whose prefix isn't
|
|
159
|
+
// an alias of `ownerModule` is returned unchanged, so re-exports of re-exports stay stable.
|
|
160
|
+
const canonicalKind = (kind, ownerModule) => {
|
|
161
|
+
if (kind.startsWith("Self."))
|
|
162
|
+
return `${ownerModule}.${kind.slice("Self.".length)}`;
|
|
163
|
+
const dot = kind.indexOf(".");
|
|
164
|
+
if (dot <= 0)
|
|
165
|
+
return kind;
|
|
166
|
+
const target = aliasToModule(ownerModule, kind.slice(0, dot));
|
|
167
|
+
return target ? `${target}.${kind.slice(dot + 1)}` : kind;
|
|
168
|
+
};
|
|
169
|
+
// Fixpoint — bounded by the number of specs (each can be satisfied at most once).
|
|
170
|
+
for (let pass = 0; pass <= specs.length; pass++) {
|
|
171
|
+
let added = false;
|
|
172
|
+
for (const spec of specs) {
|
|
173
|
+
const key = `${spec.module}\0${spec.name}`;
|
|
174
|
+
if (forwarded.has(key))
|
|
175
|
+
continue;
|
|
176
|
+
const sourceModule = aliasToModule(spec.module, spec.alias);
|
|
177
|
+
if (!sourceModule)
|
|
178
|
+
continue;
|
|
179
|
+
const src = forwarded.get(`${sourceModule}\0${spec.name}`);
|
|
180
|
+
if (!src)
|
|
181
|
+
continue; // source not forwarded yet — a later pass may satisfy it
|
|
182
|
+
const kind = canonicalKind(src.kind, sourceModule);
|
|
183
|
+
const manifest = {
|
|
184
|
+
...src,
|
|
185
|
+
kind,
|
|
186
|
+
metadata: {
|
|
187
|
+
...src.metadata,
|
|
188
|
+
name: spec.name,
|
|
189
|
+
module: spec.module,
|
|
190
|
+
forwardedExport: true,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
result.push(manifest);
|
|
194
|
+
forwarded.set(key, manifest);
|
|
195
|
+
added = true;
|
|
196
|
+
}
|
|
197
|
+
if (!added)
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Resolve every library's `exports.kinds` to a per-module map `suffix → canonical
|
|
202
|
+
* <owningModule>.<Kind>`, following re-exports (`Alias.Kind`) transitively via a fixpoint.
|
|
203
|
+
* `modules` lists each library's name + its raw `exports.kinds`; `aliasToModule(module, alias)`
|
|
204
|
+
* maps one of that module's import aliases to the imported module's name. Graph-agnostic —
|
|
205
|
+
* shared by the CLI graph path and the editor's workspace projection. */
|
|
206
|
+
export function resolveExportedKinds(modules, aliasToModule) {
|
|
207
|
+
const out = new Map();
|
|
208
|
+
const tableFor = (m) => {
|
|
209
|
+
let t = out.get(m);
|
|
210
|
+
if (!t)
|
|
211
|
+
out.set(m, (t = new Map()));
|
|
212
|
+
return t;
|
|
213
|
+
};
|
|
214
|
+
for (let pass = 0; pass <= modules.length; pass++) {
|
|
215
|
+
let changed = false;
|
|
216
|
+
for (const { module, exportsKinds } of modules) {
|
|
217
|
+
const table = tableFor(module);
|
|
218
|
+
for (const entry of exportsKinds) {
|
|
219
|
+
const { name: suffix, alias } = parseExportEntry(entry);
|
|
220
|
+
if (table.has(suffix))
|
|
221
|
+
continue;
|
|
222
|
+
if (!alias) {
|
|
223
|
+
table.set(suffix, `${module}.${suffix}`);
|
|
224
|
+
changed = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const source = aliasToModule(module, alias);
|
|
228
|
+
const canonical = source ? out.get(source)?.get(suffix) : undefined;
|
|
229
|
+
if (canonical) {
|
|
230
|
+
table.set(suffix, canonical);
|
|
231
|
+
changed = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!changed)
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
/** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
|
|
241
|
+
* target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
|
|
242
|
+
* that point at a module OTHER than the import's own target are stamped (genuine re-exports;
|
|
243
|
+
* a locally-defined kind resolves through the normal alias path). Stamped on `metadata` (which
|
|
244
|
+
* permits additional properties, like `resolvedModuleName`) since the `Telo.Import` schema
|
|
245
|
+
* forbids extra top-level fields. Shared by both paths. */
|
|
246
|
+
export function stampReExportedKinds(imports, exportedKinds) {
|
|
247
|
+
for (const { manifest, targetModule } of imports) {
|
|
248
|
+
const table = exportedKinds.get(targetModule);
|
|
249
|
+
if (!table)
|
|
250
|
+
continue;
|
|
251
|
+
const reExported = {};
|
|
252
|
+
for (const [suffix, canonical] of table) {
|
|
253
|
+
if (canonical !== `${targetModule}.${suffix}`)
|
|
254
|
+
reExported[suffix] = canonical;
|
|
255
|
+
}
|
|
256
|
+
if (Object.keys(reExported).length === 0)
|
|
257
|
+
continue;
|
|
258
|
+
manifest.metadata.reExportedKinds = reExported;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** CLI/kernel adapter: collect re-export specs + alias map from a LoadedGraph. */
|
|
262
|
+
function forwardReExports(graph, result) {
|
|
263
|
+
const ownerSourceOf = new Map();
|
|
264
|
+
const specs = [];
|
|
265
|
+
const kindModules = [];
|
|
266
|
+
for (const [source, mod] of graph.modules) {
|
|
267
|
+
if (source === graph.rootSource)
|
|
268
|
+
continue; // root is an Application — no exports
|
|
269
|
+
const libDoc = mod.owner.manifests.find((m) => m && isModuleKind(m.kind));
|
|
270
|
+
const moduleName = libDoc?.metadata?.name;
|
|
271
|
+
if (!libDoc || !moduleName)
|
|
272
|
+
continue;
|
|
273
|
+
ownerSourceOf.set(moduleName, mod.owner.source);
|
|
274
|
+
specs.push(...reExportSpecsFromExports(moduleName, libDoc.exports?.resources));
|
|
275
|
+
kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds ?? [] });
|
|
276
|
+
}
|
|
277
|
+
const aliasToModule = (module, alias) => {
|
|
278
|
+
const ownerSource = ownerSourceOf.get(module);
|
|
279
|
+
return ownerSource
|
|
280
|
+
? (graph.importEdges.get(ownerSource)?.get(alias)?.targetModuleName ?? undefined)
|
|
281
|
+
: undefined;
|
|
282
|
+
};
|
|
283
|
+
forwardReExportManifests(result, specs, aliasToModule);
|
|
284
|
+
// Resolve every library's re-exported kinds and stamp them onto the consumer-facing
|
|
285
|
+
// Telo.Import manifests so the analyzer can register the re-export mappings.
|
|
286
|
+
const exportedKinds = resolveExportedKinds(kindModules, aliasToModule);
|
|
287
|
+
const imports = [];
|
|
288
|
+
for (const m of result) {
|
|
289
|
+
if (m.kind !== "Telo.Import")
|
|
290
|
+
continue;
|
|
291
|
+
const owner = m.metadata?.source;
|
|
292
|
+
const alias = m.metadata?.name;
|
|
293
|
+
const target = owner && alias ? graph.importEdges.get(owner)?.get(alias)?.targetModuleName : undefined;
|
|
294
|
+
if (target)
|
|
295
|
+
imports.push({ manifest: m, targetModule: target });
|
|
296
|
+
}
|
|
297
|
+
stampReExportedKinds(imports, exportedKinds);
|
|
298
|
+
}
|
|
108
299
|
/** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
|
|
109
300
|
* with `metadata.module` stamped on non-module docs. The kernel's runtime
|
|
110
301
|
* entry load uses this to convert a `Loader.loadModule` result into the
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { AnalysisRegistry } from "./analysis-registry.js";
|
|
|
2
2
|
export type { RefFieldInfo } from "./analysis-registry.js";
|
|
3
3
|
export { StaticAnalyzer } from "./analyzer.js";
|
|
4
4
|
export type { GraphLoadError, ImportEdge, LoadedFile, LoadedGraph, LoadedModule, ParseError, } from "./loaded-types.js";
|
|
5
|
-
export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
|
|
5
|
+
export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
|
|
6
6
|
export { visitManifest } from "./manifest-visitor.js";
|
|
7
7
|
export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
|
|
8
8
|
export { Loader } from "./manifest-loader.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,gCAAgC,EAChC,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { AnalysisRegistry } from "./analysis-registry.js";
|
|
2
2
|
export { StaticAnalyzer } from "./analyzer.js";
|
|
3
|
-
export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
|
|
3
|
+
export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, } from "./flatten-for-analyzer.js";
|
|
4
4
|
export { visitManifest } from "./manifest-visitor.js";
|
|
5
5
|
export { Loader } from "./manifest-loader.js";
|
|
6
6
|
export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
-
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
|
|
5
|
+
* to `{kind, name}` (local) or `{kind, name, alias}` (cross-module), in place.
|
|
6
|
+
*
|
|
7
|
+
* The walk is value-tree-driven, not field-map-driven: a `!ref` tag is an
|
|
8
|
+
* *explicit* reference marker, so any sentinel found anywhere is unambiguously a
|
|
9
|
+
* reference and is resolved. This reaches sites the field map intentionally does
|
|
10
|
+
* not descend — notably `Run.Sequence` step `invoke`s (behind a local `$ref`)
|
|
11
|
+
* and references nested inside inline definitions — so every downstream consumer
|
|
12
|
+
* (Phase-5 injection, the runtime controllers, the analyzer's step-context and
|
|
13
|
+
* dependency passes) sees the uniform `{kind, name}` shape regardless of where
|
|
14
|
+
* the reference was written.
|
|
15
|
+
*
|
|
16
|
+
* Resolving a sentinel here does NOT cause Phase-5 injection: that pass is
|
|
17
|
+
* driven by the field map, which still excludes step `invoke`s, so a resolved
|
|
18
|
+
* step invoke stays `{kind, name}` and is dispatched through
|
|
19
|
+
* `executeInvokeStep` (preserving `<Kind>.<Name>.Invoked` events) rather than
|
|
20
|
+
* being replaced with a live instance.
|
|
8
21
|
*
|
|
9
22
|
* Reference grammar — the tag's source string is split on the FIRST dot:
|
|
10
23
|
* - `!ref writeLine` → local resource `writeLine`
|
|
@@ -14,12 +27,14 @@ import type { DefinitionRegistry } from "./definition-registry.js";
|
|
|
14
27
|
*
|
|
15
28
|
* Aliases are PascalCase identifiers without dots and resource names carry no dots
|
|
16
29
|
* (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
|
|
17
|
-
* name doesn't resolve
|
|
18
|
-
*
|
|
30
|
+
* name doesn't resolve (e.g. a scope-local target, or a cross-module reference in
|
|
31
|
+
* partial single-file analysis), the sentinel is left in place — the runtime
|
|
32
|
+
* resolves scope-local names on demand, and `validateReferences` emits the
|
|
33
|
+
* `UNRESOLVED_REFERENCE` diagnostic for genuine misses.
|
|
19
34
|
*
|
|
20
35
|
* Forwarded foreign resources (an imported library's exported instances, carrying a
|
|
21
36
|
* `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
|
|
22
37
|
* re-walked as sources here, since their own ref slots belong to their own module scope.
|
|
23
38
|
*/
|
|
24
|
-
export declare function resolveRefSentinels(resources: ResourceManifest[],
|
|
39
|
+
export declare function resolveRefSentinels(resources: ResourceManifest[], aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[]): void;
|
|
25
40
|
//# sourceMappingURL=resolve-ref-sentinels.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.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-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,CAgFN"}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
-
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
1
|
+
import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
|
|
3
2
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
|
|
5
|
+
* to `{kind, name}` (local) or `{kind, name, alias}` (cross-module), in place.
|
|
6
|
+
*
|
|
7
|
+
* The walk is value-tree-driven, not field-map-driven: a `!ref` tag is an
|
|
8
|
+
* *explicit* reference marker, so any sentinel found anywhere is unambiguously a
|
|
9
|
+
* reference and is resolved. This reaches sites the field map intentionally does
|
|
10
|
+
* not descend — notably `Run.Sequence` step `invoke`s (behind a local `$ref`)
|
|
11
|
+
* and references nested inside inline definitions — so every downstream consumer
|
|
12
|
+
* (Phase-5 injection, the runtime controllers, the analyzer's step-context and
|
|
13
|
+
* dependency passes) sees the uniform `{kind, name}` shape regardless of where
|
|
14
|
+
* the reference was written.
|
|
15
|
+
*
|
|
16
|
+
* Resolving a sentinel here does NOT cause Phase-5 injection: that pass is
|
|
17
|
+
* driven by the field map, which still excludes step `invoke`s, so a resolved
|
|
18
|
+
* step invoke stays `{kind, name}` and is dispatched through
|
|
19
|
+
* `executeInvokeStep` (preserving `<Kind>.<Name>.Invoked` events) rather than
|
|
20
|
+
* being replaced with a live instance.
|
|
8
21
|
*
|
|
9
22
|
* Reference grammar — the tag's source string is split on the FIRST dot:
|
|
10
23
|
* - `!ref writeLine` → local resource `writeLine`
|
|
@@ -14,14 +27,16 @@ import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
|
14
27
|
*
|
|
15
28
|
* Aliases are PascalCase identifiers without dots and resource names carry no dots
|
|
16
29
|
* (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
|
|
17
|
-
* name doesn't resolve
|
|
18
|
-
*
|
|
30
|
+
* name doesn't resolve (e.g. a scope-local target, or a cross-module reference in
|
|
31
|
+
* partial single-file analysis), the sentinel is left in place — the runtime
|
|
32
|
+
* resolves scope-local names on demand, and `validateReferences` emits the
|
|
33
|
+
* `UNRESOLVED_REFERENCE` diagnostic for genuine misses.
|
|
19
34
|
*
|
|
20
35
|
* Forwarded foreign resources (an imported library's exported instances, carrying a
|
|
21
36
|
* `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
|
|
22
37
|
* re-walked as sources here, since their own ref slots belong to their own module scope.
|
|
23
38
|
*/
|
|
24
|
-
export function resolveRefSentinels(resources,
|
|
39
|
+
export function resolveRefSentinels(resources, aliases, aliasesByModule,
|
|
25
40
|
// Extra foreign resources used only as cross-module resolution TARGETS (not mutated, not
|
|
26
41
|
// walked as sources). The kernel passes the analyzer-flattened set here so the runtime
|
|
27
42
|
// pass — which loads the entry module only — can still resolve `!ref Alias.name` against
|
|
@@ -81,138 +96,33 @@ crossModuleTargets = []) {
|
|
|
81
96
|
}
|
|
82
97
|
return undefined;
|
|
83
98
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
: registry.getFieldMapForKind(r.kind, aliases);
|
|
90
|
-
if (!fieldMap)
|
|
91
|
-
return;
|
|
92
|
-
for (const [fieldPath, entry] of fieldMap) {
|
|
93
|
-
const parts = fieldPath.split(".");
|
|
94
|
-
if (isRefEntry(entry)) {
|
|
95
|
-
descend(r, parts, resolveTarget);
|
|
96
|
-
}
|
|
97
|
-
else if (isScopeEntry(entry)) {
|
|
98
|
-
// x-telo-scope resources (e.g. a Run.Sequence `with` server) carry their own ref
|
|
99
|
-
// slots. The top-level walk skips scope contents, so recurse so a scoped resource's
|
|
100
|
-
// `!ref` (e.g. an Http.Server mount) is canonicalized to {kind, name} like any other.
|
|
101
|
-
forEachScopeResource(r, parts, processResource);
|
|
102
|
-
}
|
|
99
|
+
// Resolve every `!ref` sentinel in the tree; leave opaque tagged / precompiled
|
|
100
|
+
// nodes (e.g. `!cel`) untouched and don't descend into them.
|
|
101
|
+
const walk = (value) => {
|
|
102
|
+
if (isRefSentinel(value)) {
|
|
103
|
+
return resolveTarget(value.source) ?? value;
|
|
103
104
|
}
|
|
105
|
+
if (value === null || typeof value !== "object")
|
|
106
|
+
return value;
|
|
107
|
+
if (isTaggedSentinel(value))
|
|
108
|
+
return value;
|
|
109
|
+
if (value.__compiled)
|
|
110
|
+
return value;
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
for (let i = 0; i < value.length; i++)
|
|
113
|
+
value[i] = walk(value[i]);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
const obj = value;
|
|
117
|
+
for (const key of Object.keys(obj))
|
|
118
|
+
obj[key] = walk(obj[key]);
|
|
119
|
+
return value;
|
|
104
120
|
};
|
|
105
121
|
for (const r of resources) {
|
|
106
122
|
if (isForeign(r))
|
|
107
123
|
continue;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Navigates `obj` along the scope field path (dot notation, `[]` = array items) and
|
|
113
|
-
* invokes `cb` on every resource-like object found — any value carrying a `kind` string.
|
|
114
|
-
*
|
|
115
|
-
* Two-phase design:
|
|
116
|
-
*
|
|
117
|
-
* Phase 1 — path-walk: steps through each `parts` segment. `[]`-suffixed parts spread
|
|
118
|
-
* the array into individual elements so `current` always ends up holding scalars or
|
|
119
|
-
* plain objects, never intermediate arrays. Non-`[]` parts push the value as-is.
|
|
120
|
-
*
|
|
121
|
-
* Phase 2 — terminal visit: after the walk, `current` contains the values at the end
|
|
122
|
-
* of the path. These are always scalars or plain objects because of the `[]` spreading
|
|
123
|
-
* above, EXCEPT when a scope field is typed as an array in the schema but the path
|
|
124
|
-
* was authored WITHOUT a `[]` suffix. The `visit` function handles that case by
|
|
125
|
-
* recursing one level into arrays so `cb` is always called on resource objects, not
|
|
126
|
-
* on their container.
|
|
127
|
-
*/
|
|
128
|
-
function forEachScopeResource(obj, parts, cb) {
|
|
129
|
-
let current = [obj];
|
|
130
|
-
for (const part of parts) {
|
|
131
|
-
const isArr = part.endsWith("[]");
|
|
132
|
-
const key = isArr ? part.slice(0, -2) : part;
|
|
133
|
-
const next = [];
|
|
134
|
-
for (const node of current) {
|
|
135
|
-
if (!node || typeof node !== "object")
|
|
136
|
-
continue;
|
|
137
|
-
const val = node[key];
|
|
138
|
-
if (val == null)
|
|
139
|
-
continue;
|
|
140
|
-
if (isArr && Array.isArray(val))
|
|
141
|
-
next.push(...val);
|
|
142
|
-
else if (!isArr)
|
|
143
|
-
next.push(val);
|
|
144
|
-
}
|
|
145
|
-
current = next;
|
|
146
|
-
}
|
|
147
|
-
const visit = (node) => {
|
|
148
|
-
if (Array.isArray(node)) {
|
|
149
|
-
for (const elem of node)
|
|
150
|
-
visit(elem);
|
|
151
|
-
}
|
|
152
|
-
else if (node && typeof node === "object" && typeof node.kind === "string") {
|
|
153
|
-
cb(node);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
for (const node of current)
|
|
157
|
-
visit(node);
|
|
158
|
-
}
|
|
159
|
-
/** Walks `obj` along `fieldPath` parts (dot notation with `[]` for arrays and `{}` for
|
|
160
|
-
* additionalProperties-typed maps) and replaces any `!ref` sentinel at the terminal slot
|
|
161
|
-
* with its resolved `{kind, name, alias?}`. Mutates the parent container in place. */
|
|
162
|
-
function descend(obj, parts, resolve) {
|
|
163
|
-
if (obj == null || typeof obj !== "object" || parts.length === 0)
|
|
164
|
-
return;
|
|
165
|
-
const [head, ...rest] = parts;
|
|
166
|
-
if (head === "{}") {
|
|
167
|
-
const container = obj;
|
|
168
|
-
for (const key of Object.keys(container)) {
|
|
169
|
-
const child = container[key];
|
|
170
|
-
if (rest.length === 0) {
|
|
171
|
-
if (isRefSentinel(child)) {
|
|
172
|
-
const target = resolve(child.source);
|
|
173
|
-
if (target)
|
|
174
|
-
container[key] = target;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
descend(child, rest, resolve);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const isArr = head.endsWith("[]");
|
|
184
|
-
const key = isArr ? head.slice(0, -2) : head;
|
|
185
|
-
const container = obj;
|
|
186
|
-
const val = container[key];
|
|
187
|
-
if (val == null)
|
|
188
|
-
return;
|
|
189
|
-
if (isArr) {
|
|
190
|
-
if (!Array.isArray(val))
|
|
191
|
-
return;
|
|
192
|
-
for (let i = 0; i < val.length; i++) {
|
|
193
|
-
if (rest.length === 0) {
|
|
194
|
-
const elem = val[i];
|
|
195
|
-
if (isRefSentinel(elem)) {
|
|
196
|
-
const target = resolve(elem.source);
|
|
197
|
-
if (target)
|
|
198
|
-
val[i] = target;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
descend(val[i], rest, resolve);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
if (rest.length === 0) {
|
|
208
|
-
if (isRefSentinel(val)) {
|
|
209
|
-
const target = resolve(val.source);
|
|
210
|
-
if (target)
|
|
211
|
-
container[key] = target;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
descend(val, rest, resolve);
|
|
216
|
-
}
|
|
124
|
+
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
125
|
+
continue;
|
|
126
|
+
walk(r);
|
|
217
127
|
}
|
|
218
128
|
}
|
|
@@ -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;
|
|
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;AAaD,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CA2B/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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import AjvModule from "ajv";
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
|
-
import { isRefSentinel, isTaggedSentinel, ManifestRootSchema } from "@telorun/templating";
|
|
3
|
+
import { isRefSentinel, isTaggedSentinel, ManifestRootSchema, normalizeRefSlots } from "@telorun/templating";
|
|
4
4
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
5
5
|
/** Creates a configured AJV instance (allErrors, strict: false, with formats).
|
|
6
6
|
* Also registers the kernel manifest root schema under `telo://manifest` so
|
|
@@ -100,21 +100,41 @@ function ajvErrorToPath(err) {
|
|
|
100
100
|
}
|
|
101
101
|
return result;
|
|
102
102
|
}
|
|
103
|
+
/** Does `schema` compile as-authored? Used to tell a malformed module schema
|
|
104
|
+
* (the author's problem) apart from a fault we introduced while normalizing it. */
|
|
105
|
+
function schemaCompiles(schema) {
|
|
106
|
+
try {
|
|
107
|
+
ajv.compile(schema);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
103
114
|
/** Validate actual data against a JSON Schema. Returns issues with path info, or empty array if valid. */
|
|
104
115
|
export function validateAgainstSchema(data, schema) {
|
|
105
116
|
let validate = compiledSchemaValidators.get(schema);
|
|
106
117
|
if (!validate) {
|
|
118
|
+
// Normalize outside the try: a fault in our own ref-slot normalization must
|
|
119
|
+
// surface, never be mistaken for the module author's schema being malformed.
|
|
120
|
+
// Drop the legacy scalar `type` an older published module may still pin on
|
|
121
|
+
// its `x-telo-ref` slots so a resolved reference object validates.
|
|
122
|
+
const normalized = normalizeRefSlots(schema);
|
|
107
123
|
try {
|
|
108
|
-
validate = ajv.compile(
|
|
109
|
-
compiledSchemaValidators.set(schema, validate);
|
|
124
|
+
validate = ajv.compile(normalized);
|
|
110
125
|
}
|
|
111
|
-
catch {
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
126
|
+
catch (err) {
|
|
127
|
+
// The normalized schema didn't compile. If the original schema is itself
|
|
128
|
+
// malformed, that is the module author's error — already surfaced once,
|
|
129
|
+
// anchored on the definition, by the analyzer's `SCHEMA_COMPILE_ERROR`
|
|
130
|
+
// pre-check (`DefinitionRegistry.schemaCompileError`); re-reporting it per
|
|
131
|
+
// resource would be noise, so skip. If the original compiles and only the
|
|
132
|
+
// normalized form fails, the fault is ours — let it throw.
|
|
133
|
+
if (schemaCompiles(schema))
|
|
134
|
+
throw err;
|
|
116
135
|
return [];
|
|
117
136
|
}
|
|
137
|
+
compiledSchemaValidators.set(schema, validate);
|
|
118
138
|
}
|
|
119
139
|
if (validate(data))
|
|
120
140
|
return [];
|
|
@@ -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,
|
|
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,CAkCjC;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"}
|
|
@@ -27,6 +27,11 @@ export function resolveTypeFieldToSchema(value, allManifests) {
|
|
|
27
27
|
if (obj.type || obj.properties) {
|
|
28
28
|
return obj;
|
|
29
29
|
}
|
|
30
|
+
// Named type reference resolved from a `!ref` → { kind, name } — resolve the
|
|
31
|
+
// named Telo.Type the same way as the bare-string form.
|
|
32
|
+
if (typeof obj.name === "string") {
|
|
33
|
+
return resolveTypeFieldToSchema(obj.name, allManifests);
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
return undefined;
|
|
32
37
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reference-form validation — the single enforcement point for "a reference is
|
|
7
|
+
* written `!ref <name>` (or `!ref <Alias>.<name>`), nothing else".
|
|
8
|
+
*
|
|
9
|
+
* Runs on the RAW manifest set, BEFORE inline-resource extraction and `!ref`
|
|
10
|
+
* sentinel resolution. That ordering is load-bearing: only at this point is an
|
|
11
|
+
* author-written value still distinguishable from the resolver's own
|
|
12
|
+
* substitution. After normalization both an author's `{kind, name}` and a
|
|
13
|
+
* resolved `!ref` are the same `{kind, name}` object, so no later pass — and no
|
|
14
|
+
* JSON Schema — can tell them apart.
|
|
15
|
+
*
|
|
16
|
+
* At every `x-telo-ref` slot the only accepted value is:
|
|
17
|
+
* - a `!ref` sentinel (or any tagged sentinel — e.g. a `${{ }}` ref passed
|
|
18
|
+
* through a template), or
|
|
19
|
+
* - an inline definition: a plain object with a `kind` and NO `name` (the
|
|
20
|
+
* extractor assigns the name), or
|
|
21
|
+
* - a `${{ }}` CEL expression string (a reference flowed through CEL).
|
|
22
|
+
*
|
|
23
|
+
* Rejected, each with an actionable diagnostic pointing at `!ref`:
|
|
24
|
+
* - the object form `{ kind, name }` (the old reference object), and
|
|
25
|
+
* - a bare string (the old name / dotted-FQN reference).
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateReferenceForms(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): AnalysisDiagnostic[];
|
|
28
|
+
//# sourceMappingURL=validate-reference-forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CA+DtB"}
|