@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.
- package/dist/controller-loader.d.ts +57 -0
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +33 -3
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-loaders/napi-loader.d.ts +30 -1
- package/dist/controller-loaders/napi-loader.d.ts.map +1 -1
- package/dist/controller-loaders/napi-loader.js +52 -7
- package/dist/controller-loaders/napi-loader.js.map +1 -1
- package/dist/controller-loaders/npm-loader.d.ts +12 -1
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +9 -4
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +1 -3
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +13 -14
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts +17 -2
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +105 -28
- package/dist/evaluation-context.js.map +1 -1
- package/package.json +3 -3
- package/src/controller-loader.ts +82 -6
- package/src/controller-loaders/napi-loader.ts +82 -9
- package/src/controller-loaders/npm-loader.ts +27 -6
- package/src/controllers/resource-definition/resource-definition-controller.ts +21 -23
- package/src/evaluation-context.ts +107 -30
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 (
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
*
|
|
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
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
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).
|