@telorun/kernel 0.6.0 → 0.6.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.
@@ -27,10 +27,7 @@ type ResourceDefinitionResource = RuntimeResource & {
27
27
  class ResourceDefinition implements ResourceInstance {
28
28
  readonly kind: "ResourceDefinition" = "ResourceDefinition";
29
29
 
30
- constructor(
31
- readonly resource: ResourceDefinitionResource,
32
- private controllerLoader: ControllerLoader,
33
- ) {}
30
+ constructor(readonly resource: ResourceDefinitionResource) {}
34
31
 
35
32
  async init(ctx: ResourceContext) {
36
33
  if (!this.resource.controllers?.length) {
@@ -43,24 +40,25 @@ class ResourceDefinition implements ResourceInstance {
43
40
  );
44
41
  return;
45
42
  }
46
- ctx.emit("ControllerLoading", { controllers: this.resource.controllers });
47
- try {
48
- const controllerInstance = await this.controllerLoader.load(
49
- this.resource.controllers,
50
- this.resource.metadata.source,
51
- ctx.getControllerPolicy(),
52
- );
53
- ctx.emit("ControllerLoaded", { schema: controllerInstance.schema });
54
- ctx.registerDefinition(this.resource);
55
- await ctx.registerController(
56
- this.resource.metadata.module,
57
- this.resource.metadata.name,
58
- controllerInstance,
59
- );
60
- } catch (err) {
61
- ctx.emit("ControllerLoadFailed", { error: (err as Error).message });
62
- throw err;
63
- }
43
+ // The loader owns ControllerLoading / ControllerLoaded / ControllerLoadFailed
44
+ // emission so it can fire one event per attempted candidate (env-missing
45
+ // fallback chains), and so the payload can include the actually-picked PURL,
46
+ // which branch resolved it (`source`), and timing — none of which are known
47
+ // here at the call site.
48
+ const loader = new ControllerLoader({
49
+ emit: (e) => ctx.emit(e.name, e.payload),
50
+ });
51
+ const controllerInstance = await loader.load(
52
+ this.resource.controllers,
53
+ this.resource.metadata.source,
54
+ ctx.getControllerPolicy(),
55
+ );
56
+ ctx.registerDefinition(this.resource);
57
+ await ctx.registerController(
58
+ this.resource.metadata.module,
59
+ this.resource.metadata.name,
60
+ controllerInstance,
61
+ );
64
62
  }
65
63
  }
66
64
 
@@ -78,7 +76,7 @@ export async function create(resource: any, ctx: ResourceContext): Promise<Resou
78
76
 
79
77
  // Return a fully-formed ResourceDefinition instance
80
78
  const definition = resource as unknown as ResourceDefinitionResource;
81
- return new ResourceDefinition(definition, new ControllerLoader());
79
+ return new ResourceDefinition(definition);
82
80
  }
83
81
 
