@telorun/analyzer 0.22.0 → 0.23.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/README.md +2 -2
- package/dist/alias-resolver.d.ts +17 -0
- package/dist/alias-resolver.d.ts.map +1 -1
- package/dist/alias-resolver.js +28 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +61 -20
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +4 -2
- package/dist/cel-environment.d.ts +8 -0
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +48 -0
- package/dist/flatten-for-analyzer.d.ts +52 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +192 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/resolve-throws-union.d.ts +9 -2
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +47 -23
- package/dist/validate-throws-coverage.d.ts +2 -2
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +15 -7
- package/package.json +2 -2
- package/src/alias-resolver.ts +33 -0
- package/src/analyzer.ts +72 -19
- package/src/builtins.ts +4 -2
- package/src/cel-environment.ts +57 -0
- package/src/flatten-for-analyzer.ts +217 -4
- package/src/index.ts +7 -0
- package/src/resolve-throws-union.ts +56 -18
- package/src/validate-throws-coverage.ts +22 -6
package/README.md
CHANGED
|
@@ -55,8 +55,8 @@ metadata:
|
|
|
55
55
|
A complete feedback collection REST API — no code, pure YAML.
|
|
56
56
|
Persists entries to SQLite and serves them over HTTP.
|
|
57
57
|
imports:
|
|
58
|
-
Http: std/http-server@0.
|
|
59
|
-
Sql: std/sql@0.
|
|
58
|
+
Http: std/http-server@0.11.0
|
|
59
|
+
Sql: std/sql@0.9.0
|
|
60
60
|
targets:
|
|
61
61
|
- !ref Migrations
|
|
62
62
|
- !ref Server
|
package/dist/alias-resolver.d.ts
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
export declare class AliasResolver {
|
|
4
4
|
private readonly importAliases;
|
|
5
5
|
private readonly importedKinds;
|
|
6
|
+
/** `${alias}.${suffix}` → canonical `<owningModule>.<Kind>` for kinds an import
|
|
7
|
+
* transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
|
|
8
|
+
* import's own module. Resolved before the normal `<module>.<suffix>` construction. */
|
|
9
|
+
private readonly reExportedKinds;
|
|
6
10
|
registerImport(alias: string, targetModule: string, exportedKinds: string[]): void;
|
|
11
|
+
/** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
|
|
12
|
+
* (owned by a module the alias's target imports, possibly several hops away). */
|
|
13
|
+
registerKindReExport(alias: string, suffix: string, canonicalKind: string): void;
|
|
7
14
|
/** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
|
|
8
15
|
* Used to resolve an alias-qualified instance reference "Console.writeLine" to the
|
|
9
16
|
* forwarded resource declared in that module. The `exports.resources` gate is enforced
|
|
@@ -19,4 +26,14 @@ export declare class AliasResolver {
|
|
|
19
26
|
* back into its user-facing alias form (e.g. "Http.Server"). */
|
|
20
27
|
aliasesFor(targetModule: string): string[];
|
|
21
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The alias resolver for a resource's own lexical scope. A resource that
|
|
31
|
+
* originated in an imported library (its `ownModule` names a non-root module —
|
|
32
|
+
* e.g. an inline handler extracted from an imported Http.Api) resolves its kind
|
|
33
|
+
* aliases against THAT library's import map, so an anonymous child inherits the
|
|
34
|
+
* lexical scope of the document that declares it. Returns undefined for
|
|
35
|
+
* root/consumer-owned resources (and unknown modules), so callers fall back to
|
|
36
|
+
* the root `aliases`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function scopeResolverForModule(ownModule: string | undefined, rootModules: Set<string>, aliasesByModule: Map<string, AliasResolver>): AliasResolver | undefined;
|
|
22
39
|
//# 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;
|
|
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;IAChE;;4FAEwF;IACxF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF;sFACkF;IAClF,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAIhF;;;;qDAIiD;IACjD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAmB7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,aAAa,GAAG,SAAS,CAI3B"}
|
package/dist/alias-resolver.js
CHANGED
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
export class AliasResolver {
|
|
4
4
|
importAliases = new Map();
|
|
5
5
|
importedKinds = new Map();
|
|
6
|
+
/** `${alias}.${suffix}` → canonical `<owningModule>.<Kind>` for kinds an import
|
|
7
|
+
* transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
|
|
8
|
+
* import's own module. Resolved before the normal `<module>.<suffix>` construction. */
|
|
9
|
+
reExportedKinds = new Map();
|
|
6
10
|
registerImport(alias, targetModule, exportedKinds) {
|
|
7
11
|
this.importAliases.set(alias, targetModule);
|
|
8
12
|
if (exportedKinds.length > 0) {
|
|
9
13
|
this.importedKinds.set(alias, new Set(exportedKinds));
|
|
10
14
|
}
|
|
11
15
|
}
|
|
16
|
+
/** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
|
|
17
|
+
* (owned by a module the alias's target imports, possibly several hops away). */
|
|
18
|
+
registerKindReExport(alias, suffix, canonicalKind) {
|
|
19
|
+
this.reExportedKinds.set(`${alias}.${suffix}`, canonicalKind);
|
|
20
|
+
}
|
|
12
21
|
/** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
|
|
13
22
|
* Used to resolve an alias-qualified instance reference "Console.writeLine" to the
|
|
14
23
|
* forwarded resource declared in that module. The `exports.resources` gate is enforced
|
|
@@ -27,6 +36,11 @@ export class AliasResolver {
|
|
|
27
36
|
return undefined;
|
|
28
37
|
const prefix = kind.slice(0, dot);
|
|
29
38
|
const suffix = kind.slice(dot + 1);
|
|
39
|
+
// Re-export takes precedence: a re-exported kind resolves to its true owning module,
|
|
40
|
+
// not `${prefix-target}.${suffix}` (and bypasses the gate — it's explicitly re-exported).
|
|
41
|
+
const reExported = this.reExportedKinds.get(`${prefix}.${suffix}`);
|
|
42
|
+
if (reExported)
|
|
43
|
+
return reExported;
|
|
30
44
|
const realModule = this.importAliases.get(prefix);
|
|
31
45
|
if (!realModule)
|
|
32
46
|
return undefined;
|
|
@@ -53,3 +67,17 @@ export class AliasResolver {
|
|
|
53
67
|
return result;
|
|
54
68
|
}
|
|
55
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The alias resolver for a resource's own lexical scope. A resource that
|
|
72
|
+
* originated in an imported library (its `ownModule` names a non-root module —
|
|
73
|
+
* e.g. an inline handler extracted from an imported Http.Api) resolves its kind
|
|
74
|
+
* aliases against THAT library's import map, so an anonymous child inherits the
|
|
75
|
+
* lexical scope of the document that declares it. Returns undefined for
|
|
76
|
+
* root/consumer-owned resources (and unknown modules), so callers fall back to
|
|
77
|
+
* the root `aliases`.
|
|
78
|
+
*/
|
|
79
|
+
export function scopeResolverForModule(ownModule, rootModules, aliasesByModule) {
|
|
80
|
+
return ownModule && !rootModules.has(ownModule)
|
|
81
|
+
? aliasesByModule.get(ownModule)
|
|
82
|
+
: undefined;
|
|
83
|
+
}
|
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,
|
|
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,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA4hB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAstBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAerB,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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defaultRegistry, isTaggedSentinel } from "@telorun/templating";
|
|
2
|
-
import { AliasResolver } from "./alias-resolver.js";
|
|
3
|
-
import { buildCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.js";
|
|
2
|
+
import { AliasResolver, scopeResolverForModule } from "./alias-resolver.js";
|
|
3
|
+
import { buildCelEnvironment, buildImportInputCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.js";
|
|
4
4
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
5
5
|
import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
|
|
6
6
|
import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kernel-globals.js";
|
|
@@ -579,17 +579,18 @@ export class StaticAnalyzer {
|
|
|
579
579
|
if (resolvedModuleName) {
|
|
580
580
|
defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
|
|
581
581
|
}
|
|
582
|
+
// `metadata.reExportedKinds` (stamped by flattenForAnalyzer / the editor projection)
|
|
583
|
+
// maps an exported suffix to the true owning module's canonical kind for kinds this
|
|
584
|
+
// import transitively re-exports (`exports.kinds: [Alias.Kind]`).
|
|
585
|
+
const reExportedKinds = (m.metadata?.reExportedKinds ?? {});
|
|
582
586
|
// Alias registration is scoped: consumer imports vs. imported-library imports.
|
|
583
|
-
|
|
584
|
-
aliases
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
aliasesByModule.set(ownModule, libResolver);
|
|
591
|
-
}
|
|
592
|
-
libResolver.registerImport(alias, targetModule, exportedKinds);
|
|
587
|
+
const resolver = !ownModule || rootModules.has(ownModule)
|
|
588
|
+
? aliases
|
|
589
|
+
: (aliasesByModule.get(ownModule) ??
|
|
590
|
+
aliasesByModule.set(ownModule, new AliasResolver()).get(ownModule));
|
|
591
|
+
resolver.registerImport(alias, targetModule, exportedKinds);
|
|
592
|
+
for (const [suffix, canonical] of Object.entries(reExportedKinds)) {
|
|
593
|
+
resolver.registerKindReExport(alias, suffix, canonical);
|
|
593
594
|
}
|
|
594
595
|
}
|
|
595
596
|
}
|
|
@@ -735,6 +736,26 @@ export class StaticAnalyzer {
|
|
|
735
736
|
}
|
|
736
737
|
}
|
|
737
738
|
}
|
|
739
|
+
// `exports.resources` entries are plain names: `Db` (local) or `Alias.Name` (re-export),
|
|
740
|
+
// mirroring `exports.kinds`. The `!ref` tag is not accepted here — a `!ref` parses to a
|
|
741
|
+
// sentinel object that the schema's CEL/ref exemption would silently pass, so reject any
|
|
742
|
+
// non-string entry with an actionable message instead.
|
|
743
|
+
const exportsResources = m.exports?.resources;
|
|
744
|
+
if (Array.isArray(exportsResources)) {
|
|
745
|
+
for (let i = 0; i < exportsResources.length; i++) {
|
|
746
|
+
if (typeof exportsResources[i] === "string")
|
|
747
|
+
continue;
|
|
748
|
+
diagnostics.push({
|
|
749
|
+
severity: DiagnosticSeverity.Error,
|
|
750
|
+
code: "INVALID_EXPORT",
|
|
751
|
+
source: SOURCE,
|
|
752
|
+
message: `Telo.Library exports.resources[${i}]: write the exported name as a plain string — ` +
|
|
753
|
+
`'Name' to export a local instance, or 'Alias.Name' to re-export an imported one. ` +
|
|
754
|
+
`The '!ref' tag is not allowed in exports.resources.`,
|
|
755
|
+
data: { resource, filePath, path: `exports.resources.${i}` },
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
738
759
|
}
|
|
739
760
|
// Build typed kernel globals schema so x-telo-context chain validation
|
|
740
761
|
// recognises variables, secrets, resources, env automatically
|
|
@@ -775,8 +796,15 @@ export class StaticAnalyzer {
|
|
|
775
796
|
const resource = { kind: m.kind, name: m.metadata?.name };
|
|
776
797
|
// Resolve kind through alias if needed; direct lookup takes priority so that
|
|
777
798
|
// aliases whose name matches the module name (the common case) work without
|
|
778
|
-
// path-derived name mangling.
|
|
779
|
-
|
|
799
|
+
// path-derived name mangling. A resource that originated in an imported library
|
|
800
|
+
// (its `metadata.module` names a non-root module — e.g. an inline route handler
|
|
801
|
+
// extracted from an imported Http.Api) must resolve its kind alias against THAT
|
|
802
|
+
// library's import map, not the consumer's; an anonymous child inherits the
|
|
803
|
+
// lexical scope of the document that declares it. Mirrors the nested-inline and
|
|
804
|
+
// reference-resolution paths: own-module scope first, root/consumer aliases last.
|
|
805
|
+
const ownModule = m.metadata?.module;
|
|
806
|
+
const scopeResolver = scopeResolverForModule(ownModule, rootModules, aliasesByModule);
|
|
807
|
+
const resolvedKind = scopeResolver?.resolveKind(m.kind) ?? aliases.resolveKind(m.kind);
|
|
780
808
|
const definition = defs.resolve(m.kind) ?? (resolvedKind ? defs.resolve(resolvedKind) : undefined);
|
|
781
809
|
if (!definition) {
|
|
782
810
|
const suggestedKind = computeSuggestKind(m.kind, aliases, defs);
|
|
@@ -804,8 +832,22 @@ export class StaticAnalyzer {
|
|
|
804
832
|
},
|
|
805
833
|
}
|
|
806
834
|
: definition.schema;
|
|
807
|
-
// Phase 1: CEL type checking — walk data+schema together, check env.check() return types
|
|
808
|
-
|
|
835
|
+
// Phase 1: CEL type checking — walk data+schema together, check env.check() return types.
|
|
836
|
+
// A Telo.Import's variables/secrets are a config-only contract evaluated against the
|
|
837
|
+
// IMPORTING module's scope, so type them from the owning module doc (matched by
|
|
838
|
+
// `metadata.module`) and drop `resources`/`env` so referencing them is an error. A
|
|
839
|
+
// library's own internal import is validated against that library in the library's
|
|
840
|
+
// standalone analysis; in this flattened app pass the library doc is absent, so the
|
|
841
|
+
// importer is undefined here and variables/secrets fall back to a permissive `map`
|
|
842
|
+
// (no false positives) while resources/env stay rejected.
|
|
843
|
+
const importerModule = m.kind === "Telo.Import"
|
|
844
|
+
? allManifests.find((mm) => (mm.kind === "Telo.Application" || mm.kind === "Telo.Library") &&
|
|
845
|
+
mm.metadata?.name ===
|
|
846
|
+
m.metadata?.module)
|
|
847
|
+
: undefined;
|
|
848
|
+
const baseTypedEnv = m.kind === "Telo.Import"
|
|
849
|
+
? buildImportInputCelEnvironment(this.celEnv, importerModule)
|
|
850
|
+
: buildTypedCelEnvironment(this.celEnv, m, undefined, moduleManifest);
|
|
809
851
|
const celIssues = collectCelTypeIssues(m, schema, "", definition, m, baseTypedEnv, this.celEnv, moduleManifest);
|
|
810
852
|
// Phase 2+3: AJV on substituted data — CEL fields replaced with typed placeholders
|
|
811
853
|
const ajvIssues = validateAgainstSchema(substituteCelFields(m, schema), schema);
|
|
@@ -830,9 +872,8 @@ export class StaticAnalyzer {
|
|
|
830
872
|
// first, then the parent module's own aliases (for resources declared
|
|
831
873
|
// inside an imported module), then the root aliases. Mirrors how the
|
|
832
874
|
// analyzer resolves kinds elsewhere so module-scoped aliases don't
|
|
833
|
-
// produce false UNDEFINED_KIND diagnostics.
|
|
834
|
-
|
|
835
|
-
const scopeResolver = ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
|
|
875
|
+
// produce false UNDEFINED_KIND diagnostics. `scopeResolver` is the
|
|
876
|
+
// owning module's resolver computed above.
|
|
836
877
|
diagnostics.push(...validateNestedInlineResources(m, definition.schema, (kind) => {
|
|
837
878
|
const direct = defs.resolve(kind);
|
|
838
879
|
if (direct)
|
|
@@ -1047,7 +1088,7 @@ export class StaticAnalyzer {
|
|
|
1047
1088
|
// Validate provider coherence rules for `provide:` template-target definitions.
|
|
1048
1089
|
diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
|
|
1049
1090
|
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
|
|
1050
|
-
diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv));
|
|
1091
|
+
diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules));
|
|
1051
1092
|
// Warn about declared variables / secrets / ports that no CEL references.
|
|
1052
1093
|
diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
|
|
1053
1094
|
// Reroute diagnostics on synthetic (inline-extracted) resources back to
|
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,EAud/C,CAAC"}
|
package/dist/builtins.js
CHANGED
|
@@ -453,8 +453,10 @@ export const KERNEL_BUILTINS = [
|
|
|
453
453
|
type: "object",
|
|
454
454
|
properties: {
|
|
455
455
|
kinds: { type: "array", items: { type: "string" } },
|
|
456
|
-
//
|
|
457
|
-
//
|
|
456
|
+
// An entry is a bare name (`Db`, a locally-owned export) or a dotted `Alias.Name`
|
|
457
|
+
// (re-export of the instance reached via this library's import aliased `Alias`,
|
|
458
|
+
// under the name `Name`) — mirroring `exports.kinds`. `variables` / `secrets` are
|
|
459
|
+
// reserved on the resources.<Alias> value-flow surface, so they may not be exported.
|
|
458
460
|
resources: {
|
|
459
461
|
type: "array",
|
|
460
462
|
items: { type: "string", not: { enum: ["variables", "secrets"] } },
|
|
@@ -13,4 +13,12 @@ export type { CelHandlers } from "@telorun/templating";
|
|
|
13
13
|
* NOTE: The set of kernel globals registered here must match `KERNEL_GLOBAL_NAMES`
|
|
14
14
|
* in kernel-globals.ts, which is used for chain-access validation. */
|
|
15
15
|
export declare function buildTypedCelEnvironment(baseEnv: Environment, manifest: ResourceManifest, extraContextSchema?: Record<string, any> | null, rootModuleManifest?: ResourceManifest): Environment;
|
|
16
|
+
/** CEL environment for the `variables:`/`secrets:` expressions on a `Telo.Import`.
|
|
17
|
+
*
|
|
18
|
+
* Import inputs are a config-only contract: their expressions are evaluated
|
|
19
|
+
* against the IMPORTING module's `variables`/`secrets`, never the import's own
|
|
20
|
+
* values map (the bug) nor the imported child's. `resources`, `env`, and `ports`
|
|
21
|
+
* are registered as empty typed objects, so referencing them is a "No such key"
|
|
22
|
+
* error that steers authors to a typed `variables` entry. */
|
|
23
|
+
export declare function buildImportInputCelEnvironment(baseEnv: Environment, moduleManifest: ResourceManifest | undefined): Environment;
|
|
16
24
|
//# sourceMappingURL=cel-environment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CAuEb"}
|
|
1
|
+
{"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CAuEb;AAuBD;;;;;;8DAM8D;AAC9D,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,gBAAgB,GAAG,SAAS,GAC3C,WAAW,CAwBb"}
|
package/dist/cel-environment.js
CHANGED
|
@@ -85,3 +85,51 @@ rootModuleManifest) {
|
|
|
85
85
|
return baseEnv.clone();
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
/** Register a `variables`/`secrets` namespace typed from a module doc's schema map
|
|
89
|
+
* (`{ name: <schema>, … }`), falling back to dyn `map` when absent or untyped. */
|
|
90
|
+
function registerConfigNamespace(env, block, name) {
|
|
91
|
+
if (block !== null && typeof block === "object" && !Array.isArray(block)) {
|
|
92
|
+
const entries = Object.entries(block).filter(([, v]) => v !== null && typeof v === "object" && !Array.isArray(v));
|
|
93
|
+
if (entries.length > 0) {
|
|
94
|
+
const schema = {};
|
|
95
|
+
for (const [k, v] of entries)
|
|
96
|
+
schema[k] = jsonSchemaToCelType(v);
|
|
97
|
+
env.registerVariable({ name, schema });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
env.registerVariable(name, "map");
|
|
102
|
+
}
|
|
103
|
+
/** CEL environment for the `variables:`/`secrets:` expressions on a `Telo.Import`.
|
|
104
|
+
*
|
|
105
|
+
* Import inputs are a config-only contract: their expressions are evaluated
|
|
106
|
+
* against the IMPORTING module's `variables`/`secrets`, never the import's own
|
|
107
|
+
* values map (the bug) nor the imported child's. `resources`, `env`, and `ports`
|
|
108
|
+
* are registered as empty typed objects, so referencing them is a "No such key"
|
|
109
|
+
* error that steers authors to a typed `variables` entry. */
|
|
110
|
+
export function buildImportInputCelEnvironment(baseEnv, moduleManifest) {
|
|
111
|
+
const env = baseEnv.clone();
|
|
112
|
+
for (const brand of Object.keys(VALUE_BRAND_BASE)) {
|
|
113
|
+
env.registerType(brand, { fields: {} });
|
|
114
|
+
}
|
|
115
|
+
const mod = moduleManifest;
|
|
116
|
+
// Typing variables/secrets from the importer's schema can fail on a malformed
|
|
117
|
+
// schema; degrade those to permissive `map` if so — but never lose the
|
|
118
|
+
// resources/env/ports rejection registered below (the catch is scoped so a
|
|
119
|
+
// typing failure can't silently re-open the config-only contract).
|
|
120
|
+
try {
|
|
121
|
+
registerConfigNamespace(env, mod?.variables, "variables");
|
|
122
|
+
registerConfigNamespace(env, mod?.secrets, "secrets");
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
env.registerVariable("variables", "map");
|
|
126
|
+
env.registerVariable("secrets", "map");
|
|
127
|
+
}
|
|
128
|
+
// Override the base env's dyn `resources`/`env`/`ports` with empty typed objects
|
|
129
|
+
// so any access (`resources.X`, `env.X`) is a "No such key" error — these
|
|
130
|
+
// surfaces are not part of the config-only import contract.
|
|
131
|
+
for (const name of ["resources", "env", "ports"]) {
|
|
132
|
+
env.registerVariable({ name, schema: {} });
|
|
133
|
+
}
|
|
134
|
+
return env;
|
|
135
|
+
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import type { LoadedGraph, LoadedModule } from "./loaded-types.js";
|
|
3
|
+
/** One parsed `exports.resources` / `exports.kinds` entry. `name` is the exported
|
|
4
|
+
* instance name or kind suffix (the part after the dot, or the whole entry); `alias`
|
|
5
|
+
* (when set) is this library's own import the entry RE-EXPORTS from. */
|
|
6
|
+
export interface ParsedExportEntry {
|
|
7
|
+
name: string;
|
|
8
|
+
alias?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Parse a single dotted export entry: `Alias.Name` → `{name: "Name", alias: "Alias"}`,
|
|
11
|
+
* bare `Name` → `{name: "Name"}`. The single grammar for `exports.resources` and
|
|
12
|
+
* `exports.kinds`, shared by the kernel's import controller and the analyzer/editor so
|
|
13
|
+
* the dotted-name split can't drift. A leading dot (`.Name`) has no alias by design —
|
|
14
|
+
* the empty prefix isn't a valid alias. */
|
|
15
|
+
export declare function parseExportEntry(entry: string): ParsedExportEntry;
|
|
3
16
|
/** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
|
|
4
17
|
* CLI / kernel loader path) and the telo-editor's workspace projection so the
|
|
5
18
|
* two cannot drift. Given one module's stamped manifests and whether that
|
|
@@ -42,6 +55,45 @@ export declare function selectModuleManifestsForAnalysis(moduleManifests: Resour
|
|
|
42
55
|
* Position metadata (`positionIndex`) is NOT stamped on manifests —
|
|
43
56
|
* callers look it up via `findPositions(graph, ...)` on the LoadedGraph. */
|
|
44
57
|
export declare function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[];
|
|
58
|
+
/** A re-export declared in a library's `exports.resources` as a dotted `Alias.Name`:
|
|
59
|
+
* module `module` re-exports the instance `name` reached through its own import
|
|
60
|
+
* aliased `alias`. */
|
|
61
|
+
export interface ReExportSpec {
|
|
62
|
+
module: string;
|
|
63
|
+
alias: string;
|
|
64
|
+
name: string;
|
|
65
|
+
}
|
|
66
|
+
/** Extract re-export specs from a library doc's `exports.resources` — the dotted `Alias.Name`
|
|
67
|
+
* entries (bare-name locals are forwarded by the BFS instead). Shared by the CLI graph path
|
|
68
|
+
* and the editor's workspace projection so the two cannot drift. */
|
|
69
|
+
export declare function reExportSpecsFromExports(moduleName: string, exportsResources: readonly unknown[] | undefined): ReExportSpec[];
|
|
70
|
+
/** Forward re-exported instances (`exports.resources: [!ref Alias.name]`) transitively so a
|
|
71
|
+
* consumer's `!ref Consumer.name` resolves in `resolveRefSentinels` (keyed by the RE-EXPORTING
|
|
72
|
+
* module). The owning instance is already forwarded under its own module; here we emit an
|
|
73
|
+
* additional copy stamped under each re-exporting module, with an already-canonical kind. A
|
|
74
|
+
* fixpoint loop forwards chains of arbitrary depth (`app → api → domain → …`): each pass can
|
|
75
|
+
* resolve a re-export whose source was emitted in a prior pass. Graph-agnostic: `aliasToModule`
|
|
76
|
+
* maps `(module, alias)` to the imported module's name. Mutates `result` in place. */
|
|
77
|
+
export declare function forwardReExportManifests(result: ResourceManifest[], specs: readonly ReExportSpec[], aliasToModule: (module: string, alias: string) => string | undefined): void;
|
|
78
|
+
/** Resolve every library's `exports.kinds` to a per-module map `suffix → canonical
|
|
79
|
+
* <owningModule>.<Kind>`, following re-exports (`Alias.Kind`) transitively via a fixpoint.
|
|
80
|
+
* `modules` lists each library's name + its raw `exports.kinds`; `aliasToModule(module, alias)`
|
|
81
|
+
* maps one of that module's import aliases to the imported module's name. Graph-agnostic —
|
|
82
|
+
* shared by the CLI graph path and the editor's workspace projection. */
|
|
83
|
+
export declare function resolveExportedKinds(modules: ReadonlyArray<{
|
|
84
|
+
module: string;
|
|
85
|
+
exportsKinds: readonly string[];
|
|
86
|
+
}>, aliasToModule: (module: string, alias: string) => string | undefined): Map<string, Map<string, string>>;
|
|
87
|
+
/** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
|
|
88
|
+
* target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
|
|
89
|
+
* that point at a module OTHER than the import's own target are stamped (genuine re-exports;
|
|
90
|
+
* a locally-defined kind resolves through the normal alias path). Stamped on `metadata` (which
|
|
91
|
+
* permits additional properties, like `resolvedModuleName`) since the `Telo.Import` schema
|
|
92
|
+
* forbids extra top-level fields. Shared by both paths. */
|
|
93
|
+
export declare function stampReExportedKinds(imports: ReadonlyArray<{
|
|
94
|
+
manifest: ResourceManifest;
|
|
95
|
+
targetModule: string;
|
|
96
|
+
}>, exportedKinds: Map<string, Map<string, string>>): void;
|
|
45
97
|
/** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
|
|
46
98
|
* with `metadata.module` stamped on non-module docs. The kernel's runtime
|
|
47
99
|
* entry load uses this to convert a `Loader.loadModule` result into the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAInE;;yEAEyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;4CAI4C;AAC5C,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAGjE;AAED;;;;;;;;;;;;;;;;;;;2CAmB2C;AAC3C,wBAAgB,gCAAgC,CAC9C,eAAe,EAAE,gBAAgB,EAAE,EACnC,MAAM,EAAE,OAAO,GACd,gBAAgB,EAAE,CA6BpB;AAED;;;;;;;;;;;;;;;;;;;6EAmB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAiDzE;AAED;;uBAEuB;AACvB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;qEAEqE;AACrE,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAC/C,YAAY,EAAE,CAShB;AAED;;;;;;uFAMuF;AACvF,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnE,IAAI,CAgDN;AAED;;;;0EAI0E;AAC1E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,EAC3E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA8BlC;AAED;;;;;4DAK4D;AAC5D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5E,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,IAAI,CAWN;AAwCD;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { isModuleKind } from "./module-kinds.js";
|
|
2
|
+
/** Parse a single dotted export entry: `Alias.Name` → `{name: "Name", alias: "Alias"}`,
|
|
3
|
+
* bare `Name` → `{name: "Name"}`. The single grammar for `exports.resources` and
|
|
4
|
+
* `exports.kinds`, shared by the kernel's import controller and the analyzer/editor so
|
|
5
|
+
* the dotted-name split can't drift. A leading dot (`.Name`) has no alias by design —
|
|
6
|
+
* the empty prefix isn't a valid alias. */
|
|
7
|
+
export function parseExportEntry(entry) {
|
|
8
|
+
const dot = entry.indexOf(".");
|
|
9
|
+
return dot > 0 ? { name: entry.slice(dot + 1), alias: entry.slice(0, dot) } : { name: entry };
|
|
10
|
+
}
|
|
2
11
|
/** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
|
|
3
12
|
* CLI / kernel loader path) and the telo-editor's workspace projection so the
|
|
4
13
|
* two cannot drift. Given one module's stamped manifests and whether that
|
|
@@ -23,7 +32,15 @@ export function selectModuleManifestsForAnalysis(moduleManifests, isRoot) {
|
|
|
23
32
|
if (isRoot)
|
|
24
33
|
return moduleManifests;
|
|
25
34
|
const libDoc = moduleManifests.find((m) => isModuleKind(m.kind));
|
|
26
|
-
|
|
35
|
+
// An `exports.resources` entry is a bare name or a dotted `Alias.Name` (re-export). Only the
|
|
36
|
+
// export NAME matches a local instance below; re-exports are forwarded by `forwardReExports`.
|
|
37
|
+
const exportedResources = new Set();
|
|
38
|
+
for (const entry of libDoc?.exports
|
|
39
|
+
?.resources ?? []) {
|
|
40
|
+
if (typeof entry !== "string")
|
|
41
|
+
continue;
|
|
42
|
+
exportedResources.add(parseExportEntry(entry).name);
|
|
43
|
+
}
|
|
27
44
|
const out = [];
|
|
28
45
|
for (const m of moduleManifests) {
|
|
29
46
|
if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract" || m.kind === "Telo.Import") {
|
|
@@ -81,6 +98,7 @@ export function flattenForAnalyzer(graph) {
|
|
|
81
98
|
result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(targetModule), false));
|
|
82
99
|
}
|
|
83
100
|
}
|
|
101
|
+
forwardReExports(graph, result);
|
|
84
102
|
// Stamp resolved import identity on every Telo.Import in the result by
|
|
85
103
|
// reading the edge's pre-resolved name/namespace — no re-derivation from
|
|
86
104
|
// manifest metadata. The edge is keyed by (owner-file, alias) which is
|
|
@@ -105,6 +123,179 @@ export function flattenForAnalyzer(graph) {
|
|
|
105
123
|
}
|
|
106
124
|
return result;
|
|
107
125
|
}
|
|
126
|
+
/** Extract re-export specs from a library doc's `exports.resources` — the dotted `Alias.Name`
|
|
127
|
+
* entries (bare-name locals are forwarded by the BFS instead). Shared by the CLI graph path
|
|
128
|
+
* and the editor's workspace projection so the two cannot drift. */
|
|
129
|
+
export function reExportSpecsFromExports(moduleName, exportsResources) {
|
|
130
|
+
const specs = [];
|
|
131
|
+
for (const entry of exportsResources ?? []) {
|
|
132
|
+
if (typeof entry !== "string")
|
|
133
|
+
continue;
|
|
134
|
+
const { name, alias } = parseExportEntry(entry);
|
|
135
|
+
if (!alias || alias === "Self")
|
|
136
|
+
continue;
|
|
137
|
+
specs.push({ module: moduleName, alias, name });
|
|
138
|
+
}
|
|
139
|
+
return specs;
|
|
140
|
+
}
|
|
141
|
+
/** Forward re-exported instances (`exports.resources: [!ref Alias.name]`) transitively so a
|
|
142
|
+
* consumer's `!ref Consumer.name` resolves in `resolveRefSentinels` (keyed by the RE-EXPORTING
|
|
143
|
+
* module). The owning instance is already forwarded under its own module; here we emit an
|
|
144
|
+
* additional copy stamped under each re-exporting module, with an already-canonical kind. A
|
|
145
|
+
* fixpoint loop forwards chains of arbitrary depth (`app → api → domain → …`): each pass can
|
|
146
|
+
* resolve a re-export whose source was emitted in a prior pass. Graph-agnostic: `aliasToModule`
|
|
147
|
+
* maps `(module, alias)` to the imported module's name. Mutates `result` in place. */
|
|
148
|
+
export function forwardReExportManifests(result, specs, aliasToModule) {
|
|
149
|
+
// Index forwarded instances by `module\0name` (only re-export TARGETS are forwarded).
|
|
150
|
+
const forwarded = new Map();
|
|
151
|
+
for (const m of result) {
|
|
152
|
+
const meta = m.metadata;
|
|
153
|
+
if (meta?.forwardedExport && meta.module && meta.name) {
|
|
154
|
+
forwarded.set(`${meta.module}\0${meta.name}`, m);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Canonicalize an authored/forwarded kind to a scope-independent `<module>.<Kind>` using the
|
|
158
|
+
// owning module's own import aliases. Idempotent: an already-canonical kind whose prefix isn't
|
|
159
|
+
// an alias of `ownerModule` is returned unchanged, so re-exports of re-exports stay stable.
|
|
160
|
+
const canonicalKind = (kind, ownerModule) => {
|
|
161
|
+
if (kind.startsWith("Self."))
|
|
162
|
+
return `${ownerModule}.${kind.slice("Self.".length)}`;
|
|
163
|
+
const dot = kind.indexOf(".");
|
|
164
|
+
if (dot <= 0)
|
|
165
|
+
return kind;
|
|
166
|
+
const target = aliasToModule(ownerModule, kind.slice(0, dot));
|
|
167
|
+
return target ? `${target}.${kind.slice(dot + 1)}` : kind;
|
|
168
|
+
};
|
|
169
|
+
// Fixpoint — bounded by the number of specs (each can be satisfied at most once).
|
|
170
|
+
for (let pass = 0; pass <= specs.length; pass++) {
|
|
171
|
+
let added = false;
|
|
172
|
+
for (const spec of specs) {
|
|
173
|
+
const key = `${spec.module}\0${spec.name}`;
|
|
174
|
+
if (forwarded.has(key))
|
|
175
|
+
continue;
|
|
176
|
+
const sourceModule = aliasToModule(spec.module, spec.alias);
|
|
177
|
+
if (!sourceModule)
|
|
178
|
+
continue;
|
|
179
|
+
const src = forwarded.get(`${sourceModule}\0${spec.name}`);
|
|
180
|
+
if (!src)
|
|
181
|
+
continue; // source not forwarded yet — a later pass may satisfy it
|
|
182
|
+
const kind = canonicalKind(src.kind, sourceModule);
|
|
183
|
+
const manifest = {
|
|
184
|
+
...src,
|
|
185
|
+
kind,
|
|
186
|
+
metadata: {
|
|
187
|
+
...src.metadata,
|
|
188
|
+
name: spec.name,
|
|
189
|
+
module: spec.module,
|
|
190
|
+
forwardedExport: true,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
result.push(manifest);
|
|
194
|
+
forwarded.set(key, manifest);
|
|
195
|
+
added = true;
|
|
196
|
+
}
|
|
197
|
+
if (!added)
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Resolve every library's `exports.kinds` to a per-module map `suffix → canonical
|
|
202
|
+
* <owningModule>.<Kind>`, following re-exports (`Alias.Kind`) transitively via a fixpoint.
|
|
203
|
+
* `modules` lists each library's name + its raw `exports.kinds`; `aliasToModule(module, alias)`
|
|
204
|
+
* maps one of that module's import aliases to the imported module's name. Graph-agnostic —
|
|
205
|
+
* shared by the CLI graph path and the editor's workspace projection. */
|
|
206
|
+
export function resolveExportedKinds(modules, aliasToModule) {
|
|
207
|
+
const out = new Map();
|
|
208
|
+
const tableFor = (m) => {
|
|
209
|
+
let t = out.get(m);
|
|
210
|
+
if (!t)
|
|
211
|
+
out.set(m, (t = new Map()));
|
|
212
|
+
return t;
|
|
213
|
+
};
|
|
214
|
+
for (let pass = 0; pass <= modules.length; pass++) {
|
|
215
|
+
let changed = false;
|
|
216
|
+
for (const { module, exportsKinds } of modules) {
|
|
217
|
+
const table = tableFor(module);
|
|
218
|
+
for (const entry of exportsKinds) {
|
|
219
|
+
const { name: suffix, alias } = parseExportEntry(entry);
|
|
220
|
+
if (table.has(suffix))
|
|
221
|
+
continue;
|
|
222
|
+
if (!alias) {
|
|
223
|
+
table.set(suffix, `${module}.${suffix}`);
|
|
224
|
+
changed = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const source = aliasToModule(module, alias);
|
|
228
|
+
const canonical = source ? out.get(source)?.get(suffix) : undefined;
|
|
229
|
+
if (canonical) {
|
|
230
|
+
table.set(suffix, canonical);
|
|
231
|
+
changed = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!changed)
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
/** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
|
|
241
|
+
* target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
|
|
242
|
+
* that point at a module OTHER than the import's own target are stamped (genuine re-exports;
|
|
243
|
+
* a locally-defined kind resolves through the normal alias path). Stamped on `metadata` (which
|
|
244
|
+
* permits additional properties, like `resolvedModuleName`) since the `Telo.Import` schema
|
|
245
|
+
* forbids extra top-level fields. Shared by both paths. */
|
|
246
|
+
export function stampReExportedKinds(imports, exportedKinds) {
|
|
247
|
+
for (const { manifest, targetModule } of imports) {
|
|
248
|
+
const table = exportedKinds.get(targetModule);
|
|
249
|
+
if (!table)
|
|
250
|
+
continue;
|
|
251
|
+
const reExported = {};
|
|
252
|
+
for (const [suffix, canonical] of table) {
|
|
253
|
+
if (canonical !== `${targetModule}.${suffix}`)
|
|
254
|
+
reExported[suffix] = canonical;
|
|
255
|
+
}
|
|
256
|
+
if (Object.keys(reExported).length === 0)
|
|
257
|
+
continue;
|
|
258
|
+
manifest.metadata.reExportedKinds = reExported;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** CLI/kernel adapter: collect re-export specs + alias map from a LoadedGraph. */
|
|
262
|
+
function forwardReExports(graph, result) {
|
|
263
|
+
const ownerSourceOf = new Map();
|
|
264
|
+
const specs = [];
|
|
265
|
+
const kindModules = [];
|
|
266
|
+
for (const [source, mod] of graph.modules) {
|
|
267
|
+
if (source === graph.rootSource)
|
|
268
|
+
continue; // root is an Application — no exports
|
|
269
|
+
const libDoc = mod.owner.manifests.find((m) => m && isModuleKind(m.kind));
|
|
270
|
+
const moduleName = libDoc?.metadata?.name;
|
|
271
|
+
if (!libDoc || !moduleName)
|
|
272
|
+
continue;
|
|
273
|
+
ownerSourceOf.set(moduleName, mod.owner.source);
|
|
274
|
+
specs.push(...reExportSpecsFromExports(moduleName, libDoc.exports?.resources));
|
|
275
|
+
kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds ?? [] });
|
|
276
|
+
}
|
|
277
|
+
const aliasToModule = (module, alias) => {
|
|
278
|
+
const ownerSource = ownerSourceOf.get(module);
|
|
279
|
+
return ownerSource
|
|
280
|
+
? (graph.importEdges.get(ownerSource)?.get(alias)?.targetModuleName ?? undefined)
|
|
281
|
+
: undefined;
|
|
282
|
+
};
|
|
283
|
+
forwardReExportManifests(result, specs, aliasToModule);
|
|
284
|
+
// Resolve every library's re-exported kinds and stamp them onto the consumer-facing
|
|
285
|
+
// Telo.Import manifests so the analyzer can register the re-export mappings.
|
|
286
|
+
const exportedKinds = resolveExportedKinds(kindModules, aliasToModule);
|
|
287
|
+
const imports = [];
|
|
288
|
+
for (const m of result) {
|
|
289
|
+
if (m.kind !== "Telo.Import")
|
|
290
|
+
continue;
|
|
291
|
+
const owner = m.metadata?.source;
|
|
292
|
+
const alias = m.metadata?.name;
|
|
293
|
+
const target = owner && alias ? graph.importEdges.get(owner)?.get(alias)?.targetModuleName : undefined;
|
|
294
|
+
if (target)
|
|
295
|
+
imports.push({ manifest: m, targetModule: target });
|
|
296
|
+
}
|
|
297
|
+
stampReExportedKinds(imports, exportedKinds);
|
|
298
|
+
}
|
|
108
299
|
/** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
|
|
109
300
|
* with `metadata.module` stamped on non-module docs. The kernel's runtime
|
|
110
301
|
* entry load uses this to convert a `Loader.loadModule` result into the
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { AnalysisRegistry } from "./analysis-registry.js";
|
|
|
2
2
|
export type { RefFieldInfo } from "./analysis-registry.js";
|
|
3
3
|
export { StaticAnalyzer } from "./analyzer.js";
|
|
4
4
|
export type { GraphLoadError, ImportEdge, LoadedFile, LoadedGraph, LoadedModule, ParseError, } from "./loaded-types.js";
|
|
5
|
-
export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
|
|
5
|
+
export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
|
|
6
6
|
export { visitManifest } from "./manifest-visitor.js";
|
|
7
7
|
export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
|
|
8
8
|
export { Loader } from "./manifest-loader.js";
|