@telorun/analyzer 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis-registry.d.ts +4 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +5 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +3 -3
- package/dist/definition-registry.d.ts +13 -1
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +58 -2
- package/dist/dependency-graph.d.ts +1 -1
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +8 -2
- package/dist/normalize-inline-resources.d.ts +1 -1
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +7 -2
- package/dist/reference-field-map.d.ts +5 -0
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +9 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +6 -1
- package/package.json +3 -3
- package/src/analysis-registry.ts +9 -1
- package/src/analyzer.ts +13 -3
- package/src/definition-registry.ts +76 -3
- package/src/dependency-graph.ts +9 -1
- package/src/normalize-inline-resources.ts +8 -1
- package/src/reference-field-map.ts +13 -0
- package/src/validate-references.ts +6 -1
|
@@ -16,6 +16,10 @@ export declare class AnalysisRegistry {
|
|
|
16
16
|
/**
|
|
17
17
|
* Iterates a resource's reference and scope fields as declared by its definition.
|
|
18
18
|
* Calls onRef for each plain reference field and onScope for each scope field.
|
|
19
|
+
*
|
|
20
|
+
* Uses the expanded field map so x-telo-schema-from entries contribute their
|
|
21
|
+
* nested ref/scope slots — Phase 5 injection sees encoders that live inside a
|
|
22
|
+
* sub-schema (e.g. Server.notFoundHandler.returns[].content[mime].encoder).
|
|
19
23
|
*/
|
|
20
24
|
iterateFieldEntries(resource: ResourceManifest, onRef: (fieldPath: string) => void, onScope: (fieldPath: string) => void): void;
|
|
21
25
|
/**
|
|
@@ -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;AAMzE,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
|
|
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;AAMzE,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;;;;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,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
|
|
@@ -27,9 +27,13 @@ export class AnalysisRegistry {
|
|
|
27
27
|
/**
|
|
28
28
|
* Iterates a resource's reference and scope fields as declared by its definition.
|
|
29
29
|
* Calls onRef for each plain reference field and onScope for each scope field.
|
|
30
|
+
*
|
|
31
|
+
* Uses the expanded field map so x-telo-schema-from entries contribute their
|
|
32
|
+
* nested ref/scope slots — Phase 5 injection sees encoders that live inside a
|
|
33
|
+
* sub-schema (e.g. Server.notFoundHandler.returns[].content[mime].encoder).
|
|
30
34
|
*/
|
|
31
35
|
iterateFieldEntries(resource, onRef, onScope) {
|
|
32
|
-
const fieldMap = this.defs.
|
|
36
|
+
const fieldMap = this.defs.expandedFieldMapForResource(resource, this.aliases, this.aliasesByModule);
|
|
33
37
|
if (!fieldMap)
|
|
34
38
|
return;
|
|
35
39
|
for (const [fieldPath, entry] of fieldMap) {
|
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;AAa9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgX/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,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAqUvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;
|
|
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;AAa9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgX/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,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAqUvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAUxF,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
|
@@ -412,7 +412,7 @@ export class StaticAnalyzer {
|
|
|
412
412
|
defs.register(normalized);
|
|
413
413
|
}
|
|
414
414
|
// Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
|
|
415
|
-
const allManifests = normalizeInlineResources(manifests, defs, aliases);
|
|
415
|
+
const allManifests = normalizeInlineResources(manifests, defs, aliases, aliasesByModule);
|
|
416
416
|
// Build a name→manifest map for looking up referenced resources
|
|
417
417
|
const byName = new Map();
|
|
418
418
|
for (const m of allManifests) {
|
|
@@ -587,7 +587,7 @@ export class StaticAnalyzer {
|
|
|
587
587
|
}
|
|
588
588
|
normalize(manifests, registry) {
|
|
589
589
|
const ctx = registry._context();
|
|
590
|
-
return normalizeInlineResources(manifests, ctx.definitions, ctx.aliases);
|
|
590
|
+
return normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
|
|
591
591
|
}
|
|
592
592
|
prepare(manifests, registry) {
|
|
593
593
|
const ctx = registry._context();
|
|
@@ -596,7 +596,7 @@ export class StaticAnalyzer {
|
|
|
596
596
|
if (errors.length > 0) {
|
|
597
597
|
return { diagnostics: errors, order: null, cycleError: null };
|
|
598
598
|
}
|
|
599
|
-
const graph = buildDependencyGraph(manifests, ctx.definitions, ctx.aliases);
|
|
599
|
+
const graph = buildDependencyGraph(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
|
|
600
600
|
if (graph.cycle) {
|
|
601
601
|
return { diagnostics: [], order: null, cycleError: formatCycle(graph.cycle) };
|
|
602
602
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ResourceDefinition } from "@telorun/sdk";
|
|
1
|
+
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
2
3
|
import { type ReferenceFieldMap } from "./reference-field-map.js";
|
|
3
4
|
/** Pure kind → ResourceDefinition map. No controller loading, no lifecycle. */
|
|
4
5
|
export declare class DefinitionRegistry {
|
|
@@ -50,6 +51,17 @@ export declare class DefinitionRegistry {
|
|
|
50
51
|
getFieldMapForKind(kind: string, aliases?: {
|
|
51
52
|
resolveKind(k: string): string | undefined;
|
|
52
53
|
}): ReferenceFieldMap | undefined;
|
|
54
|
+
/** Returns the field map for `resource.kind` with x-telo-schema-from entries replaced
|
|
55
|
+
* by their nested ref/scope slots — so Phase 2 inline normalization and Phase 5
|
|
56
|
+
* injection see encoders nested behind a schema-from indirection (e.g.
|
|
57
|
+
* http-server `Server.notFoundHandler.returns[].content[mime].encoder`).
|
|
58
|
+
*
|
|
59
|
+
* Only static absolute schema-from paths with a dotted alias anchor are expanded
|
|
60
|
+
* (e.g. "HttpDispatch.Outcomes/$defs/Returns"). Relative and unqualified absolute
|
|
61
|
+
* anchors depend on a sibling property at runtime and stay unexpanded; the
|
|
62
|
+
* analyzer's reference validation phase already flags the cases that matter. */
|
|
63
|
+
expandedFieldMapForResource(resource: ResourceManifest, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>): ReferenceFieldMap | undefined;
|
|
64
|
+
private resolveSchemaFromSubMap;
|
|
53
65
|
/** Returns all definitions that transitively extend the given abstract kind.
|
|
54
66
|
* Follows the capability chain to any depth (equivalent to instanceof in OOP).
|
|
55
67
|
* Definitions are included regardless of registration order. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAgB1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;wFACoF;IACpF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
2
|
-
import { buildReferenceFieldMap } from "./reference-field-map.js";
|
|
3
|
-
import { createAjv, formatSingleError } from "./schema-compat.js";
|
|
2
|
+
import { buildFieldMapAtPath, buildReferenceFieldMap, isSchemaFromEntry, } from "./reference-field-map.js";
|
|
3
|
+
import { createAjv, formatSingleError, navigateJsonPointer } from "./schema-compat.js";
|
|
4
4
|
/** Pure kind → ResourceDefinition map. No controller loading, no lifecycle. */
|
|
5
5
|
export class DefinitionRegistry {
|
|
6
6
|
constructor() {
|
|
@@ -147,6 +147,62 @@ export class DefinitionRegistry {
|
|
|
147
147
|
const resolved = aliases?.resolveKind(kind);
|
|
148
148
|
return resolved ? this.getFieldMap(resolved) : undefined;
|
|
149
149
|
}
|
|
150
|
+
/** Returns the field map for `resource.kind` with x-telo-schema-from entries replaced
|
|
151
|
+
* by their nested ref/scope slots — so Phase 2 inline normalization and Phase 5
|
|
152
|
+
* injection see encoders nested behind a schema-from indirection (e.g.
|
|
153
|
+
* http-server `Server.notFoundHandler.returns[].content[mime].encoder`).
|
|
154
|
+
*
|
|
155
|
+
* Only static absolute schema-from paths with a dotted alias anchor are expanded
|
|
156
|
+
* (e.g. "HttpDispatch.Outcomes/$defs/Returns"). Relative and unqualified absolute
|
|
157
|
+
* anchors depend on a sibling property at runtime and stay unexpanded; the
|
|
158
|
+
* analyzer's reference validation phase already flags the cases that matter. */
|
|
159
|
+
expandedFieldMapForResource(resource, aliases, aliasesByModule) {
|
|
160
|
+
const baseMap = this.getFieldMapForKind(resource.kind, aliases);
|
|
161
|
+
if (!baseMap)
|
|
162
|
+
return undefined;
|
|
163
|
+
const resolvedKind = aliases.resolveKind(resource.kind) ?? resource.kind;
|
|
164
|
+
const def = this.resolve(resource.kind) ?? this.resolve(resolvedKind);
|
|
165
|
+
const ownerModule = def?.metadata?.module;
|
|
166
|
+
const ownerScope = (ownerModule ? aliasesByModule.get(ownerModule) : undefined) ?? aliases;
|
|
167
|
+
const expanded = new Map();
|
|
168
|
+
for (const [path, entry] of baseMap) {
|
|
169
|
+
if (!isSchemaFromEntry(entry)) {
|
|
170
|
+
expanded.set(path, entry);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const sub = this.resolveSchemaFromSubMap(entry.schemaFrom, path, ownerScope);
|
|
174
|
+
if (!sub)
|
|
175
|
+
continue;
|
|
176
|
+
for (const [subPath, subEntry] of sub)
|
|
177
|
+
expanded.set(subPath, subEntry);
|
|
178
|
+
}
|
|
179
|
+
return expanded;
|
|
180
|
+
}
|
|
181
|
+
resolveSchemaFromSubMap(schemaFrom, fieldPath, ownerScope) {
|
|
182
|
+
const isAbsolute = schemaFrom.startsWith("/");
|
|
183
|
+
const expr = isAbsolute ? schemaFrom.slice(1) : schemaFrom;
|
|
184
|
+
const slashIdx = expr.indexOf("/");
|
|
185
|
+
if (slashIdx === -1)
|
|
186
|
+
return null;
|
|
187
|
+
const anchorName = expr.slice(0, slashIdx);
|
|
188
|
+
const jsonPointer = "/" + expr.slice(slashIdx + 1);
|
|
189
|
+
// Static form: absolute path whose anchor is a dotted alias (e.g.
|
|
190
|
+
// "HttpDispatch.Outcomes/$defs/Returns"). Polymorphic forms — relative
|
|
191
|
+
// anchors or single-segment absolute anchors — only resolve once we know a
|
|
192
|
+
// sibling property's value, which is per-resource.
|
|
193
|
+
if (!anchorName.includes("."))
|
|
194
|
+
return null;
|
|
195
|
+
const targetKind = ownerScope.resolveKind(anchorName);
|
|
196
|
+
if (!targetKind)
|
|
197
|
+
return null;
|
|
198
|
+
const targetDef = this.resolve(targetKind);
|
|
199
|
+
if (!targetDef?.schema)
|
|
200
|
+
return null;
|
|
201
|
+
const subSchema = navigateJsonPointer(targetDef.schema, jsonPointer);
|
|
202
|
+
if (!subSchema || typeof subSchema !== "object")
|
|
203
|
+
return null;
|
|
204
|
+
return buildFieldMapAtPath(subSchema, fieldPath);
|
|
205
|
+
}
|
|
150
206
|
/** Returns all definitions that transitively extend the given abstract kind.
|
|
151
207
|
* Follows the capability chain to any depth (equivalent to instanceof in OOP).
|
|
152
208
|
* Definitions are included regardless of registration order. */
|
|
@@ -25,7 +25,7 @@ export interface DependencyGraph {
|
|
|
25
25
|
* The registry is queried for each resource's field map by kind — callers do
|
|
26
26
|
* not pre-compute or pass field maps separately.
|
|
27
27
|
*/
|
|
28
|
-
export declare function buildDependencyGraph(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver): DependencyGraph;
|
|
28
|
+
export declare function buildDependencyGraph(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): DependencyGraph;
|
|
29
29
|
/**
|
|
30
30
|
* Formats a cycle result into a human-readable error string matching the spec:
|
|
31
31
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B;kDAC8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpC;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrC;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B;kDAC8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpC;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrC;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,eAAe,CA6FjB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,MAAM,CAOtE"}
|
package/dist/dependency-graph.js
CHANGED
|
@@ -21,7 +21,7 @@ const nodeKey = (kind, name) => `${kind}\0${name}`;
|
|
|
21
21
|
* The registry is queried for each resource's field map by kind — callers do
|
|
22
22
|
* not pre-compute or pass field maps separately.
|
|
23
23
|
*/
|
|
24
|
-
export function buildDependencyGraph(resources, registry, aliases) {
|
|
24
|
+
export function buildDependencyGraph(resources, registry, aliases, aliasesByModule) {
|
|
25
25
|
// --- Build node set ---
|
|
26
26
|
const nodes = new Map();
|
|
27
27
|
for (const r of resources) {
|
|
@@ -38,7 +38,13 @@ export function buildDependencyGraph(resources, registry, aliases) {
|
|
|
38
38
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
39
39
|
continue;
|
|
40
40
|
const sourceKey = nodeKey(r.kind, r.metadata.name);
|
|
41
|
-
|
|
41
|
+
// Use the expanded map so refs nested behind x-telo-schema-from contribute
|
|
42
|
+
// edges to the DAG. Without these, a parent (e.g. Http.Server) can init
|
|
43
|
+
// before its extracted encoder and Phase 5 injection fires against a
|
|
44
|
+
// not-yet-created dependency.
|
|
45
|
+
const fieldMap = aliases && aliasesByModule
|
|
46
|
+
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
47
|
+
: registry.getFieldMapForKind(r.kind, aliases);
|
|
42
48
|
if (!fieldMap)
|
|
43
49
|
continue;
|
|
44
50
|
// Collect names of resources declared inside scope fields — these are initialized
|
|
@@ -18,5 +18,5 @@ import type { AliasResolver } from "./alias-resolver.js";
|
|
|
18
18
|
* Returns a new array containing the original manifests (mutated in-place) plus all
|
|
19
19
|
* extracted manifests. The original array is not mutated.
|
|
20
20
|
*/
|
|
21
|
-
export declare function normalizeInlineResources(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver): ResourceManifest[];
|
|
21
|
+
export declare function normalizeInlineResources(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): ResourceManifest[];
|
|
22
22
|
//# sourceMappingURL=normalize-inline-resources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-inline-resources.d.ts","sourceRoot":"","sources":["../src/normalize-inline-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"normalize-inline-resources.d.ts","sourceRoot":"","sources":["../src/normalize-inline-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,gBAAgB,EAAE,CAwDpB"}
|
|
@@ -26,14 +26,19 @@ function sanitizeName(raw) {
|
|
|
26
26
|
* Returns a new array containing the original manifests (mutated in-place) plus all
|
|
27
27
|
* extracted manifests. The original array is not mutated.
|
|
28
28
|
*/
|
|
29
|
-
export function normalizeInlineResources(resources, registry, aliases) {
|
|
29
|
+
export function normalizeInlineResources(resources, registry, aliases, aliasesByModule) {
|
|
30
30
|
const result = [...resources];
|
|
31
31
|
// Queue: all non-system resources with a name. Extracted resources are appended.
|
|
32
32
|
const queue = resources.filter((r) => typeof r.metadata?.name === "string" && !!r.kind && !SYSTEM_KINDS.has(r.kind));
|
|
33
33
|
let i = 0;
|
|
34
34
|
while (i < queue.length) {
|
|
35
35
|
const resource = queue[i++];
|
|
36
|
-
|
|
36
|
+
// When aliasesByModule is available, use the expanded map so inline refs
|
|
37
|
+
// hidden behind an x-telo-schema-from indirection (e.g. an encoder inside
|
|
38
|
+
// an HttpDispatch.Outcomes/$defs/Returns sub-schema) reach extraction.
|
|
39
|
+
const fieldMap = aliases && aliasesByModule
|
|
40
|
+
? registry.expandedFieldMapForResource(resource, aliases, aliasesByModule)
|
|
41
|
+
: registry.getFieldMapForKind(resource.kind, aliases);
|
|
37
42
|
if (!fieldMap)
|
|
38
43
|
continue;
|
|
39
44
|
const parentName = resource.metadata.name;
|
|
@@ -61,4 +61,9 @@ export declare function resolveFieldValues(obj: unknown, path: string): unknown[
|
|
|
61
61
|
* - A node with `properties` → recurse into each property
|
|
62
62
|
*/
|
|
63
63
|
export declare function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFieldMap;
|
|
64
|
+
/** Traverses an arbitrary JSON Schema starting at the given path prefix. Used to
|
|
65
|
+
* expand x-telo-schema-from sub-schemas into nested ref/scope entries so Phase 2
|
|
66
|
+
* inline normalization and Phase 5 injection see slots that the local field map
|
|
67
|
+
* hid behind the schema-from indirection. */
|
|
68
|
+
export declare function buildFieldMapAtPath(schema: Record<string, any>, pathPrefix: string): ReferenceFieldMap;
|
|
64
69
|
//# sourceMappingURL=reference-field-map.d.ts.map
|
|
@@ -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;;;;;kEAKkE;AAClE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA6BxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF"}
|
|
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;;;;;kEAKkE;AAClE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA6BxE;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"}
|
|
@@ -101,6 +101,15 @@ function collectRefs(node) {
|
|
|
101
101
|
}
|
|
102
102
|
return refs;
|
|
103
103
|
}
|
|
104
|
+
/** Traverses an arbitrary JSON Schema starting at the given path prefix. Used to
|
|
105
|
+
* expand x-telo-schema-from sub-schemas into nested ref/scope entries so Phase 2
|
|
106
|
+
* inline normalization and Phase 5 injection see slots that the local field map
|
|
107
|
+
* hid behind the schema-from indirection. */
|
|
108
|
+
export function buildFieldMapAtPath(schema, pathPrefix) {
|
|
109
|
+
const map = new Map();
|
|
110
|
+
traverseNode(schema, pathPrefix, map);
|
|
111
|
+
return map;
|
|
112
|
+
}
|
|
104
113
|
function traverseNode(node, path, map) {
|
|
105
114
|
// Scope slot — record and stop; do not recurse into scope contents
|
|
106
115
|
if ("x-telo-scope" in node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAkD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAkD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2UtB"}
|
|
@@ -75,7 +75,12 @@ export function validateReferences(resources, context) {
|
|
|
75
75
|
for (const r of resources) {
|
|
76
76
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
77
77
|
continue;
|
|
78
|
-
|
|
78
|
+
// Use the expanded map so refs nested behind x-telo-schema-from get the
|
|
79
|
+
// same kind-check / unresolved-name validation as locally-declared refs.
|
|
80
|
+
// Falls back to the base map when aliasesByModule isn't supplied.
|
|
81
|
+
const fieldMap = aliasesByModule
|
|
82
|
+
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
83
|
+
: registry.getFieldMapForKind(r.kind, aliases);
|
|
79
84
|
if (!fieldMap)
|
|
80
85
|
continue;
|
|
81
86
|
const resourceLabel = `${r.kind}/${r.metadata.name}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"ajv-formats": "^3.0.1",
|
|
42
42
|
"jsonpath-plus": "^10.3.0",
|
|
43
43
|
"yaml": "^2.8.3",
|
|
44
|
-
"@telorun/sdk": "0.
|
|
45
|
-
"@telorun/templating": "0.2.
|
|
44
|
+
"@telorun/sdk": "0.11.1",
|
|
45
|
+
"@telorun/templating": "0.2.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^20.0.0",
|
package/src/analysis-registry.ts
CHANGED
|
@@ -35,13 +35,21 @@ export class AnalysisRegistry {
|
|
|
35
35
|
/**
|
|
36
36
|
* Iterates a resource's reference and scope fields as declared by its definition.
|
|
37
37
|
* Calls onRef for each plain reference field and onScope for each scope field.
|
|
38
|
+
*
|
|
39
|
+
* Uses the expanded field map so x-telo-schema-from entries contribute their
|
|
40
|
+
* nested ref/scope slots — Phase 5 injection sees encoders that live inside a
|
|
41
|
+
* sub-schema (e.g. Server.notFoundHandler.returns[].content[mime].encoder).
|
|
38
42
|
*/
|
|
39
43
|
iterateFieldEntries(
|
|
40
44
|
resource: ResourceManifest,
|
|
41
45
|
onRef: (fieldPath: string) => void,
|
|
42
46
|
onScope: (fieldPath: string) => void,
|
|
43
47
|
): void {
|
|
44
|
-
const fieldMap = this.defs.
|
|
48
|
+
const fieldMap = this.defs.expandedFieldMapForResource(
|
|
49
|
+
resource,
|
|
50
|
+
this.aliases,
|
|
51
|
+
this.aliasesByModule,
|
|
52
|
+
);
|
|
45
53
|
if (!fieldMap) return;
|
|
46
54
|
for (const [fieldPath, entry] of fieldMap) {
|
|
47
55
|
if (isScopeEntry(entry)) {
|
package/src/analyzer.ts
CHANGED
|
@@ -525,7 +525,7 @@ export class StaticAnalyzer {
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
|
|
528
|
-
const allManifests = normalizeInlineResources(manifests, defs, aliases);
|
|
528
|
+
const allManifests = normalizeInlineResources(manifests, defs, aliases, aliasesByModule);
|
|
529
529
|
|
|
530
530
|
// Build a name→manifest map for looking up referenced resources
|
|
531
531
|
const byName = new Map<string, ResourceManifest>();
|
|
@@ -740,7 +740,12 @@ export class StaticAnalyzer {
|
|
|
740
740
|
|
|
741
741
|
normalize(manifests: ResourceManifest[], registry: AnalysisRegistry): ResourceManifest[] {
|
|
742
742
|
const ctx = registry._context();
|
|
743
|
-
return normalizeInlineResources(
|
|
743
|
+
return normalizeInlineResources(
|
|
744
|
+
manifests,
|
|
745
|
+
ctx.definitions!,
|
|
746
|
+
ctx.aliases,
|
|
747
|
+
ctx.aliasesByModule,
|
|
748
|
+
);
|
|
744
749
|
}
|
|
745
750
|
|
|
746
751
|
prepare(
|
|
@@ -753,7 +758,12 @@ export class StaticAnalyzer {
|
|
|
753
758
|
if (errors.length > 0) {
|
|
754
759
|
return { diagnostics: errors, order: null, cycleError: null };
|
|
755
760
|
}
|
|
756
|
-
const graph = buildDependencyGraph(
|
|
761
|
+
const graph = buildDependencyGraph(
|
|
762
|
+
manifests,
|
|
763
|
+
ctx.definitions!,
|
|
764
|
+
ctx.aliases,
|
|
765
|
+
ctx.aliasesByModule,
|
|
766
|
+
);
|
|
757
767
|
if (graph.cycle) {
|
|
758
768
|
return { diagnostics: [], order: null, cycleError: formatCycle(graph.cycle) };
|
|
759
769
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type { ResourceDefinition } from "@telorun/sdk";
|
|
1
|
+
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
2
3
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
4
|
+
import {
|
|
5
|
+
buildFieldMapAtPath,
|
|
6
|
+
buildReferenceFieldMap,
|
|
7
|
+
isSchemaFromEntry,
|
|
8
|
+
type ReferenceFieldMap,
|
|
9
|
+
} from "./reference-field-map.js";
|
|
10
|
+
import { createAjv, formatSingleError, navigateJsonPointer } from "./schema-compat.js";
|
|
5
11
|
|
|
6
12
|
/** Pure kind → ResourceDefinition map. No controller loading, no lifecycle. */
|
|
7
13
|
export class DefinitionRegistry {
|
|
@@ -163,6 +169,73 @@ export class DefinitionRegistry {
|
|
|
163
169
|
return resolved ? this.getFieldMap(resolved) : undefined;
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
/** Returns the field map for `resource.kind` with x-telo-schema-from entries replaced
|
|
173
|
+
* by their nested ref/scope slots — so Phase 2 inline normalization and Phase 5
|
|
174
|
+
* injection see encoders nested behind a schema-from indirection (e.g.
|
|
175
|
+
* http-server `Server.notFoundHandler.returns[].content[mime].encoder`).
|
|
176
|
+
*
|
|
177
|
+
* Only static absolute schema-from paths with a dotted alias anchor are expanded
|
|
178
|
+
* (e.g. "HttpDispatch.Outcomes/$defs/Returns"). Relative and unqualified absolute
|
|
179
|
+
* anchors depend on a sibling property at runtime and stay unexpanded; the
|
|
180
|
+
* analyzer's reference validation phase already flags the cases that matter. */
|
|
181
|
+
expandedFieldMapForResource(
|
|
182
|
+
resource: ResourceManifest,
|
|
183
|
+
aliases: AliasResolver,
|
|
184
|
+
aliasesByModule: Map<string, AliasResolver>,
|
|
185
|
+
): ReferenceFieldMap | undefined {
|
|
186
|
+
const baseMap = this.getFieldMapForKind(resource.kind, aliases);
|
|
187
|
+
if (!baseMap) return undefined;
|
|
188
|
+
|
|
189
|
+
const resolvedKind = aliases.resolveKind(resource.kind) ?? resource.kind;
|
|
190
|
+
const def = this.resolve(resource.kind) ?? this.resolve(resolvedKind);
|
|
191
|
+
const ownerModule = (def?.metadata as { module?: string } | undefined)?.module;
|
|
192
|
+
const ownerScope =
|
|
193
|
+
(ownerModule ? aliasesByModule.get(ownerModule) : undefined) ?? aliases;
|
|
194
|
+
|
|
195
|
+
const expanded: ReferenceFieldMap = new Map();
|
|
196
|
+
for (const [path, entry] of baseMap) {
|
|
197
|
+
if (!isSchemaFromEntry(entry)) {
|
|
198
|
+
expanded.set(path, entry);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const sub = this.resolveSchemaFromSubMap(entry.schemaFrom, path, ownerScope);
|
|
202
|
+
if (!sub) continue;
|
|
203
|
+
for (const [subPath, subEntry] of sub) expanded.set(subPath, subEntry);
|
|
204
|
+
}
|
|
205
|
+
return expanded;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private resolveSchemaFromSubMap(
|
|
209
|
+
schemaFrom: string,
|
|
210
|
+
fieldPath: string,
|
|
211
|
+
ownerScope: AliasResolver,
|
|
212
|
+
): ReferenceFieldMap | null {
|
|
213
|
+
const isAbsolute = schemaFrom.startsWith("/");
|
|
214
|
+
const expr = isAbsolute ? schemaFrom.slice(1) : schemaFrom;
|
|
215
|
+
const slashIdx = expr.indexOf("/");
|
|
216
|
+
if (slashIdx === -1) return null;
|
|
217
|
+
const anchorName = expr.slice(0, slashIdx);
|
|
218
|
+
const jsonPointer = "/" + expr.slice(slashIdx + 1);
|
|
219
|
+
|
|
220
|
+
// Static form: absolute path whose anchor is a dotted alias (e.g.
|
|
221
|
+
// "HttpDispatch.Outcomes/$defs/Returns"). Polymorphic forms — relative
|
|
222
|
+
// anchors or single-segment absolute anchors — only resolve once we know a
|
|
223
|
+
// sibling property's value, which is per-resource.
|
|
224
|
+
if (!anchorName.includes(".")) return null;
|
|
225
|
+
|
|
226
|
+
const targetKind = ownerScope.resolveKind(anchorName);
|
|
227
|
+
if (!targetKind) return null;
|
|
228
|
+
const targetDef = this.resolve(targetKind);
|
|
229
|
+
if (!targetDef?.schema) return null;
|
|
230
|
+
const subSchema = navigateJsonPointer(
|
|
231
|
+
targetDef.schema as Record<string, unknown>,
|
|
232
|
+
jsonPointer,
|
|
233
|
+
);
|
|
234
|
+
if (!subSchema || typeof subSchema !== "object") return null;
|
|
235
|
+
|
|
236
|
+
return buildFieldMapAtPath(subSchema as Record<string, any>, fieldPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
166
239
|
/** Returns all definitions that transitively extend the given abstract kind.
|
|
167
240
|
* Follows the capability chain to any depth (equivalent to instanceof in OOP).
|
|
168
241
|
* Definitions are included regardless of registration order. */
|
package/src/dependency-graph.ts
CHANGED
|
@@ -45,6 +45,7 @@ export function buildDependencyGraph(
|
|
|
45
45
|
resources: ResourceManifest[],
|
|
46
46
|
registry: DefinitionRegistry,
|
|
47
47
|
aliases?: AliasResolver,
|
|
48
|
+
aliasesByModule?: Map<string, AliasResolver>,
|
|
48
49
|
): DependencyGraph {
|
|
49
50
|
// --- Build node set ---
|
|
50
51
|
const nodes = new Map<string, ResourceNode>();
|
|
@@ -62,7 +63,14 @@ export function buildDependencyGraph(
|
|
|
62
63
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind)) continue;
|
|
63
64
|
|
|
64
65
|
const sourceKey = nodeKey(r.kind, r.metadata.name as string);
|
|
65
|
-
|
|
66
|
+
// Use the expanded map so refs nested behind x-telo-schema-from contribute
|
|
67
|
+
// edges to the DAG. Without these, a parent (e.g. Http.Server) can init
|
|
68
|
+
// before its extracted encoder and Phase 5 injection fires against a
|
|
69
|
+
// not-yet-created dependency.
|
|
70
|
+
const fieldMap =
|
|
71
|
+
aliases && aliasesByModule
|
|
72
|
+
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
73
|
+
: registry.getFieldMapForKind(r.kind, aliases);
|
|
66
74
|
if (!fieldMap) continue;
|
|
67
75
|
|
|
68
76
|
// Collect names of resources declared inside scope fields — these are initialized
|
|
@@ -36,6 +36,7 @@ export function normalizeInlineResources(
|
|
|
36
36
|
resources: ResourceManifest[],
|
|
37
37
|
registry: DefinitionRegistry,
|
|
38
38
|
aliases?: AliasResolver,
|
|
39
|
+
aliasesByModule?: Map<string, AliasResolver>,
|
|
39
40
|
): ResourceManifest[] {
|
|
40
41
|
const result = [...resources];
|
|
41
42
|
|
|
@@ -48,7 +49,13 @@ export function normalizeInlineResources(
|
|
|
48
49
|
let i = 0;
|
|
49
50
|
while (i < queue.length) {
|
|
50
51
|
const resource = queue[i++];
|
|
51
|
-
|
|
52
|
+
// When aliasesByModule is available, use the expanded map so inline refs
|
|
53
|
+
// hidden behind an x-telo-schema-from indirection (e.g. an encoder inside
|
|
54
|
+
// an HttpDispatch.Outcomes/$defs/Returns sub-schema) reach extraction.
|
|
55
|
+
const fieldMap =
|
|
56
|
+
aliases && aliasesByModule
|
|
57
|
+
? registry.expandedFieldMapForResource(resource, aliases, aliasesByModule)
|
|
58
|
+
: registry.getFieldMapForKind(resource.kind, aliases);
|
|
52
59
|
if (!fieldMap) continue;
|
|
53
60
|
|
|
54
61
|
const parentName = resource.metadata.name as string;
|
|
@@ -135,6 +135,19 @@ function collectRefs(node: Record<string, any>): string[] {
|
|
|
135
135
|
return refs;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/** Traverses an arbitrary JSON Schema starting at the given path prefix. Used to
|
|
139
|
+
* expand x-telo-schema-from sub-schemas into nested ref/scope entries so Phase 2
|
|
140
|
+
* inline normalization and Phase 5 injection see slots that the local field map
|
|
141
|
+
* hid behind the schema-from indirection. */
|
|
142
|
+
export function buildFieldMapAtPath(
|
|
143
|
+
schema: Record<string, any>,
|
|
144
|
+
pathPrefix: string,
|
|
145
|
+
): ReferenceFieldMap {
|
|
146
|
+
const map: ReferenceFieldMap = new Map();
|
|
147
|
+
traverseNode(schema, pathPrefix, map);
|
|
148
|
+
return map;
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
function traverseNode(node: Record<string, any>, path: string, map: ReferenceFieldMap): void {
|
|
139
152
|
// Scope slot — record and stop; do not recurse into scope contents
|
|
140
153
|
if ("x-telo-scope" in node) {
|
|
@@ -87,7 +87,12 @@ export function validateReferences(
|
|
|
87
87
|
for (const r of resources) {
|
|
88
88
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind)) continue;
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
// Use the expanded map so refs nested behind x-telo-schema-from get the
|
|
91
|
+
// same kind-check / unresolved-name validation as locally-declared refs.
|
|
92
|
+
// Falls back to the base map when aliasesByModule isn't supplied.
|
|
93
|
+
const fieldMap = aliasesByModule
|
|
94
|
+
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
95
|
+
: registry.getFieldMapForKind(r.kind, aliases);
|
|
91
96
|
if (!fieldMap) continue;
|
|
92
97
|
|
|
93
98
|
const resourceLabel = `${r.kind}/${r.metadata.name as string}`;
|