@telorun/analyzer 0.13.0 → 0.15.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/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 +1 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +111 -5
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +66 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +17 -0
- package/dist/manifest-visitor.d.ts +15 -0
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +73 -2
- 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 +86 -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/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +108 -7
- package/package.json +5 -4
- package/src/alias-resolver.ts +9 -0
- package/src/analysis-registry.ts +1 -1
- package/src/analyzer.ts +130 -5
- package/src/builtins.ts +66 -0
- package/src/flatten-for-analyzer.ts +20 -0
- package/src/manifest-visitor.ts +91 -2
- package/src/reference-field-map.ts +14 -0
- package/src/resolve-ref-sentinels.ts +89 -44
- package/src/resolve-throws-union.ts +36 -8
- package/src/validate-references.ts +110 -8
package/dist/alias-resolver.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export declare class AliasResolver {
|
|
|
4
4
|
private readonly importAliases;
|
|
5
5
|
private readonly importedKinds;
|
|
6
6
|
registerImport(alias: string, targetModule: string, exportedKinds: string[]): void;
|
|
7
|
+
/** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
|
|
8
|
+
* Used to resolve an alias-qualified instance reference "Console.writeLine" to the
|
|
9
|
+
* forwarded resource declared in that module. The `exports.resources` gate is enforced
|
|
10
|
+
* upstream by `flattenForAnalyzer` (only exported instances are forwarded), so a name
|
|
11
|
+
* that isn't exported simply won't be found. */
|
|
12
|
+
moduleForAlias(alias: string): string | undefined;
|
|
7
13
|
/** Resolves "Http.Api" → "http-server.Api". Returns undefined if alias is unknown. */
|
|
8
14
|
resolveKind(kind: string): string | undefined;
|
|
9
15
|
hasAlias(alias: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAe7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C"}
|
|
1
|
+
{"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF;;;;qDAIiD;IACjD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAe7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C"}
|
package/dist/alias-resolver.js
CHANGED
|
@@ -9,6 +9,14 @@ export class AliasResolver {
|
|
|
9
9
|
this.importedKinds.set(alias, new Set(exportedKinds));
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
/** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
|
|
13
|
+
* Used to resolve an alias-qualified instance reference "Console.writeLine" to the
|
|
14
|
+
* forwarded resource declared in that module. The `exports.resources` gate is enforced
|
|
15
|
+
* upstream by `flattenForAnalyzer` (only exported instances are forwarded), so a name
|
|
16
|
+
* that isn't exported simply won't be found. */
|
|
17
|
+
moduleForAlias(alias) {
|
|
18
|
+
return this.importAliases.get(alias);
|
|
19
|
+
}
|
|
12
20
|
/** Resolves "Http.Api" → "http-server.Api". Returns undefined if alias is unknown. */
|
|
13
21
|
resolveKind(kind) {
|
|
14
22
|
if (!kind) {
|
|
@@ -33,6 +33,7 @@ export declare class AnalysisRegistry {
|
|
|
33
33
|
visitManifest(resources: ResourceManifest[], visitor: ManifestVisitor, opts?: {
|
|
34
34
|
skipKinds?: ReadonlySet<string>;
|
|
35
35
|
expand?: boolean;
|
|
36
|
+
discoverNestedRefs?: boolean;
|
|
36
37
|
}): void;
|
|
37
38
|
/**
|
|
38
39
|
* Returns the built-in kernel definitions. The underlying DefinitionRegistry already
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,
|
|
1
|
+
{"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,IAAI;IAQP;;;;OAIG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAM/D,QAAQ,IAAI,MAAM,EAAE;IAIpB;mEAC+D;IAC/D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC;;;gCAG4B;IAC5B,oBAAoB,IAAI,MAAM,EAAE;IAIhC;wEACoE;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;;;;gEAK4D;IAC5D,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IA+B7D,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
|
package/dist/analyzer.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare class StaticAnalyzer {
|
|
|
25
25
|
*/
|
|
26
26
|
analyze(manifests: ResourceManifest[], options?: AnalysisOptions, registry?: AnalysisRegistry): AnalysisDiagnostic[];
|
|
27
27
|
analyzeErrors(manifests: ResourceManifest[], options?: AnalysisOptions, registry?: AnalysisRegistry): AnalysisDiagnostic[];
|
|
28
|
-
normalize(manifests: ResourceManifest[], registry: AnalysisRegistry): ResourceManifest[];
|
|
28
|
+
normalize(manifests: ResourceManifest[], registry: AnalysisRegistry, crossModuleTargets?: ResourceManifest[]): ResourceManifest[];
|
|
29
29
|
prepare(manifests: ResourceManifest[], registry: AnalysisRegistry): {
|
|
30
30
|
diagnostics: AnalysisDiagnostic[];
|
|
31
31
|
order: string[] | null;
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuhB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAimBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -307,6 +307,73 @@ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases
|
|
|
307
307
|
}
|
|
308
308
|
return undefined;
|
|
309
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Collect every field annotated with `x-telo-error-context` anywhere in a
|
|
312
|
+
* definition schema (resolving local `$ref`s into `$defs`, cycle-safe), mapping
|
|
313
|
+
* the annotated field name to its declared error-shape schema. The field name
|
|
314
|
+
* is matched against CEL paths so the context applies at any nesting depth under
|
|
315
|
+
* that field — e.g. `error` inside a `catch:` nested inside another `try:`. No
|
|
316
|
+
* specific field name (or `Run.Sequence`) is hardcoded; any composer that tags
|
|
317
|
+
* its error-bearing branch fields opts in the same way.
|
|
318
|
+
*/
|
|
319
|
+
function collectErrorContextScopes(defSchema) {
|
|
320
|
+
const out = new Map();
|
|
321
|
+
if (!defSchema || typeof defSchema !== "object")
|
|
322
|
+
return out;
|
|
323
|
+
const seen = new Set();
|
|
324
|
+
const walk = (schema) => {
|
|
325
|
+
if (!schema || typeof schema !== "object" || seen.has(schema))
|
|
326
|
+
return;
|
|
327
|
+
seen.add(schema);
|
|
328
|
+
const props = schema.properties;
|
|
329
|
+
if (props) {
|
|
330
|
+
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
331
|
+
if (fieldSchema && typeof fieldSchema === "object") {
|
|
332
|
+
const errCtx = fieldSchema["x-telo-error-context"];
|
|
333
|
+
if (errCtx && typeof errCtx === "object" && !out.has(fieldName)) {
|
|
334
|
+
out.set(fieldName, errCtx);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
walk(resolveLocalRef(fieldSchema, defSchema));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (schema.items)
|
|
341
|
+
walk(resolveLocalRef(schema.items, defSchema));
|
|
342
|
+
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
343
|
+
const arr = schema[key];
|
|
344
|
+
if (Array.isArray(arr))
|
|
345
|
+
for (const sub of arr)
|
|
346
|
+
walk(resolveLocalRef(sub, defSchema));
|
|
347
|
+
}
|
|
348
|
+
if (schema.$defs && typeof schema.$defs === "object") {
|
|
349
|
+
for (const sub of Object.values(schema.$defs)) {
|
|
350
|
+
walk(sub);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
walk(defSchema);
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Return the error-context schema for a CEL `path` when the path lies within
|
|
359
|
+
* (any depth under) one of the error-bearing fields, else undefined. A path is
|
|
360
|
+
* "within" field `f` when it contains a segment `f[<index>]`. When multiple
|
|
361
|
+
* error-bearing fields match (e.g. a `finally` nested inside a `catch`), the
|
|
362
|
+
* deepest — the one whose segment appears latest in the path — wins, so the
|
|
363
|
+
* innermost branch's schema governs.
|
|
364
|
+
*/
|
|
365
|
+
function errorContextForPath(path, scopes) {
|
|
366
|
+
let best;
|
|
367
|
+
for (const [fieldName, schema] of scopes) {
|
|
368
|
+
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
369
|
+
for (const match of path.matchAll(new RegExp(`(^|\\.)${escaped}\\[\\d+\\]`, "g"))) {
|
|
370
|
+
if (best === undefined || match.index > best.index) {
|
|
371
|
+
best = { index: match.index, schema };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return best?.schema;
|
|
376
|
+
}
|
|
310
377
|
const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
|
|
311
378
|
const CEL_EXPR_RE = /\$\{\{\s*([^}]+?)\s*\}\}/;
|
|
312
379
|
/** Recursively walk `data`+`schema` together, type-checking every pure CEL template
|
|
@@ -444,9 +511,12 @@ export class StaticAnalyzer {
|
|
|
444
511
|
// through the same alias machinery as user-declared Telo.Imports —
|
|
445
512
|
// honours the library's `exports.kinds` list, no special cases.
|
|
446
513
|
if (moduleName) {
|
|
447
|
-
|
|
514
|
+
// `Self` resolves the library's own kinds UNGATED — a library may reference
|
|
515
|
+
// its own kinds regardless of `exports.kinds`, which gates importers, not
|
|
516
|
+
// internal use. This is what lets a library declare an instance of a kind it
|
|
517
|
+
// does not export (e.g. console's `writeLine`) to enforce a singleton.
|
|
448
518
|
if (rootModules.has(moduleName)) {
|
|
449
|
-
aliases.registerImport("Self", moduleName,
|
|
519
|
+
aliases.registerImport("Self", moduleName, []);
|
|
450
520
|
}
|
|
451
521
|
else {
|
|
452
522
|
let libResolver = aliasesByModule.get(moduleName);
|
|
@@ -454,7 +524,7 @@ export class StaticAnalyzer {
|
|
|
454
524
|
libResolver = new AliasResolver();
|
|
455
525
|
aliasesByModule.set(moduleName, libResolver);
|
|
456
526
|
}
|
|
457
|
-
libResolver.registerImport("Self", moduleName,
|
|
527
|
+
libResolver.registerImport("Self", moduleName, []);
|
|
458
528
|
}
|
|
459
529
|
}
|
|
460
530
|
}
|
|
@@ -626,6 +696,14 @@ export class StaticAnalyzer {
|
|
|
626
696
|
if (m.kind === "Telo.Abstract") {
|
|
627
697
|
continue;
|
|
628
698
|
}
|
|
699
|
+
// Forwarded exports (flagged by flattenForAnalyzer) are an imported library's exported
|
|
700
|
+
// instances, already validated in their own module's standalone analysis; their
|
|
701
|
+
// `kind`/CEL are authored in that module's scope (e.g. `Self.X` → that module, not the
|
|
702
|
+
// consumer). Re-validating against the consumer's scope yields false UNDEFINED_KIND /
|
|
703
|
+
// scope-mismatch errors, so skip — they participate here only as resolution targets.
|
|
704
|
+
if (m.metadata?.forwardedExport === true) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
629
707
|
const resource = { kind: m.kind, name: m.metadata?.name };
|
|
630
708
|
// Resolve kind through alias if needed; direct lookup takes priority so that
|
|
631
709
|
// aliases whose name matches the module name (the common case) work without
|
|
@@ -784,6 +862,7 @@ export class StaticAnalyzer {
|
|
|
784
862
|
// context — which require analyzer state to build — are stashed here.
|
|
785
863
|
let celStepContextSchema;
|
|
786
864
|
let celInvocationContext;
|
|
865
|
+
let celErrorScopes = new Map();
|
|
787
866
|
visitManifest(allManifests, defs, {
|
|
788
867
|
onResourceEnter: (e) => {
|
|
789
868
|
const m = e.source;
|
|
@@ -791,6 +870,7 @@ export class StaticAnalyzer {
|
|
|
791
870
|
celStepContextSchema = e.definition?.schema
|
|
792
871
|
? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases)
|
|
793
872
|
: undefined;
|
|
873
|
+
celErrorScopes = collectErrorContextScopes(e.definition?.schema);
|
|
794
874
|
},
|
|
795
875
|
onCel: (e) => {
|
|
796
876
|
const m = e.source;
|
|
@@ -808,6 +888,19 @@ export class StaticAnalyzer {
|
|
|
808
888
|
},
|
|
809
889
|
};
|
|
810
890
|
}
|
|
891
|
+
// `error` is only in scope inside an error-bearing branch (e.g. a
|
|
892
|
+
// `catch:` / `finally:`), so it's merged per-path, not resource-wide.
|
|
893
|
+
const errorSchema = celErrorScopes.size > 0 ? errorContextForPath(path, celErrorScopes) : undefined;
|
|
894
|
+
if (errorSchema) {
|
|
895
|
+
const base = matchedContext ?? { type: "object", properties: {}, additionalProperties: true };
|
|
896
|
+
matchedContext = {
|
|
897
|
+
...base,
|
|
898
|
+
properties: {
|
|
899
|
+
...(base.properties ?? {}),
|
|
900
|
+
error: errorSchema,
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
}
|
|
811
904
|
let effectiveContext = null;
|
|
812
905
|
if (matchedContext) {
|
|
813
906
|
const manifestItem = matchedScope
|
|
@@ -855,6 +948,15 @@ export class StaticAnalyzer {
|
|
|
855
948
|
data: { resource, filePath, path },
|
|
856
949
|
});
|
|
857
950
|
}
|
|
951
|
+
else if (f.code === "CEL_NULLABLE_ACCESS") {
|
|
952
|
+
diagnostics.push({
|
|
953
|
+
severity: DiagnosticSeverity.Error,
|
|
954
|
+
code: "CEL_NULLABLE_ACCESS",
|
|
955
|
+
source: SOURCE,
|
|
956
|
+
message: `${m.kind}/${resource.name}: CEL at '${path}': ${f.message}`,
|
|
957
|
+
data: { resource, filePath, path },
|
|
958
|
+
});
|
|
959
|
+
}
|
|
858
960
|
else {
|
|
859
961
|
// Unknown code from a future engine — pass the message through,
|
|
860
962
|
// tagged with a generic ENGINE_DIAGNOSTIC code so downstream
|
|
@@ -887,13 +989,17 @@ export class StaticAnalyzer {
|
|
|
887
989
|
analyzeErrors(manifests, options, registry) {
|
|
888
990
|
return this.analyze(manifests, options, registry).filter((d) => d.severity === DiagnosticSeverity.Error);
|
|
889
991
|
}
|
|
890
|
-
normalize(manifests, registry
|
|
992
|
+
normalize(manifests, registry,
|
|
993
|
+
// Forwarded foreign exports used only as cross-module resolution targets (see
|
|
994
|
+
// resolveRefSentinels). The kernel passes its analyzer-flattened set so the
|
|
995
|
+
// entry-only runtime pass can still resolve `!ref Alias.name`.
|
|
996
|
+
crossModuleTargets) {
|
|
891
997
|
const ctx = registry._context();
|
|
892
998
|
const normalized = normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
|
|
893
999
|
// Resolve !ref sentinels after normalize so both the original and
|
|
894
1000
|
// inline-extracted manifests get their refs canonicalized to
|
|
895
1001
|
// {kind, name} for the kernel that consumes this output.
|
|
896
|
-
resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
|
|
1002
|
+
resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
|
|
897
1003
|
return normalized;
|
|
898
1004
|
}
|
|
899
1005
|
prepare(manifests, registry) {
|
package/dist/builtins.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA4Z/C,CAAC"}
|
package/dist/builtins.js
CHANGED
|
@@ -232,6 +232,66 @@ export const KERNEL_BUILTINS = [
|
|
|
232
232
|
},
|
|
233
233
|
additionalProperties: true,
|
|
234
234
|
},
|
|
235
|
+
// Gated reference: run() a Runnable/Service only when the
|
|
236
|
+
// `when` CEL guard holds. Discriminated by the `ref` key. `ref`
|
|
237
|
+
// is a bare name or a resolved `!ref` (`{ kind, name }`).
|
|
238
|
+
{
|
|
239
|
+
type: "object",
|
|
240
|
+
required: ["ref"],
|
|
241
|
+
properties: {
|
|
242
|
+
ref: {
|
|
243
|
+
anyOf: [
|
|
244
|
+
{ type: "string", "x-telo-ref": "telo#Runnable" },
|
|
245
|
+
{ type: "string", "x-telo-ref": "telo#Service" },
|
|
246
|
+
{
|
|
247
|
+
type: "object",
|
|
248
|
+
required: ["kind", "name"],
|
|
249
|
+
properties: {
|
|
250
|
+
kind: { type: "string" },
|
|
251
|
+
name: { type: "string" },
|
|
252
|
+
},
|
|
253
|
+
additionalProperties: true,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
when: { type: "string" },
|
|
258
|
+
},
|
|
259
|
+
additionalProperties: false,
|
|
260
|
+
},
|
|
261
|
+
// Inline flat invoke step: invoke an Invocable / Runnable on boot
|
|
262
|
+
// with an optional `name` (for steps.<name>.result plumbing),
|
|
263
|
+
// `when` guard, and `inputs`. Discriminated by the `invoke` key.
|
|
264
|
+
// Control flow (if/while/switch/try) is not available here —
|
|
265
|
+
// reach for Run.Sequence. `invoke` is ref-only and must resolve
|
|
266
|
+
// to a `{ kind, name }` reference (a `!ref` / `{kind,name}`):
|
|
267
|
+
// requiring `name` rejects an inline `{ kind }` definition (no
|
|
268
|
+
// name) at analysis instead of failing at boot with an undefined
|
|
269
|
+
// resource name. The Invocable/Runnable kind set mirrors
|
|
270
|
+
// Run.Sequence invoke steps.
|
|
271
|
+
{
|
|
272
|
+
type: "object",
|
|
273
|
+
required: ["invoke"],
|
|
274
|
+
properties: {
|
|
275
|
+
name: { type: "string" },
|
|
276
|
+
invoke: {
|
|
277
|
+
"x-telo-topology-role": "invoke",
|
|
278
|
+
type: "object",
|
|
279
|
+
required: ["kind", "name"],
|
|
280
|
+
properties: {
|
|
281
|
+
kind: { type: "string" },
|
|
282
|
+
name: { type: "string" },
|
|
283
|
+
},
|
|
284
|
+
additionalProperties: true,
|
|
285
|
+
anyOf: [
|
|
286
|
+
{ "x-telo-ref": "telo#Invocable" },
|
|
287
|
+
{ "x-telo-ref": "telo#Runnable" },
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
inputs: { type: "object", additionalProperties: true },
|
|
291
|
+
when: { type: "string" },
|
|
292
|
+
},
|
|
293
|
+
additionalProperties: false,
|
|
294
|
+
},
|
|
235
295
|
],
|
|
236
296
|
},
|
|
237
297
|
},
|
|
@@ -336,6 +396,12 @@ export const KERNEL_BUILTINS = [
|
|
|
336
396
|
type: "object",
|
|
337
397
|
properties: {
|
|
338
398
|
kinds: { type: "array", items: { type: "string" } },
|
|
399
|
+
// `variables` / `secrets` are reserved on the resources.<Alias> value-flow
|
|
400
|
+
// surface, so a library may not export instances under those names.
|
|
401
|
+
resources: {
|
|
402
|
+
type: "array",
|
|
403
|
+
items: { type: "string", not: { enum: ["variables", "secrets"] } },
|
|
404
|
+
},
|
|
339
405
|
},
|
|
340
406
|
additionalProperties: true,
|
|
341
407
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;6EAkB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;6EAkB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CA6EzE;AAED;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
|
|
@@ -38,12 +38,29 @@ export function flattenForAnalyzer(graph) {
|
|
|
38
38
|
if (!targetModule)
|
|
39
39
|
continue;
|
|
40
40
|
const stamped = collectModuleManifests(targetModule);
|
|
41
|
+
const libDoc = stamped.find((m) => isModuleKind(m.kind));
|
|
42
|
+
const exportedResources = new Set(libDoc?.exports?.resources ?? []);
|
|
41
43
|
for (const m of stamped) {
|
|
42
44
|
if (m.kind === "Telo.Definition" ||
|
|
43
45
|
m.kind === "Telo.Abstract" ||
|
|
44
46
|
m.kind === "Telo.Import") {
|
|
45
47
|
result.push(m);
|
|
46
48
|
}
|
|
49
|
+
else if (!isModuleKind(m.kind) &&
|
|
50
|
+
typeof m.metadata?.name === "string" &&
|
|
51
|
+
exportedResources.has(m.metadata.name)) {
|
|
52
|
+
// Forward instances listed in the library's `exports.resources` so the
|
|
53
|
+
// consumer's analyzer can resolve, gate, kind-check, and draw topology edges
|
|
54
|
+
// for cross-module `!ref Alias.name`. The gate IS this forwarding — only
|
|
55
|
+
// exported names cross the boundary. `metadata.forwardedExport` marks them as
|
|
56
|
+
// cross-module resolution targets only (keyed by `metadata.module`), so ref
|
|
57
|
+
// resolution / validation treats them as targets, never as local sources to
|
|
58
|
+
// re-validate or walk.
|
|
59
|
+
result.push({
|
|
60
|
+
...m,
|
|
61
|
+
metadata: { ...m.metadata, forwardedExport: true },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
47
64
|
}
|
|
48
65
|
}
|
|
49
66
|
}
|
|
@@ -65,6 +65,13 @@ export interface RefSiteEvent {
|
|
|
65
65
|
inScope: boolean;
|
|
66
66
|
/** Scope manifests visible to this ref path (non-empty only when `inScope`). */
|
|
67
67
|
visibleScopeManifests: ResourceManifest[];
|
|
68
|
+
/** True when the site was found by value-tree scanning rather than the field
|
|
69
|
+
* map (only when `discoverNestedRefs` is set) — a ref nested behind a `$ref`
|
|
70
|
+
* the field map doesn't descend (e.g. `Run.Sequence` `steps[].invoke`).
|
|
71
|
+
* Nested sites carry no x-telo-ref constraint (`entry.refs` is empty) and no
|
|
72
|
+
* scope info; `concretePath` still points at the exact location, so consumers
|
|
73
|
+
* can anchor to it. */
|
|
74
|
+
nested?: boolean;
|
|
68
75
|
}
|
|
69
76
|
export interface SchemaFromSiteEvent {
|
|
70
77
|
source: ResourceManifest;
|
|
@@ -104,6 +111,14 @@ export interface VisitOptions {
|
|
|
104
111
|
* so refs nested behind `x-telo-schema-from` are surfaced. `SchemaFromSite`
|
|
105
112
|
* events are always emitted from the base map regardless of this flag. */
|
|
106
113
|
expand?: boolean;
|
|
114
|
+
/** When true, additionally discover refs by scanning each resource's value
|
|
115
|
+
* tree for `!ref` sentinels and `{kind, name}` reference objects — surfacing
|
|
116
|
+
* refs the field map never lists because they sit behind a `$ref` it doesn't
|
|
117
|
+
* descend (notably `Run.Sequence` step `invoke`s). Emitted as `RefSite`s with
|
|
118
|
+
* `nested: true`, deduped against the field-map sites by concrete path.
|
|
119
|
+
* Opt-in: the validators / dependency graph must NOT enable it (those refs
|
|
120
|
+
* are runtime-resolved, not boot dependencies). */
|
|
121
|
+
discoverNestedRefs?: boolean;
|
|
107
122
|
}
|
|
108
123
|
export declare function visitManifest(resources: ResourceManifest[], registry: DefinitionRegistry, visitor: ManifestVisitor, options?: VisitOptions): void;
|
|
109
124
|
//# sourceMappingURL=manifest-visitor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAGlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAC;IACzB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAC;IACzB,wEAAwE;IACxE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uEAAuE;IACvE,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACpD;iFAC6E;IAC7E,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,KAAK,EAAE,OAAO,CAAC;IACf,oEAAoE;IACpE,KAAK,EAAE,aAAa,CAAC;IACrB;iEAC6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,qBAAqB,EAAE,gBAAgB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"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"}
|
package/dist/manifest-visitor.js
CHANGED
|
@@ -1,16 +1,59 @@
|
|
|
1
|
-
import { walkCelExpressions } from "@telorun/templating";
|
|
1
|
+
import { isRefSentinel, isTaggedSentinel, walkCelExpressions } from "@telorun/templating";
|
|
2
2
|
import { isRefEntry, isSchemaFromEntry, isScopeEntry, resolveFieldEntries, resolveFieldValues, } from "./reference-field-map.js";
|
|
3
3
|
import { extractContextsFromSchema, pathMatchesScope } from "./validate-cel-context.js";
|
|
4
|
+
/** Synthetic entry for a value-tree-discovered ref — these carry no declared
|
|
5
|
+
* x-telo-ref constraint. */
|
|
6
|
+
const NESTED_REF_ENTRY = { refs: [], isArray: false };
|
|
7
|
+
/** Scans a value tree for ref-shaped values, emitting each with its concrete
|
|
8
|
+
* path. Recognizes `!ref <name>` sentinels and named `{kind, name}` reference
|
|
9
|
+
* objects. Other tagged sentinels (`!cel`, `!literal`) and precompiled nodes
|
|
10
|
+
* are leaves. Path format matches `resolveFieldEntries` / `walkCelExpressions`
|
|
11
|
+
* (`a.b[0].c`).
|
|
12
|
+
*
|
|
13
|
+
* Stops at every `{kind, …}` resource boundary: a named ref is emitted, an
|
|
14
|
+
* inline resource (`{kind}` with no name) is left alone, and **neither is
|
|
15
|
+
* descended into**. A nested resource's own refs belong to its inner topology,
|
|
16
|
+
* not the enclosing node — e.g. an inline `Sql.Exec` step's `connection` is the
|
|
17
|
+
* Exec's dependency, not the surrounding `Run.Sequence`'s. The scan is started
|
|
18
|
+
* per top-level field (not on the resource object) so the resource's own
|
|
19
|
+
* `kind` doesn't trip this boundary. */
|
|
20
|
+
function walkRefValues(value, path, cb) {
|
|
21
|
+
if (isRefSentinel(value)) {
|
|
22
|
+
cb(value, path);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (isTaggedSentinel(value))
|
|
26
|
+
return;
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
value.forEach((v, i) => walkRefValues(v, `${path}[${i}]`, cb));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (value === null || typeof value !== "object")
|
|
32
|
+
return;
|
|
33
|
+
if (value.__compiled)
|
|
34
|
+
return;
|
|
35
|
+
const obj = value;
|
|
36
|
+
if (typeof obj.kind === "string") {
|
|
37
|
+
// Resource boundary — emit if it's a named ref, then stop descending.
|
|
38
|
+
if (typeof obj.name === "string")
|
|
39
|
+
cb(value, path);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
43
|
+
walkRefValues(v, path ? `${path}.${k}` : k, cb);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
4
46
|
const scopePrefixOf = (pointer) => pointer.replace(/^\//, "").replace(/\//g, ".");
|
|
5
47
|
const pathUnderPrefix = (fieldPath, prefix) => fieldPath === prefix ||
|
|
6
48
|
fieldPath.startsWith(prefix + ".") ||
|
|
7
49
|
fieldPath.startsWith(prefix + "[");
|
|
8
50
|
export function visitManifest(resources, registry, visitor, options = {}) {
|
|
9
|
-
const { aliases, aliasesByModule, skipKinds, expand } = options;
|
|
51
|
+
const { aliases, aliasesByModule, skipKinds, expand, discoverNestedRefs } = options;
|
|
10
52
|
const wantsRefs = !!visitor.onRef;
|
|
11
53
|
const wantsScope = !!visitor.onScope;
|
|
12
54
|
const wantsSchemaFrom = !!visitor.onSchemaFrom;
|
|
13
55
|
const wantsCel = !!visitor.onCel;
|
|
56
|
+
const wantsNested = wantsRefs && !!discoverNestedRefs;
|
|
14
57
|
for (const r of resources) {
|
|
15
58
|
if (!r.metadata?.name || !r.kind)
|
|
16
59
|
continue;
|
|
@@ -20,6 +63,9 @@ export function visitManifest(resources, registry, visitor, options = {}) {
|
|
|
20
63
|
const definition = registry.resolve(r.kind) ??
|
|
21
64
|
(resolvedKind ? registry.resolve(resolvedKind) : undefined);
|
|
22
65
|
visitor.onResourceEnter?.({ source: r, definition });
|
|
66
|
+
// Concrete paths emitted from the field map — so the value-tree scan below
|
|
67
|
+
// doesn't re-emit a ref the field map already covered.
|
|
68
|
+
const emittedRefPaths = wantsNested ? new Set() : null;
|
|
23
69
|
if (wantsRefs || wantsScope || wantsSchemaFrom) {
|
|
24
70
|
const baseMap = aliases
|
|
25
71
|
? registry.getFieldMapForKind(r.kind, aliases)
|
|
@@ -69,6 +115,7 @@ export function visitManifest(resources, registry, visitor, options = {}) {
|
|
|
69
115
|
for (const { value, path: concretePath } of resolveFieldEntries(r, fieldPath)) {
|
|
70
116
|
if (!value)
|
|
71
117
|
continue;
|
|
118
|
+
emittedRefPaths?.add(concretePath);
|
|
72
119
|
visitor.onRef({
|
|
73
120
|
source: r,
|
|
74
121
|
fieldPath,
|
|
@@ -90,6 +137,30 @@ export function visitManifest(resources, registry, visitor, options = {}) {
|
|
|
90
137
|
}
|
|
91
138
|
}
|
|
92
139
|
}
|
|
140
|
+
// Value-tree-driven nested ref discovery — refs the field map can't reach
|
|
141
|
+
// because they sit behind a `$ref` it doesn't descend (e.g. Run.Sequence
|
|
142
|
+
// step `invoke`s). Deduped against the field-map sites by concrete path.
|
|
143
|
+
// Scanned per top-level field so the resource's own `kind` isn't treated as
|
|
144
|
+
// a resource boundary by `walkRefValues`.
|
|
145
|
+
if (wantsNested) {
|
|
146
|
+
const emitNested = (value, path) => {
|
|
147
|
+
if (emittedRefPaths.has(path))
|
|
148
|
+
return;
|
|
149
|
+
visitor.onRef({
|
|
150
|
+
source: r,
|
|
151
|
+
fieldPath: path,
|
|
152
|
+
concretePath: path,
|
|
153
|
+
value,
|
|
154
|
+
entry: NESTED_REF_ENTRY,
|
|
155
|
+
inScope: false,
|
|
156
|
+
visibleScopeManifests: [],
|
|
157
|
+
nested: true,
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
for (const [key, value] of Object.entries(r)) {
|
|
161
|
+
walkRefValues(value, key, emitNested);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
93
164
|
if (wantsCel) {
|
|
94
165
|
const contexts = definition?.schema ? extractContextsFromSchema(definition.schema) : [];
|
|
95
166
|
walkCelExpressions(r, "", (expr, path, engineName) => {
|
|
@@ -157,6 +157,22 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
157
157
|
if (node["x-telo-context"])
|
|
158
158
|
entry.context = node["x-telo-context"];
|
|
159
159
|
map.set(path, entry);
|
|
160
|
+
// A node can mix item-level ref branches (a bare string / `{kind, name}`)
|
|
161
|
+
// with object branches that carry their OWN nested refs — e.g. Application
|
|
162
|
+
// `targets`: a bare ref vs inline `{ invoke }` vs gated `{ ref }`. Descend
|
|
163
|
+
// into the variant objects so those nested slots register too (and their
|
|
164
|
+
// `!ref` sentinels resolve). Pure x-telo-ref branches have no properties
|
|
165
|
+
// and contribute nothing here.
|
|
166
|
+
for (const variantKey of ["oneOf", "anyOf", "allOf"]) {
|
|
167
|
+
const variants = node[variantKey];
|
|
168
|
+
if (!Array.isArray(variants))
|
|
169
|
+
continue;
|
|
170
|
+
for (const variant of variants) {
|
|
171
|
+
if (!variant || typeof variant !== "object")
|
|
172
|
+
continue;
|
|
173
|
+
traverseVariant(variant, path, map, root, visitedRefs);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
160
176
|
return;
|
|
161
177
|
}
|
|
162
178
|
// Array — recurse into items
|
|
@@ -3,25 +3,23 @@ import type { AliasResolver } from "./alias-resolver.js";
|
|
|
3
3
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
4
|
/**
|
|
5
5
|
* Walks every `x-telo-ref` slot in every non-system resource and rewrites
|
|
6
|
-
* `!ref <name>` sentinels in-place to `{kind
|
|
6
|
+
* `!ref <name>` sentinels in-place to `{kind, name}` (local) or
|
|
7
|
+
* `{kind, name, alias}` (cross-module).
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Reference grammar — the tag's source string is split on the FIRST dot:
|
|
10
|
+
* - `!ref writeLine` → local resource `writeLine`
|
|
11
|
+
* - `!ref Self.writeLine` → local resource `writeLine` (explicit self-qualifier)
|
|
12
|
+
* - `!ref Console.writeLine` → instance `writeLine` exported by the import aliased
|
|
13
|
+
* `Console`, resolved against the forwarded foreign set
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Aliases are PascalCase identifiers without dots and resource names carry no dots
|
|
16
|
+
* (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
|
|
17
|
+
* name doesn't resolve, the sentinel is left in place so `validateReferences` emits the
|
|
18
|
+
* `UNRESOLVED_REFERENCE` diagnostic with full context.
|
|
18
19
|
*
|
|
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.
|
|
20
|
+
* Forwarded foreign resources (an imported library's exported instances, carrying a
|
|
21
|
+
* `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
|
|
22
|
+
* re-walked as sources here, since their own ref slots belong to their own module scope.
|
|
25
23
|
*/
|
|
26
|
-
export declare function resolveRefSentinels(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver
|
|
24
|
+
export declare function resolveRefSentinels(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[]): void;
|
|
27
25
|
//# 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;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,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;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,CAwEN"}
|