84
82
  export const schema = {
@@ -18,6 +18,52 @@ import { RuntimeError } from "@telorun/sdk";
18
18
 
19
19
  export { resourceKey };
20
20
 
21
+ type Walker = (ctx: Record<string, unknown>) => unknown;
22
+
23
+ /** Compile a manifest subtree into a tightly-bound walker closure. The returned
24
+ * function takes an activation object and rebuilds a fresh container of the
25
+ * same shape with all `${{ }}` CompiledValues evaluated against the activation.
26
+ * Per-call overhead is one closure invocation per node — no isCompiledValue /
27
+ * Array.isArray / typeof / Object.entries checks at runtime. */
28
+ function compileWalker(value: unknown): Walker {
29
+ if (isCompiledValue(value)) {
30
+ const compiled = value;
31
+ return (ctx) => {
32
+ try {
33
+ return compiled.call(ctx);
34
+ } catch (error) {
35
+ const expr = compiled.source ? `\${{ ${compiled.source} }}` : "unknown expression";
36
+ const msg = error instanceof Error ? error.message : String(error);
37
+ throw new Error(`Expression ${expr} failed: ${msg}`);
38
+ }
39
+ };
40
+ }
41
+ if (Array.isArray(value)) {
42
+ const childWalkers = value.map(compileWalker);
43
+ const n = childWalkers.length;
44
+ return (ctx) => {
45
+ const out = new Array(n);
46
+ for (let i = 0; i < n; i++) out[i] = childWalkers[i]!(ctx);
47
+ return out;
48
+ };
49
+ }
50
+ if (value !== null && typeof value === "object") {
51
+ const entries = Object.entries(value as Record<string, unknown>).map(
52
+ ([k, v]) => [k, compileWalker(v)] as const,
53
+ );
54
+ const n = entries.length;
55
+ return (ctx) => {
56
+ const out: Record<string, unknown> = {};
57
+ for (let i = 0; i < n; i++) {
58
+ const [k, fn] = entries[i]!;
59
+ out[k] = fn(ctx);
60
+ }
61
+ return out;
62
+ };
63
+ }
64
+ return () => value;
65
+ }
66
+
21
67
  /**
22
68
  * Base class for all evaluation contexts. Owns template
23
69
  * expansion, secrets redaction, and the generic resource lifecycle tree.
@@ -453,7 +499,9 @@ export class EvaluationContext implements IEvaluationContext {
453
499
  if (declaredCodes && !declaredCodes.has(err.code)) {
454
500
  await this.emit(`${kind}.${name}.InvokeRejected.Undeclared`, payload);
455
501
  }
456
- } else if (err instanceof Error) {
502
+ throw err;
503
+ }
504
+ if (err instanceof Error) {
457
505
  await this.emit(`${kind}.${name}.InvokeFailed`, {
458
506
  name: err.name,
459
507
  message: err.message,
@@ -464,7 +512,22 @@ export class EvaluationContext implements IEvaluationContext {
464
512
  message: String(err),
465
513
  });
466
514
  }
467
- throw err;
515
+ // Already enriched at an inner invoke: keep the innermost (most
516
+ // specific) resource as the failure location.
517
+ if (err instanceof RuntimeError && err.diagnostics?.length) throw err;
518
+ const message = err instanceof Error ? err.message : String(err);
519
+ const code = err instanceof Error ? err.name : undefined;
520
+ // Keep `message` raw so callers (Run.Sequence catch blocks, assertions)
521
+ // see the original error text unchanged. Resource context lives only on
522
+ // the attached diagnostic, which the CLI's formatter renders as the
523
+ // location prefix. Attach the original error as `cause` so
524
+ // formatErrorForDiagnostic walks the chain and surfaces the underlying
525
+ // stack and well-known error fields (AWS, pg, Node system errors).
526
+ const wrapped = new RuntimeError("ERR_EXECUTION_FAILED", message, [
527
+ { kind, resource: name, message, code },
528
+ ]);
529
+ (wrapped as { cause?: unknown }).cause = err;
530
+ throw wrapped;
468
531
  }
469
532
  }
470
533
 
@@ -497,48 +560,62 @@ export class EvaluationContext implements IEvaluationContext {
497
560
 
498
561
  /**
499
562
  * Expand a value that may contain precompiled ${{ }} templates.
500
- * Works recursively over CompiledValues, arrays, and objects.
563
+ *
564
+ * Hot path: each unique manifest subtree is compiled once into a tightly-bound
565
+ * walker closure (no per-call `isCompiledValue` / `Array.isArray` / `typeof` /
566
+ * `Object.entries` overhead, no recursive method dispatch). The walker tree is
567
+ * cached by the input value's identity in `walkerCache`, so subsequent calls
568
+ * with the same manifest data reuse it. The walker reads from `this._context`
569
+ * — which `expandWith` mutates in place — and emits a fresh container per call
570
+ * to preserve the original recursive `expand`'s semantics.
501
571
  */
502
572
  expand(value: unknown): unknown {
503
- if (isCompiledValue(value)) {
504
- try {
505
- return value.call(this._context);
506
- } catch (error) {
507
- const expr = value.source ? `\${{ ${value.source} }}` : "unknown expression";
508
- const msg = error instanceof Error ? error.message : String(error);
509
- throw new Error(`Expression ${expr} failed: ${msg}`);
510
- }
511
- }
512
- if (Array.isArray(value)) {
513
- return value.map((entry) => this.expand(entry));
514
- }
515
- if (value !== null && typeof value === "object") {
516
- const resolved: Record<string, unknown> = {};
517
- for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
518
- resolved[key] = this.expand(entry);
519
- }
520
- return resolved;
521
- }
522
- return value;
573
+ if (value === null || typeof value !== "object") return value;
574
+ const cached = this.walkerCache.get(value as object);
575
+ if (cached) return cached(this._context);
576
+ const walker = compileWalker(value);
577
+ this.walkerCache.set(value as object, walker);
578
+ return walker(this._context);
523
579
  }
524
580
 
525
581
  /**
526
582
  * Expand a value using this context merged with additional properties.
527
- * Equivalent to merge(extraContext).expand(value) without allocating a context object.
583
+ *
584
+ * Hot path optimisation: rather than allocate a fresh prototype-less object
585
+ * per call (one allocation + N property copies for N keys in the saved
586
+ * context), we mutate `_context` in place — adding or overwriting only the
587
+ * `extraContext` keys — and restore the previous values on exit. Safe because
588
+ * `expand` is synchronous; cel-vm closures only read from the activation.
528
589
  */
529
590
  expandWith(value: unknown, extraContext: Record<string, unknown>): unknown {
530
- const saved = this._context;
531
- this._context = Object.assign(Object.create(null), saved, extraContext) as Record<
532
- string,
533
- unknown
534
- >;
591
+ const ctx = this._context as Record<string, unknown>;
592
+ const keys = Object.keys(extraContext);
593
+ const savedValues: unknown[] = new Array(keys.length);
594
+ const hadKey: boolean[] = new Array(keys.length);
595
+ for (let i = 0; i < keys.length; i++) {
596
+ const k = keys[i]!;
597
+ hadKey[i] = k in ctx;
598
+ if (hadKey[i]) savedValues[i] = ctx[k];
599
+ ctx[k] = extraContext[k];
600
+ }
535
601
  try {
536
602
  return this.expand(value);
537
603
  } finally {
538
- this._context = saved;
604
+ for (let i = 0; i < keys.length; i++) {
605
+ const k = keys[i]!;
606
+ if (hadKey[i]) ctx[k] = savedValues[i];
607
+ else delete ctx[k];
608
+ }
539
609
  }
540
610
  }
541
611
 
612
+ /** Cache of compiled walker closures keyed on the manifest subtree they walk.
613
+ * WeakMap so entries are GC'd if the manifest is reloaded. */
614
+ private readonly walkerCache = new WeakMap<
615
+ object,
616
+ (ctx: Record<string, unknown>) => unknown
617
+ >();
618
+
542
619
  /**
543
620
  * Expand specific dot-paths within an object. '**' expands the entire object.
544
621
  * Paths listed in excludePaths are left untouched (runtime takes precedence).