@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/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;CACjE,CAAC;AAEJ,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
+ {"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,6 @@
1
1
  {
2
2
  "name": "@telorun/sdk",
3
- "version": "0.49.0",
3
+ "version": "0.54.0",
4
4
  "description": "Telo SDK - Public API for Telo module authors.",
5
5
  "keywords": [
6
6
  "telo",
@@ -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
- if (field && typeof (field as Invocable).invoke === "function") {
35
- const instance = field as ResourceInstance & Invocable;
36
- const id = getRefIdentity(field as object);
37
- return (inputs, invokeCtx) =>
38
- id
39
- ? ctx.invokeResolved(id.kind, id.name, instance, inputs, invokeCtx)
40
- : instance.invoke(inputs, invokeCtx);
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 ref = field as { kind: string; name: string; alias?: string } | undefined;
43
- if (!ref || typeof ref.name !== "string") {
44
- throw new Error(`${describe()}: 'invoke' must reference an invocable.`);
45
- }
46
- const resolved = (
47
- ref.alias && ref.alias !== "Self"
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
+ }