@telorun/analyzer 0.15.0 → 0.16.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.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuhB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAimBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuhB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAypBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
package/dist/analyzer.js CHANGED
@@ -498,6 +498,11 @@ export class StaticAnalyzer {
498
498
  }
499
499
  }
500
500
  const aliasesByModule = ctx?.aliasesByModule ?? new Map();
501
+ // Per-module-scope seen aliases for DUPLICATE_IMPORT_ALIAS. Authored
502
+ // Telo.Import docs and synthetic-from-inline-`imports:` share one alias
503
+ // namespace per module, so a repeat — across either form — is an error
504
+ // rather than the silent last-writer-wins the resolver would otherwise do.
505
+ const seenAliasByScope = new Map();
501
506
  for (const m of manifests) {
502
507
  if (isModuleKind(m.kind)) {
503
508
  const namespace = m.metadata.namespace ?? null;
@@ -535,6 +540,34 @@ export class StaticAnalyzer {
535
540
  const resolvedModuleName = m.metadata.resolvedModuleName;
536
541
  const resolvedNamespace = m.metadata.resolvedNamespace;
537
542
  const ownModule = m.metadata?.module;
543
+ if (alias) {
544
+ const scopeKey = ownModule ?? "";
545
+ let seen = seenAliasByScope.get(scopeKey);
546
+ if (!seen) {
547
+ seen = new Set();
548
+ seenAliasByScope.set(scopeKey, seen);
549
+ }
550
+ if (seen.has(alias)) {
551
+ diagnostics.push({
552
+ severity: DiagnosticSeverity.Error,
553
+ code: "DUPLICATE_IMPORT_ALIAS",
554
+ source: SOURCE,
555
+ message: `Duplicate import alias '${alias}'. An alias may be declared once per module — ` +
556
+ `across both inline 'imports:' entries and 'Telo.Import' documents. ` +
557
+ `Rename or remove the duplicate.`,
558
+ data: {
559
+ resource: { kind: "Telo.Import", name: alias },
560
+ filePath: m.metadata?.source,
561
+ path: "metadata.name",
562
+ },
563
+ });
564
+ // Keep the first alias→target mapping intact; don't re-register the
565
+ // duplicate (last-writer-wins would shadow the original and cascade
566
+ // misleading follow-on diagnostics).
567
+ continue;
568
+ }
569
+ seen.add(alias);
570
+ }
538
571
  if (alias && source) {
539
572
  const targetModule = resolvedModuleName ?? source.split("/").filter(Boolean).pop() ?? source;
540
573
  // Module identity is registered globally so x-telo-ref resolution sees
@@ -557,6 +590,29 @@ export class StaticAnalyzer {
557
590
  }
558
591
  }
559
592
  }
593
+ // Seed `Self` for every module that contributes definitions but whose own
594
+ // Telo.Library doc isn't in this manifest set. `flattenForAnalyzer` forwards an
595
+ // imported library's definitions/abstracts/imports but NOT its module doc, so the
596
+ // module-doc loop above can't register `Self` for imported modules. Without this, a
597
+ // definition's `extends: Self.X` (a kind defined in the same library as the abstract)
598
+ // can't resolve and its `extendedBy` edge mis-keys under the literal "Self.X" — which
599
+ // stays invisible until another module also implements that abstract and flips the
600
+ // reference check from lenient to strict. `Self` always maps a module to its own name.
601
+ for (const m of manifests) {
602
+ if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract")
603
+ continue;
604
+ const ownModule = m.metadata?.module;
605
+ if (!ownModule || rootModules.has(ownModule))
606
+ continue;
607
+ let libResolver = aliasesByModule.get(ownModule);
608
+ if (!libResolver) {
609
+ libResolver = new AliasResolver();
610
+ aliasesByModule.set(ownModule, libResolver);
611
+ }
612
+ if (!libResolver.hasAlias("Self")) {
613
+ libResolver.registerImport("Self", ownModule, []);
614
+ }
615
+ }
560
616
  // Register definitions from Telo.Definition AND Telo.Abstract resources.
561
617
  // Abstracts declare contracts that implementations target via `extends` (canonical)
562
618
  // or `capability: <AbstractKind>` (legacy). Until they're registered, validateReferences
@@ -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,EA4Z/C,CAAC"}
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,EAsd/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -299,6 +299,37 @@ export const KERNEL_BUILTINS = [
299
299
  type: "array",
300
300
  items: { type: "string" },
301
301
  },
302
+ // Inline imports — name-keyed map sugar for separate `Telo.Import`
303
+ // documents. The key is the PascalCase alias (the import's
304
+ // `metadata.name`). Each value is either a bare source string
305
+ // (shorthand for `{ source }`) or the full object form. The loader
306
+ // desugars each entry into a synthetic `Telo.Import` before discovery;
307
+ // authored `Telo.Import` docs keep working alongside this. See
308
+ // analyzer/nodejs/src/inline-imports.ts.
309
+ imports: {
310
+ type: "object",
311
+ additionalProperties: {
312
+ oneOf: [
313
+ { type: "string" },
314
+ {
315
+ type: "object",
316
+ required: ["source"],
317
+ properties: {
318
+ source: { type: "string" },
319
+ variables: { type: "object" },
320
+ secrets: { type: "object" },
321
+ runtime: {
322
+ oneOf: [
323
+ { type: "string" },
324
+ { type: "array", items: { type: "string" } },
325
+ ],
326
+ },
327
+ },
328
+ additionalProperties: false,
329
+ },
330
+ ],
331
+ },
332
+ },
302
333
  // Application-level environment contract. Each entry layers `env:`
303
334
  // (required, names the source env var) and `default:` (optional, used
304
335
  // when the env var is unset) on top of an open JSON Schema property
@@ -392,6 +423,33 @@ export const KERNEL_BUILTINS = [
392
423
  type: "array",
393
424
  items: { type: "string" },
394
425
  },
426
+ // Inline imports — same name-keyed map sugar as Telo.Application; the
427
+ // loader desugars each entry into a synthetic Telo.Import. See the
428
+ // Application schema above and analyzer/nodejs/src/inline-imports.ts.
429
+ imports: {
430
+ type: "object",
431
+ additionalProperties: {
432
+ oneOf: [
433
+ { type: "string" },
434
+ {
435
+ type: "object",
436
+ required: ["source"],
437
+ properties: {
438
+ source: { type: "string" },
439
+ variables: { type: "object" },
440
+ secrets: { type: "object" },
441
+ runtime: {
442
+ oneOf: [
443
+ { type: "string" },
444
+ { type: "array", items: { type: "string" } },
445
+ ],
446
+ },
447
+ },
448
+ additionalProperties: false,
449
+ },
450
+ ],
451
+ },
452
+ },
395
453
  exports: {
396
454
  type: "object",
397
455
  properties: {
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
9
9
  export type { ModuleKind } from "./module-kinds.js";
10
10
  export { parseLoadedFile } from "./parse-loaded-file.js";
11
11
  export type { ParseOptions } from "./parse-loaded-file.js";
12
+ export { desugarLoadedFile, inlineImportManifests } from "./inline-imports.js";
13
+ export type { SyntheticImport } from "./inline-imports.js";
12
14
  export { residualEntrySchema, residualEntrySchemaMap } from "./residual-schema.js";
13
15
  export { buildDocumentPositions, buildLineOffsets, buildPositionIndex, documentLineOffsets, } from "./position-metadata.js";
14
16
  export type { DocumentPosition } from "./position-metadata.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { visitManifest } from "./manifest-visitor.js";
5
5
  export { Loader } from "./manifest-loader.js";
6
6
  export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
7
7
  export { parseLoadedFile } from "./parse-loaded-file.js";
8
+ export { desugarLoadedFile, inlineImportManifests } from "./inline-imports.js";
8
9
  export { residualEntrySchema, residualEntrySchemaMap } from "./residual-schema.js";
9
10
  export { buildDocumentPositions, buildLineOffsets, buildPositionIndex, documentLineOffsets, } from "./position-metadata.js";
10
11
  export { HttpSource } from "./sources/http-source.js";
@@ -0,0 +1,34 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { LoadedFile } from "./loaded-types.js";
3
+ import type { DocumentPosition } from "./position-metadata.js";
4
+ /** A synthetic `Telo.Import` produced by desugaring an `imports:` map entry,
5
+ * paired with the position metadata that pins its diagnostics back to the
6
+ * authoring line in the module document. */
7
+ export interface SyntheticImport {
8
+ manifest: ResourceManifest;
9
+ position: DocumentPosition;
10
+ }
11
+ /**
12
+ * Desugar a module document's inline `imports:` map into synthetic
13
+ * `Telo.Import` manifests. Each entry value is either a bare source string
14
+ * (shorthand for `{ source }`) or the full object form carrying
15
+ * `variables` / `secrets` / `runtime`. Malformed entries (object without a
16
+ * string `source`) are skipped here — the module document's own schema
17
+ * validation reports them against the precise `imports.<Alias>.source` path.
18
+ *
19
+ * The synthetic manifests are indistinguishable from authored `Telo.Import`
20
+ * documents downstream (alias registration, discovery, the kernel's
21
+ * import-controller), so the feature is purely additive at the declaration
22
+ * site. Pure and browser-safe — no I/O, no Node built-ins.
23
+ */
24
+ export declare function inlineImportManifests(moduleManifest: ResourceManifest, modulePosition: DocumentPosition | undefined): SyntheticImport[];
25
+ /** Returns a copy of `file` with synthetic `Telo.Import` manifests (from the
26
+ * module document's inline `imports:` map) appended to `manifests` and
27
+ * `positions`. `documents` is intentionally left untouched: it is the raw
28
+ * YAML-AST array round-trip consumers pair by index, and a synthetic import
29
+ * has no backing node. Every flatten/discovery loop iterates `manifests` and
30
+ * indexes `positions[i]` — never `documents[i]` in lockstep — so the trailing
31
+ * synthetics are visible to resolution while the AST round-trip stays intact.
32
+ * Returns `file` unchanged when there is no module doc or no inline imports. */
33
+ export declare function desugarLoadedFile(file: LoadedFile): LoadedFile;
34
+ //# sourceMappingURL=inline-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-imports.d.ts","sourceRoot":"","sources":["../src/inline-imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D;;6CAE6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,gBAAgB,EAChC,cAAc,EAAE,gBAAgB,GAAG,SAAS,GAC3C,eAAe,EAAE,CA0BnB;AAED;;;;;;;iFAOiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAmB9D"}
@@ -0,0 +1,106 @@
1
+ import { isModuleKind } from "./module-kinds.js";
2
+ /**
3
+ * Desugar a module document's inline `imports:` map into synthetic
4
+ * `Telo.Import` manifests. Each entry value is either a bare source string
5
+ * (shorthand for `{ source }`) or the full object form carrying
6
+ * `variables` / `secrets` / `runtime`. Malformed entries (object without a
7
+ * string `source`) are skipped here — the module document's own schema
8
+ * validation reports them against the precise `imports.<Alias>.source` path.
9
+ *
10
+ * The synthetic manifests are indistinguishable from authored `Telo.Import`
11
+ * documents downstream (alias registration, discovery, the kernel's
12
+ * import-controller), so the feature is purely additive at the declaration
13
+ * site. Pure and browser-safe — no I/O, no Node built-ins.
14
+ */
15
+ export function inlineImportManifests(moduleManifest, modulePosition) {
16
+ const raw = moduleManifest.imports;
17
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
18
+ return [];
19
+ const out = [];
20
+ for (const [alias, value] of Object.entries(raw)) {
21
+ const scalar = typeof value === "string";
22
+ const entry = scalar
23
+ ? { source: value }
24
+ : value && typeof value === "object" && !Array.isArray(value)
25
+ ? value
26
+ : undefined;
27
+ if (!entry || typeof entry.source !== "string")
28
+ continue;
29
+ const manifest = {
30
+ kind: "Telo.Import",
31
+ metadata: { name: alias },
32
+ source: entry.source,
33
+ ...(entry.variables !== undefined ? { variables: entry.variables } : {}),
34
+ ...(entry.secrets !== undefined ? { secrets: entry.secrets } : {}),
35
+ ...(entry.runtime !== undefined ? { runtime: entry.runtime } : {}),
36
+ };
37
+ out.push({ manifest, position: synthPosition(modulePosition, alias, scalar) });
38
+ }
39
+ return out;
40
+ }
41
+ /** Returns a copy of `file` with synthetic `Telo.Import` manifests (from the
42
+ * module document's inline `imports:` map) appended to `manifests` and
43
+ * `positions`. `documents` is intentionally left untouched: it is the raw
44
+ * YAML-AST array round-trip consumers pair by index, and a synthetic import
45
+ * has no backing node. Every flatten/discovery loop iterates `manifests` and
46
+ * indexes `positions[i]` — never `documents[i]` in lockstep — so the trailing
47
+ * synthetics are visible to resolution while the AST round-trip stays intact.
48
+ * Returns `file` unchanged when there is no module doc or no inline imports. */
49
+ export function desugarLoadedFile(file) {
50
+ let moduleIndex = -1;
51
+ for (let i = 0; i < file.manifests.length; i++) {
52
+ const m = file.manifests[i];
53
+ if (m && isModuleKind(m.kind)) {
54
+ moduleIndex = i;
55
+ break;
56
+ }
57
+ }
58
+ if (moduleIndex < 0)
59
+ return file;
60
+ const synthetic = inlineImportManifests(file.manifests[moduleIndex], file.positions[moduleIndex]);
61
+ if (synthetic.length === 0)
62
+ return file;
63
+ return {
64
+ ...file,
65
+ manifests: [...file.manifests, ...synthetic.map((s) => s.manifest)],
66
+ positions: [...file.positions, ...synthetic.map((s) => s.position)],
67
+ };
68
+ }
69
+ /** Build a `DocumentPosition` for a synthetic import by re-rooting the module
70
+ * document's `imports.<Alias>` position subtree at the import manifest's own
71
+ * paths (`source`, `variables.*`, `metadata.name`, …). This makes a
72
+ * diagnostic on the synthetic's `source` land on the `imports:` entry's
73
+ * authoring line rather than a phantom document. */
74
+ function synthPosition(modulePosition, alias, scalar) {
75
+ if (!modulePosition)
76
+ return { sourceLine: 0, positionIndex: new Map() };
77
+ const base = modulePosition.positionIndex;
78
+ const index = new Map();
79
+ const keyRange = base.get(`@key:imports.${alias}`);
80
+ const valueRange = base.get(`imports.${alias}`);
81
+ if (keyRange) {
82
+ index.set("metadata.name", keyRange);
83
+ index.set("@key:metadata.name", keyRange);
84
+ }
85
+ if (scalar) {
86
+ // `Console: std/console@1.2.3` — the entry value IS the source scalar.
87
+ if (valueRange)
88
+ index.set("source", valueRange);
89
+ }
90
+ else {
91
+ const valuePrefix = `imports.${alias}.`;
92
+ const keyPrefix = `@key:imports.${alias}.`;
93
+ for (const [path, range] of base) {
94
+ if (path.startsWith(valuePrefix)) {
95
+ index.set(path.slice(valuePrefix.length), range);
96
+ }
97
+ else if (path.startsWith(keyPrefix)) {
98
+ index.set(`@key:${path.slice(keyPrefix.length)}`, range);
99
+ }
100
+ }
101
+ }
102
+ if (valueRange)
103
+ index.set("", valueRange);
104
+ const sourceLine = (keyRange ?? valueRange)?.start.line ?? modulePosition.sourceLine;
105
+ return { sourceLine, positionIndex: index };
106
+ }
@@ -30,6 +30,15 @@ export declare class Loader {
30
30
  * private mutable copy must call `parseLoadedFile` directly with the
31
31
  * LoadedFile's `text`. */
32
32
  loadFile(url: string, options?: LoadOptions): Promise<LoadedFile>;
33
+ /** Parse `text` into a LoadedFile, then desugar inline `imports:` when the
34
+ * caller opted in. Desugaring lives here, not in the pure `parseLoadedFile`,
35
+ * so round-trip consumers (the editor) keep a raw manifest/AST/position
36
+ * triple they can pair by index; only resolved consumers that pass
37
+ * `desugarImports` see synthetic Telo.Import manifests. */
38
+ private parseAndMaybeDesugar;
39
+ /** Raw text of any already-cached variant for `source`, so a cache miss on
40
+ * one (compile, desugar) variant reparses without a second source read. */
41
+ private findCachedText;
33
42
  /** Load an owner file plus every partial reachable through its `include:`
34
43
  * list. Globs are expanded via the owning source's `expandGlob`. The
35
44
  * partials list is empty when the owner declares no `include:`. */
@@ -57,7 +66,7 @@ export declare class Loader {
57
66
  * owner) and return the `LoadedGraph` rooted at that owner. Returns
58
67
  * `null` only when `fileUrl` is neither an owner nor reachable from one
59
68
  * via parent-directory traversal. */
60
- loadGraphForFile(fileUrl: string): Promise<{
69
+ loadGraphForFile(fileUrl: string, options?: LoadOptions): Promise<{
61
70
  graph: LoadedGraph;
62
71
  ownerUrl: string;
63
72
  } | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,UAAU,EACV,WAAW,EACX,YAAY,EACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AASpB,qBAAa,MAAM;IACjB;;;yEAGqE;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAE3D;;;;;8BAK0B;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,qBAAqB,GAAE,cAAc,EAAE,GAAG,iBAAsB;IAmB5E,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKtC,OAAO,CAAC,IAAI;IAMN,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUrD;;;;2CAIuC;IACvC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAM7C;;;+BAG2B;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAgDvE;;wEAEoE;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAsB3E;;;qCAGiC;IAC3B,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAwG9E;;;;;;;;sBAQkB;IAClB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAOlE,OAAO,CAAC,6BAA6B;IAarC,OAAO,CAAC,mCAAmC;IAc3C,OAAO,CAAC,2BAA2B;YAkCrB,eAAe;IAmB7B;;;0CAGsC;IAChC,gBAAgB,CACpB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CA0B5D"}
1
+ {"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,UAAU,EACV,WAAW,EACX,YAAY,EACb,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAiBpB,qBAAa,MAAM;IACjB;;;yEAGqE;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAE3D;;;;;8BAK0B;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,qBAAqB,GAAE,cAAc,EAAE,GAAG,iBAAsB;IAmB5E,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKtC,OAAO,CAAC,IAAI;IAMN,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUrD;;;;2CAIuC;IACvC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAM7C;;;+BAG2B;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAyCvE;;;;gEAI4D;IAC5D,OAAO,CAAC,oBAAoB;IAa5B;gFAC4E;IAC5E,OAAO,CAAC,cAAc;IAQtB;;wEAEoE;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAsB3E;;;qCAGiC;IAC3B,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAwG9E;;;;;;;;sBAQkB;IAClB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAOlE,OAAO,CAAC,6BAA6B;IAarC,OAAO,CAAC,mCAAmC;IAc3C,OAAO,CAAC,2BAA2B;YAkCrB,eAAe;IAmB7B;;;0CAGsC;IAChC,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CA0B5D"}
@@ -1,6 +1,7 @@
1
1
  import { HttpSource } from "./sources/http-source.js";
2
2
  import { RegistrySource } from "./sources/registry-source.js";
3
3
  import { buildCelEnvironment } from "./cel-environment.js";
4
+ import { desugarLoadedFile } from "./inline-imports.js";
4
5
  import { isModuleKind } from "./module-kinds.js";
5
6
  import { parseLoadedFile } from "./parse-loaded-file.js";
6
7
  import { DEFAULT_MANIFEST_FILENAME, } from "./types.js";
@@ -10,6 +11,13 @@ const SYSTEM_KINDS = new Set([
10
11
  "Telo.Import",
11
12
  "Telo.Definition",
12
13
  ]);
14
+ /** File cache variant tags: compile (c/r) × desugarImports (d/n). A desugared
15
+ * and a raw load of the same file are distinct entries so neither sees the
16
+ * wrong manifest tree. */
17
+ const CACHE_VARIANTS = ["rn", "rd", "cn", "cd"];
18
+ function variantKey(options) {
19
+ return `${options?.compile ? "c" : "r"}${options?.desugarImports ? "d" : "n"}`;
20
+ }
13
21
  export class Loader {
14
22
  /** LoadedFile cache keyed by `${compile ? "compiled" : "raw"}:${source}`.
15
23
  * Same dual-keying as the legacy ResourceManifest[] cache: a compile-mode
@@ -74,30 +82,26 @@ export class Loader {
74
82
  * private mutable copy must call `parseLoadedFile` directly with the
75
83
  * LoadedFile's `text`. */
76
84
  async loadFile(url, options) {
77
- const compileKey = options?.compile ? "compiled" : "raw";
85
+ const variant = variantKey(options);
78
86
  const knownSource = this.urlToSource.get(url);
79
87
  if (knownSource) {
80
- const cached = this.fileCache.get(`${compileKey}:${knownSource}`);
88
+ const cached = this.fileCache.get(`${variant}:${knownSource}`);
81
89
  if (cached)
82
90
  return cached;
83
- // The other compile-mode entry is cached — reparse from its text
91
+ // Another variant of this source is cached — reparse from its text
84
92
  // instead of re-reading the source.
85
93
  //
86
94
  // NOTE for watch-mode reactivation (cli/nodejs/src/commands/run.ts
87
95
  // currently has `setupWatchMode` commented out): this branch
88
96
  // assumes file contents don't change underneath a single Loader.
89
97
  // Reviving watch mode will need a public `invalidate(url)` (or
90
- // similar) that drops both `urlToSource[url]` and the cached
91
- // entries for its canonical source before the loader serves the
92
- // file again.
93
- const altKey = `${compileKey === "compiled" ? "raw" : "compiled"}:${knownSource}`;
94
- const alt = this.fileCache.get(altKey);
95
- if (alt) {
96
- const reparsed = parseLoadedFile(knownSource, url, alt.text, {
97
- compile: options?.compile,
98
- celEnv: this.celEnv,
99
- });
100
- this.fileCache.set(`${compileKey}:${knownSource}`, reparsed);
98
+ // similar) that drops both `urlToSource[url]` and every cached
99
+ // variant entry for its canonical source before the loader serves
100
+ // the file again.
101
+ const altText = this.findCachedText(knownSource);
102
+ if (altText !== undefined) {
103
+ const reparsed = this.parseAndMaybeDesugar(knownSource, url, altText, options);
104
+ this.fileCache.set(`${variant}:${knownSource}`, reparsed);
101
105
  return reparsed;
102
106
  }
103
107
  }
@@ -109,16 +113,35 @@ export class Loader {
109
113
  // for that exact URL — hit the urlToSource fast path instead of
110
114
  // falling through to a redundant `pick(url).read(url)`.
111
115
  this.urlToSource.set(source, source);
112
- const cacheKey = `${compileKey}:${source}`;
116
+ const cacheKey = `${variant}:${source}`;
113
117
  const cached = this.fileCache.get(cacheKey);
114
118
  if (cached && cached.text === text)
115
119
  return cached;
116
- const loaded = parseLoadedFile(source, url, text, {
120
+ const loaded = this.parseAndMaybeDesugar(source, url, text, options);
121
+ this.fileCache.set(cacheKey, loaded);
122
+ return loaded;
123
+ }
124
+ /** Parse `text` into a LoadedFile, then desugar inline `imports:` when the
125
+ * caller opted in. Desugaring lives here, not in the pure `parseLoadedFile`,
126
+ * so round-trip consumers (the editor) keep a raw manifest/AST/position
127
+ * triple they can pair by index; only resolved consumers that pass
128
+ * `desugarImports` see synthetic Telo.Import manifests. */
129
+ parseAndMaybeDesugar(source, requestedUrl, text, options) {
130
+ const loaded = parseLoadedFile(source, requestedUrl, text, {
117
131
  compile: options?.compile,
118
132
  celEnv: this.celEnv,
119
133
  });
120
- this.fileCache.set(cacheKey, loaded);
121
- return loaded;
134
+ return options?.desugarImports ? desugarLoadedFile(loaded) : loaded;
135
+ }
136
+ /** Raw text of any already-cached variant for `source`, so a cache miss on
137
+ * one (compile, desugar) variant reparses without a second source read. */
138
+ findCachedText(source) {
139
+ for (const v of CACHE_VARIANTS) {
140
+ const cached = this.fileCache.get(`${v}:${source}`);
141
+ if (cached)
142
+ return cached.text;
143
+ }
144
+ return undefined;
122
145
  }
123
146
  /** Load an owner file plus every partial reachable through its `include:`
124
147
  * list. Globs are expanded via the owning source's `expandGlob`. The
@@ -319,12 +342,12 @@ export class Loader {
319
342
  * owner) and return the `LoadedGraph` rooted at that owner. Returns
320
343
  * `null` only when `fileUrl` is neither an owner nor reachable from one
321
344
  * via parent-directory traversal. */
322
- async loadGraphForFile(fileUrl) {
345
+ async loadGraphForFile(fileUrl, options) {
323
346
  try {
324
- const owner = await this.loadFile(fileUrl);
347
+ const owner = await this.loadFile(fileUrl, options);
325
348
  const isOwner = owner.manifests.some((m) => m && isModuleKind(m.kind));
326
349
  if (isOwner) {
327
- const graph = await this.loadGraph(fileUrl);
350
+ const graph = await this.loadGraph(fileUrl, options);
328
351
  return { graph, ownerUrl: graph.rootSource };
329
352
  }
330
353
  }
@@ -341,7 +364,7 @@ export class Loader {
341
364
  const ownerUrl = await source.resolveOwnerOf(fileUrl);
342
365
  if (!ownerUrl)
343
366
  return null;
344
- const graph = await this.loadGraph(ownerUrl);
367
+ const graph = await this.loadGraph(ownerUrl, options);
345
368
  return { graph, ownerUrl: graph.rootSource };
346
369
  }
347
370
  }
package/dist/types.d.ts CHANGED
@@ -57,6 +57,15 @@ export interface LoadOptions {
57
57
  * so the kernel can evaluate them at runtime. Leave unset (false) for static analysis —
58
58
  * the analyzer works on raw strings and does not need compiled values. */
59
59
  compile?: boolean;
60
+ /** When true, each module document's inline `imports:` map is desugared into
61
+ * synthetic `Telo.Import` manifests appended to the file's `manifests` /
62
+ * `positions` (the AST `documents` array is left raw). On for every resolved
63
+ * consumer — the kernel's analysis and runtime loads, and the analyzer — so
64
+ * inline imports participate in discovery, alias resolution, and execution.
65
+ * Off for the editor's round-trip view, which reads the raw `imports:` map and
66
+ * pairs manifests to YAML nodes by index. Folded into the file cache key so a
67
+ * desugared and a raw load of the same file never collide. */
68
+ desugarImports?: boolean;
60
69
  }
61
70
  export interface LoaderInitOptions {
62
71
  /** Sources inserted with highest priority before built-ins. */
@@ -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;IACzB;;;;;;;;;;sDAUkD;IAClD,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
+ {"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;IAClB;;;;;;;mEAO+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;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;IACzB;;;;;;;;;;sDAUkD;IAClD,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -48,7 +48,7 @@
48
48
  "@types/node": "^20.0.0",
49
49
  "typescript": "^5.0.0",
50
50
  "vitest": "^2.1.8",
51
- "@telorun/sdk": "0.16.0"
51
+ "@telorun/sdk": "0.17.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
package/src/analyzer.ts CHANGED
@@ -621,6 +621,11 @@ export class StaticAnalyzer {
621
621
  }
622
622
  }
623
623
  const aliasesByModule = ctx?.aliasesByModule ?? new Map<string, AliasResolver>();
624
+ // Per-module-scope seen aliases for DUPLICATE_IMPORT_ALIAS. Authored
625
+ // Telo.Import docs and synthetic-from-inline-`imports:` share one alias
626
+ // namespace per module, so a repeat — across either form — is an error
627
+ // rather than the silent last-writer-wins the resolver would otherwise do.
628
+ const seenAliasByScope = new Map<string, Set<string>>();
624
629
  for (const m of manifests) {
625
630
  if (isModuleKind(m.kind)) {
626
631
  const namespace = ((m.metadata as any).namespace as string | undefined) ?? null;
@@ -659,6 +664,35 @@ export class StaticAnalyzer {
659
664
  | null
660
665
  | undefined;
661
666
  const ownModule = (m.metadata as { module?: string } | undefined)?.module;
667
+ if (alias) {
668
+ const scopeKey = ownModule ?? "";
669
+ let seen = seenAliasByScope.get(scopeKey);
670
+ if (!seen) {
671
+ seen = new Set<string>();
672
+ seenAliasByScope.set(scopeKey, seen);
673
+ }
674
+ if (seen.has(alias)) {
675
+ diagnostics.push({
676
+ severity: DiagnosticSeverity.Error,
677
+ code: "DUPLICATE_IMPORT_ALIAS",
678
+ source: SOURCE,
679
+ message:
680
+ `Duplicate import alias '${alias}'. An alias may be declared once per module — ` +
681
+ `across both inline 'imports:' entries and 'Telo.Import' documents. ` +
682
+ `Rename or remove the duplicate.`,
683
+ data: {
684
+ resource: { kind: "Telo.Import", name: alias },
685
+ filePath: (m.metadata as { source?: string } | undefined)?.source,
686
+ path: "metadata.name",
687
+ },
688
+ });
689
+ // Keep the first alias→target mapping intact; don't re-register the
690
+ // duplicate (last-writer-wins would shadow the original and cascade
691
+ // misleading follow-on diagnostics).
692
+ continue;
693
+ }
694
+ seen.add(alias);
695
+ }
662
696
  if (alias && source) {
663
697
  const targetModule =
664
698
  resolvedModuleName ?? source.split("/").filter(Boolean).pop() ?? source;
@@ -682,6 +716,28 @@ export class StaticAnalyzer {
682
716
  }
683
717
  }
684
718
 
719
+ // Seed `Self` for every module that contributes definitions but whose own
720
+ // Telo.Library doc isn't in this manifest set. `flattenForAnalyzer` forwards an
721
+ // imported library's definitions/abstracts/imports but NOT its module doc, so the
722
+ // module-doc loop above can't register `Self` for imported modules. Without this, a
723
+ // definition's `extends: Self.X` (a kind defined in the same library as the abstract)
724
+ // can't resolve and its `extendedBy` edge mis-keys under the literal "Self.X" — which
725
+ // stays invisible until another module also implements that abstract and flips the
726
+ // reference check from lenient to strict. `Self` always maps a module to its own name.
727
+ for (const m of manifests) {
728
+ if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
729
+ const ownModule = (m.metadata as { module?: string } | undefined)?.module;
730
+ if (!ownModule || rootModules.has(ownModule)) continue;
731
+ let libResolver = aliasesByModule.get(ownModule);
732
+ if (!libResolver) {
733
+ libResolver = new AliasResolver();
734
+ aliasesByModule.set(ownModule, libResolver);
735
+ }
736
+ if (!libResolver.hasAlias("Self")) {
737
+ libResolver.registerImport("Self", ownModule, []);
738
+ }
739
+ }
740
+
685
741
  // Register definitions from Telo.Definition AND Telo.Abstract resources.
686
742
  // Abstracts declare contracts that implementations target via `extends` (canonical)
687
743
  // or `capability: <AbstractKind>` (legacy). Until they're registered, validateReferences
package/src/builtins.ts CHANGED
@@ -301,6 +301,37 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
301
301
  type: "array",
302
302
  items: { type: "string" },
303
303
  },
304
+ // Inline imports — name-keyed map sugar for separate `Telo.Import`
305
+ // documents. The key is the PascalCase alias (the import's
306
+ // `metadata.name`). Each value is either a bare source string
307
+ // (shorthand for `{ source }`) or the full object form. The loader
308
+ // desugars each entry into a synthetic `Telo.Import` before discovery;
309
+ // authored `Telo.Import` docs keep working alongside this. See
310
+ // analyzer/nodejs/src/inline-imports.ts.
311
+ imports: {
312
+ type: "object",
313
+ additionalProperties: {
314
+ oneOf: [
315
+ { type: "string" },
316
+ {
317
+ type: "object",
318
+ required: ["source"],
319
+ properties: {
320
+ source: { type: "string" },
321
+ variables: { type: "object" },
322
+ secrets: { type: "object" },
323
+ runtime: {
324
+ oneOf: [
325
+ { type: "string" },
326
+ { type: "array", items: { type: "string" } },
327
+ ],
328
+ },
329
+ },
330
+ additionalProperties: false,
331
+ },
332
+ ],
333
+ },
334
+ },
304
335
  // Application-level environment contract. Each entry layers `env:`
305
336
  // (required, names the source env var) and `default:` (optional, used
306
337
  // when the env var is unset) on top of an open JSON Schema property
@@ -394,6 +425,33 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
394
425
  type: "array",
395
426
  items: { type: "string" },
396
427
  },
428
+ // Inline imports — same name-keyed map sugar as Telo.Application; the
429
+ // loader desugars each entry into a synthetic Telo.Import. See the
430
+ // Application schema above and analyzer/nodejs/src/inline-imports.ts.
431
+ imports: {
432
+ type: "object",
433
+ additionalProperties: {
434
+ oneOf: [
435
+ { type: "string" },
436
+ {
437
+ type: "object",
438
+ required: ["source"],
439
+ properties: {
440
+ source: { type: "string" },
441
+ variables: { type: "object" },
442
+ secrets: { type: "object" },
443
+ runtime: {
444
+ oneOf: [
445
+ { type: "string" },
446
+ { type: "array", items: { type: "string" } },
447
+ ],
448
+ },
449
+ },
450
+ additionalProperties: false,
451
+ },
452
+ ],
453
+ },
454
+ },
397
455
  exports: {
398
456
  type: "object",
399
457
  properties: {
package/src/index.ts CHANGED
@@ -25,6 +25,8 @@ export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
25
25
  export type { ModuleKind } from "./module-kinds.js";
26
26
  export { parseLoadedFile } from "./parse-loaded-file.js";
27
27
  export type { ParseOptions } from "./parse-loaded-file.js";
28
+ export { desugarLoadedFile, inlineImportManifests } from "./inline-imports.js";
29
+ export type { SyntheticImport } from "./inline-imports.js";
28
30
  export { residualEntrySchema, residualEntrySchemaMap } from "./residual-schema.js";
29
31
  export {
30
32
  buildDocumentPositions,
@@ -0,0 +1,130 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { LoadedFile } from "./loaded-types.js";
3
+ import { isModuleKind } from "./module-kinds.js";
4
+ import type { DocumentPosition } from "./position-metadata.js";
5
+ import type { PositionIndex } from "./types.js";
6
+
7
+ /** A synthetic `Telo.Import` produced by desugaring an `imports:` map entry,
8
+ * paired with the position metadata that pins its diagnostics back to the
9
+ * authoring line in the module document. */
10
+ export interface SyntheticImport {
11
+ manifest: ResourceManifest;
12
+ position: DocumentPosition;
13
+ }
14
+
15
+ /**
16
+ * Desugar a module document's inline `imports:` map into synthetic
17
+ * `Telo.Import` manifests. Each entry value is either a bare source string
18
+ * (shorthand for `{ source }`) or the full object form carrying
19
+ * `variables` / `secrets` / `runtime`. Malformed entries (object without a
20
+ * string `source`) are skipped here — the module document's own schema
21
+ * validation reports them against the precise `imports.<Alias>.source` path.
22
+ *
23
+ * The synthetic manifests are indistinguishable from authored `Telo.Import`
24
+ * documents downstream (alias registration, discovery, the kernel's
25
+ * import-controller), so the feature is purely additive at the declaration
26
+ * site. Pure and browser-safe — no I/O, no Node built-ins.
27
+ */
28
+ export function inlineImportManifests(
29
+ moduleManifest: ResourceManifest,
30
+ modulePosition: DocumentPosition | undefined,
31
+ ): SyntheticImport[] {
32
+ const raw = (moduleManifest as { imports?: unknown }).imports;
33
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
34
+
35
+ const out: SyntheticImport[] = [];
36
+ for (const [alias, value] of Object.entries(raw as Record<string, unknown>)) {
37
+ const scalar = typeof value === "string";
38
+ const entry = scalar
39
+ ? { source: value as string }
40
+ : value && typeof value === "object" && !Array.isArray(value)
41
+ ? (value as Record<string, unknown>)
42
+ : undefined;
43
+ if (!entry || typeof entry.source !== "string") continue;
44
+
45
+ const manifest = {
46
+ kind: "Telo.Import",
47
+ metadata: { name: alias },
48
+ source: entry.source,
49
+ ...(entry.variables !== undefined ? { variables: entry.variables } : {}),
50
+ ...(entry.secrets !== undefined ? { secrets: entry.secrets } : {}),
51
+ ...(entry.runtime !== undefined ? { runtime: entry.runtime } : {}),
52
+ } as unknown as ResourceManifest;
53
+
54
+ out.push({ manifest, position: synthPosition(modulePosition, alias, scalar) });
55
+ }
56
+ return out;
57
+ }
58
+
59
+ /** Returns a copy of `file` with synthetic `Telo.Import` manifests (from the
60
+ * module document's inline `imports:` map) appended to `manifests` and
61
+ * `positions`. `documents` is intentionally left untouched: it is the raw
62
+ * YAML-AST array round-trip consumers pair by index, and a synthetic import
63
+ * has no backing node. Every flatten/discovery loop iterates `manifests` and
64
+ * indexes `positions[i]` — never `documents[i]` in lockstep — so the trailing
65
+ * synthetics are visible to resolution while the AST round-trip stays intact.
66
+ * Returns `file` unchanged when there is no module doc or no inline imports. */
67
+ export function desugarLoadedFile(file: LoadedFile): LoadedFile {
68
+ let moduleIndex = -1;
69
+ for (let i = 0; i < file.manifests.length; i++) {
70
+ const m = file.manifests[i];
71
+ if (m && isModuleKind(m.kind)) {
72
+ moduleIndex = i;
73
+ break;
74
+ }
75
+ }
76
+ if (moduleIndex < 0) return file;
77
+
78
+ const synthetic = inlineImportManifests(file.manifests[moduleIndex]!, file.positions[moduleIndex]);
79
+ if (synthetic.length === 0) return file;
80
+
81
+ return {
82
+ ...file,
83
+ manifests: [...file.manifests, ...synthetic.map((s) => s.manifest)],
84
+ positions: [...file.positions, ...synthetic.map((s) => s.position)],
85
+ };
86
+ }
87
+
88
+ /** Build a `DocumentPosition` for a synthetic import by re-rooting the module
89
+ * document's `imports.<Alias>` position subtree at the import manifest's own
90
+ * paths (`source`, `variables.*`, `metadata.name`, …). This makes a
91
+ * diagnostic on the synthetic's `source` land on the `imports:` entry's
92
+ * authoring line rather than a phantom document. */
93
+ function synthPosition(
94
+ modulePosition: DocumentPosition | undefined,
95
+ alias: string,
96
+ scalar: boolean,
97
+ ): DocumentPosition {
98
+ if (!modulePosition) return { sourceLine: 0, positionIndex: new Map() };
99
+
100
+ const base = modulePosition.positionIndex;
101
+ const index: PositionIndex = new Map();
102
+
103
+ const keyRange = base.get(`@key:imports.${alias}`);
104
+ const valueRange = base.get(`imports.${alias}`);
105
+
106
+ if (keyRange) {
107
+ index.set("metadata.name", keyRange);
108
+ index.set("@key:metadata.name", keyRange);
109
+ }
110
+
111
+ if (scalar) {
112
+ // `Console: std/console@1.2.3` — the entry value IS the source scalar.
113
+ if (valueRange) index.set("source", valueRange);
114
+ } else {
115
+ const valuePrefix = `imports.${alias}.`;
116
+ const keyPrefix = `@key:imports.${alias}.`;
117
+ for (const [path, range] of base) {
118
+ if (path.startsWith(valuePrefix)) {
119
+ index.set(path.slice(valuePrefix.length), range);
120
+ } else if (path.startsWith(keyPrefix)) {
121
+ index.set(`@key:${path.slice(keyPrefix.length)}`, range);
122
+ }
123
+ }
124
+ }
125
+
126
+ if (valueRange) index.set("", valueRange);
127
+
128
+ const sourceLine = (keyRange ?? valueRange)?.start.line ?? modulePosition.sourceLine;
129
+ return { sourceLine, positionIndex: index };
130
+ }
@@ -10,6 +10,7 @@ import type {
10
10
  LoadedGraph,
11
11
  LoadedModule,
12
12
  } from "./loaded-types.js";
13
+ import { desugarLoadedFile } from "./inline-imports.js";
13
14
  import { isModuleKind } from "./module-kinds.js";
14
15
  import { parseLoadedFile } from "./parse-loaded-file.js";
15
16
  import {
@@ -26,6 +27,14 @@ const SYSTEM_KINDS = new Set([
26
27
  "Telo.Definition",
27
28
  ]);
28
29
 
30
+ /** File cache variant tags: compile (c/r) × desugarImports (d/n). A desugared
31
+ * and a raw load of the same file are distinct entries so neither sees the
32
+ * wrong manifest tree. */
33
+ const CACHE_VARIANTS = ["rn", "rd", "cn", "cd"] as const;
34
+ function variantKey(options?: LoadOptions): string {
35
+ return `${options?.compile ? "c" : "r"}${options?.desugarImports ? "d" : "n"}`;
36
+ }
37
+
29
38
  export class Loader {
30
39
  /** LoadedFile cache keyed by `${compile ? "compiled" : "raw"}:${source}`.
31
40
  * Same dual-keying as the legacy ResourceManifest[] cache: a compile-mode
@@ -100,29 +109,25 @@ export class Loader {
100
109
  * private mutable copy must call `parseLoadedFile` directly with the
101
110
  * LoadedFile's `text`. */
102
111
  async loadFile(url: string, options?: LoadOptions): Promise<LoadedFile> {
103
- const compileKey = options?.compile ? "compiled" : "raw";
112
+ const variant = variantKey(options);
104
113
  const knownSource = this.urlToSource.get(url);
105
114
  if (knownSource) {
106
- const cached = this.fileCache.get(`${compileKey}:${knownSource}`);
115
+ const cached = this.fileCache.get(`${variant}:${knownSource}`);
107
116
  if (cached) return cached;
108
- // The other compile-mode entry is cached — reparse from its text
117
+ // Another variant of this source is cached — reparse from its text
109
118
  // instead of re-reading the source.
110
119
  //
111
120
  // NOTE for watch-mode reactivation (cli/nodejs/src/commands/run.ts
112
121
  // currently has `setupWatchMode` commented out): this branch
113
122
  // assumes file contents don't change underneath a single Loader.
114
123
  // Reviving watch mode will need a public `invalidate(url)` (or
115
- // similar) that drops both `urlToSource[url]` and the cached
116
- // entries for its canonical source before the loader serves the
117
- // file again.
118
- const altKey = `${compileKey === "compiled" ? "raw" : "compiled"}:${knownSource}`;
119
- const alt = this.fileCache.get(altKey);
120
- if (alt) {
121
- const reparsed = parseLoadedFile(knownSource, url, alt.text, {
122
- compile: options?.compile,
123
- celEnv: this.celEnv,
124
- });
125
- this.fileCache.set(`${compileKey}:${knownSource}`, reparsed);
124
+ // similar) that drops both `urlToSource[url]` and every cached
125
+ // variant entry for its canonical source before the loader serves
126
+ // the file again.
127
+ const altText = this.findCachedText(knownSource);
128
+ if (altText !== undefined) {
129
+ const reparsed = this.parseAndMaybeDesugar(knownSource, url, altText, options);
130
+ this.fileCache.set(`${variant}:${knownSource}`, reparsed);
126
131
  return reparsed;
127
132
  }
128
133
  }
@@ -135,16 +140,41 @@ export class Loader {
135
140
  // for that exact URL — hit the urlToSource fast path instead of
136
141
  // falling through to a redundant `pick(url).read(url)`.
137
142
  this.urlToSource.set(source, source);
138
- const cacheKey = `${compileKey}:${source}`;
143
+ const cacheKey = `${variant}:${source}`;
139
144
  const cached = this.fileCache.get(cacheKey);
140
145
  if (cached && cached.text === text) return cached;
141
146
 
142
- const loaded = parseLoadedFile(source, url, text, {
147
+ const loaded = this.parseAndMaybeDesugar(source, url, text, options);
148
+ this.fileCache.set(cacheKey, loaded);
149
+ return loaded;
150
+ }
151
+
152
+ /** Parse `text` into a LoadedFile, then desugar inline `imports:` when the
153
+ * caller opted in. Desugaring lives here, not in the pure `parseLoadedFile`,
154
+ * so round-trip consumers (the editor) keep a raw manifest/AST/position
155
+ * triple they can pair by index; only resolved consumers that pass
156
+ * `desugarImports` see synthetic Telo.Import manifests. */
157
+ private parseAndMaybeDesugar(
158
+ source: string,
159
+ requestedUrl: string,
160
+ text: string,
161
+ options?: LoadOptions,
162
+ ): LoadedFile {
163
+ const loaded = parseLoadedFile(source, requestedUrl, text, {
143
164
  compile: options?.compile,
144
165
  celEnv: this.celEnv,
145
166
  });
146
- this.fileCache.set(cacheKey, loaded);
147
- return loaded;
167
+ return options?.desugarImports ? desugarLoadedFile(loaded) : loaded;
168
+ }
169
+
170
+ /** Raw text of any already-cached variant for `source`, so a cache miss on
171
+ * one (compile, desugar) variant reparses without a second source read. */
172
+ private findCachedText(source: string): string | undefined {
173
+ for (const v of CACHE_VARIANTS) {
174
+ const cached = this.fileCache.get(`${v}:${source}`);
175
+ if (cached) return cached.text;
176
+ }
177
+ return undefined;
148
178
  }
149
179
 
150
180
  /** Load an owner file plus every partial reachable through its `include:`
@@ -382,12 +412,13 @@ export class Loader {
382
412
  * via parent-directory traversal. */
383
413
  async loadGraphForFile(
384
414
  fileUrl: string,
415
+ options?: LoadOptions,
385
416
  ): Promise<{ graph: LoadedGraph; ownerUrl: string } | null> {
386
417
  try {
387
- const owner = await this.loadFile(fileUrl);
418
+ const owner = await this.loadFile(fileUrl, options);
388
419
  const isOwner = owner.manifests.some((m) => m && isModuleKind(m.kind));
389
420
  if (isOwner) {
390
- const graph = await this.loadGraph(fileUrl);
421
+ const graph = await this.loadGraph(fileUrl, options);
391
422
  return { graph, ownerUrl: graph.rootSource };
392
423
  }
393
424
  } catch (err) {
@@ -404,7 +435,7 @@ export class Loader {
404
435
  if (!source.resolveOwnerOf) return null;
405
436
  const ownerUrl = await source.resolveOwnerOf(fileUrl);
406
437
  if (!ownerUrl) return null;
407
- const graph = await this.loadGraph(ownerUrl);
438
+ const graph = await this.loadGraph(ownerUrl, options);
408
439
  return { graph, ownerUrl: graph.rootSource };
409
440
  }
410
441
 
package/src/types.ts CHANGED
@@ -63,6 +63,15 @@ export interface LoadOptions {
63
63
  * so the kernel can evaluate them at runtime. Leave unset (false) for static analysis —
64
64
  * the analyzer works on raw strings and does not need compiled values. */
65
65
  compile?: boolean;
66
+ /** When true, each module document's inline `imports:` map is desugared into
67
+ * synthetic `Telo.Import` manifests appended to the file's `manifests` /
68
+ * `positions` (the AST `documents` array is left raw). On for every resolved
69
+ * consumer — the kernel's analysis and runtime loads, and the analyzer — so
70
+ * inline imports participate in discovery, alias resolution, and execution.
71
+ * Off for the editor's round-trip view, which reads the raw `imports:` map and
72
+ * pairs manifests to YAML nodes by index. Folded into the file cache key so a
73
+ * desugared and a raw load of the same file never collide. */
74
+ desugarImports?: boolean;
66
75
  }
67
76
 
68
77
  export interface LoaderInitOptions {