@telorun/analyzer 0.8.1 → 0.9.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/analysis-registry.d.ts +1 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +2 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +2 -2
- package/dist/flatten-for-analyzer.d.ts +30 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -0
- package/dist/flatten-for-analyzer.js +119 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/loaded-types.d.ts +81 -0
- package/dist/loaded-types.d.ts.map +1 -0
- package/dist/loaded-types.js +1 -0
- package/dist/manifest-loader.d.ts +30 -9
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +197 -417
- package/dist/parse-loaded-file.d.ts +12 -0
- package/dist/parse-loaded-file.d.ts.map +1 -0
- package/dist/parse-loaded-file.js +50 -0
- package/dist/position-metadata.d.ts +27 -0
- package/dist/position-metadata.d.ts.map +1 -0
- package/dist/position-metadata.js +88 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +62 -0
- package/package.json +3 -3
- package/src/analysis-registry.ts +2 -1
- package/src/analyzer.ts +4 -2
- package/src/flatten-for-analyzer.ts +134 -0
- package/src/index.ts +18 -0
- package/src/loaded-types.ts +86 -0
- package/src/manifest-loader.ts +230 -459
- package/src/parse-loaded-file.ts +70 -0
- package/src/position-metadata.ts +106 -0
- package/src/types.ts +6 -0
- package/src/validate-references.ts +68 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Environment } from "@marcbachmann/cel-js";
|
|
2
|
+
import type { LoadedFile } from "./loaded-types.js";
|
|
3
|
+
export interface ParseOptions {
|
|
4
|
+
/** When true, runs `precompileDoc` per document and stamps compiled CEL
|
|
5
|
+
* on the manifests — same flag `LoadOptions.compile` carries today. */
|
|
6
|
+
compile?: boolean;
|
|
7
|
+
/** CEL environment for precompile. Defaults to `buildCelEnvironment()`. */
|
|
8
|
+
celEnv?: Environment;
|
|
9
|
+
}
|
|
10
|
+
/** Pure: text in, structured load result out. No I/O, no caches. */
|
|
11
|
+
export declare function parseLoadedFile(source: string, requestedUrl: string, text: string, options?: ParseOptions): LoadedFile;
|
|
12
|
+
//# sourceMappingURL=parse-loaded-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-loaded-file.d.ts","sourceRoot":"","sources":["../src/parse-loaded-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKxD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAIhE,MAAM,WAAW,YAAY;IAC3B;4EACwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,UAAU,CA8CZ"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defaultCustomTags } from "@telorun/templating";
|
|
2
|
+
import { parseAllDocuments } from "yaml";
|
|
3
|
+
import { buildCelEnvironment } from "./cel-environment.js";
|
|
4
|
+
import { buildDocumentPositions } from "./position-metadata.js";
|
|
5
|
+
import { precompileDoc } from "./precompile.js";
|
|
6
|
+
/** Pure: text in, structured load result out. No I/O, no caches. */
|
|
7
|
+
export function parseLoadedFile(source, requestedUrl, text, options) {
|
|
8
|
+
const documents = parseAllDocuments(text, { customTags: defaultCustomTags() });
|
|
9
|
+
const positions = buildDocumentPositions(text, documents);
|
|
10
|
+
const parseErrors = [];
|
|
11
|
+
documents.forEach((doc, documentIndex) => {
|
|
12
|
+
for (const err of doc.errors) {
|
|
13
|
+
parseErrors.push({
|
|
14
|
+
documentIndex,
|
|
15
|
+
message: err.message,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const manifests = [];
|
|
20
|
+
let env;
|
|
21
|
+
for (const doc of documents) {
|
|
22
|
+
const raw = doc.toJSON();
|
|
23
|
+
if (raw === null || raw === undefined) {
|
|
24
|
+
manifests.push(null);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (options?.compile) {
|
|
28
|
+
env ??= options.celEnv ?? buildCelEnvironment();
|
|
29
|
+
try {
|
|
30
|
+
const compiled = precompileDoc(raw, env);
|
|
31
|
+
manifests.push(compiled);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`Failed to compile manifest in ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
manifests.push(raw);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
source,
|
|
43
|
+
requestedUrl,
|
|
44
|
+
text,
|
|
45
|
+
documents,
|
|
46
|
+
manifests,
|
|
47
|
+
positions,
|
|
48
|
+
parseErrors,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Document } from "yaml";
|
|
2
|
+
import type { PositionIndex } from "./types.js";
|
|
3
|
+
/** Single source of truth for "given the source text of a multi-document YAML
|
|
4
|
+
* file, where does each document start, and what is the byte→(line,char)
|
|
5
|
+
* table for the file." Both the analyzer's `Loader` and editor frontends
|
|
6
|
+
* feed the same parsed `yaml.Document[]` through this so diagnostics
|
|
7
|
+
* resolved against `positionIndex` / `sourceLine` line up identically
|
|
8
|
+
* across hosts. */
|
|
9
|
+
/** Per-document position metadata used by `normalizeDiagnostic`'s fallback chain. */
|
|
10
|
+
export interface DocumentPosition {
|
|
11
|
+
sourceLine: number;
|
|
12
|
+
positionIndex: PositionIndex;
|
|
13
|
+
}
|
|
14
|
+
/** Builds DocumentPosition entries aligned to `parsedDocs[i]`. */
|
|
15
|
+
export declare function buildDocumentPositions(text: string, parsedDocs: Document[]): DocumentPosition[];
|
|
16
|
+
/** Line numbers (0-indexed) where each YAML document in a multi-doc file
|
|
17
|
+
* starts. The first document is always at line 0; subsequent entries point
|
|
18
|
+
* to the line after each `---` directive. */
|
|
19
|
+
export declare function documentLineOffsets(text: string): number[];
|
|
20
|
+
/** Byte-offset → start-of-line lookup table. Index `i` is the byte offset of
|
|
21
|
+
* the first character on line `i`. Used with `offsetToPosition` to turn a
|
|
22
|
+
* yaml-AST node range into Range coordinates. */
|
|
23
|
+
export declare function buildLineOffsets(text: string): number[];
|
|
24
|
+
/** Walks the YAML AST and records source ranges for every field value, keyed
|
|
25
|
+
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path"). */
|
|
26
|
+
export declare function buildPositionIndex(doc: Document, lineOffsets: number[]): PositionIndex;
|
|
27
|
+
//# sourceMappingURL=position-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;+EAC+E;AAC/E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CAuCtF"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isMap, isPair, isScalar, isSeq } from "yaml";
|
|
2
|
+
/** Builds DocumentPosition entries aligned to `parsedDocs[i]`. */
|
|
3
|
+
export function buildDocumentPositions(text, parsedDocs) {
|
|
4
|
+
const docOffsets = documentLineOffsets(text);
|
|
5
|
+
const lineOffsets = buildLineOffsets(text);
|
|
6
|
+
return parsedDocs.map((doc, i) => ({
|
|
7
|
+
sourceLine: docOffsets[i] ?? 0,
|
|
8
|
+
positionIndex: buildPositionIndex(doc, lineOffsets),
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
/** Line numbers (0-indexed) where each YAML document in a multi-doc file
|
|
12
|
+
* starts. The first document is always at line 0; subsequent entries point
|
|
13
|
+
* to the line after each `---` directive. */
|
|
14
|
+
export function documentLineOffsets(text) {
|
|
15
|
+
const offsets = [0];
|
|
16
|
+
const lines = text.split("\n");
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const t = lines[i].trimEnd();
|
|
19
|
+
if (t === "---" || t.startsWith("--- "))
|
|
20
|
+
offsets.push(i + 1);
|
|
21
|
+
}
|
|
22
|
+
return offsets;
|
|
23
|
+
}
|
|
24
|
+
/** Byte-offset → start-of-line lookup table. Index `i` is the byte offset of
|
|
25
|
+
* the first character on line `i`. Used with `offsetToPosition` to turn a
|
|
26
|
+
* yaml-AST node range into Range coordinates. */
|
|
27
|
+
export function buildLineOffsets(text) {
|
|
28
|
+
const offsets = [0];
|
|
29
|
+
for (let i = 0; i < text.length; i++) {
|
|
30
|
+
if (text[i] === "\n")
|
|
31
|
+
offsets.push(i + 1);
|
|
32
|
+
}
|
|
33
|
+
return offsets;
|
|
34
|
+
}
|
|
35
|
+
function offsetToPosition(offset, lineOffsets) {
|
|
36
|
+
let lo = 0;
|
|
37
|
+
let hi = lineOffsets.length - 1;
|
|
38
|
+
while (lo < hi) {
|
|
39
|
+
const mid = (lo + hi + 1) >> 1;
|
|
40
|
+
if (lineOffsets[mid] <= offset)
|
|
41
|
+
lo = mid;
|
|
42
|
+
else
|
|
43
|
+
hi = mid - 1;
|
|
44
|
+
}
|
|
45
|
+
return { line: lo, character: offset - lineOffsets[lo] };
|
|
46
|
+
}
|
|
47
|
+
/** Walks the YAML AST and records source ranges for every field value, keyed
|
|
48
|
+
* by dotted path (e.g. "kind", "config.handler", "config.routes[0].path"). */
|
|
49
|
+
export function buildPositionIndex(doc, lineOffsets) {
|
|
50
|
+
const index = new Map();
|
|
51
|
+
function recordNode(node, path) {
|
|
52
|
+
if (!node || !node.range)
|
|
53
|
+
return;
|
|
54
|
+
const [start, , end] = node.range;
|
|
55
|
+
index.set(path, {
|
|
56
|
+
start: offsetToPosition(start, lineOffsets),
|
|
57
|
+
end: offsetToPosition(end, lineOffsets),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function walk(node, path) {
|
|
61
|
+
if (isMap(node)) {
|
|
62
|
+
for (const pair of node.items) {
|
|
63
|
+
if (!isPair(pair))
|
|
64
|
+
continue;
|
|
65
|
+
const key = isScalar(pair.key) ? String(pair.key.value) : null;
|
|
66
|
+
if (key == null)
|
|
67
|
+
continue;
|
|
68
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
69
|
+
if (pair.value != null) {
|
|
70
|
+
recordNode(pair.value, childPath);
|
|
71
|
+
walk(pair.value, childPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (isSeq(node)) {
|
|
76
|
+
for (let i = 0; i < node.items.length; i++) {
|
|
77
|
+
const item = node.items[i];
|
|
78
|
+
const childPath = `${path}[${i}]`;
|
|
79
|
+
recordNode(item, childPath);
|
|
80
|
+
walk(item, childPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (doc.contents) {
|
|
85
|
+
walk(doc.contents, "");
|
|
86
|
+
}
|
|
87
|
+
return index;
|
|
88
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -83,5 +83,11 @@ export interface AnalysisOptions {
|
|
|
83
83
|
export interface AnalysisContext {
|
|
84
84
|
aliases?: import("./alias-resolver.js").AliasResolver;
|
|
85
85
|
definitions?: import("./definition-registry.js").DefinitionRegistry;
|
|
86
|
+
/** Per-library alias resolvers keyed by the library's module name. Populated by
|
|
87
|
+
* the analyzer when imports are forwarded from inside imported libraries.
|
|
88
|
+
* Validators that resolve schema-side annotations (e.g. x-telo-schema-from
|
|
89
|
+
* pointing at an imported kind) consult the kind owner's scope here, since
|
|
90
|
+
* the consumer's aliases will not contain a library's private imports. */
|
|
91
|
+
aliasesByModule?: Map<string, import("./alias-resolver.js").AliasResolver>;
|
|
86
92
|
}
|
|
87
93
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
|
|
@@ -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,CAsUtB"}
|
|
@@ -61,6 +61,7 @@ export function validateReferences(resources, context) {
|
|
|
61
61
|
const diagnostics = [];
|
|
62
62
|
const aliases = context.aliases;
|
|
63
63
|
const registry = context.definitions;
|
|
64
|
+
const aliasesByModule = context.aliasesByModule;
|
|
64
65
|
if (!aliases || !registry)
|
|
65
66
|
return diagnostics;
|
|
66
67
|
// Build outer resource lookup by name for resolution check.
|
|
@@ -228,6 +229,67 @@ export function validateReferences(resources, context) {
|
|
|
228
229
|
}
|
|
229
230
|
const anchorName = expr.slice(0, slashIdx);
|
|
230
231
|
const jsonPointer = "/" + expr.slice(slashIdx + 1);
|
|
232
|
+
// Aliased absolute kind path — first segment carries a dot, e.g.
|
|
233
|
+
// "HttpDispatch.Outcomes/$defs/Returns". Resolves the alias through the
|
|
234
|
+
// *kind owner's* scope (not the consumer's), navigates the JSON Pointer
|
|
235
|
+
// into the resolved definition's schema, and validates each field value.
|
|
236
|
+
//
|
|
237
|
+
// Relative anchors are property names that cannot contain a dot
|
|
238
|
+
// (CEL-style identifiers), so a dot in anchorName is unambiguous.
|
|
239
|
+
if (!isAbsolute && anchorName.includes(".")) {
|
|
240
|
+
const resolvedResourceKind = aliases.resolveKind(r.kind) ?? r.kind;
|
|
241
|
+
const resourceDef = registry.resolve(r.kind) ?? registry.resolve(resolvedResourceKind);
|
|
242
|
+
const owningModule = resourceDef?.metadata?.module;
|
|
243
|
+
const ownerScope = (owningModule ? aliasesByModule?.get(owningModule) : undefined) ?? aliases;
|
|
244
|
+
const targetKind = ownerScope.resolveKind(anchorName);
|
|
245
|
+
if (!targetKind) {
|
|
246
|
+
diagnostics.push({
|
|
247
|
+
severity: DiagnosticSeverity.Error,
|
|
248
|
+
code: "SCHEMA_FROM_MISSING_PATH",
|
|
249
|
+
source: SOURCE,
|
|
250
|
+
message: `${resourceLabel}: x-telo-schema-from at '${fieldPath}' → cannot resolve alias '${anchorName}'`,
|
|
251
|
+
data: { resource: resourceData, filePath, path: fieldPath },
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const targetDef = registry.resolve(targetKind);
|
|
256
|
+
if (!targetDef?.schema) {
|
|
257
|
+
diagnostics.push({
|
|
258
|
+
severity: DiagnosticSeverity.Error,
|
|
259
|
+
code: "SCHEMA_FROM_MISSING_PATH",
|
|
260
|
+
source: SOURCE,
|
|
261
|
+
message: `${resourceLabel}: x-telo-schema-from at '${fieldPath}' → kind '${targetKind}' has no schema`,
|
|
262
|
+
data: { resource: resourceData, filePath, path: fieldPath },
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const subSchema = navigateJsonPointer(targetDef.schema, jsonPointer);
|
|
267
|
+
if (subSchema === undefined) {
|
|
268
|
+
diagnostics.push({
|
|
269
|
+
severity: DiagnosticSeverity.Error,
|
|
270
|
+
code: "SCHEMA_FROM_MISSING_PATH",
|
|
271
|
+
source: SOURCE,
|
|
272
|
+
message: `${resourceLabel}: x-telo-schema-from at '${fieldPath}' → kind '${targetKind}' has no schema path '${jsonPointer}'`,
|
|
273
|
+
data: { resource: resourceData, filePath, path: fieldPath },
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
for (const fieldValue of resolveFieldValues(r, fieldPath)) {
|
|
278
|
+
if (fieldValue == null)
|
|
279
|
+
continue;
|
|
280
|
+
const issues = registry.validateWithRefs(fieldValue, subSchema);
|
|
281
|
+
for (const issue of issues) {
|
|
282
|
+
diagnostics.push({
|
|
283
|
+
severity: DiagnosticSeverity.Error,
|
|
284
|
+
code: "DEPENDENT_SCHEMA_MISMATCH",
|
|
285
|
+
source: SOURCE,
|
|
286
|
+
message: `${resourceLabel}: '${fieldPath}' does not match schema from '${anchorName}${jsonPointer}': ${issue}`,
|
|
287
|
+
data: { resource: resourceData, filePath, path: fieldPath },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
231
293
|
// Derive the anchor path in the resource config.
|
|
232
294
|
let anchorPath;
|
|
233
295
|
if (isAbsolute) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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.10.0",
|
|
45
|
+
"@telorun/templating": "0.2.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^20.0.0",
|
package/src/analysis-registry.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { AnalysisContext } from "./types.js";
|
|
|
14
14
|
export class AnalysisRegistry {
|
|
15
15
|
private readonly defs = new DefinitionRegistry();
|
|
16
16
|
private readonly aliases = new AliasResolver();
|
|
17
|
+
private readonly aliasesByModule = new Map<string, AliasResolver>();
|
|
17
18
|
|
|
18
19
|
registerDefinition(def: ResourceDefinition): void {
|
|
19
20
|
this.defs.register(def);
|
|
@@ -94,6 +95,6 @@ export class AnalysisRegistry {
|
|
|
94
95
|
|
|
95
96
|
/** @internal Bridge for StaticAnalyzer — do not use outside the analyzer package. */
|
|
96
97
|
_context(): AnalysisContext {
|
|
97
|
-
return { aliases: this.aliases, definitions: this.defs };
|
|
98
|
+
return { aliases: this.aliases, definitions: this.defs, aliasesByModule: this.aliasesByModule };
|
|
98
99
|
}
|
|
99
100
|
}
|
package/src/analyzer.ts
CHANGED
|
@@ -433,7 +433,7 @@ export class StaticAnalyzer {
|
|
|
433
433
|
rootModules.add(m.metadata.name as string);
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
|
-
const aliasesByModule = new Map<string, AliasResolver>();
|
|
436
|
+
const aliasesByModule = ctx?.aliasesByModule ?? new Map<string, AliasResolver>();
|
|
437
437
|
for (const m of manifests) {
|
|
438
438
|
if (isModuleKind(m.kind)) {
|
|
439
439
|
const namespace = ((m.metadata as any).namespace as string | undefined) ?? null;
|
|
@@ -715,7 +715,9 @@ export class StaticAnalyzer {
|
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
// Validate resource references (Phase 3)
|
|
718
|
-
diagnostics.push(
|
|
718
|
+
diagnostics.push(
|
|
719
|
+
...validateReferences(allManifests, { aliases, definitions: defs, aliasesByModule }),
|
|
720
|
+
);
|
|
719
721
|
|
|
720
722
|
// Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
|
|
721
723
|
diagnostics.push(...validateExtends(allManifests, defs, aliases));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { LoadedFile, LoadedGraph, LoadedModule } from "./loaded-types.js";
|
|
3
|
+
import { isModuleKind } from "./module-kinds.js";
|
|
4
|
+
|
|
5
|
+
/** Produce the flat manifest list `analyze()` consumes today.
|
|
6
|
+
*
|
|
7
|
+
* Combines the entry module's manifests with `Telo.Definition`,
|
|
8
|
+
* `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries.
|
|
9
|
+
* Stamps three flavours of metadata along the way:
|
|
10
|
+
*
|
|
11
|
+
* - `metadata.source` and `metadata.sourceLine` — already on each manifest
|
|
12
|
+
* from `parseLoadedFile`, copied here unchanged.
|
|
13
|
+
* - `metadata.module` — the owning module's `Telo.Application` /
|
|
14
|
+
* `Telo.Library` `metadata.name`, applied to non-module manifests that
|
|
15
|
+
* don't already carry one.
|
|
16
|
+
* - `metadata.resolvedModuleName` / `metadata.resolvedNamespace` — for every
|
|
17
|
+
* `Telo.Import` manifest, looked up via `graph.importEdges` to find the
|
|
18
|
+
* target module's own `Telo.Library` identity. Without this, the
|
|
19
|
+
* analyzer's alias resolver and `validate-extends` fall back to
|
|
20
|
+
* path-derived identity and produce spurious diagnostics.
|
|
21
|
+
*
|
|
22
|
+
* Position metadata (`positionIndex`) is NOT stamped on manifests —
|
|
23
|
+
* callers look it up via `findPositions(graph, ...)` on the LoadedGraph. */
|
|
24
|
+
export function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[] {
|
|
25
|
+
const result: ResourceManifest[] = [];
|
|
26
|
+
|
|
27
|
+
const stampedEntry = collectModuleManifests(graph.entry);
|
|
28
|
+
result.push(...stampedEntry);
|
|
29
|
+
|
|
30
|
+
const seen = new Set<string>([graph.rootSource]);
|
|
31
|
+
const queue: string[] = [graph.rootSource];
|
|
32
|
+
|
|
33
|
+
while (queue.length > 0) {
|
|
34
|
+
const fromSource = queue.shift()!;
|
|
35
|
+
const edges = graph.importEdges.get(fromSource);
|
|
36
|
+
if (!edges) continue;
|
|
37
|
+
|
|
38
|
+
for (const edge of edges.values()) {
|
|
39
|
+
if (seen.has(edge.targetSource)) continue;
|
|
40
|
+
seen.add(edge.targetSource);
|
|
41
|
+
queue.push(edge.targetSource);
|
|
42
|
+
|
|
43
|
+
const targetModule = graph.modules.get(edge.targetSource);
|
|
44
|
+
if (!targetModule) continue;
|
|
45
|
+
|
|
46
|
+
const stamped = collectModuleManifests(targetModule);
|
|
47
|
+
for (const m of stamped) {
|
|
48
|
+
if (
|
|
49
|
+
m.kind === "Telo.Definition" ||
|
|
50
|
+
m.kind === "Telo.Abstract" ||
|
|
51
|
+
m.kind === "Telo.Import"
|
|
52
|
+
) {
|
|
53
|
+
result.push(m);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Stamp resolved import identity on every Telo.Import in the result by
|
|
60
|
+
// reading the edge's pre-resolved name/namespace — no re-derivation from
|
|
61
|
+
// manifest metadata. The edge is keyed by (owner-file, alias) which is
|
|
62
|
+
// exactly the (metadata.source, metadata.name) pair on each Telo.Import.
|
|
63
|
+
for (let i = 0; i < result.length; i++) {
|
|
64
|
+
const m = result[i];
|
|
65
|
+
if (m.kind !== "Telo.Import") continue;
|
|
66
|
+
const owner = (m.metadata as { source?: string } | undefined)?.source;
|
|
67
|
+
const alias = m.metadata?.name as string | undefined;
|
|
68
|
+
if (!owner || !alias) continue;
|
|
69
|
+
const edge = graph.importEdges.get(owner)?.get(alias);
|
|
70
|
+
if (!edge?.targetModuleName) continue;
|
|
71
|
+
|
|
72
|
+
const newMetadata: Record<string, unknown> = {
|
|
73
|
+
...m.metadata,
|
|
74
|
+
resolvedModuleName: edge.targetModuleName,
|
|
75
|
+
resolvedNamespace: edge.targetNamespace,
|
|
76
|
+
};
|
|
77
|
+
result[i] = { ...m, metadata: newMetadata as ResourceManifest["metadata"] };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
|
|
84
|
+
* with `metadata.module` stamped on non-module docs. The kernel's runtime
|
|
85
|
+
* entry load uses this to convert a `Loader.loadModule` result into the
|
|
86
|
+
* classic ResourceManifest[] shape it iterates over. Imports are not
|
|
87
|
+
* followed — the kernel's import-controller loads each import's module
|
|
88
|
+
* separately at runtime. */
|
|
89
|
+
export function flattenLoadedModule(mod: LoadedModule): ResourceManifest[] {
|
|
90
|
+
return collectModuleManifests(mod);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collectModuleManifests(mod: LoadedModule): ResourceManifest[] {
|
|
94
|
+
const owner = stampFile(mod.owner, ownerModuleName(mod.owner));
|
|
95
|
+
const partials: ResourceManifest[] = [];
|
|
96
|
+
for (const p of mod.partials) {
|
|
97
|
+
partials.push(...stampFile(p, ownerModuleName(mod.owner)));
|
|
98
|
+
}
|
|
99
|
+
return [...owner, ...partials];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ownerModuleName(file: LoadedFile): string | undefined {
|
|
103
|
+
for (const m of file.manifests) {
|
|
104
|
+
if (m && isModuleKind(m.kind)) {
|
|
105
|
+
const name = m.metadata?.name;
|
|
106
|
+
if (typeof name === "string") return name;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function stampFile(
|
|
113
|
+
file: LoadedFile,
|
|
114
|
+
ownerModule: string | undefined,
|
|
115
|
+
): ResourceManifest[] {
|
|
116
|
+
const out: ResourceManifest[] = [];
|
|
117
|
+
for (let i = 0; i < file.manifests.length; i++) {
|
|
118
|
+
const m = file.manifests[i];
|
|
119
|
+
if (m === null || m === undefined) continue;
|
|
120
|
+
const { sourceLine } = file.positions[i];
|
|
121
|
+
|
|
122
|
+
const metadata: Record<string, unknown> = {
|
|
123
|
+
...m.metadata,
|
|
124
|
+
source: file.source,
|
|
125
|
+
sourceLine,
|
|
126
|
+
};
|
|
127
|
+
if (ownerModule && !isModuleKind(m.kind) && !metadata.module) {
|
|
128
|
+
metadata.module = ownerModule;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
out.push({ ...m, metadata: metadata as ResourceManifest["metadata"] });
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
export { AnalysisRegistry } from "./analysis-registry.js";
|
|
2
2
|
export { StaticAnalyzer } from "./analyzer.js";
|
|
3
|
+
export type {
|
|
4
|
+
GraphLoadError,
|
|
5
|
+
ImportEdge,
|
|
6
|
+
LoadedFile,
|
|
7
|
+
LoadedGraph,
|
|
8
|
+
LoadedModule,
|
|
9
|
+
ParseError,
|
|
10
|
+
} from "./loaded-types.js";
|
|
11
|
+
export { flattenForAnalyzer, flattenLoadedModule } from "./flatten-for-analyzer.js";
|
|
3
12
|
export { Loader } from "./manifest-loader.js";
|
|
4
13
|
export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
|
|
5
14
|
export type { ModuleKind } from "./module-kinds.js";
|
|
15
|
+
export { parseLoadedFile } from "./parse-loaded-file.js";
|
|
16
|
+
export type { ParseOptions } from "./parse-loaded-file.js";
|
|
17
|
+
export {
|
|
18
|
+
buildDocumentPositions,
|
|
19
|
+
buildLineOffsets,
|
|
20
|
+
buildPositionIndex,
|
|
21
|
+
documentLineOffsets,
|
|
22
|
+
} from "./position-metadata.js";
|
|
23
|
+
export type { DocumentPosition } from "./position-metadata.js";
|
|
6
24
|
export { HttpSource } from "./sources/http-source.js";
|
|
7
25
|
export { RegistrySource } from "./sources/registry-source.js";
|
|
8
26
|
export { DEFAULT_MANIFEST_FILENAME, DiagnosticSeverity } from "./types.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { Document } from "yaml";
|
|
3
|
+
import type { DocumentPosition } from "./position-metadata.js";
|
|
4
|
+
import type { Range } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/** One physical file's parsed result. Returned for the owner manifest, for
|
|
7
|
+
* each `include:` partial, and for each external import target.
|
|
8
|
+
*
|
|
9
|
+
* Identity rule: every map key, every cross-reference, every editor-side
|
|
10
|
+
* cache uses `source` — the URL the source adapter's `read()` returned. */
|
|
11
|
+
export interface LoadedFile {
|
|
12
|
+
/** Canonical identity. The URL the source adapter's `read()` returned —
|
|
13
|
+
* HTTPS for http/registry, an absolute path for local. */
|
|
14
|
+
source: string;
|
|
15
|
+
/** The URL the caller supplied (e.g. registry ref `std/javascript@0.3.0`).
|
|
16
|
+
* Differs from `source` only for adapter-resolved URLs. */
|
|
17
|
+
requestedUrl: string;
|
|
18
|
+
/** Raw text exactly as `read()` returned it. */
|
|
19
|
+
text: string;
|
|
20
|
+
/** Per-document parsed AST, in source order. */
|
|
21
|
+
documents: Document[];
|
|
22
|
+
/** Per-document JSON projection (`doc.toJSON()`). Aligned to `documents`. */
|
|
23
|
+
manifests: Array<ResourceManifest | null>;
|
|
24
|
+
/** Per-document `{sourceLine, positionIndex}`. Aligned to `documents`. */
|
|
25
|
+
positions: DocumentPosition[];
|
|
26
|
+
/** Document-level parse errors aggregated from `yaml.Document.errors`. */
|
|
27
|
+
parseErrors: ParseError[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ParseError {
|
|
31
|
+
documentIndex: number;
|
|
32
|
+
message: string;
|
|
33
|
+
/** Line/character of the failure, when the yaml parser provided one. */
|
|
34
|
+
range?: Range;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** An owner file plus the partial files it includes. The unit
|
|
38
|
+
* `Loader.loadModule` returns. */
|
|
39
|
+
export interface LoadedModule {
|
|
40
|
+
owner: LoadedFile;
|
|
41
|
+
/** Each `include:` target as its own LoadedFile. Empty when no `include:`.
|
|
42
|
+
* Order matches the `include:` list (after glob expansion). */
|
|
43
|
+
partials: LoadedFile[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolved Telo.Import edge: where the import points and what library
|
|
47
|
+
* identity it resolves to. Carrying name/namespace on the edge means
|
|
48
|
+
* `flattenForAnalyzer` can stamp `metadata.resolvedModuleName` /
|
|
49
|
+
* `resolvedNamespace` from this single source rather than re-deriving
|
|
50
|
+
* the target from manifest metadata, which would silently miss whenever
|
|
51
|
+
* a future projection forgets to stamp `metadata.source` consistently. */
|
|
52
|
+
export interface ImportEdge {
|
|
53
|
+
/** Canonical resolved URL of the target — a key into `modules`. */
|
|
54
|
+
targetSource: string;
|
|
55
|
+
/** Target library's `metadata.name`, or `null` when the target had no
|
|
56
|
+
* Telo.Library doc (an error case captured in `LoadedGraph.errors`). */
|
|
57
|
+
targetModuleName: string | null;
|
|
58
|
+
/** Target library's `metadata.namespace` (or `null` when unset). */
|
|
59
|
+
targetNamespace: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** An entry plus every transitively-imported library. Returned by
|
|
63
|
+
* `Loader.loadGraph`. */
|
|
64
|
+
export interface LoadedGraph {
|
|
65
|
+
/** Canonical entry source — equals `entry.owner.source`. */
|
|
66
|
+
rootSource: string;
|
|
67
|
+
entry: LoadedModule;
|
|
68
|
+
/** Map keyed by `LoadedFile.source` (canonical resolved URL). Includes
|
|
69
|
+
* entry, partials, and every transitively reachable Telo.Import target +
|
|
70
|
+
* its partials. */
|
|
71
|
+
modules: Map<string, LoadedModule>;
|
|
72
|
+
/** Per-Telo.Import resolution. Keyed by the resolved URL of the file the
|
|
73
|
+
* Telo.Import was declared in, then by the import's PascalCase alias. */
|
|
74
|
+
importEdges: Map<string, Map<string, ImportEdge>>;
|
|
75
|
+
/** Surface-level errors that did not abort the graph load (e.g. an import
|
|
76
|
+
* whose target failed to fetch). */
|
|
77
|
+
errors: GraphLoadError[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface GraphLoadError {
|
|
81
|
+
/** URL of the file that failed to load. */
|
|
82
|
+
url: string;
|
|
83
|
+
/** Source of the import that triggered the load, or null for the entry. */
|
|
84
|
+
fromSource: string | null;
|
|
85
|
+
error: Error;
|
|
86
|
+
}
|