@telorun/kernel 0.37.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import {
3
+ getRefIdentity,
3
4
  isCompiledValue,
4
5
  isInvokeError,
5
6
  isCancellationError,
@@ -16,12 +17,14 @@ import {
16
17
  type ResourceDefinition,
17
18
  type ResourceInstance,
18
19
  type ResourceManifest,
20
+ type ResourceOwner,
19
21
  type RuntimeDiagnostic,
20
22
  type ScopeContext,
21
23
  type ScopeHandle,
22
24
  type Tracer,
23
25
  } from "@telorun/sdk";
24
26
  import { RuntimeError } from "@telorun/sdk";
27
+ import { evalPathCovers } from "@telorun/analyzer";
25
28
 
26
29
  export { resourceKey };
27
30
 
@@ -40,16 +43,28 @@ function isResolvedRef(value: unknown): value is ResourceRef {
40
43
  return Object.keys(v).every((k) => k === "kind" || k === "name" || k === "alias");
41
44
  }
42
45
 
46
+ /** The system kinds whose top-level `schema:` is, by definition, a JSON Schema
47
+ * document (with `examples` / `default` / `const`) rather than config — a
48
+ * `{kind, name}` inside one is documentation data, not a `!ref`. For these the
49
+ * `schema` field is skipped when walking for refs/properties; for every other
50
+ * kind a `schema` field is ordinary config and is walked normally, so a real
51
+ * `schema: !ref X` still resolves. Narrowly scoped (rather than skipping any
52
+ * field named `schema` on every kind) so the heuristic can't misfire. */
53
+ const SCHEMA_AS_CONTRACT_KINDS = new Set(["Telo.Definition", "Telo.Abstract", "Telo.Type"]);
54
+
43
55
  /**
44
56
  * Walk a resource manifest's config and collect every resolved `{kind, name,
45
57
  * alias?}` reference it points at — the outbound edges for the dependency graph.
46
58
  * Called at create time, before Phase-5 injection swaps refs for live instances,
47
59
  * so the targets are still inspectable plain objects (and there are no instance
48
- * cycles to guard against). `metadata` is skipped (it holds the resource's own
49
- * identity, never refs); ref leaves are not descended into. Deduped by alias+name.
60
+ * cycles to guard against). Deduped by alias+name. `metadata` is always skipped
61
+ * (the resource's own identity); `schema` is skipped only for the system kinds
62
+ * that carry a JSON-Schema contract there (see {@link SCHEMA_AS_CONTRACT_KINDS}).
63
+ * Ref leaves are not descended into.
50
64
  */
51
65
  function collectResourceRefs(resource: ResourceManifest): ResourceRef[] {
52
66
  const found = new Map<string, ResourceRef>();
67
+ const skipSchema = SCHEMA_AS_CONTRACT_KINDS.has(resource.kind as string);
53
68
  const visit = (value: unknown): void => {
54
69
  if (isResolvedRef(value)) {
55
70
  const key = `${value.alias ?? ""}::${value.name}`;
@@ -60,18 +75,86 @@ function collectResourceRefs(resource: ResourceManifest): ResourceRef[] {
60
75
  for (const item of value) visit(item);
61
76
  } else if (value && typeof value === "object") {
62
77
  for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
63
- if (k === "metadata") continue;
78
+ if (k === "metadata" || (skipSchema && k === "schema")) continue;
64
79
  visit(v);
65
80
  }
66
81
  }
67
82
  };
68
83
  for (const [k, v] of Object.entries(resource as Record<string, unknown>)) {
69
- if (k === "kind" || k === "metadata") continue;
84
+ if (k === "kind" || k === "metadata" || (skipSchema && k === "schema")) continue;
70
85
  visit(v);
71
86
  }
72
87
  return [...found.values()];
73
88
  }
74
89
 
90
+ /**
91
+ * Build a resource's resolved properties for the debug stream — its config "after
92
+ * templating", with `${{ }}` / `!cel` reduced to concrete values. The manifest is
93
+ * already compile-evaluated by the time it is created, so this just makes the
94
+ * remaining live forms wire-friendly:
95
+ * - a resolved `!ref` (`{kind, name, alias?}`) → its `{kind, name}` target;
96
+ * - a deferred runtime expression (a `CompiledValue` left for per-call eval, e.g.
97
+ * an Http.Api route reading `request`) → its `${{ source }}` text — there is no
98
+ * concrete value for it at the resource level;
99
+ * - a string carrying a known secret value → `[secret]` (substring-scrubbed), so
100
+ * a compile-time `${{ secrets.x }}` never lands in the stream verbatim.
101
+ * `metadata` / `schema` are omitted (identity and JSON-Schema, not config).
102
+ */
103
+ /** Substring-scrubbing a secret shorter than this risks redacting unrelated
104
+ * content (a short/common-word value matches everywhere), so below it only a
105
+ * whole-value match is redacted. */
106
+ const MIN_SUBSTRING_SCRUB_LEN = 5;
107
+
108
+ export function buildResolvedProperties(
109
+ resource: ResourceManifest,
110
+ secretValues: Set<string>,
111
+ ): Record<string, unknown> {
112
+ const skipSchema = SCHEMA_AS_CONTRACT_KINDS.has(resource.kind as string);
113
+ const scrub = (s: string): string => {
114
+ let out = s;
115
+ for (const secret of secretValues) {
116
+ if (!secret) continue;
117
+ // Exact match is always a secret; substring-redact only longer values so a
118
+ // short secret can't garble unrelated text it happens to appear in.
119
+ if (out === secret) out = "[secret]";
120
+ else if (secret.length >= MIN_SUBSTRING_SCRUB_LEN && out.includes(secret)) {
121
+ out = out.split(secret).join("[secret]");
122
+ }
123
+ }
124
+ return out;
125
+ };
126
+ const visit = (value: unknown): unknown => {
127
+ if (isCompiledValue(value)) {
128
+ const src = (value as { source?: unknown }).source;
129
+ return typeof src === "string" ? `\${{ ${src} }}` : "[expression]";
130
+ }
131
+ if (isResolvedRef(value)) return { kind: value.kind, name: value.name };
132
+ if (typeof value === "string") return scrub(value);
133
+ if (Array.isArray(value)) return value.map(visit);
134
+ if (value && typeof value === "object") {
135
+ // A live instance injected into a ref slot (e.g. a template child whose
136
+ // `connection` was resolved to the real connection): show its identity, not
137
+ // its internals. Other class instances (streams, clients) get a marker.
138
+ const id = getRefIdentity(value);
139
+ if (id) return { kind: id.kind, name: id.name };
140
+ const proto = Object.getPrototypeOf(value);
141
+ if (proto !== Object.prototype && proto !== null) {
142
+ return `[${(value as { constructor?: { name?: string } }).constructor?.name ?? "Object"}]`;
143
+ }
144
+ const out: Record<string, unknown> = {};
145
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = visit(v);
146
+ return out;
147
+ }
148
+ return value;
149
+ };
150
+ const out: Record<string, unknown> = {};
151
+ for (const [k, v] of Object.entries(resource as Record<string, unknown>)) {
152
+ if (k === "kind" || k === "metadata" || (skipSchema && k === "schema")) continue;
153
+ out[k] = visit(v);
154
+ }
155
+ return out;
156
+ }
157
+
75
158
  /**
76
159
  * Kernel-internal propagation of the current invocation tree's cancellation
77
160
  * scope. NEVER the controller-facing contract — controllers always receive the
@@ -188,6 +271,38 @@ export class EvaluationContext implements IEvaluationContext {
188
271
  */
189
272
  tracer?: Tracer;
190
273
 
274
+ /**
275
+ * The resource that owns this context's resources — stamped by a template
276
+ * controller on the child context it registers its `resources:` into.
277
+ * Propagated through spawnChild() so scoped/nested children inherit it. Drives
278
+ * the hierarchical `id` and `owner` every lifecycle/dispatch event carries, so
279
+ * a debug consumer nests children under their parent and two instances of the
280
+ * same templated kind don't collide by name. Undefined ⇒ top-level resources.
281
+ */
282
+ owner?: ResourceOwner;
283
+
284
+ /** Id prefix for a resource created in this context: `owner.id + "/"`, or ""
285
+ * at the top level. A resource's full id is `ownerPrefix + kind + "." + name`. */
286
+ get ownerPrefix(): string {
287
+ return this.owner ? `${this.owner.id}/` : "";
288
+ }
289
+
290
+ /** The full hierarchical id of a resource emitted from this context. */
291
+ private resourceId(kind: string, name: string): string {
292
+ return `${this.ownerPrefix}${kind}.${name}`;
293
+ }
294
+
295
+ /** Stamp each dependency ref with the target node's hierarchical id. A local
296
+ * (no-alias) sibling lives in this same context, so it carries this prefix; a
297
+ * cross-module (`alias`) target lives elsewhere, so it stays unqualified
298
+ * (best-effort — the renderer drops an edge that finds no node). */
299
+ private qualifyDeps(refs: ResourceRef[]): (ResourceRef & { id: string })[] {
300
+ return refs.map((ref) => ({
301
+ ...ref,
302
+ id: ref.alias ? `${ref.kind}.${ref.name}` : this.resourceId(ref.kind, ref.name),
303
+ }));
304
+ }
305
+
191
306
  constructor(
192
307
  readonly source: string,
193
308
  context: Record<string, unknown>,
@@ -271,6 +386,12 @@ export class EvaluationContext implements IEvaluationContext {
271
386
  if (this.tracer && !child.tracer) {
272
387
  child.tracer = this.tracer;
273
388
  }
389
+ // Inherit ownership so a scope/import opened inside a template's child
390
+ // context keeps its resources nested under the same owner. A template
391
+ // controller overrides this on its own child context after spawning.
392
+ if (this.owner && !child.owner) {
393
+ child.owner = this.owner;
394
+ }
274
395
  return child;
275
396
  }
276
397
 
@@ -348,14 +469,29 @@ export class EvaluationContext implements IEvaluationContext {
348
469
  if (idx >= 0) this.pendingResources.splice(idx, 1);
349
470
  errors.delete(name);
350
471
  progress = true;
351
- await this.emit(`${created.resource.kind}.${created.resource.metadata.name}.Created`, {
472
+ const createdRes = created.resource;
473
+ const payload: Record<string, unknown> = {
352
474
  resource: {
353
- kind: created.resource.kind,
354
- name: created.resource.metadata.name,
355
- module: created.resource.metadata.module,
475
+ kind: createdRes.kind,
476
+ name: createdRes.metadata.name,
477
+ module: createdRes.metadata.module,
478
+ id: this.resourceId(createdRes.kind, createdRes.metadata.name),
356
479
  },
357
- dependencies: collectResourceRefs(created.resource),
480
+ ...(this.owner ? { owner: this.owner } : {}),
481
+ dependencies: this.qualifyDeps(collectResourceRefs(createdRes)),
482
+ };
483
+ // `properties` (the resolved config) is a second full config walk plus
484
+ // a secret scrub. Build it lazily: the EventBus short-circuits when
485
+ // nothing is subscribed (the no-debug-consumer case), so this getter
486
+ // only runs when a consumer actually serializes the payload. Memoized
487
+ // so multiple sinks (JSONL + SSE) don't rebuild it.
488
+ let props: Record<string, unknown> | undefined;
489
+ Object.defineProperty(payload, "properties", {
490
+ enumerable: true,
491
+ configurable: true,
492
+ get: () => (props ??= buildResolvedProperties(createdRes, this.secretValues)),
358
493
  });
494
+ await this.emit(`${createdRes.kind}.${createdRes.metadata.name}.Created`, payload);
359
495
  }
360
496
  } catch (error) {
361
497
  if (error instanceof RuntimeError && (error.code === "ERR_VISIBILITY_DENIED" || error.code === "ERR_FATAL")) throw error;
@@ -387,7 +523,12 @@ export class EvaluationContext implements IEvaluationContext {
387
523
  errors.delete(name);
388
524
  progress = true;
389
525
  await this.emit(`${resource.kind}.${resource.metadata.name}.Initialized`, {
390
- resource: { kind: resource.kind, name: resource.metadata.name },
526
+ resource: {
527
+ kind: resource.kind,
528
+ name: resource.metadata.name,
529
+ id: this.resourceId(resource.kind, resource.metadata.name),
530
+ },
531
+ ...(this.owner ? { owner: this.owner } : {}),
391
532
  });
392
533
  } catch (error) {
393
534
  if (error instanceof RuntimeError && (error.code === "ERR_VISIBILITY_DENIED" || error.code === "ERR_FATAL")) throw error;
@@ -555,7 +696,12 @@ export class EvaluationContext implements IEvaluationContext {
555
696
  for (const [key, { resource, instance }] of entries) {
556
697
  if (instance.teardown) await instance.teardown();
557
698
  await this.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
558
- resource: { kind: resource.kind, name: resource.metadata.name },
699
+ resource: {
700
+ kind: resource.kind,
701
+ name: resource.metadata.name,
702
+ id: this.resourceId(resource.kind, resource.metadata.name),
703
+ },
704
+ ...(this.owner ? { owner: this.owner } : {}),
559
705
  });
560
706
  this.resourceInstances.delete(key);
561
707
  }
@@ -673,7 +819,8 @@ export class EvaluationContext implements IEvaluationContext {
673
819
  capability,
674
820
  phase,
675
821
  ...(outcome !== undefined ? { outcome } : {}),
676
- ref: { kind, name },
822
+ ref: { kind, name, id: this.resourceId(kind, name) },
823
+ ...(this.owner ? { owner: this.owner } : {}),
677
824
  ...detail,
678
825
  };
679
826
  }
@@ -1124,10 +1271,12 @@ export class EvaluationContext implements IEvaluationContext {
1124
1271
  }
1125
1272
  }
1126
1273
 
1274
+ /** A compile path is excluded from compile-time expansion when it overlaps a
1275
+ * runtime path in either direction — the runtime path is a descendant of it, or
1276
+ * vice versa. Both directions go through the shared {@link evalPathCovers}
1277
+ * containment rule so this stays in lockstep with the analyzer's coverage check. */
1127
1278
  function isExcluded(path: string, excludePaths: string[]): boolean {
1128
- return excludePaths.some(
1129
- (ep) => ep === path || ep === "**" || path.startsWith(ep + ".") || ep.startsWith(path + "."),
1130
- );
1279
+ return excludePaths.some((ep) => evalPathCovers(ep, path) || evalPathCovers(path, ep));
1131
1280
  }
1132
1281
 
1133
1282
  function getNestedValue(obj: Record<string, unknown>, parts: string[]): unknown {
package/src/kernel.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AnalysisRegistry,
3
+ buildEvalPaths,
3
4
  defaultSources,
4
5
  flattenForAnalyzer,
5
6
  flattenLoadedModule,
@@ -36,7 +37,6 @@ import { ModuleContext } from "./module-context.js";
36
37
  import { ResourceContextImpl } from "./resource-context.js";
37
38
  import { nodeCelHandlers } from "./cel-handlers.js";
38
39
  import { parseRef, seedInvokeSource } from "./invoke-dispatch.js";
39
- import { buildEvalPaths } from "./eval-paths.js";
40
40
  import { stripCompiledValues } from "./schema-compiled-values.js";
41
41
  import { injectAtPath } from "./dependency-injection.js";
42
42
  import {
@@ -895,6 +895,7 @@ export class Kernel implements IKernel {
895
895
  moduleContext: IModuleContext,
896
896
  resource: ResourceManifest,
897
897
  args?: ParsedArgs,
898
+ ownerPrefix = "",
898
899
  ): ResourceContext {
899
900
  return new ResourceContextImpl(
900
901
  this,
@@ -906,6 +907,7 @@ export class Kernel implements IKernel {
906
907
  this.stdout,
907
908
  this.stderr,
908
909
  args,
910
+ ownerPrefix,
909
911
  );
910
912
  }
911
913
 
@@ -1043,7 +1045,12 @@ export class Kernel implements IKernel {
1043
1045
 
1044
1046
  const parsedArgs = this.parseArgsForController(controller);
1045
1047
  const moduleCtx = this.findModuleContext(evalContext);
1046
- const ctx = this.createResourceContext(moduleCtx, processedResource, parsedArgs);
1048
+ const ctx = this.createResourceContext(
1049
+ moduleCtx,
1050
+ processedResource,
1051
+ parsedArgs,
1052
+ evalContext.ownerPrefix,
1053
+ );
1047
1054
  const instance = await controller.create(processedResource, ctx);
1048
1055
  if (!instance) return null;
1049
1056
 
@@ -1,8 +1,8 @@
1
1
  import { DEFAULT_MANIFEST_FILENAME, type ManifestSource } from "@telorun/analyzer";
2
+ import { selectByPatterns } from "@telorun/glob";
2
3
  import * as fs from "fs/promises";
3
4
  import * as path from "path";
4
5
  import { fileURLToPath, pathToFileURL } from "url";
5
- import { minimatch } from "minimatch";
6
6
 
7
7
  function toFilePath(pathOrUrl: string): string {
8
8
  return pathOrUrl.startsWith("file://") ? fileURLToPath(pathOrUrl) : pathOrUrl;
@@ -49,17 +49,18 @@ export class LocalFileSource implements ManifestSource {
49
49
  async expandGlob(base: string, patterns: string[]): Promise<string[]> {
50
50
  const baseDir = path.dirname(path.resolve(toFilePath(base)));
51
51
  const entries = await fs.readdir(baseDir, { recursive: true, withFileTypes: true });
52
- const normalizedPatterns = patterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
53
- const matched: string[] = [];
52
+ const rels: string[] = [];
54
53
  for (const entry of entries) {
55
54
  if (!entry.isFile()) continue;
56
- const relative = path.relative(baseDir, path.join(entry.parentPath, entry.name));
57
- const normalized = relative.replace(/\\/g, "/");
58
- if (normalizedPatterns.some((p) => minimatch(normalized, p))) {
59
- matched.push(toFileUrl(path.resolve(baseDir, relative)));
60
- }
55
+ rels.push(path.relative(baseDir, path.join(entry.parentPath, entry.name)).replace(/\\/g, "/"));
61
56
  }
62
- return matched.sort();
57
+ // `include:` resolution may reach any co-located partial, so it opts out of
58
+ // the soft default-ignore tier (parity with `telo publish`'s include path).
59
+ // The hard tier (`node_modules`/`.git`/`.telo`) is always denied, so a broad
60
+ // `**` include never recurses into the manifest cache.
61
+ return selectByPatterns(rels, patterns, { applyDefaultIgnore: false }).map((rel) =>
62
+ toFileUrl(path.resolve(baseDir, rel)),
63
+ );
63
64
  }
64
65
 
65
66
  async resolveOwnerOf(fileUrl: string): Promise<string | null> {
@@ -38,6 +38,10 @@ export class ResourceContextImpl implements ResourceContext {
38
38
  readonly stdout: NodeJS.WritableStream;
39
39
  readonly stderr: NodeJS.WritableStream;
40
40
  readonly args: ParsedArgs;
41
+ /** Id prefix of the context this resource was created in. A controller that
42
+ * spawns sub-resources composes their ids as `ownerPrefix + kind + "." + name`
43
+ * and stamps the owner on the child context it registers them into. */
44
+ readonly ownerPrefix: string;
41
45
 
42
46
  constructor(
43
47
  readonly kernel: Kernel,
@@ -49,12 +53,14 @@ export class ResourceContextImpl implements ResourceContext {
49
53
  stdout?: NodeJS.WritableStream,
50
54
  stderr?: NodeJS.WritableStream,
51
55
  args?: ParsedArgs,
56
+ ownerPrefix = "",
52
57
  ) {
53
58
  this.env = env ?? process.env;
54
59
  this.stdin = stdin ?? process.stdin;
55
60
  this.stdout = stdout ?? process.stdout;
56
61
  this.stderr = stderr ?? process.stderr;
57
62
  this.args = args ?? { _: [] };
63
+ this.ownerPrefix = ownerPrefix;
58
64
  }
59
65
 
60
66
  createSchemaValidator(schema: any) {
@@ -1,10 +0,0 @@
1
- /**
2
- * Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
3
- * Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
4
- * Property-level annotations produce the dot-notation path to that property.
5
- */
6
- export declare function buildEvalPaths(schema: Record<string, any>): {
7
- compile: string[];
8
- runtime: string[];
9
- };
10
- //# sourceMappingURL=eval-paths.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"eval-paths.d.ts","sourceRoot":"","sources":["../src/eval-paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAcA"}
@@ -1,35 +0,0 @@
1
- /**
2
- * Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
3
- * Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
4
- * Property-level annotations produce the dot-notation path to that property.
5
- */
6
- export function buildEvalPaths(schema) {
7
- const compile = [];
8
- const runtime = [];
9
- if (schema["x-telo-eval"] === "compile")
10
- compile.push("**");
11
- else if (schema["x-telo-eval"] === "runtime")
12
- runtime.push("**");
13
- if (schema.properties) {
14
- for (const [key, propSchema] of Object.entries(schema.properties)) {
15
- collectEvalPathsNode(propSchema, key, compile, runtime);
16
- }
17
- }
18
- return { compile, runtime };
19
- }
20
- function collectEvalPathsNode(node, path, compile, runtime) {
21
- if (node["x-telo-eval"] === "compile") {
22
- compile.push(path);
23
- return;
24
- }
25
- if (node["x-telo-eval"] === "runtime") {
26
- runtime.push(path);
27
- return;
28
- }
29
- if (node.properties) {
30
- for (const [key, propSchema] of Object.entries(node.properties)) {
31
- collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
32
- }
33
- }
34
- }
35
- //# sourceMappingURL=eval-paths.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"eval-paths.js","sourceRoot":"","sources":["../src/eval-paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAA2B;IAIxD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACvD,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEjE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAiC,CAAC,EAAE,CAAC;YACzF,oBAAoB,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,oBAAoB,CAC3B,IAAyB,EACzB,IAAY,EACZ,OAAiB,EACjB,OAAiB;IAEjB,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAiC,CAAC,EAAE,CAAC;YACvF,oBAAoB,CAAC,UAAU,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;AACH,CAAC"}
package/src/eval-paths.ts DELETED
@@ -1,44 +0,0 @@
1
- /**
2
- * Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
3
- * Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
4
- * Property-level annotations produce the dot-notation path to that property.
5
- */
6
- export function buildEvalPaths(schema: Record<string, any>): {
7
- compile: string[];
8
- runtime: string[];
9
- } {
10
- const compile: string[] = [];
11
- const runtime: string[] = [];
12
-
13
- if (schema["x-telo-eval"] === "compile") compile.push("**");
14
- else if (schema["x-telo-eval"] === "runtime") runtime.push("**");
15
-
16
- if (schema.properties) {
17
- for (const [key, propSchema] of Object.entries(schema.properties as Record<string, any>)) {
18
- collectEvalPathsNode(propSchema, key, compile, runtime);
19
- }
20
- }
21
-
22
- return { compile, runtime };
23
- }
24
-
25
- function collectEvalPathsNode(
26
- node: Record<string, any>,
27
- path: string,
28
- compile: string[],
29
- runtime: string[],
30
- ): void {
31
- if (node["x-telo-eval"] === "compile") {
32
- compile.push(path);
33
- return;
34
- }
35
- if (node["x-telo-eval"] === "runtime") {
36
- runtime.push(path);
37
- return;
38
- }
39
- if (node.properties) {
40
- for (const [key, propSchema] of Object.entries(node.properties as Record<string, any>)) {
41
- collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
42
- }
43
- }
44
- }