@telorun/analyzer 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/alias-resolver.d.ts +4 -0
- package/dist/alias-resolver.d.ts.map +1 -1
- package/dist/alias-resolver.js +11 -0
- package/dist/analysis-registry.d.ts +11 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +18 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +68 -23
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +11 -1
- package/dist/definition-registry.d.ts +1 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +25 -7
- package/dist/kind-suggest.d.ts +15 -0
- package/dist/kind-suggest.d.ts.map +1 -0
- package/dist/kind-suggest.js +63 -0
- package/dist/levenshtein.d.ts +5 -0
- package/dist/levenshtein.d.ts.map +1 -0
- package/dist/levenshtein.js +34 -0
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +65 -32
- package/dist/validate-extends.d.ts +26 -0
- package/dist/validate-extends.d.ts.map +1 -0
- package/dist/validate-extends.js +163 -0
- package/package.json +2 -2
- package/src/alias-resolver.ts +11 -0
- package/src/analysis-registry.ts +21 -0
- package/src/analyzer.ts +69 -23
- package/src/builtins.ts +11 -1
- package/src/definition-registry.ts +24 -6
- package/src/kind-suggest.ts +77 -0
- package/src/levenshtein.ts +36 -0
- package/src/manifest-loader.ts +72 -30
- package/src/validate-extends.ts +175 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Telo: YAML-driven execution engine for declarative backends with micro-kernel architecture and language-agnostic design"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# ⚡ Telo
|
|
2
6
|
|
|
3
7
|
Runtime for declarative backends.
|
|
@@ -73,6 +77,7 @@ connection:
|
|
|
73
77
|
kind: Sql.Migration
|
|
74
78
|
metadata:
|
|
75
79
|
name: Migration_20260413_182154_CreateFeedback
|
|
80
|
+
version: 20260413_182154_CreateFeedback
|
|
76
81
|
sql: |
|
|
77
82
|
CREATE TABLE IF NOT EXISTS feedback (
|
|
78
83
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
package/dist/alias-resolver.d.ts
CHANGED
|
@@ -8,5 +8,9 @@ export declare class AliasResolver {
|
|
|
8
8
|
resolveKind(kind: string): string | undefined;
|
|
9
9
|
hasAlias(alias: string): boolean;
|
|
10
10
|
knownAliases(): string[];
|
|
11
|
+
/** Returns every alias that currently points at `targetModule`.
|
|
12
|
+
* Used by clients that need to convert a canonical kind key (e.g. "http-server.Server")
|
|
13
|
+
* back into its user-facing alias form (e.g. "Http.Server"). */
|
|
14
|
+
aliasesFor(targetModule: string): string[];
|
|
11
15
|
}
|
|
12
16
|
//# sourceMappingURL=alias-resolver.d.ts.map
|
|
@@ -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;
|
|
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"}
|
package/dist/alias-resolver.js
CHANGED
|
@@ -33,4 +33,15 @@ export class AliasResolver {
|
|
|
33
33
|
knownAliases() {
|
|
34
34
|
return Array.from(this.importAliases.keys());
|
|
35
35
|
}
|
|
36
|
+
/** Returns every alias that currently points at `targetModule`.
|
|
37
|
+
* Used by clients that need to convert a canonical kind key (e.g. "http-server.Server")
|
|
38
|
+
* back into its user-facing alias form (e.g. "Http.Server"). */
|
|
39
|
+
aliasesFor(targetModule) {
|
|
40
|
+
const result = [];
|
|
41
|
+
for (const [alias, mod] of this.importAliases) {
|
|
42
|
+
if (mod === targetModule)
|
|
43
|
+
result.push(alias);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
36
47
|
}
|
|
@@ -25,6 +25,17 @@ export declare class AnalysisRegistry {
|
|
|
25
25
|
builtinDefinitions(): ResourceDefinition[];
|
|
26
26
|
resolveDefinition(kind: string): ResourceDefinition | undefined;
|
|
27
27
|
allKinds(): string[];
|
|
28
|
+
/** Returns every import alias that points at `moduleName` (the canonical, kebab-case
|
|
29
|
+
* module name). Empty when no import declares that target. */
|
|
30
|
+
aliasesFor(moduleName: string): string[];
|
|
31
|
+
/** Returns every user-facing kind that is legal in the current scope:
|
|
32
|
+
* Telo root kinds plus the alias form of each non-abstract imported definition.
|
|
33
|
+
* Used by editor hosts to drive completion and by the analyzer to produce
|
|
34
|
+
* "did you mean" hints. */
|
|
35
|
+
validUserFacingKinds(): string[];
|
|
36
|
+
/** Returns the closest user-facing kind to `badKind`, or undefined when nothing
|
|
37
|
+
* is close enough (or multiple candidates tie). Case-sensitive. */
|
|
38
|
+
suggestKind(badKind: string): string | undefined;
|
|
28
39
|
/** @internal Bridge for StaticAnalyzer — do not use outside the analyzer package. */
|
|
29
40
|
_context(): AnalysisContext;
|
|
30
41
|
}
|
|
@@ -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;
|
|
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;IAE/C,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;;;OAGG;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;IAcP;;;;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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AliasResolver } from "./alias-resolver.js";
|
|
2
2
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
3
3
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.js";
|
|
4
5
|
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
5
6
|
/**
|
|
6
7
|
* Accumulates type and alias knowledge for a running kernel or analysis session.
|
|
@@ -56,6 +57,23 @@ export class AnalysisRegistry {
|
|
|
56
57
|
allKinds() {
|
|
57
58
|
return this._context().definitions?.kinds() ?? [];
|
|
58
59
|
}
|
|
60
|
+
/** Returns every import alias that points at `moduleName` (the canonical, kebab-case
|
|
61
|
+
* module name). Empty when no import declares that target. */
|
|
62
|
+
aliasesFor(moduleName) {
|
|
63
|
+
return this.aliases.aliasesFor(moduleName);
|
|
64
|
+
}
|
|
65
|
+
/** Returns every user-facing kind that is legal in the current scope:
|
|
66
|
+
* Telo root kinds plus the alias form of each non-abstract imported definition.
|
|
67
|
+
* Used by editor hosts to drive completion and by the analyzer to produce
|
|
68
|
+
* "did you mean" hints. */
|
|
69
|
+
validUserFacingKinds() {
|
|
70
|
+
return computeValidUserFacingKinds(this.aliases, this.defs);
|
|
71
|
+
}
|
|
72
|
+
/** Returns the closest user-facing kind to `badKind`, or undefined when nothing
|
|
73
|
+
* is close enough (or multiple candidates tie). Case-sensitive. */
|
|
74
|
+
suggestKind(badKind) {
|
|
75
|
+
return computeSuggestKind(badKind, this.aliases, this.defs);
|
|
76
|
+
}
|
|
59
77
|
/** @internal Bridge for StaticAnalyzer — do not use outside the analyzer package. */
|
|
60
78
|
_context() {
|
|
61
79
|
return { aliases: this.aliases, definitions: this.defs };
|
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;AAGzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGzE,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;AAuP/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;IAsSvB,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;IAKxF,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;CAiB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -3,11 +3,13 @@ import { buildCelEnvironment, buildTypedCelEnvironment, } from "./cel-environmen
|
|
|
3
3
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
4
4
|
import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
|
|
5
5
|
import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kernel-globals.js";
|
|
6
|
+
import { computeSuggestKind } from "./kind-suggest.js";
|
|
6
7
|
import { isModuleKind } from "./module-kinds.js";
|
|
7
8
|
import { normalizeInlineResources } from "./normalize-inline-resources.js";
|
|
8
9
|
import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
|
|
9
10
|
import { DiagnosticSeverity } from "./types.js";
|
|
10
11
|
import { extractAccessChains, getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, validateChainAgainstSchema, } from "./validate-cel-context.js";
|
|
12
|
+
import { validateExtends } from "./validate-extends.js";
|
|
11
13
|
import { validateReferences } from "./validate-references.js";
|
|
12
14
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
13
15
|
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
@@ -209,9 +211,24 @@ export class StaticAnalyzer {
|
|
|
209
211
|
// Register module identities and aliases.
|
|
210
212
|
// The root module doc (Telo.Application or Telo.Library) provides its own
|
|
211
213
|
// identity; imported modules surface their identity via resolvedModuleName/
|
|
212
|
-
// resolvedNamespace stamped onto the Telo.Import by the loader
|
|
213
|
-
//
|
|
214
|
-
//
|
|
214
|
+
// resolvedNamespace stamped onto the Telo.Import by the loader.
|
|
215
|
+
//
|
|
216
|
+
// Two alias scopes are tracked:
|
|
217
|
+
// - `aliases` — the consumer's aliases, populated from Telo.Imports declared in
|
|
218
|
+
// the entry manifest (its own module).
|
|
219
|
+
// - `aliasesByModule` — per-imported-library aliases, populated from Telo.Imports
|
|
220
|
+
// forwarded by the loader from inside imported libraries. A library may use
|
|
221
|
+
// different alias names than the consumer for the same dependency; resolving
|
|
222
|
+
// a forwarded def's `extends` / `capability` against the consumer's scope
|
|
223
|
+
// would either fail or pick the wrong target. Each forwarded def is normalized
|
|
224
|
+
// in its own library's scope.
|
|
225
|
+
const rootModules = new Set();
|
|
226
|
+
for (const m of manifests) {
|
|
227
|
+
if (isModuleKind(m.kind) && m.metadata?.name) {
|
|
228
|
+
rootModules.add(m.metadata.name);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const aliasesByModule = new Map();
|
|
215
232
|
for (const m of manifests) {
|
|
216
233
|
if (isModuleKind(m.kind)) {
|
|
217
234
|
const namespace = m.metadata.namespace ?? null;
|
|
@@ -225,26 +242,58 @@ export class StaticAnalyzer {
|
|
|
225
242
|
const exportedKinds = m.exports?.kinds ?? [];
|
|
226
243
|
const resolvedModuleName = m.metadata.resolvedModuleName;
|
|
227
244
|
const resolvedNamespace = m.metadata.resolvedNamespace;
|
|
245
|
+
const ownModule = m.metadata?.module;
|
|
228
246
|
if (alias && source) {
|
|
229
247
|
const targetModule = resolvedModuleName ?? source.split("/").filter(Boolean).pop() ?? source;
|
|
230
|
-
|
|
248
|
+
// Module identity is registered globally so x-telo-ref resolution sees
|
|
249
|
+
// transitively-imported modules regardless of which scope brought them in.
|
|
231
250
|
if (resolvedModuleName) {
|
|
232
251
|
defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
|
|
233
252
|
}
|
|
253
|
+
// Alias registration is scoped: consumer imports vs. imported-library imports.
|
|
254
|
+
if (!ownModule || rootModules.has(ownModule)) {
|
|
255
|
+
aliases.registerImport(alias, targetModule, exportedKinds);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
let libResolver = aliasesByModule.get(ownModule);
|
|
259
|
+
if (!libResolver) {
|
|
260
|
+
libResolver = new AliasResolver();
|
|
261
|
+
aliasesByModule.set(ownModule, libResolver);
|
|
262
|
+
}
|
|
263
|
+
libResolver.registerImport(alias, targetModule, exportedKinds);
|
|
264
|
+
}
|
|
234
265
|
}
|
|
235
266
|
}
|
|
236
267
|
}
|
|
237
|
-
// Register definitions from Telo.Definition resources.
|
|
238
|
-
//
|
|
239
|
-
//
|
|
268
|
+
// Register definitions from Telo.Definition AND Telo.Abstract resources.
|
|
269
|
+
// Abstracts declare contracts that implementations target via `extends` (canonical)
|
|
270
|
+
// or `capability: <AbstractKind>` (legacy). Until they're registered, validateReferences
|
|
271
|
+
// can't resolve x-telo-ref entries pointing at library-declared abstracts — so abstracts
|
|
272
|
+
// must go through register() too, not just the kernel builtins in the constructor.
|
|
273
|
+
//
|
|
274
|
+
// Normalize alias-prefixed `capability` and `extends` to canonical form using the
|
|
275
|
+
// declaring scope's resolver, so `extendedBy` is keyed by canonical kind regardless
|
|
276
|
+
// of alias choices. `capability` covers the legacy implements-this-abstract overload;
|
|
277
|
+
// `extends` is the canonical first-class form.
|
|
240
278
|
for (const m of manifests) {
|
|
241
|
-
if (m.kind
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
279
|
+
if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract")
|
|
280
|
+
continue;
|
|
281
|
+
const def = m;
|
|
282
|
+
const ownModule = def.metadata?.module;
|
|
283
|
+
const scopeResolver = ownModule && !rootModules.has(ownModule)
|
|
284
|
+
? (aliasesByModule.get(ownModule) ?? new AliasResolver())
|
|
285
|
+
: aliases;
|
|
286
|
+
const resolvedCapability = def.capability
|
|
287
|
+
? (scopeResolver.resolveKind(def.capability) ?? def.capability)
|
|
288
|
+
: def.capability;
|
|
289
|
+
const resolvedExtends = def.extends
|
|
290
|
+
? (scopeResolver.resolveKind(def.extends) ?? def.extends)
|
|
291
|
+
: def.extends;
|
|
292
|
+
const needsPatch = resolvedCapability !== def.capability || resolvedExtends !== def.extends;
|
|
293
|
+
const normalized = needsPatch
|
|
294
|
+
? { ...def, capability: resolvedCapability, extends: resolvedExtends }
|
|
295
|
+
: def;
|
|
296
|
+
defs.register(normalized);
|
|
248
297
|
}
|
|
249
298
|
// Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
|
|
250
299
|
const allManifests = normalizeInlineResources(manifests, defs, aliases);
|
|
@@ -281,20 +330,14 @@ export class StaticAnalyzer {
|
|
|
281
330
|
const resolvedKind = aliases.resolveKind(m.kind);
|
|
282
331
|
const definition = defs.resolve(m.kind) ?? (resolvedKind ? defs.resolve(resolvedKind) : undefined);
|
|
283
332
|
if (!definition) {
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const parts = [];
|
|
287
|
-
if (knownAliases.length > 0)
|
|
288
|
-
parts.push(`imports: ${knownAliases.join(", ")}`);
|
|
289
|
-
if (knownKinds.length > 0)
|
|
290
|
-
parts.push(`kinds: ${knownKinds.join(", ")}`);
|
|
291
|
-
const hint = parts.length > 0 ? ` Known ${parts.join(" | ")}` : "";
|
|
333
|
+
const suggestedKind = computeSuggestKind(m.kind, aliases, defs);
|
|
334
|
+
const hint = suggestedKind ? ` Did you mean '${suggestedKind}'?` : "";
|
|
292
335
|
diagnostics.push({
|
|
293
336
|
severity: DiagnosticSeverity.Error,
|
|
294
337
|
code: "UNDEFINED_KIND",
|
|
295
338
|
source: SOURCE,
|
|
296
339
|
message: `No Telo.Definition found for kind '${m.kind}'.${hint}`,
|
|
297
|
-
data: { resource, filePath, path: "kind" },
|
|
340
|
+
data: { resource, filePath, path: "kind", suggestedKind },
|
|
298
341
|
});
|
|
299
342
|
continue;
|
|
300
343
|
}
|
|
@@ -406,6 +449,8 @@ export class StaticAnalyzer {
|
|
|
406
449
|
}
|
|
407
450
|
// Validate resource references (Phase 3)
|
|
408
451
|
diagnostics.push(...validateReferences(allManifests, { aliases, definitions: defs }));
|
|
452
|
+
// Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
|
|
453
|
+
diagnostics.push(...validateExtends(allManifests, defs, aliases));
|
|
409
454
|
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
|
|
410
455
|
diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv));
|
|
411
456
|
return diagnostics;
|
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,EAsJ/C,CAAC"}
|
package/dist/builtins.js
CHANGED
|
@@ -25,9 +25,13 @@ export const KERNEL_BUILTINS = [
|
|
|
25
25
|
additionalProperties: true,
|
|
26
26
|
},
|
|
27
27
|
capability: { type: "string" },
|
|
28
|
+
schema: { type: "object", additionalProperties: true },
|
|
28
29
|
},
|
|
29
30
|
required: ["metadata"],
|
|
30
|
-
|
|
31
|
+
// Telo.Abstract is an extension point by design — it must accept forward-compatible
|
|
32
|
+
// fields (e.g. inputType/outputType from the typed-abstracts plan) without requiring
|
|
33
|
+
// the analyzer to enumerate them here.
|
|
34
|
+
additionalProperties: true,
|
|
31
35
|
},
|
|
32
36
|
},
|
|
33
37
|
{
|
|
@@ -53,6 +57,12 @@ export const KERNEL_BUILTINS = [
|
|
|
53
57
|
source: { type: "string" },
|
|
54
58
|
variables: { type: "object" },
|
|
55
59
|
secrets: { type: "object" },
|
|
60
|
+
runtime: {
|
|
61
|
+
oneOf: [
|
|
62
|
+
{ type: "string" },
|
|
63
|
+
{ type: "array", items: { type: "string" } },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
56
66
|
},
|
|
57
67
|
required: ["metadata", "source"],
|
|
58
68
|
additionalProperties: false,
|
|
@@ -20,6 +20,7 @@ export declare class DefinitionRegistry {
|
|
|
20
20
|
* Used to compute definition $id values for the AJV schema store. */
|
|
21
21
|
private readonly reverseIdentityMap;
|
|
22
22
|
register(definition: ResourceDefinition): void;
|
|
23
|
+
private addExtendedBy;
|
|
23
24
|
/** Register a module identity for x-telo-ref resolution.
|
|
24
25
|
* Call once per module doc (Telo.Application or Telo.Library) when the manifest is loaded.
|
|
25
26
|
* @param namespace The module's metadata.namespace (e.g. "std"), or null for telo built-ins.
|
|
@@ -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;AAEvD,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG1F,+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;
|
|
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;AAEvD,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG1F,+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;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
|
|
@@ -28,14 +28,22 @@ export class DefinitionRegistry {
|
|
|
28
28
|
const key = mod ? `${mod}.${name}` : name;
|
|
29
29
|
this.defs.set(key, definition);
|
|
30
30
|
this.fieldMaps.set(key, buildReferenceFieldMap(definition.schema ?? {}));
|
|
31
|
+
// `capability` populates extendedBy for backward-compat with the legacy pattern where
|
|
32
|
+
// a concrete definition overloaded `capability: <AbstractKind>` to mean "implements
|
|
33
|
+
// this abstract." The canonical pattern is `extends` (below). Both populate the index,
|
|
34
|
+
// unioned — so in-flight modules pre-migration keep working.
|
|
31
35
|
if (definition.capability) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
this.addExtendedBy(definition.capability, key);
|
|
37
|
+
}
|
|
38
|
+
// `extends` — first-class "implements-this-abstract" edge. Alias-form resolution
|
|
39
|
+
// happens in the analyzer before register() is called (analyzer.ts pre-resolves
|
|
40
|
+
// via aliases.resolveKind), so the value here is already the canonical kind string
|
|
41
|
+
// (e.g. "workflow.Backend"). If the analyzer could not resolve the alias (partial
|
|
42
|
+
// context, or the declaring file doesn't import the target's alias), the value
|
|
43
|
+
// stays as the original alias-prefixed form; validateExtends emits EXTENDS_MALFORMED
|
|
44
|
+
// or EXTENDS_UNKNOWN_TARGET depending on the case.
|
|
45
|
+
if (definition.extends) {
|
|
46
|
+
this.addExtendedBy(definition.extends, key);
|
|
39
47
|
}
|
|
40
48
|
// Auto-register the telo identity when any Telo built-in is registered.
|
|
41
49
|
if (definition.kind === "Telo.Abstract" && mod === "Telo") {
|
|
@@ -47,6 +55,16 @@ export class DefinitionRegistry {
|
|
|
47
55
|
this.tryRegisterSchema(mod, name, definition.schema);
|
|
48
56
|
}
|
|
49
57
|
}
|
|
58
|
+
addExtendedBy(parent, child) {
|
|
59
|
+
const children = this.extendedBy.get(parent);
|
|
60
|
+
if (children) {
|
|
61
|
+
if (!children.includes(child))
|
|
62
|
+
children.push(child);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.extendedBy.set(parent, [child]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
50
68
|
/** Register a module identity for x-telo-ref resolution.
|
|
51
69
|
* Call once per module doc (Telo.Application or Telo.Library) when the manifest is loaded.
|
|
52
70
|
* @param namespace The module's metadata.namespace (e.g. "std"), or null for telo built-ins.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
2
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
3
|
+
/** Computes the set of user-facing kind strings available in the given
|
|
4
|
+
* (aliases, defs) context:
|
|
5
|
+
* - The hardcoded Telo root kinds.
|
|
6
|
+
* - For every registered non-abstract definition, the alias form
|
|
7
|
+
* `${alias}.${TypeName}` for each import alias that points at its
|
|
8
|
+
* module. Canonical kebab-case forms (e.g. `http-server.Server`) are
|
|
9
|
+
* deliberately excluded — users write manifests via alias. */
|
|
10
|
+
export declare function computeValidUserFacingKinds(aliases: AliasResolver, defs: DefinitionRegistry): string[];
|
|
11
|
+
/** Returns the closest user-facing kind to `badKind` within an edit-distance
|
|
12
|
+
* threshold, or undefined when nothing close enough exists (including ties).
|
|
13
|
+
* Case-sensitive — kinds are PascalCase by contract. */
|
|
14
|
+
export declare function computeSuggestKind(badKind: string, aliases: AliasResolver, defs: DefinitionRegistry): string | undefined;
|
|
15
|
+
//# sourceMappingURL=kind-suggest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kind-suggest.d.ts","sourceRoot":"","sources":["../src/kind-suggest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAenE;;;;;;mEAMmE;AACnE,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,kBAAkB,GACvB,MAAM,EAAE,CAkBV;AAED;;yDAEyD;AACzD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,kBAAkB,GACvB,MAAM,GAAG,SAAS,CAuBpB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { distance } from "./levenshtein.js";
|
|
2
|
+
/** User-facing root kinds that are always legal regardless of imports. */
|
|
3
|
+
const ROOT_KINDS = [
|
|
4
|
+
"Telo.Application",
|
|
5
|
+
"Telo.Library",
|
|
6
|
+
"Telo.Import",
|
|
7
|
+
"Telo.Definition",
|
|
8
|
+
];
|
|
9
|
+
/** Definition kinds that are not user-instantiable and should be excluded
|
|
10
|
+
* from completion / suggestion lists. */
|
|
11
|
+
const ABSTRACT_DEF_KINDS = new Set(["Telo.Abstract", "Telo.Template"]);
|
|
12
|
+
/** Computes the set of user-facing kind strings available in the given
|
|
13
|
+
* (aliases, defs) context:
|
|
14
|
+
* - The hardcoded Telo root kinds.
|
|
15
|
+
* - For every registered non-abstract definition, the alias form
|
|
16
|
+
* `${alias}.${TypeName}` for each import alias that points at its
|
|
17
|
+
* module. Canonical kebab-case forms (e.g. `http-server.Server`) are
|
|
18
|
+
* deliberately excluded — users write manifests via alias. */
|
|
19
|
+
export function computeValidUserFacingKinds(aliases, defs) {
|
|
20
|
+
const out = new Set(ROOT_KINDS);
|
|
21
|
+
for (const kind of defs.kinds()) {
|
|
22
|
+
const def = defs.resolve(kind);
|
|
23
|
+
if (!def || ABSTRACT_DEF_KINDS.has(def.kind))
|
|
24
|
+
continue;
|
|
25
|
+
const dot = kind.indexOf(".");
|
|
26
|
+
if (dot === -1)
|
|
27
|
+
continue;
|
|
28
|
+
const moduleName = kind.slice(0, dot);
|
|
29
|
+
const typeName = kind.slice(dot + 1);
|
|
30
|
+
for (const alias of aliases.aliasesFor(moduleName)) {
|
|
31
|
+
out.add(`${alias}.${typeName}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return Array.from(out);
|
|
35
|
+
}
|
|
36
|
+
/** Returns the closest user-facing kind to `badKind` within an edit-distance
|
|
37
|
+
* threshold, or undefined when nothing close enough exists (including ties).
|
|
38
|
+
* Case-sensitive — kinds are PascalCase by contract. */
|
|
39
|
+
export function computeSuggestKind(badKind, aliases, defs) {
|
|
40
|
+
if (!badKind)
|
|
41
|
+
return undefined;
|
|
42
|
+
const candidates = computeValidUserFacingKinds(aliases, defs);
|
|
43
|
+
const threshold = Math.min(3, Math.floor(badKind.length / 3));
|
|
44
|
+
if (threshold < 1)
|
|
45
|
+
return undefined;
|
|
46
|
+
let best;
|
|
47
|
+
let bestDist = threshold + 1;
|
|
48
|
+
let tied = false;
|
|
49
|
+
for (const c of candidates) {
|
|
50
|
+
const d = distance(badKind, c);
|
|
51
|
+
if (d < bestDist) {
|
|
52
|
+
best = c;
|
|
53
|
+
bestDist = d;
|
|
54
|
+
tied = false;
|
|
55
|
+
}
|
|
56
|
+
else if (d === bestDist) {
|
|
57
|
+
tied = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!best || bestDist > threshold || tied)
|
|
61
|
+
return undefined;
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Classic Wagner-Fischer Levenshtein distance. Iterative with two rolling rows
|
|
2
|
+
* so memory is O(min(a, b)); sufficient for short strings like resource kind
|
|
3
|
+
* identifiers (a few dozen characters at most). */
|
|
4
|
+
export declare function distance(a: string, b: string): number;
|
|
5
|
+
//# sourceMappingURL=levenshtein.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"levenshtein.d.ts","sourceRoot":"","sources":["../src/levenshtein.ts"],"names":[],"mappings":"AAAA;;oDAEoD;AACpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgCrD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Classic Wagner-Fischer Levenshtein distance. Iterative with two rolling rows
|
|
2
|
+
* so memory is O(min(a, b)); sufficient for short strings like resource kind
|
|
3
|
+
* identifiers (a few dozen characters at most). */
|
|
4
|
+
export function distance(a, b) {
|
|
5
|
+
if (a === b)
|
|
6
|
+
return 0;
|
|
7
|
+
if (a.length === 0)
|
|
8
|
+
return b.length;
|
|
9
|
+
if (b.length === 0)
|
|
10
|
+
return a.length;
|
|
11
|
+
// Ensure b is the shorter string so the row buffer stays small.
|
|
12
|
+
if (a.length < b.length) {
|
|
13
|
+
const tmp = a;
|
|
14
|
+
a = b;
|
|
15
|
+
b = tmp;
|
|
16
|
+
}
|
|
17
|
+
let prev = new Array(b.length + 1);
|
|
18
|
+
let curr = new Array(b.length + 1);
|
|
19
|
+
for (let j = 0; j <= b.length; j++)
|
|
20
|
+
prev[j] = j;
|
|
21
|
+
for (let i = 1; i <= a.length; i++) {
|
|
22
|
+
curr[0] = i;
|
|
23
|
+
for (let j = 1; j <= b.length; j++) {
|
|
24
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
25
|
+
curr[j] = Math.min(curr[j - 1] + 1, // insertion
|
|
26
|
+
prev[j] + 1, // deletion
|
|
27
|
+
prev[j - 1] + cost);
|
|
28
|
+
}
|
|
29
|
+
const swap = prev;
|
|
30
|
+
prev = curr;
|
|
31
|
+
curr = swap;
|
|
32
|
+
}
|
|
33
|
+
return prev[b.length];
|
|
34
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAOtE,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AASpB,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAG/B;IAEJ,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,sBAAsB,GAAE,eAAe,EAAE,GAAG,iBAAsB;IAmB9E,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAKxC,OAAO,CAAC,IAAI;IAMN,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAqGnE,eAAe;YAoBf,eAAe;IAgEvB,iBAAiB,CACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;QAC9B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;KAClD,GAAG,IAAI,CAAC;IAiCH,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,GAC5C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAsCrC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAOtE,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AASpB,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAG/B;IAEJ,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,sBAAsB,GAAE,eAAe,EAAE,GAAG,iBAAsB;IAmB9E,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAKxC,OAAO,CAAC,IAAI;IAMN,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAqGnE,eAAe;YAoBf,eAAe;IAgEvB,iBAAiB,CACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;QAC9B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;KAClD,GAAG,IAAI,CAAC;IAiCH,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,GAC5C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAsCrC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CA2GnE"}
|
package/dist/manifest-loader.js
CHANGED
|
@@ -277,7 +277,30 @@ export class Loader {
|
|
|
277
277
|
}
|
|
278
278
|
async loadManifests(entryUrl) {
|
|
279
279
|
const visited = new Set([entryUrl]);
|
|
280
|
+
// Cache resolved library identity per import URL so a Telo.Import re-encountered
|
|
281
|
+
// through a different chain still gets `resolvedModuleName` / `resolvedNamespace`
|
|
282
|
+
// stamped — without re-loading the target. The early `visited` short-circuit used
|
|
283
|
+
// to silently leave duplicate Telo.Imports unstamped, which broke alias resolution
|
|
284
|
+
// when the same library was imported by two different files in the same analysis set.
|
|
285
|
+
const libraryIdentityByUrl = new Map();
|
|
280
286
|
const entry = await this.loadModule(entryUrl);
|
|
287
|
+
// Forward Telo.Definition, Telo.Abstract, AND Telo.Import docs from imported
|
|
288
|
+
// libraries to the analyzer so its downstream passes can see them:
|
|
289
|
+
// - Definitions / Abstracts feed cross-package `x-telo-ref` resolution and
|
|
290
|
+
// `extends` target validation.
|
|
291
|
+
// - Imports feed the per-library alias resolver — alias-form `extends` inside
|
|
292
|
+
// a library (e.g. ai-openai's `extends: Ai.Model`) resolves against THAT
|
|
293
|
+
// library's own `Telo.Import` declarations, not the root manifest's. Without
|
|
294
|
+
// forwarding the imports, importing such a library would surface a spurious
|
|
295
|
+
// EXTENDS_MALFORMED for an alias the library legitimately uses internally.
|
|
296
|
+
// Alias resolution itself stays in the analyzer; the loader's only semantic
|
|
297
|
+
// action is stamping `resolvedModuleName` / `resolvedNamespace` — recording the
|
|
298
|
+
// result of loading. Identity is cached per URL (see libraryIdentityByUrl above)
|
|
299
|
+
// because the same library can be reached through multiple chains, and every
|
|
300
|
+
// Telo.Import doc — including the duplicates short-circuited by `visited` —
|
|
301
|
+
// must end up stamped, otherwise per-scope alias resolution falls back to a
|
|
302
|
+
// path-derived string (e.g. "abstract-lib.yaml") and produces wrong canonical
|
|
303
|
+
// kinds.
|
|
281
304
|
const importedDefs = [];
|
|
282
305
|
const queue = [...entry];
|
|
283
306
|
while (queue.length > 0) {
|
|
@@ -291,36 +314,52 @@ export class Loader {
|
|
|
291
314
|
const importUrl = importSource.startsWith(".") || importSource.startsWith("/")
|
|
292
315
|
? this.pick(base).resolveRelative(base, importSource)
|
|
293
316
|
: importSource;
|
|
294
|
-
if (visited.has(importUrl))
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
317
|
+
if (!visited.has(importUrl)) {
|
|
318
|
+
visited.add(importUrl);
|
|
319
|
+
let imported;
|
|
320
|
+
try {
|
|
321
|
+
imported = await this.loadModule(importUrl);
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
325
|
+
e.sourceLine = m.metadata?.sourceLine ?? 0;
|
|
326
|
+
throw e;
|
|
327
|
+
}
|
|
328
|
+
// Import target must be a Telo.Library. Check the Library branch
|
|
329
|
+
// explicitly rather than "anything that's a module kind" so that a
|
|
330
|
+
// future third kind can't silently slip past as a valid import target.
|
|
331
|
+
const importedLibrary = imported.find((im) => im.kind === "Telo.Library");
|
|
332
|
+
const importedApplication = imported.find((im) => im.kind === "Telo.Application");
|
|
333
|
+
if (importedApplication) {
|
|
334
|
+
const e = new Error(`Telo.Import target '${importSource}' is a Telo.Application. ` +
|
|
335
|
+
`Only Telo.Library modules may be imported. Applications are run directly, not imported.`);
|
|
336
|
+
e.sourceLine = m.metadata?.sourceLine ?? 0;
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
if (importedLibrary?.metadata?.name) {
|
|
340
|
+
libraryIdentityByUrl.set(importUrl, {
|
|
341
|
+
name: importedLibrary.metadata.name,
|
|
342
|
+
namespace: importedLibrary.metadata.namespace ?? null,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
for (const im of imported) {
|
|
346
|
+
if (im.kind === "Telo.Definition" ||
|
|
347
|
+
im.kind === "Telo.Abstract" ||
|
|
348
|
+
im.kind === "Telo.Import") {
|
|
349
|
+
importedDefs.push(im);
|
|
350
|
+
}
|
|
351
|
+
if (im.kind === "Telo.Import")
|
|
352
|
+
queue.push(im);
|
|
353
|
+
}
|
|
316
354
|
}
|
|
317
|
-
|
|
318
|
-
|
|
355
|
+
// Stamp m with cached identity (works for both fresh and duplicate visits).
|
|
356
|
+
const identity = libraryIdentityByUrl.get(importUrl);
|
|
357
|
+
if (identity) {
|
|
319
358
|
const pi = m.metadata?.positionIndex;
|
|
320
359
|
m.metadata = {
|
|
321
360
|
...m.metadata,
|
|
322
|
-
resolvedModuleName:
|
|
323
|
-
resolvedNamespace:
|
|
361
|
+
resolvedModuleName: identity.name,
|
|
362
|
+
resolvedNamespace: identity.namespace,
|
|
324
363
|
};
|
|
325
364
|
if (pi) {
|
|
326
365
|
Object.defineProperty(m.metadata, "positionIndex", {
|
|
@@ -331,12 +370,6 @@ export class Loader {
|
|
|
331
370
|
});
|
|
332
371
|
}
|
|
333
372
|
}
|
|
334
|
-
for (const im of imported) {
|
|
335
|
-
if (im.kind === "Telo.Definition")
|
|
336
|
-
importedDefs.push(im);
|
|
337
|
-
if (im.kind === "Telo.Import")
|
|
338
|
-
queue.push(im);
|
|
339
|
-
}
|
|
340
373
|
}
|
|
341
374
|
return [...entry, ...importedDefs];
|
|
342
375
|
}
|