@telorun/kernel 0.31.0 → 0.33.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.
- package/dist/dependency-injection.d.ts +1 -1
- package/dist/dependency-injection.d.ts.map +1 -1
- package/dist/dependency-injection.js +18 -4
- package/dist/dependency-injection.js.map +1 -1
- package/dist/evaluation-context.d.ts +58 -1
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +205 -30
- package/dist/evaluation-context.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +6 -0
- package/dist/events.js.map +1 -1
- package/dist/kernel.d.ts +1 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +16 -4
- package/dist/kernel.js.map +1 -1
- package/dist/module-context.d.ts +4 -1
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +77 -15
- package/dist/module-context.js.map +1 -1
- package/dist/resource-context.d.ts +16 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +52 -1
- package/dist/resource-context.js.map +1 -1
- package/dist/tracing.d.ts +3 -0
- package/dist/tracing.d.ts.map +1 -1
- package/dist/tracing.js +6 -0
- package/dist/tracing.js.map +1 -1
- package/package.json +2 -2
- package/src/dependency-injection.ts +21 -3
- package/src/evaluation-context.ts +308 -45
- package/src/events.ts +5 -0
- package/src/kernel.ts +20 -4
- package/src/module-context.ts +100 -17
- package/src/resource-context.ts +58 -1
- package/src/tracing.ts +7 -0
|
@@ -6,5 +6,5 @@ import { ResourceInstance, ResourceManifest } from "@telorun/sdk";
|
|
|
6
6
|
* returned live ResourceInstance. Values where getInstance returns undefined are
|
|
7
7
|
* left unchanged.
|
|
8
8
|
*/
|
|
9
|
-
export declare function injectAtPath(resource: ResourceManifest, fieldPath: string, getInstance: (name: string, alias?: string) => ResourceInstance | undefined): void;
|
|
9
|
+
export declare function injectAtPath(resource: ResourceManifest, fieldPath: string, getInstance: (name: string, alias?: string) => ResourceInstance | undefined, isPending?: (name: string) => boolean): void;
|
|
10
10
|
//# sourceMappingURL=dependency-injection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-injection.d.ts","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"dependency-injection.d.ts","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAkC,MAAM,cAAc,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,EAC3E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACpC,IAAI,CAmGN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RuntimeError } from "@telorun/sdk";
|
|
1
|
+
import { RuntimeError, stampRefIdentity } from "@telorun/sdk";
|
|
2
2
|
/**
|
|
3
3
|
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal,
|
|
4
4
|
* `{}` = map traversal). For each leaf value that looks like a {kind, name}
|
|
@@ -6,20 +6,34 @@ import { RuntimeError } from "@telorun/sdk";
|
|
|
6
6
|
* returned live ResourceInstance. Values where getInstance returns undefined are
|
|
7
7
|
* left unchanged.
|
|
8
8
|
*/
|
|
9
|
-
export function injectAtPath(resource, fieldPath, getInstance) {
|
|
9
|
+
export function injectAtPath(resource, fieldPath, getInstance, isPending) {
|
|
10
10
|
const parts = fieldPath.split(".");
|
|
11
11
|
// Resolve a {kind, name, alias?} reference to its live instance. A non-`Self` alias is a
|
|
12
12
|
// cross-module reference into an import's published exports; if that import hasn't
|
|
13
13
|
// finished init() yet the instance is absent, so we throw to defer this resource to a
|
|
14
14
|
// later pass of the multi-pass init loop (which catches and retries) rather than leaving
|
|
15
|
-
// the ref unresolved.
|
|
16
|
-
//
|
|
15
|
+
// the ref unresolved. A LOCAL ref (no alias) that names a resource registered in this
|
|
16
|
+
// context but not yet initialized is deferred the same way — create-success order does
|
|
17
|
+
// not always match init order (e.g. a globally-registered controller lets a dependent
|
|
18
|
+
// create before its dependency's controller has loaded), so injection can run before the
|
|
19
|
+
// dependency inits. Without this defer the slot would be left unresolved and surface as a
|
|
20
|
+
// runtime ERR_RESOURCE_NOT_INVOKABLE. A local ref that names nothing pending is left as-is
|
|
21
|
+
// (topo ordering / later diagnostics), matching prior behaviour.
|
|
17
22
|
function resolveInto(ref) {
|
|
18
23
|
const alias = typeof ref.alias === "string" ? ref.alias : undefined;
|
|
19
24
|
const instance = getInstance(ref.name, alias);
|
|
20
25
|
if (!instance && alias && alias !== "Self") {
|
|
21
26
|
throw new RuntimeError("ERR_CROSS_MODULE_REF_PENDING", `Cross-module reference '${alias}.${String(ref.name)}' is not available yet (import not initialized)`);
|
|
22
27
|
}
|
|
28
|
+
if (!instance && (!alias || alias === "Self") && isPending?.(ref.name)) {
|
|
29
|
+
throw new RuntimeError("ERR_LOCAL_REF_PENDING", `Local reference '${String(ref.name)}' is registered but not initialized yet (deferring to a later init pass)`);
|
|
30
|
+
}
|
|
31
|
+
// Tag the instance with the kind+name it resolved from, so a consumer that
|
|
32
|
+
// holds only the bare instance (an invoke-step target) can dispatch it
|
|
33
|
+
// through the traced chokepoint rather than calling `.invoke()` directly.
|
|
34
|
+
if (instance && typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
35
|
+
stampRefIdentity(instance, ref.kind, ref.name);
|
|
36
|
+
}
|
|
23
37
|
return instance;
|
|
24
38
|
}
|
|
25
39
|
function traverse(obj, partsLeft) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-injection.js","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"dependency-injection.js","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resourceKey, type EvaluationContext as IEvaluationContext, type EmitEvent, type InstanceFactory, type InvokeContext, type LifecycleState, type PreInitHook, type ResourceDefinition, type ResourceInstance, type ResourceManifest, type ScopeHandle, type Tracer } from "@telorun/sdk";
|
|
1
|
+
import { resourceKey, type EvaluationContext as IEvaluationContext, type EmitEvent, type InstanceFactory, type InvokeContext, type LifecycleState, type OpenSpan, type OpenSpanOptions, type PreInitHook, type ResourceDefinition, type ResourceInstance, type ResourceManifest, type ScopeHandle, type Tracer } from "@telorun/sdk";
|
|
2
2
|
export { resourceKey };
|
|
3
3
|
/**
|
|
4
4
|
* Base class for all evaluation contexts. Owns template
|
|
@@ -135,9 +135,66 @@ export declare class EvaluationContext implements IEvaluationContext {
|
|
|
135
135
|
* regardless of which path reached the instance.
|
|
136
136
|
*/
|
|
137
137
|
invokeResolved<TInputs>(kind: string, name: string, instance: ResourceInstance, inputs: TInputs, ctx?: InvokeContext): Promise<any>;
|
|
138
|
+
/**
|
|
139
|
+
* Build the structured trace payload every capability dispatch emits. The
|
|
140
|
+
* event *name* stays human-meaningful (`<name>.Invoked`) for bus subscribers;
|
|
141
|
+
* everything a debug consumer needs to rebuild the call tree rides here, so the
|
|
142
|
+
* consumer never parses the dotted name. `spanId`/`parentSpanId` are the
|
|
143
|
+
* tracer's `invocationId`/`parentInvocationId` (present only while tracing);
|
|
144
|
+
* `ref` carries the kind+name the name no longer encodes; `detail` is the
|
|
145
|
+
* per-capability data (inputs/outputs, error fields, cancellation reason).
|
|
146
|
+
*/
|
|
147
|
+
/**
|
|
148
|
+
* A redacted snapshot of the CEL root scope a debug consumer should see for a
|
|
149
|
+
* trace — `variables`, masked `secrets`, resource `snapshots`, `ports`. Attached
|
|
150
|
+
* to a trace's *root* span so the consumer can inspect what data the execution
|
|
151
|
+
* could reference (beyond its own inputs/outputs). Only a `ModuleContext` owns a
|
|
152
|
+
* root scope; child scopes return undefined. Host `env` is deliberately omitted
|
|
153
|
+
* (it is the raw process environment — too broad/sensitive to dump).
|
|
154
|
+
*/
|
|
155
|
+
protected traceRootScope(): Record<string, unknown> | undefined;
|
|
156
|
+
protected tracePayload(kind: string, name: string, spanId: number | undefined, parentSpanId: number | undefined, traceId: string | undefined, capability: "invoke" | "run" | "provide" | "request", phase: "start" | "end", outcome: "ok" | "failed" | "rejected" | "cancelled" | undefined, detail: Record<string, unknown>): Record<string, unknown>;
|
|
138
157
|
private runInvoke;
|
|
158
|
+
/**
|
|
159
|
+
* The declared capability of a kind, or undefined if unknown. Definitions are
|
|
160
|
+
* keyed by their canonical `<module>.<Kind>`, but a resource carries the alias
|
|
161
|
+
* kind it was written with (`Http.Server`), so resolve the alias first.
|
|
162
|
+
* Best-effort: `resolveKind` exists only on a `ModuleContext` and throws for
|
|
163
|
+
* unqualified / ungated kinds, so guard and fall back to the raw kind.
|
|
164
|
+
*/
|
|
165
|
+
/** Resolve an alias kind (`Http.Server`) to its canonical `<module>.<Kind>`.
|
|
166
|
+
* Only a `ModuleContext` carries the import-alias table; the base returns the
|
|
167
|
+
* kind unchanged. A typed seam (overridden in `ModuleContext`), matching the
|
|
168
|
+
* `traceRootScope()` pattern, rather than reaching across the boundary. */
|
|
169
|
+
protected resolveKindSafe(kind: string): string;
|
|
170
|
+
private capabilityOf;
|
|
139
171
|
private getDeclaredThrowCodes;
|
|
140
172
|
run(name: string, ctx?: InvokeContext): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Like run(), but the caller has already resolved the instance (e.g. a
|
|
175
|
+
* Phase-5-injected `!ref` boot target). Shares the single span-emitting path so
|
|
176
|
+
* a pre-resolved runnable is instrumented exactly like a by-name dispatch
|
|
177
|
+
* instead of escaping the chokepoint with a direct `instance.run()` call.
|
|
178
|
+
*/
|
|
179
|
+
runResolved(kind: string, name: string, instance: ResourceInstance, ctx?: InvokeContext): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Open a trace span for an inbound boundary (an HTTP request). Mints a span
|
|
182
|
+
* that roots a fresh trace (or continues `opts.inbound`), emits its `start`,
|
|
183
|
+
* and returns a child context to thread into `invokeResolved` so the handler
|
|
184
|
+
* nests under it. A no-op pass-through when tracing is off.
|
|
185
|
+
*/
|
|
186
|
+
openSpan(base: InvokeContext | undefined, opts: OpenSpanOptions): Promise<OpenSpan>;
|
|
187
|
+
private runInstance;
|
|
188
|
+
/**
|
|
189
|
+
* Run `fn` detached from the caller's cancellation/trace scope. The current
|
|
190
|
+
* ambient `InvokeContext` (a request's token + span) is replaced with the
|
|
191
|
+
* uncancellable root, so request-scope teardown cannot abort the work and it
|
|
192
|
+
* does not nest under the request's trace. This is the bare scope primitive —
|
|
193
|
+
* tracking/draining a detached task is the owning resource's concern (the
|
|
194
|
+
* per-resource `ResourceContext` records the task and drains it in the
|
|
195
|
+
* resource's teardown).
|
|
196
|
+
*/
|
|
197
|
+
runDetached<T>(fn: () => Promise<T>): Promise<T>;
|
|
141
198
|
/**
|
|
142
199
|
* Expand a value that may contain precompiled ${{ }} templates.
|
|
143
200
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,WAAW,EAEX,KAAK,iBAAiB,IAAI,kBAAkB,EAC5C,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAGrB,KAAK,WAAW,EAChB,KAAK,MAAM,EACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,WAAW,EAAE,CAAC;AA0GvB;;;;;;;;;;GAUG;AACH,qBAAa,iBAAkB,YAAW,kBAAkB;IAiDxD,QAAQ,CAAC,MAAM,EAAE,MAAM;IAhDzB,QAAQ,CAAC,EAAE,SAA0C;IACrD,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,SAAS,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,MAAM,EAAE,kBAAkB,GAAG,SAAS,CAAa;IACnD,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAM;IAE7C,oDAAoD;IACpD,KAAK,EAAE,cAAc,CAAa;IAElC,6EAA6E;IAC7E,QAAQ,CAAC,iBAAiB;kBAEZ,gBAAgB;kBAAY,gBAAgB;OACtD;IAEJ,iFAAiF;IACjF,SAAS,CAAC,QAAQ,CAAC,gBAAgB;kBAErB,gBAAgB;kBAAY,gBAAgB;aAAO,GAAG;OAChE;IAEJ,gEAAgE;IAChE,OAAO,CAAC,gBAAgB,CAA0B;IAElD;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IAEjE;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;gBAGL,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,cAAc,EAAE,eAAe,YAAmB,EAClD,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,EACzB,IAAI,EAAE,SAAS;IAQjB,IAAI,cAAc,IAAI,eAAe,CAEpC;IAED,8FAA8F;IAC9F,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAEpF,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErC;IAED,IAAI,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,CAE9B;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IASnC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQlC,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAWlD;;;OAGG;IACH,UAAU,CAAC,CAAC,SAAS,kBAAkB,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC;IAiBrD;;;;;OAKG;IACH,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIlF;;;;;;;;;;;;;;;;;;OAkBG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -279,7 +279,7 @@ export class EvaluationContext {
|
|
|
279
279
|
if (this.preInitHook) {
|
|
280
280
|
this.preInitHook(resource, (n, alias) => alias && alias !== "Self"
|
|
281
281
|
? this.resolveImportedInstance(alias, n)
|
|
282
|
-
: this.resourceInstances.get(n)?.instance);
|
|
282
|
+
: this.resourceInstances.get(n)?.instance, (n) => this.hasManifest(n) && !this.resourceInstances.has(n));
|
|
283
283
|
}
|
|
284
284
|
if (instance.init)
|
|
285
285
|
await instance.init(ctx);
|
|
@@ -371,10 +371,14 @@ export class EvaluationContext {
|
|
|
371
371
|
// Propagate injection hook: extend getInstance to also resolve parent singleton instances.
|
|
372
372
|
if (parent.preInitHook) {
|
|
373
373
|
const parentHook = parent.preInitHook;
|
|
374
|
-
child.preInitHook = (resource, childGetInstance) => {
|
|
374
|
+
child.preInitHook = (resource, childGetInstance, childIsPending) => {
|
|
375
375
|
parentHook(resource, (name, alias) => alias && alias !== "Self"
|
|
376
376
|
? parent.resolveImportedInstance(alias, name)
|
|
377
|
-
: childGetInstance(name) ?? parent.resourceInstances.get(name)?.instance
|
|
377
|
+
: childGetInstance(name) ?? parent.resourceInstances.get(name)?.instance,
|
|
378
|
+
// A scoped ref resolves against the scope's own resources or an already-inited
|
|
379
|
+
// outer one; only a scope-local dependency can still be pending. The outer is
|
|
380
|
+
// live by the time a scope opens, so the child's own predicate suffices.
|
|
381
|
+
childIsPending);
|
|
378
382
|
};
|
|
379
383
|
}
|
|
380
384
|
try {
|
|
@@ -471,6 +475,38 @@ export class EvaluationContext {
|
|
|
471
475
|
}
|
|
472
476
|
return this.runInvoke(kind, name, instance, inputs, ctx);
|
|
473
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Build the structured trace payload every capability dispatch emits. The
|
|
480
|
+
* event *name* stays human-meaningful (`<name>.Invoked`) for bus subscribers;
|
|
481
|
+
* everything a debug consumer needs to rebuild the call tree rides here, so the
|
|
482
|
+
* consumer never parses the dotted name. `spanId`/`parentSpanId` are the
|
|
483
|
+
* tracer's `invocationId`/`parentInvocationId` (present only while tracing);
|
|
484
|
+
* `ref` carries the kind+name the name no longer encodes; `detail` is the
|
|
485
|
+
* per-capability data (inputs/outputs, error fields, cancellation reason).
|
|
486
|
+
*/
|
|
487
|
+
/**
|
|
488
|
+
* A redacted snapshot of the CEL root scope a debug consumer should see for a
|
|
489
|
+
* trace — `variables`, masked `secrets`, resource `snapshots`, `ports`. Attached
|
|
490
|
+
* to a trace's *root* span so the consumer can inspect what data the execution
|
|
491
|
+
* could reference (beyond its own inputs/outputs). Only a `ModuleContext` owns a
|
|
492
|
+
* root scope; child scopes return undefined. Host `env` is deliberately omitted
|
|
493
|
+
* (it is the raw process environment — too broad/sensitive to dump).
|
|
494
|
+
*/
|
|
495
|
+
traceRootScope() {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
tracePayload(kind, name, spanId, parentSpanId, traceId, capability, phase, outcome, detail) {
|
|
499
|
+
return {
|
|
500
|
+
traceId,
|
|
501
|
+
spanId,
|
|
502
|
+
parentSpanId,
|
|
503
|
+
capability,
|
|
504
|
+
phase,
|
|
505
|
+
...(outcome !== undefined ? { outcome } : {}),
|
|
506
|
+
ref: { kind, name },
|
|
507
|
+
...detail,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
474
510
|
async runInvoke(kind, name, instance, inputs, ctx) {
|
|
475
511
|
// Explicit seed (trigger / embedder) wins; otherwise inherit the ambient
|
|
476
512
|
// tree token; otherwise open a fresh, never-cancellable scope. The token
|
|
@@ -479,28 +515,40 @@ export class EvaluationContext {
|
|
|
479
515
|
const baseCtx = ctx ?? ambient ?? UNCANCELLABLE_CONTEXT;
|
|
480
516
|
const token = baseCtx.cancellation;
|
|
481
517
|
// Tracing gate (a debug consumer is attached): mint a monotonic id for this
|
|
482
|
-
// invocation, parent it to the ambient one, and ride both
|
|
483
|
-
//
|
|
484
|
-
// `
|
|
518
|
+
// invocation, parent it to the ambient one, and ride both in every event's
|
|
519
|
+
// payload so the consumer can rebuild the call tree. Off by default —
|
|
520
|
+
// `invokeCtx` stays `baseCtx` and the fast `=== ambient` skip is preserved.
|
|
485
521
|
const tracing = this.tracer?.enabled === true;
|
|
486
522
|
const invocationId = tracing ? this.tracer.next() : undefined;
|
|
487
523
|
// Parent precedence mirrors the token: an explicit seed `ctx` wins, then the
|
|
488
524
|
// ambient (ALS) invocation. A caller threading its own `ctx.invocationId` thus
|
|
489
525
|
// has it honored as the parent, not silently dropped for the ALS value.
|
|
490
526
|
const parentInvocationId = ctx?.invocationId ?? ambient?.invocationId;
|
|
491
|
-
|
|
527
|
+
// Inherit the trace from the parent (explicit ctx wins, then ambient); mint a
|
|
528
|
+
// fresh one only at a root. Carried on every span so an OTel exporter groups
|
|
529
|
+
// the trace without walking the parent chain.
|
|
530
|
+
const traceId = tracing
|
|
531
|
+
? (ctx?.traceId ?? ambient?.traceId ?? this.tracer.newTraceId())
|
|
532
|
+
: undefined;
|
|
533
|
+
// Capture the root CEL scope once, on the trace's root span's terminal event.
|
|
534
|
+
const rootScope = tracing && parentInvocationId === undefined ? this.traceRootScope() : undefined;
|
|
535
|
+
const span = (phase, outcome, detail) => this.tracePayload(kind, name, invocationId, parentInvocationId, traceId, "invoke", phase, outcome, phase === "end" && rootScope ? { ...detail, context: rootScope } : detail);
|
|
492
536
|
// When tracing, a fresh context carries the new id down the tree so nested
|
|
493
537
|
// invokes read it as their parent; it is never `=== ambient`, so the call
|
|
494
538
|
// always (re)establishes the ALS scope.
|
|
495
539
|
const invokeCtx = tracing
|
|
496
|
-
? { cancellation: token, invocationId, parentInvocationId }
|
|
540
|
+
? { cancellation: token, invocationId, parentInvocationId, traceId }
|
|
497
541
|
: baseCtx;
|
|
498
542
|
// Pre-dispatch gate: a sub-invoke reached after the tree was cancelled is
|
|
499
543
|
// refused without ever touching the controller.
|
|
500
544
|
if (token.isCancelled) {
|
|
501
|
-
await this.emit(`${
|
|
545
|
+
await this.emit(`${name}.InvokeCancelled`, span("end", "cancelled", { inputs, reason: token.reason }));
|
|
502
546
|
throw new RuntimeError("ERR_INVOKE_CANCELLED", `Invoke ${kind}.${name} was cancelled${token.reason ? `: ${token.reason}` : ""}`);
|
|
503
547
|
}
|
|
548
|
+
// Start span — only under tracing, so non-traced behaviour stays exactly
|
|
549
|
+
// one terminal event per call (subscribers to `<name>.Invoked` are unaffected).
|
|
550
|
+
if (tracing)
|
|
551
|
+
await this.emit(`${name}.Invoking`, span("start", undefined, { inputs }));
|
|
504
552
|
try {
|
|
505
553
|
// Only (re)establish the ALS scope when the token differs from the ambient
|
|
506
554
|
// one — nested invokes that inherited it skip the redundant `run`.
|
|
@@ -515,7 +563,7 @@ export class EvaluationContext {
|
|
|
515
563
|
const outputs = await (invokeCtx === ambient
|
|
516
564
|
? call()
|
|
517
565
|
: cancellationStore.run(invokeCtx, call));
|
|
518
|
-
await this.emit(`${
|
|
566
|
+
await this.emit(`${name}.Invoked`, span("end", "ok", { inputs, outputs }));
|
|
519
567
|
return outputs;
|
|
520
568
|
}
|
|
521
569
|
catch (err) {
|
|
@@ -523,23 +571,23 @@ export class EvaluationContext {
|
|
|
523
571
|
// observable event family rather than masquerading as a rejection/failure.
|
|
524
572
|
if (isCancellationError(err)) {
|
|
525
573
|
const reason = err instanceof Error ? err.message : String(err);
|
|
526
|
-
await this.emit(`${
|
|
574
|
+
await this.emit(`${name}.InvokeCancelled`, span("end", "cancelled", { inputs, reason }));
|
|
527
575
|
throw err;
|
|
528
576
|
}
|
|
529
577
|
if (isInvokeError(err)) {
|
|
530
|
-
const
|
|
531
|
-
await this.emit(`${
|
|
578
|
+
const detail = { inputs, code: err.code, message: err.message, data: err.data };
|
|
579
|
+
await this.emit(`${name}.InvokeRejected`, span("end", "rejected", detail));
|
|
532
580
|
const declaredCodes = this.getDeclaredThrowCodes(kind);
|
|
533
581
|
if (declaredCodes && !declaredCodes.has(err.code)) {
|
|
534
|
-
await this.emit(`${
|
|
582
|
+
await this.emit(`${name}.InvokeRejected.Undeclared`, span("end", "rejected", detail));
|
|
535
583
|
}
|
|
536
584
|
throw err;
|
|
537
585
|
}
|
|
538
586
|
if (err instanceof Error) {
|
|
539
|
-
await this.emit(`${
|
|
587
|
+
await this.emit(`${name}.InvokeFailed`, span("end", "failed", { inputs, name: err.name, message: err.message }));
|
|
540
588
|
}
|
|
541
589
|
else {
|
|
542
|
-
await this.emit(`${
|
|
590
|
+
await this.emit(`${name}.InvokeFailed`, span("end", "failed", { inputs, name: "UnknownError", message: String(err) }));
|
|
543
591
|
}
|
|
544
592
|
// Already enriched at an inner invoke: keep the innermost (most
|
|
545
593
|
// specific) resource as the failure location.
|
|
@@ -560,6 +608,24 @@ export class EvaluationContext {
|
|
|
560
608
|
throw wrapped;
|
|
561
609
|
}
|
|
562
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* The declared capability of a kind, or undefined if unknown. Definitions are
|
|
613
|
+
* keyed by their canonical `<module>.<Kind>`, but a resource carries the alias
|
|
614
|
+
* kind it was written with (`Http.Server`), so resolve the alias first.
|
|
615
|
+
* Best-effort: `resolveKind` exists only on a `ModuleContext` and throws for
|
|
616
|
+
* unqualified / ungated kinds, so guard and fall back to the raw kind.
|
|
617
|
+
*/
|
|
618
|
+
/** Resolve an alias kind (`Http.Server`) to its canonical `<module>.<Kind>`.
|
|
619
|
+
* Only a `ModuleContext` carries the import-alias table; the base returns the
|
|
620
|
+
* kind unchanged. A typed seam (overridden in `ModuleContext`), matching the
|
|
621
|
+
* `traceRootScope()` pattern, rather than reaching across the boundary. */
|
|
622
|
+
resolveKindSafe(kind) {
|
|
623
|
+
return kind;
|
|
624
|
+
}
|
|
625
|
+
capabilityOf(kind) {
|
|
626
|
+
const resolved = this.resolveKindSafe(kind);
|
|
627
|
+
return this.getDefinition?.(resolved)?.capability ?? this.getDefinition?.(kind)?.capability;
|
|
628
|
+
}
|
|
563
629
|
getDeclaredThrowCodes(kind) {
|
|
564
630
|
if (!this.getDefinition)
|
|
565
631
|
return null;
|
|
@@ -582,22 +648,131 @@ export class EvaluationContext {
|
|
|
582
648
|
}
|
|
583
649
|
async run(name, ctx) {
|
|
584
650
|
const entry = this.resourceInstances.get(name);
|
|
585
|
-
if (entry && typeof entry.instance.run === "function") {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
651
|
+
if (!(entry && typeof entry.instance.run === "function")) {
|
|
652
|
+
throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${name} is not runnable or not found. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`);
|
|
653
|
+
}
|
|
654
|
+
return this.runInstance(entry.resource.kind, name, entry.instance, ctx);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Like run(), but the caller has already resolved the instance (e.g. a
|
|
658
|
+
* Phase-5-injected `!ref` boot target). Shares the single span-emitting path so
|
|
659
|
+
* a pre-resolved runnable is instrumented exactly like a by-name dispatch
|
|
660
|
+
* instead of escaping the chokepoint with a direct `instance.run()` call.
|
|
661
|
+
*/
|
|
662
|
+
async runResolved(kind, name, instance, ctx) {
|
|
663
|
+
if (typeof instance.run !== "function") {
|
|
664
|
+
throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${kind}.${name} does not have a run method`);
|
|
665
|
+
}
|
|
666
|
+
return this.runInstance(kind, name, instance, ctx);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Open a trace span for an inbound boundary (an HTTP request). Mints a span
|
|
670
|
+
* that roots a fresh trace (or continues `opts.inbound`), emits its `start`,
|
|
671
|
+
* and returns a child context to thread into `invokeResolved` so the handler
|
|
672
|
+
* nests under it. A no-op pass-through when tracing is off.
|
|
673
|
+
*/
|
|
674
|
+
async openSpan(base, opts) {
|
|
675
|
+
const ctx = base ?? UNCANCELLABLE_CONTEXT;
|
|
676
|
+
if (this.tracer?.enabled !== true) {
|
|
677
|
+
return { context: ctx, settle: async () => { } };
|
|
678
|
+
}
|
|
679
|
+
const spanId = this.tracer.next();
|
|
680
|
+
const traceId = opts.inbound?.traceId ?? this.tracer.newTraceId();
|
|
681
|
+
const parentSpanId = opts.inbound?.parentSpanId;
|
|
682
|
+
// A root request span (not continuing an upstream trace) carries the root scope.
|
|
683
|
+
const rootScope = parentSpanId === undefined ? this.traceRootScope() : undefined;
|
|
684
|
+
const detail = {
|
|
685
|
+
...(opts.label !== undefined ? { label: opts.label } : {}),
|
|
686
|
+
...(opts.attributes !== undefined ? { attributes: opts.attributes } : {}),
|
|
687
|
+
};
|
|
688
|
+
const payload = (phase, outcome, extra = {}) => this.tracePayload(opts.ref.kind, opts.ref.name, spanId, parentSpanId, traceId, "request", phase, outcome, { ...detail, ...extra });
|
|
689
|
+
await this.emit(`${opts.ref.name}.Requesting`, payload("start", undefined));
|
|
690
|
+
const context = {
|
|
691
|
+
cancellation: ctx.cancellation,
|
|
692
|
+
invocationId: spanId,
|
|
693
|
+
parentInvocationId: parentSpanId,
|
|
694
|
+
traceId,
|
|
695
|
+
};
|
|
696
|
+
let settled = false;
|
|
697
|
+
return {
|
|
698
|
+
context,
|
|
699
|
+
settle: async (outcome, extra) => {
|
|
700
|
+
if (settled)
|
|
701
|
+
return;
|
|
702
|
+
settled = true;
|
|
703
|
+
await this.emit(`${opts.ref.name}.Request`, payload("end", outcome, rootScope ? { ...extra, context: rootScope } : extra));
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async runInstance(kind, name, instance, ctx) {
|
|
708
|
+
const ambient = cancellationStore.getStore();
|
|
709
|
+
const baseCtx = ctx ?? ambient ?? UNCANCELLABLE_CONTEXT;
|
|
710
|
+
const token = baseCtx.cancellation;
|
|
711
|
+
// A long-lived Service's `run()` is not a one-shot dispatch: it stays pending
|
|
712
|
+
// for the process lifetime, so wrapping it in the cancellation/trace ALS scope
|
|
713
|
+
// would leak that scope onto every async resource the service creates (e.g. an
|
|
714
|
+
// HTTP server's listening socket → every inbound request callback). Such a
|
|
715
|
+
// service must NOT establish an ambient: its token reaches it via the explicit
|
|
716
|
+
// `run(invokeCtx)` argument (how it observes shutdown), and its externally
|
|
717
|
+
// triggered work then starts with a clean ambient — separate traces, no
|
|
718
|
+
// inherited cancellation. Runnables (one-shot, e.g. `Run.Sequence`) keep the
|
|
719
|
+
// ALS scope so their steps nest and inherit cancellation.
|
|
720
|
+
const isService = this.capabilityOf(kind) === "Telo.Service";
|
|
721
|
+
// Span instrumentation mirrors `runInvoke` — minting an id here makes
|
|
722
|
+
// Runnables (a `Run.Sequence` boot target) appear in the trace and re-parents
|
|
723
|
+
// their nested invokes. A long-lived Service emits only the `start` span (its
|
|
724
|
+
// `run()` resolves at teardown), the "running" signal a debug consumer wants.
|
|
725
|
+
const tracing = this.tracer?.enabled === true;
|
|
726
|
+
const invocationId = tracing ? this.tracer.next() : undefined;
|
|
727
|
+
const parentInvocationId = ctx?.invocationId ?? ambient?.invocationId;
|
|
728
|
+
const traceId = tracing
|
|
729
|
+
? (ctx?.traceId ?? ambient?.traceId ?? this.tracer.newTraceId())
|
|
730
|
+
: undefined;
|
|
731
|
+
const rootScope = tracing && parentInvocationId === undefined ? this.traceRootScope() : undefined;
|
|
732
|
+
const span = (phase, outcome, detail) => this.tracePayload(kind, name, invocationId, parentInvocationId, traceId, "run", phase, outcome, phase === "end" && rootScope ? { ...detail, context: rootScope } : detail);
|
|
733
|
+
const invokeCtx = tracing
|
|
734
|
+
? { cancellation: token, invocationId, parentInvocationId, traceId }
|
|
735
|
+
: baseCtx;
|
|
736
|
+
// Refuse a target reached after the boot run was cancelled.
|
|
737
|
+
if (token.isCancelled) {
|
|
738
|
+
await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason: token.reason }));
|
|
739
|
+
throw new RuntimeError("ERR_INVOKE_CANCELLED", `Run ${kind}.${name} was cancelled${token.reason ? `: ${token.reason}` : ""}`);
|
|
740
|
+
}
|
|
741
|
+
if (tracing)
|
|
742
|
+
await this.emit(`${name}.Running`, span("start", undefined, {}));
|
|
743
|
+
try {
|
|
744
|
+
// Runnable: run inside the ALS scope so nested invokes inherit the token and
|
|
745
|
+
// trace id (skip the redundant `run` when the token is already ambient).
|
|
746
|
+
// Service: call directly with the explicit context and NO ambient scope, so
|
|
747
|
+
// its long-lived async work does not capture this scope.
|
|
748
|
+
const call = () => instance.run(invokeCtx);
|
|
749
|
+
await (isService || invokeCtx === ambient ? call() : cancellationStore.run(invokeCtx, call));
|
|
750
|
+
await this.emit(`${name}.Run`, span("end", "ok", {}));
|
|
751
|
+
}
|
|
752
|
+
catch (err) {
|
|
753
|
+
if (isCancellationError(err)) {
|
|
754
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
755
|
+
await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason }));
|
|
756
|
+
throw err;
|
|
593
757
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
758
|
+
const detail = err instanceof Error
|
|
759
|
+
? { name: err.name, message: err.message }
|
|
760
|
+
: { name: "UnknownError", message: String(err) };
|
|
761
|
+
await this.emit(`${name}.RunFailed`, span("end", "failed", detail));
|
|
762
|
+
throw err;
|
|
599
763
|
}
|
|
600
|
-
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Run `fn` detached from the caller's cancellation/trace scope. The current
|
|
767
|
+
* ambient `InvokeContext` (a request's token + span) is replaced with the
|
|
768
|
+
* uncancellable root, so request-scope teardown cannot abort the work and it
|
|
769
|
+
* does not nest under the request's trace. This is the bare scope primitive —
|
|
770
|
+
* tracking/draining a detached task is the owning resource's concern (the
|
|
771
|
+
* per-resource `ResourceContext` records the task and drains it in the
|
|
772
|
+
* resource's teardown).
|
|
773
|
+
*/
|
|
774
|
+
runDetached(fn) {
|
|
775
|
+
return cancellationStore.run(UNCANCELLABLE_CONTEXT, fn);
|
|
601
776
|
}
|
|
602
777
|
/**
|
|
603
778
|
* Expand a value that may contain precompiled ${{ }} templates.
|