@telorun/sdk 0.49.0 → 0.54.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/dispatch-invoke-ref.d.ts.map +1 -1
- package/dist/dispatch-invoke-ref.js +12 -17
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/json-value.d.ts +20 -0
- package/dist/json-value.d.ts.map +1 -0
- package/dist/json-value.js +31 -0
- package/dist/log-record.d.ts +72 -0
- package/dist/log-record.d.ts.map +1 -0
- package/dist/log-record.js +34 -0
- package/dist/log-severity.d.ts +55 -0
- package/dist/log-severity.d.ts.map +1 -0
- package/dist/log-severity.js +110 -0
- package/dist/log-sink.d.ts +91 -0
- package/dist/log-sink.d.ts.map +1 -0
- package/dist/log-sink.js +16 -0
- package/dist/logger.d.ts +85 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +30 -0
- package/dist/resolve-ref-instance.d.ts +39 -0
- package/dist/resolve-ref-instance.d.ts.map +1 -0
- package/dist/resolve-ref-instance.js +57 -0
- package/dist/resource-context.d.ts +48 -0
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-instance.d.ts +17 -0
- package/dist/resource-instance.d.ts.map +1 -1
- package/dist/resource-instance.js +4 -0
- package/package.json +1 -1
- package/src/dispatch-invoke-ref.ts +20 -20
- package/src/index.ts +6 -0
- package/src/json-value.ts +43 -0
- package/src/log-record.ts +113 -0
- package/src/log-severity.ts +128 -0
- package/src/log-sink.ts +115 -0
- package/src/logger.ts +125 -0
- package/src/resolve-ref-instance.ts +81 -0
- package/src/resource-context.ts +53 -0
- package/src/resource-instance.ts +18 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Resolve a {@link LogValuer} if the value is one, else pass it through. */
|
|
2
|
+
export function isLogValuer(value) {
|
|
3
|
+
return (typeof value === "object" &&
|
|
4
|
+
value !== null &&
|
|
5
|
+
typeof value.toLogValue === "function");
|
|
6
|
+
}
|
|
7
|
+
/** A logger that discards everything. Used where a logger is structurally
|
|
8
|
+
* required before one is available, and by tests that assert silence. */
|
|
9
|
+
export const NOOP_LOGGER = {
|
|
10
|
+
enabled: () => false,
|
|
11
|
+
log: () => { },
|
|
12
|
+
with: () => NOOP_LOGGER,
|
|
13
|
+
flush: async () => { },
|
|
14
|
+
trace: () => { },
|
|
15
|
+
debug: () => { },
|
|
16
|
+
info: () => { },
|
|
17
|
+
warn: () => { },
|
|
18
|
+
error: () => { },
|
|
19
|
+
fatal: () => { },
|
|
20
|
+
};
|
|
21
|
+
/** The bound-attribute merge of {@link Logger.with}, exposed so a runtime's
|
|
22
|
+
* child-logger implementation and its conformance vectors share one definition
|
|
23
|
+
* of "record attributes win". */
|
|
24
|
+
export function mergeBoundAttributes(bound, record) {
|
|
25
|
+
if (!bound)
|
|
26
|
+
return record;
|
|
27
|
+
if (!record)
|
|
28
|
+
return bound;
|
|
29
|
+
return { ...bound, ...record };
|
|
30
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ModuleContext } from "./module-context.js";
|
|
2
|
+
/** The slice of `ResourceContext` needed to resolve a reference. */
|
|
3
|
+
export interface RefResolveContext {
|
|
4
|
+
readonly moduleContext: ModuleContext;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a `!ref` config field to a live instance of `T`. Controllers reach
|
|
8
|
+
* this as `ctx.resolveRef(value, guard, describe, expects)`; the standalone form
|
|
9
|
+
* is for callers holding only a `{ moduleContext }` slice rather than a full
|
|
10
|
+
* `ResourceContext`.
|
|
11
|
+
*
|
|
12
|
+
* Phase 5 injection normally replaces the slot with the live `ResourceInstance`
|
|
13
|
+
* before `init()` — local and cross-module refs alike, since injection resolves
|
|
14
|
+
* an aliased ref through the import's export table (and defers, rather than
|
|
15
|
+
* leaving a raw ref, when the import hasn't published its exports yet). So the
|
|
16
|
+
* common path here is the guard short-circuit.
|
|
17
|
+
*
|
|
18
|
+
* A raw {@link KindRef} still reaches a controller where injection does not
|
|
19
|
+
* reach the slot: a kind whose definition yields no field map, or a ref the
|
|
20
|
+
* controller obtained itself via `ctx.ensureKindRef`. Both are gaps worth
|
|
21
|
+
* closing in the kernel — until they are, both shapes must be accepted here, and
|
|
22
|
+
* an aliased ref routes through the import's exported scope because a bare local
|
|
23
|
+
* lookup would miss it.
|
|
24
|
+
*
|
|
25
|
+
* `guard` decides what counts as the right kind of instance — a duck-type check
|
|
26
|
+
* on the methods the caller will actually invoke, so a mis-wired ref fails with a
|
|
27
|
+
* clear message here rather than as `undefined is not a function` later.
|
|
28
|
+
* `describe` labels the owning resource and slot; `expects` names the contract
|
|
29
|
+
* the slot wants — the slot's own `x-telo-ref` string (`std/cache#Store`) — so
|
|
30
|
+
* the message says what was missing, not just that something was.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const store = resolveRefInstance(
|
|
34
|
+
* this.resource.store, this.ctx, isKvStore,
|
|
35
|
+
* () => `Idempotency.Once "${name}": 'store'`, "std/kv-store#Store",
|
|
36
|
+
* );
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveRefInstance<T>(value: unknown, ctx: RefResolveContext, guard: (candidate: unknown) => candidate is T, describe: () => string, expects?: string): T;
|
|
39
|
+
//# sourceMappingURL=resolve-ref-instance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-ref-instance.d.ts","sourceRoot":"","sources":["../src/resolve-ref-instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,oEAAoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC,EAC7C,QAAQ,EAAE,MAAM,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,CAAC,CAiCH"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { RuntimeError } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a `!ref` config field to a live instance of `T`. Controllers reach
|
|
4
|
+
* this as `ctx.resolveRef(value, guard, describe, expects)`; the standalone form
|
|
5
|
+
* is for callers holding only a `{ moduleContext }` slice rather than a full
|
|
6
|
+
* `ResourceContext`.
|
|
7
|
+
*
|
|
8
|
+
* Phase 5 injection normally replaces the slot with the live `ResourceInstance`
|
|
9
|
+
* before `init()` — local and cross-module refs alike, since injection resolves
|
|
10
|
+
* an aliased ref through the import's export table (and defers, rather than
|
|
11
|
+
* leaving a raw ref, when the import hasn't published its exports yet). So the
|
|
12
|
+
* common path here is the guard short-circuit.
|
|
13
|
+
*
|
|
14
|
+
* A raw {@link KindRef} still reaches a controller where injection does not
|
|
15
|
+
* reach the slot: a kind whose definition yields no field map, or a ref the
|
|
16
|
+
* controller obtained itself via `ctx.ensureKindRef`. Both are gaps worth
|
|
17
|
+
* closing in the kernel — until they are, both shapes must be accepted here, and
|
|
18
|
+
* an aliased ref routes through the import's exported scope because a bare local
|
|
19
|
+
* lookup would miss it.
|
|
20
|
+
*
|
|
21
|
+
* `guard` decides what counts as the right kind of instance — a duck-type check
|
|
22
|
+
* on the methods the caller will actually invoke, so a mis-wired ref fails with a
|
|
23
|
+
* clear message here rather than as `undefined is not a function` later.
|
|
24
|
+
* `describe` labels the owning resource and slot; `expects` names the contract
|
|
25
|
+
* the slot wants — the slot's own `x-telo-ref` string (`std/cache#Store`) — so
|
|
26
|
+
* the message says what was missing, not just that something was.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const store = resolveRefInstance(
|
|
30
|
+
* this.resource.store, this.ctx, isKvStore,
|
|
31
|
+
* () => `Idempotency.Once "${name}": 'store'`, "std/kv-store#Store",
|
|
32
|
+
* );
|
|
33
|
+
*/
|
|
34
|
+
export function resolveRefInstance(value, ctx, guard, describe, expects) {
|
|
35
|
+
// Phase-5-injected: already the instance.
|
|
36
|
+
if (guard(value))
|
|
37
|
+
return value;
|
|
38
|
+
const target = expects ? `resource satisfying \`${expects}\`` : "resource";
|
|
39
|
+
if (value === undefined || value === null) {
|
|
40
|
+
throw new RuntimeError("ERR_REF_REQUIRED", `${describe()} is required — reference a ${target}.`);
|
|
41
|
+
}
|
|
42
|
+
const ref = value;
|
|
43
|
+
if (typeof ref.name !== "string") {
|
|
44
|
+
throw new RuntimeError("ERR_REF_UNRESOLVED", `${describe()} must be a \`!ref\` to a ${target}.`);
|
|
45
|
+
}
|
|
46
|
+
// `Self` names the declaring library's own scope, so it resolves locally —
|
|
47
|
+
// it is an alias that crosses no import boundary.
|
|
48
|
+
const instance = ref.alias && ref.alias !== "Self"
|
|
49
|
+
? ctx.moduleContext.resolveImportedInstance(ref.alias, ref.name)
|
|
50
|
+
: ctx.moduleContext.getInstance(ref.name);
|
|
51
|
+
if (!guard(instance)) {
|
|
52
|
+
const label = ref.alias ? `${ref.alias}.${ref.name}` : ref.name;
|
|
53
|
+
throw new RuntimeError("ERR_REF_UNRESOLVED", `${describe()} reference '${label}' did not resolve to a ${target}` +
|
|
54
|
+
`${instance === undefined ? " (nothing is registered under that name)" : ""}.`);
|
|
55
|
+
}
|
|
56
|
+
return instance;
|
|
57
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { CancellationSource, InvokeContext, OpenSpan, OpenSpanOptions } from "./cancellation.js";
|
|
2
2
|
import { ControllerContext } from "./controller-context.js";
|
|
3
|
+
import type { Logger } from "./logger.js";
|
|
4
|
+
import type { LoggingHost } from "./log-sink.js";
|
|
3
5
|
import { ControllerPolicy } from "./controller-policy.js";
|
|
4
6
|
import { EvaluationContext } from "./evaluation-context.js";
|
|
5
7
|
import { ModuleContext } from "./module-context.js";
|
|
8
|
+
import type { KindRef } from "./ref.js";
|
|
6
9
|
import { ResourceInstance } from "./resource-instance.js";
|
|
7
10
|
import { ResourceManifest } from "./resource-manifest.js";
|
|
8
11
|
import { RuntimeResource } from "./runtime-resource.js";
|
|
@@ -69,10 +72,33 @@ export interface ResourceContext extends ControllerContext {
|
|
|
69
72
|
spawnChildContext(): EvaluationContext;
|
|
70
73
|
transientChild(context: Record<string, any>): EvaluationContext;
|
|
71
74
|
withManifests<T>(manifests: any[], fn: () => T): T;
|
|
75
|
+
/**
|
|
76
|
+
* Normalize a nested slot value to a {@link KindRef}. The value is an inline
|
|
77
|
+
* definition (`{ kind, …config }`), an already-normalized `{ kind, name }`
|
|
78
|
+
* ref, or a `!ref` sentinel. An inline definition is *registered* into this
|
|
79
|
+
* module's scope first — minting `resourceName` (or a generated one) as its
|
|
80
|
+
* name — so the returned ref always points at a resource the kernel knows.
|
|
81
|
+
*
|
|
82
|
+
* The inverse of {@link resolveRef}: this goes slot value → ref, that goes
|
|
83
|
+
* ref → live instance. Controllers that dispatch through
|
|
84
|
+
* `invokeResolved(kind, name, …)` want the ref, so the invocation keeps its
|
|
85
|
+
* identity for tracing and error wrapping.
|
|
86
|
+
*/
|
|
87
|
+
ensureKindRef(value: any, resourceName?: string): KindRef;
|
|
88
|
+
/** @deprecated Renamed to {@link ensureKindRef} — it produces a reference
|
|
89
|
+
* (registering an inline definition on the way), it does not resolve one. */
|
|
72
90
|
resolveChildren(resource: any, resourceName?: string): {
|
|
73
91
|
kind: string;
|
|
74
92
|
name: string;
|
|
75
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Resolve a `!ref` config field to a live instance of `T`. See
|
|
96
|
+
* {@link resolveRefInstance} — this is the same resolution, reached through
|
|
97
|
+
* the context a controller already holds. `expects` names the contract the
|
|
98
|
+
* slot wants — its `x-telo-ref` string (`std/cache#Store`) — so a mis-wire
|
|
99
|
+
* says what was missing.
|
|
100
|
+
*/
|
|
101
|
+
resolveRef<T>(value: unknown, guard: (candidate: unknown) => candidate is T, describe: () => string, expects?: string): T;
|
|
76
102
|
validateSchema(value: any, schema: any): void;
|
|
77
103
|
createSchemaValidator(schema: any): DataValidator;
|
|
78
104
|
registerSchema(name: string, schema: object): void;
|
|
@@ -119,6 +145,28 @@ export interface ResourceContext extends ControllerContext {
|
|
|
119
145
|
* manifests. Use this when you need the full kind surface area visible from
|
|
120
146
|
* the module. */
|
|
121
147
|
loadManifests(url: string): Promise<ResourceManifest[]>;
|
|
148
|
+
/**
|
|
149
|
+
* The structured logger for this resource — `kernel/specs/logging.md` §13.2.
|
|
150
|
+
*
|
|
151
|
+
* Ambient rather than a resource (D3), because it must work before any
|
|
152
|
+
* resource initializes. Records are automatically stamped with this
|
|
153
|
+
* resource's identity, its module, its import-alias scope, and the active
|
|
154
|
+
* dispatch span's trace and span ids — a controller never passes those.
|
|
155
|
+
*
|
|
156
|
+
* A controller emits diagnostics **only** through this. Writing to
|
|
157
|
+
* stdout/stderr for diagnostic purposes is forbidden; writing to stdout as
|
|
158
|
+
* *data* (as the `Console` module does) is a separate, legitimate concern and
|
|
159
|
+
* is unaffected.
|
|
160
|
+
*/
|
|
161
|
+
readonly log: Logger;
|
|
162
|
+
/**
|
|
163
|
+
* Sink attach/detach and drop accounting — the surface a `Telo.Sink`
|
|
164
|
+
* controller needs and nothing else. §10.2 keeps the sink set open to the
|
|
165
|
+
* ecosystem, so a third-party sink module reaches the pipeline through this
|
|
166
|
+
* rather than through a kernel-internal import. Ordinary controllers use
|
|
167
|
+
* {@link log}.
|
|
168
|
+
*/
|
|
169
|
+
readonly logging: LoggingHost;
|
|
122
170
|
readonly moduleContext: ModuleContext;
|
|
123
171
|
readonly env: Record<string, string | undefined>;
|
|
124
172
|
readonly stdin: NodeJS.ReadableStream;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,24 @@ export type ResourceInstance<TInput = Record<string, any>, TOutput = any> = Part
|
|
|
6
6
|
init?(ctx?: ResourceContext): Promise<void>;
|
|
7
7
|
teardown?(): void | Promise<void>;
|
|
8
8
|
snapshot?(): Record<string, any> | Promise<Record<string, any>>;
|
|
9
|
+
/**
|
|
10
|
+
* Teardown ordering hint. Instances tear down in ascending priority — a
|
|
11
|
+
* higher number means *later*. Default `0`; within one priority the base
|
|
12
|
+
* order (reverse init) is preserved.
|
|
13
|
+
*
|
|
14
|
+
* This exists because the base order is reverse *insertion* order, which the
|
|
15
|
+
* multi-pass init retry can perturb, so a resource that must reliably outlive
|
|
16
|
+
* the rest at shutdown cannot express that through the dependency graph. Log
|
|
17
|
+
* sinks set {@link TEARDOWN_LAST} so they flush after every resource that
|
|
18
|
+
* might log while shutting down — a generic mechanism, not a logging-specific
|
|
19
|
+
* carve-out in the teardown path.
|
|
20
|
+
*/
|
|
21
|
+
teardownPriority?: number;
|
|
9
22
|
};
|
|
23
|
+
/** Teardown-last priority (see {@link ResourceInstance.teardownPriority}). Log
|
|
24
|
+
* sinks use it so anything logging during its own teardown still reaches a live
|
|
25
|
+
* destination. */
|
|
26
|
+
export declare const TEARDOWN_LAST = 1000;
|
|
10
27
|
/** The kind+name an instance was resolved from. */
|
|
11
28
|
export interface RefIdentity {
|
|
12
29
|
kind: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-instance.d.ts","sourceRoot":"","sources":["../src/resource-instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,CACjF,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3B,GACC,OAAO,CAAC,QAAQ,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;IAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"resource-instance.d.ts","sourceRoot":"","sources":["../src/resource-instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,CACjF,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3B,GACC,OAAO,CAAC,QAAQ,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;IAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEJ;;mBAEmB;AACnB,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,OAAO,MAAuC,CAAC;AAE1E;sFACsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CASnF;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAExE"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** Teardown-last priority (see {@link ResourceInstance.teardownPriority}). Log
|
|
2
|
+
* sinks use it so anything logging during its own teardown still reaches a live
|
|
3
|
+
* destination. */
|
|
4
|
+
export const TEARDOWN_LAST = 1000;
|
|
1
5
|
/**
|
|
2
6
|
* Non-enumerable identity tag the kernel stamps on a live instance when it
|
|
3
7
|
* injects a resolved `!ref` into a slot. A consumer that holds only the bare
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { InvokeContext } from "./cancellation.js";
|
|
2
2
|
import type { Invocable } from "./capabilities/invokable.js";
|
|
3
3
|
import type { ModuleContext } from "./module-context.js";
|
|
4
|
+
import type { KindRef } from "./ref.js";
|
|
5
|
+
import { resolveRefInstance } from "./resolve-ref-instance.js";
|
|
4
6
|
import { getRefIdentity, type ResourceInstance } from "./resource-instance.js";
|
|
5
7
|
|
|
6
8
|
/** The context a decorator kind composes to dispatch its wrapped target. */
|
|
@@ -31,25 +33,23 @@ export function resolveInvocableDispatcher(
|
|
|
31
33
|
ctx: DispatchContext,
|
|
32
34
|
describe: () => string,
|
|
33
35
|
): (inputs: Record<string, unknown>, invokeCtx?: InvokeContext) => Promise<unknown> {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const target = resolveRefInstance(
|
|
37
|
+
field,
|
|
38
|
+
ctx,
|
|
39
|
+
isInvocableInstance,
|
|
40
|
+
() => `${describe()}: 'invoke'`,
|
|
41
|
+
"telo#Invocable",
|
|
42
|
+
);
|
|
43
|
+
// Dispatch through the traced chokepoint needs the target's kind+name: from
|
|
44
|
+
// the `!ref` identity the kernel stamped at injection, else from the raw ref.
|
|
45
|
+
const id = getRefIdentity(target as object) ?? (field as Partial<KindRef> | undefined);
|
|
46
|
+
if (!id || typeof id.kind !== "string" || typeof id.name !== "string") {
|
|
47
|
+
return (inputs, invokeCtx) => target.invoke(inputs, invokeCtx);
|
|
41
48
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
? ctx.moduleContext.resolveImportedInstance(ref.alias, ref.name)
|
|
49
|
-
: ctx.moduleContext.getInstance(ref.name)
|
|
50
|
-
) as ResourceInstance | undefined;
|
|
51
|
-
if (!resolved || typeof resolved.invoke !== "function") {
|
|
52
|
-
throw new Error(`${describe()}: 'invoke' reference '${ref.name}' did not resolve to an invocable.`);
|
|
53
|
-
}
|
|
54
|
-
return (inputs, invokeCtx) => ctx.invokeResolved(ref.kind, ref.name, resolved, inputs, invokeCtx);
|
|
49
|
+
const { kind, name } = id;
|
|
50
|
+
return (inputs, invokeCtx) => ctx.invokeResolved(kind, name, target, inputs, invokeCtx);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isInvocableInstance(value: unknown): value is ResourceInstance & Invocable {
|
|
54
|
+
return typeof (value as Invocable | undefined)?.invoke === "function";
|
|
55
55
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export * from "./capabilities/provider.js";
|
|
|
9
9
|
export * from "./capabilities/runnable.js";
|
|
10
10
|
export * from "./context-provider.js";
|
|
11
11
|
export * from "./duration.js";
|
|
12
|
+
export * from "./json-value.js";
|
|
13
|
+
export * from "./resolve-ref-instance.js";
|
|
12
14
|
export * from "./controller-context.js";
|
|
13
15
|
export * from "./controller-policy.js";
|
|
14
16
|
export * from "./evaluation-context.js";
|
|
@@ -17,6 +19,10 @@ export * from "./resource-context.js";
|
|
|
17
19
|
export * from "./resource-instance.js";
|
|
18
20
|
export * from "./resource-manifest.js";
|
|
19
21
|
export * from "./invoke-error.js";
|
|
22
|
+
export * from "./log-record.js";
|
|
23
|
+
export * from "./log-sink.js";
|
|
24
|
+
export * from "./log-severity.js";
|
|
25
|
+
export * from "./logger.js";
|
|
20
26
|
export * from "./network-fetch.js";
|
|
21
27
|
export * from "./runtime-error.js";
|
|
22
28
|
export * from "./runtime-event.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON encoding for values that cross a persistence boundary.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.stringify` THROWS on a BigInt, and CEL integers surface as BigInt in
|
|
5
|
+
* this runtime — so any controller that persists a result computed in CEL
|
|
6
|
+
* (`{ charged: 500 }` from a `Run.Sequence` output) hits it. A store that lets
|
|
7
|
+
* that throw escape is worse than one that never persisted: the caller sees an
|
|
8
|
+
* opaque TypeError, and a decorator built on the store can mistake it for the
|
|
9
|
+
* body having failed.
|
|
10
|
+
*
|
|
11
|
+
* BigInt is encoded as a tagged object rather than a plain string or a Number:
|
|
12
|
+
* a string would come back a different type than went in, and Number is lossy
|
|
13
|
+
* past 2^53. A replayed value must equal the freshly-produced one, or
|
|
14
|
+
* at-most-once execution silently changes its answer on the second call.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const BIGINT_TAG = "$bigint";
|
|
18
|
+
|
|
19
|
+
interface TaggedBigInt {
|
|
20
|
+
[BIGINT_TAG]: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isTaggedBigInt(value: unknown): value is TaggedBigInt {
|
|
24
|
+
return (
|
|
25
|
+
typeof value === "object" &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
!Array.isArray(value) &&
|
|
28
|
+
typeof (value as TaggedBigInt)[BIGINT_TAG] === "string" &&
|
|
29
|
+
Object.keys(value).length === 1
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Serialize a value to JSON text, preserving BigInt exactly. */
|
|
34
|
+
export function encodeJsonValue(value: unknown): string {
|
|
35
|
+
return JSON.stringify(value ?? null, (_k, v) =>
|
|
36
|
+
typeof v === "bigint" ? { [BIGINT_TAG]: v.toString() } : v,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Inverse of {@link encodeJsonValue}; BigInt values are restored as BigInt. */
|
|
41
|
+
export function decodeJsonValue(text: string): unknown {
|
|
42
|
+
return JSON.parse(text, (_k, v) => (isTaggedBigInt(v) ? BigInt(v[BIGINT_TAG]) : v));
|
|
43
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { SeverityNumber } from "./log-severity.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Telo log record model — `kernel/specs/logging.md` §4. Maps 1:1 onto an
|
|
5
|
+
* OpenTelemetry `LogRecord`; encodings (§11) determine spelling and MUST NOT add
|
|
6
|
+
* or remove semantics.
|
|
7
|
+
*
|
|
8
|
+
* The one deliberate deviation from OTel is {@link LogRecord.message}: OTel's
|
|
9
|
+
* `Body` is an `AnyValue` and may be structured, while Telo requires a string and
|
|
10
|
+
* routes structured data to `attributes`. That keeps the console encoding total —
|
|
11
|
+
* every record has a renderable headline — and matches slog, pino, and zap.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The attribute value type (§6.1). `null` is a valid value and is preserved. */
|
|
15
|
+
export type AnyValue =
|
|
16
|
+
| string
|
|
17
|
+
| boolean
|
|
18
|
+
| number
|
|
19
|
+
| bigint
|
|
20
|
+
| Uint8Array
|
|
21
|
+
| null
|
|
22
|
+
| AnyValue[]
|
|
23
|
+
| { [key: string]: AnyValue };
|
|
24
|
+
|
|
25
|
+
export type LogAttributes = Record<string, AnyValue>;
|
|
26
|
+
|
|
27
|
+
/** Structured error (§4.2). The `cause` chain is bounded per §6.3. */
|
|
28
|
+
export interface ErrorValue {
|
|
29
|
+
/** Error class or code, e.g. `ERR_INVOKE_CANCELLED`. */
|
|
30
|
+
type: string;
|
|
31
|
+
message: string;
|
|
32
|
+
/** Multi-line, unmodified. */
|
|
33
|
+
stack?: string;
|
|
34
|
+
cause?: ErrorValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The emitting Telo resource (§7.3). `id` is the full hierarchical id, which is
|
|
38
|
+
* what distinguishes two instances of the same templated kind. */
|
|
39
|
+
export interface ResourceRef {
|
|
40
|
+
kind: string;
|
|
41
|
+
name: string;
|
|
42
|
+
id?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LogRecord {
|
|
46
|
+
/** Nanoseconds since the Unix epoch, by the origin clock. */
|
|
47
|
+
timestamp: bigint;
|
|
48
|
+
/** When the runtime observed the event, when that differs from `timestamp`
|
|
49
|
+
* (a bridged third-party logger, §13.3). */
|
|
50
|
+
observedTimestamp?: bigint;
|
|
51
|
+
severityNumber: SeverityNumber;
|
|
52
|
+
/** Canonical short name, or the original source spelling when bridging. */
|
|
53
|
+
severityText: string;
|
|
54
|
+
/** May be empty; never absent. */
|
|
55
|
+
message: string;
|
|
56
|
+
attributes?: LogAttributes;
|
|
57
|
+
/** 32 lowercase hex chars. */
|
|
58
|
+
traceId?: string;
|
|
59
|
+
/** 16 lowercase hex chars. Never present without `traceId`. */
|
|
60
|
+
spanId?: string;
|
|
61
|
+
/** Bit 0 = sampled, bit 1 reserved (§7.5), bits 2–7 zero. */
|
|
62
|
+
traceFlags?: number;
|
|
63
|
+
resource?: ResourceRef;
|
|
64
|
+
/** Module name of the emitter. Not unique — see `scope`. */
|
|
65
|
+
module?: string;
|
|
66
|
+
/** Dotted import-alias path identifying which *instance* emitted the record
|
|
67
|
+
* (`Api.Domain.Db`). Absent for the root Application's own resources. */
|
|
68
|
+
scope?: string;
|
|
69
|
+
/** Identifies a class of event; max 256 chars. */
|
|
70
|
+
eventName?: string;
|
|
71
|
+
error?: ErrorValue;
|
|
72
|
+
/** Non-zero when §6.3 limits truncated attributes. */
|
|
73
|
+
droppedAttributesCount?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Written as `BigInt(...)` rather than as `1_000_000n` literals: this module is
|
|
77
|
+
// consumed from source by the browser-targeted editor, whose tsconfig targets
|
|
78
|
+
// below ES2020 and cannot parse the literal syntax.
|
|
79
|
+
const NANOS_PER_MS = BigInt(1_000_000);
|
|
80
|
+
const NANOS_PER_SECOND = BigInt(1_000_000_000);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Node has no true nanosecond wall clock: `Date` is millisecond-resolution and
|
|
84
|
+
* `hrtime.bigint()` is monotonic rather than epoch-anchored. The best available
|
|
85
|
+
* is the performance origin plus the monotonic offset, which yields microsecond
|
|
86
|
+
* resolution zero-padded to nine digits. Format-conformant with §11.1; the extra
|
|
87
|
+
* three digits are always zero.
|
|
88
|
+
*
|
|
89
|
+
* The origin is captured once as a bigint so the addition never routes a
|
|
90
|
+
* 16-significant-digit value through a float64 and loses the low microseconds.
|
|
91
|
+
*/
|
|
92
|
+
const ORIGIN_NANOS = BigInt(Math.round(performance.timeOrigin * 1e6));
|
|
93
|
+
|
|
94
|
+
export function nowUnixNano(): bigint {
|
|
95
|
+
return ORIGIN_NANOS + BigInt(Math.round(performance.now() * 1e6));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Epoch nanoseconds for a millisecond-resolution instant — used when bridging a
|
|
99
|
+
* third-party record that carries a `Date` or epoch-millis timestamp. */
|
|
100
|
+
export function unixNanoFromMillis(epochMillis: number): bigint {
|
|
101
|
+
return BigInt(Math.round(epochMillis)) * NANOS_PER_MS;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* RFC 3339, UTC, nanosecond precision, `Z` suffix — the `time` key of the `json`
|
|
106
|
+
* encoding (§11.1).
|
|
107
|
+
*/
|
|
108
|
+
export function formatUnixNano(timestamp: bigint): string {
|
|
109
|
+
const seconds = timestamp / NANOS_PER_SECOND;
|
|
110
|
+
const nanos = timestamp - seconds * NANOS_PER_SECOND;
|
|
111
|
+
const isoSeconds = new Date(Number(seconds) * 1000).toISOString().slice(0, 19);
|
|
112
|
+
return `${isoSeconds}.${nanos.toString().padStart(9, "0")}Z`;
|
|
113
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenTelemetry `SeverityNumber` scale (1–24, stable), which Telo adopts
|
|
3
|
+
* verbatim — see `kernel/specs/logging.md` §5.
|
|
4
|
+
*
|
|
5
|
+
* Higher is more severe. All comparison, filtering, and threshold logic uses the
|
|
6
|
+
* number; severity *text* is presentation only and MUST NOT be compared. The
|
|
7
|
+
* full 24-value range stays valid on the wire so records bridged from a
|
|
8
|
+
* third-party logger survive a round-trip with their original spelling intact.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** An OTel SeverityNumber. `0` (UNSPECIFIED) is never emitted by a Telo runtime. */
|
|
12
|
+
export type SeverityNumber = number;
|
|
13
|
+
|
|
14
|
+
/** The six levels Telo names. Each is the floor of its four-value OTel range. */
|
|
15
|
+
export const SEVERITY = {
|
|
16
|
+
trace: 1,
|
|
17
|
+
debug: 5,
|
|
18
|
+
info: 9,
|
|
19
|
+
warn: 13,
|
|
20
|
+
error: 17,
|
|
21
|
+
fatal: 21,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export type LevelName = keyof typeof SEVERITY;
|
|
25
|
+
|
|
26
|
+
export const LEVEL_NAMES: readonly LevelName[] = ["trace", "debug", "info", "warn", "error", "fatal"];
|
|
27
|
+
|
|
28
|
+
/** The severity at or above which a record describes an error (§5.1). This is
|
|
29
|
+
* the portable error predicate; runtimes expose it rather than re-deriving it. */
|
|
30
|
+
export const ERROR_SEVERITY_FLOOR = 17;
|
|
31
|
+
|
|
32
|
+
const FLOORS: readonly number[] = [1, 5, 9, 13, 17, 21];
|
|
33
|
+
|
|
34
|
+
const TEXT_BY_FLOOR: Readonly<Record<number, string>> = {
|
|
35
|
+
1: "TRACE",
|
|
36
|
+
5: "DEBUG",
|
|
37
|
+
9: "INFO",
|
|
38
|
+
13: "WARN",
|
|
39
|
+
17: "ERROR",
|
|
40
|
+
21: "FATAL",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The range floor for a severity number — the canonical level a value maps onto.
|
|
45
|
+
* Out-of-range values clamp into 1–24 rather than producing `0`, which §5.1
|
|
46
|
+
* forbids emitting.
|
|
47
|
+
*/
|
|
48
|
+
export function severityFloor(severity: SeverityNumber): number {
|
|
49
|
+
const clamped = severity < 1 ? 1 : severity > 24 ? 24 : Math.trunc(severity);
|
|
50
|
+
let floor = FLOORS[0]!;
|
|
51
|
+
for (const candidate of FLOORS) {
|
|
52
|
+
if (candidate <= clamped) floor = candidate;
|
|
53
|
+
else break;
|
|
54
|
+
}
|
|
55
|
+
return floor;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Canonical short name (`TRACE`…`FATAL`) for a severity number. */
|
|
59
|
+
export function severityText(severity: SeverityNumber): string {
|
|
60
|
+
return TEXT_BY_FLOOR[severityFloor(severity)]!;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `true` when the record describes an error (§5.1). */
|
|
64
|
+
export function isErrorSeverity(severity: SeverityNumber): boolean {
|
|
65
|
+
return severity >= ERROR_SEVERITY_FLOOR;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve a manifest `level:` name to its severity number. */
|
|
69
|
+
export function severityForLevel(level: LevelName): number {
|
|
70
|
+
return SEVERITY[level];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Map a level name of unknown provenance onto the scale. A name Telo does not
|
|
75
|
+
* recognize yields `undefined` so the caller can preserve the original spelling
|
|
76
|
+
* in `severity_text` while landing the number on a range floor (§5.1).
|
|
77
|
+
*/
|
|
78
|
+
export function parseLevelName(name: string): number | undefined {
|
|
79
|
+
const key = name.trim().toLowerCase();
|
|
80
|
+
// Own-property check, not `in`: `in` also matches inherited members, so
|
|
81
|
+
// `parseLevelName("toString")` would otherwise return a Function and defeat
|
|
82
|
+
// the `?? fallback` at every call site. Written as `hasOwnProperty.call`
|
|
83
|
+
// rather than `Object.hasOwn` because this module is consumed from source by
|
|
84
|
+
// the browser-targeted editor, whose tsconfig targets below ES2022.
|
|
85
|
+
return Object.prototype.hasOwnProperty.call(SEVERITY, key)
|
|
86
|
+
? SEVERITY[key as LevelName]
|
|
87
|
+
: undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Go's `log/slog` documents that subtracting 9 from an OTel severity converts it
|
|
92
|
+
* to the slog range — an exact, officially sanctioned relation, so a Go runtime
|
|
93
|
+
* uses arithmetic rather than a table (§5.2). Exposed here so the conformance
|
|
94
|
+
* vectors can assert the relation from the Node side too.
|
|
95
|
+
*/
|
|
96
|
+
export const SLOG_OFFSET = 9;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* pino's scale is 10× and offset, with no arithmetic relation to OTel, so §5.2
|
|
100
|
+
* requires a table. Used by the Fastify logger replacement (§13.3).
|
|
101
|
+
*/
|
|
102
|
+
const PINO_BY_SEVERITY: Readonly<Record<number, number>> = {
|
|
103
|
+
1: 10,
|
|
104
|
+
5: 20,
|
|
105
|
+
9: 30,
|
|
106
|
+
13: 40,
|
|
107
|
+
17: 50,
|
|
108
|
+
21: 60,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const SEVERITY_BY_PINO: Readonly<Record<number, number>> = {
|
|
112
|
+
10: 1,
|
|
113
|
+
20: 5,
|
|
114
|
+
30: 9,
|
|
115
|
+
40: 13,
|
|
116
|
+
50: 17,
|
|
117
|
+
60: 21,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export function pinoLevelForSeverity(severity: SeverityNumber): number {
|
|
121
|
+
return PINO_BY_SEVERITY[severityFloor(severity)]!;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `undefined` for a pino level Telo does not name, so the caller preserves the
|
|
125
|
+
* source spelling and falls back to the nearest floor. */
|
|
126
|
+
export function severityForPinoLevel(level: number): number | undefined {
|
|
127
|
+
return SEVERITY_BY_PINO[level];
|
|
128
|
+
}
|