@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/src/log-sink.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { LogRecord } from "./log-record.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `Telo.Sink` capability contract — `kernel/specs/logging.md` §10.
|
|
5
|
+
*
|
|
6
|
+
* This lives in the SDK rather than the kernel because §10.2 makes the sink set
|
|
7
|
+
* **open to the ecosystem**: a third party ships a sink by publishing a module
|
|
8
|
+
* whose kind extends `Telo.LogSink`. That module is an ordinary module author's
|
|
9
|
+
* artifact, so the contract it implements belongs on the module-author surface,
|
|
10
|
+
* not behind a kernel-internal import.
|
|
11
|
+
*
|
|
12
|
+
* The logger writes to a sink through this contract directly and **never**
|
|
13
|
+
* through `ctx.invoke`: per-record dispatch is far too slow for a logging hot
|
|
14
|
+
* path, and the dispatch chokepoint emits trace events, so logging through it
|
|
15
|
+
* would generate telemetry from inside the telemetry path.
|
|
16
|
+
*
|
|
17
|
+
* The contract is deliberately payload-opaque — no filtering, no encoding —
|
|
18
|
+
* which is what lets a future `Telo.TraceSink` reuse the capability with a
|
|
19
|
+
* different record type. Log-specific configuration lives on the
|
|
20
|
+
* `Telo.LogSink` abstract instead.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type DropCause = "buffer_full" | "sampled" | "encode_failure" | "sink_error";
|
|
24
|
+
|
|
25
|
+
/** Policy for a saturated buffer (§10.3). A runtime that cannot honour `block`
|
|
26
|
+
* rejects the manifest at load rather than silently substituting a dropping
|
|
27
|
+
* policy. */
|
|
28
|
+
export type OnFull = "block" | "drop_new" | "drop_old";
|
|
29
|
+
|
|
30
|
+
export interface SinkBufferPolicy {
|
|
31
|
+
/** Bounded. Never unbounded. */
|
|
32
|
+
buffer: number;
|
|
33
|
+
onFull: OnFull;
|
|
34
|
+
/** Max time a record may sit buffered, in milliseconds. */
|
|
35
|
+
flushIntervalMs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_BUFFER_POLICY: SinkBufferPolicy = {
|
|
39
|
+
buffer: 8192,
|
|
40
|
+
onFull: "drop_new",
|
|
41
|
+
flushIntervalMs: 1000,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export interface LogSinkInstance {
|
|
45
|
+
/** Identity for drop accounting (§10.4): the resource name for a `!ref`, or
|
|
46
|
+
* kind plus position for an inline definition. */
|
|
47
|
+
readonly sinkId: string;
|
|
48
|
+
|
|
49
|
+
/** This sink's own fan-out filter, applied *after* the record is created. It
|
|
50
|
+
* never decides whether a record is created at all — that is the pipeline's
|
|
51
|
+
* minimum-level gate. */
|
|
52
|
+
readonly level: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether the sink can be drained to its destination from inside a
|
|
56
|
+
* synchronous call, with no scheduler turn. A file descriptor write can; a
|
|
57
|
+
* network round-trip cannot, and neither can a transport living on another
|
|
58
|
+
* thread — the producer cannot drain a queue it does not own.
|
|
59
|
+
*
|
|
60
|
+
* A capability tier, not a language carve-out: the same rule makes an OTLP
|
|
61
|
+
* sink best-effort in Rust and Go, where blocking a producer thread is
|
|
62
|
+
* possible but still would not make a round-trip synchronous.
|
|
63
|
+
*/
|
|
64
|
+
readonly syncFlushable: boolean;
|
|
65
|
+
|
|
66
|
+
/** Accept a record. MUST NOT throw — a sink failure is reported out-of-band
|
|
67
|
+
* and counted, never propagated to the caller (§8.4). */
|
|
68
|
+
write(record: LogRecord): void;
|
|
69
|
+
|
|
70
|
+
/** Drain asynchronously. */
|
|
71
|
+
flush(): Promise<void>;
|
|
72
|
+
|
|
73
|
+
/** Drain to completion before returning. A no-op when {@link syncFlushable}
|
|
74
|
+
* is `false`; the `fatal` path initiates those sinks' flushes without
|
|
75
|
+
* waiting, because blocking on a sink it cannot synchronously drain is a
|
|
76
|
+
* deadlock on an event loop, not durability. */
|
|
77
|
+
flushSync(): void;
|
|
78
|
+
|
|
79
|
+
/** Release the destination. Called during teardown, after the final flush. */
|
|
80
|
+
close(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The pipeline surface a sink controller reaches for — attach, detach, resolve a
|
|
85
|
+
* level, count a drop. Deliberately narrow: everything else about the pipeline
|
|
86
|
+
* stays private to the runtime, so a third-party sink depends on this and
|
|
87
|
+
* nothing deeper.
|
|
88
|
+
*/
|
|
89
|
+
export interface LoggingHost {
|
|
90
|
+
attach(sink: LogSinkInstance): void;
|
|
91
|
+
detach(sink: LogSinkInstance): void;
|
|
92
|
+
/** Resolve a sink's declared `level:` to a severity number, falling back to
|
|
93
|
+
* the effective scope threshold when the sink declares none (§12.1). */
|
|
94
|
+
levelFor(level: string | undefined): number;
|
|
95
|
+
/** Count `count` dropped records against this sink so §10.4's accounting stays
|
|
96
|
+
* complete. `count` defaults to 1; a sink that loses a whole batch at once
|
|
97
|
+
* (an OTLP export failure) passes the batch size so the total is not
|
|
98
|
+
* undercounted to one-per-failure. */
|
|
99
|
+
recordDrop(sinkId: string, cause: DropCause, count?: number): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The diagnostic §10.3 requires when a runtime cannot honour `on_full: block`.
|
|
103
|
+
* Rejecting is deliberate: `on_full` exists so an operator can state durability
|
|
104
|
+
* intent, and silently substituting a dropping policy hands back the opposite
|
|
105
|
+
* guarantee — discovered from a gap in an audit trail rather than from an
|
|
106
|
+
* error. */
|
|
107
|
+
export function blockUnsupportedMessage(sinkId: string): string {
|
|
108
|
+
return (
|
|
109
|
+
`Sink "${sinkId}": on_full: block is not supported by this runtime ` +
|
|
110
|
+
`(single-threaded event loop — blocking the producer would stall the writer). ` +
|
|
111
|
+
`Use \`drop_new\` or \`drop_old\`, or move this sink to a worker thread.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const BLOCK_UNSUPPORTED = "ERR_LOG_SINK_ON_FULL_UNSUPPORTED";
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { AnyValue, LogAttributes } from "./log-record.js";
|
|
2
|
+
import type { SeverityNumber } from "./log-severity.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The logger surface every Telo runtime exposes — `kernel/specs/logging.md` §8.
|
|
6
|
+
*
|
|
7
|
+
* Reached ambiently as `ctx.log`. The logger is ambient rather than a resource
|
|
8
|
+
* because it must work before any resource initializes; its *sinks* are
|
|
9
|
+
* resources, which is what keeps the destination set open to the ecosystem.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A value resolved only on the emit path (§8.2) — slog's `LogValuer`, zap's
|
|
14
|
+
* `ObjectMarshaler`, `tracing`'s `Value`. A deferred value attached to a
|
|
15
|
+
* suppressed record is never resolved, so an expensive rendering costs nothing
|
|
16
|
+
* below the threshold. This is RECOMMENDED sugar and does **not** substitute for
|
|
17
|
+
* {@link Logger.enabled}, which is the only mechanism that avoids evaluating a
|
|
18
|
+
* call's *arguments*.
|
|
19
|
+
*/
|
|
20
|
+
export interface LogValuer {
|
|
21
|
+
toLogValue(): AnyValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type LogAttributeInput = AnyValue | LogValuer;
|
|
25
|
+
export type LogAttributesInput = Record<string, LogAttributeInput>;
|
|
26
|
+
|
|
27
|
+
/** Per-record extras that are top-level record fields rather than attributes.
|
|
28
|
+
* Kept out of the attribute map so they cannot collide with a reserved key. */
|
|
29
|
+
export interface LogOptions {
|
|
30
|
+
/** Any thrown value. Normalized to the record's `error` (§4.2), with the
|
|
31
|
+
* `cause` chain bounded per §6.3. */
|
|
32
|
+
error?: unknown;
|
|
33
|
+
/** Identifies a class of event; max 256 chars. Bridges to the event bus. */
|
|
34
|
+
eventName?: string;
|
|
35
|
+
/** When the event occurred, if earlier than the moment `log()` was called —
|
|
36
|
+
* set by a bridge, which also stamps `observedTimestamp` (§13.3). */
|
|
37
|
+
timestamp?: bigint;
|
|
38
|
+
/** The original source spelling of the level, preserved when bridging a level
|
|
39
|
+
* Telo does not name (§5.1). Defaults to the canonical short name. */
|
|
40
|
+
severityText?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Logger {
|
|
44
|
+
/**
|
|
45
|
+
* Whether a record at this severity would reach any sink. The load-bearing
|
|
46
|
+
* performance primitive: guard an expensive call with it so the *arguments*
|
|
47
|
+
* are never evaluated.
|
|
48
|
+
*
|
|
49
|
+
* Never blocks, never throws. The result is **not static** — it changes when
|
|
50
|
+
* configuration changes or a sink attaches or detaches (§12.4), so callers
|
|
51
|
+
* re-check per emission rather than caching a boolean.
|
|
52
|
+
*/
|
|
53
|
+
enabled(severity: SeverityNumber): boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Emit a record. Never throws, under any condition, including sink failure —
|
|
57
|
+
* a sink failure is reported on the fallback diagnostic stream and counted,
|
|
58
|
+
* never propagated and never swallowed (§8.4).
|
|
59
|
+
*/
|
|
60
|
+
log(
|
|
61
|
+
severity: SeverityNumber,
|
|
62
|
+
message: string,
|
|
63
|
+
attributes?: LogAttributesInput,
|
|
64
|
+
options?: LogOptions,
|
|
65
|
+
): void;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A child logger whose bound attributes are merged into every record it emits.
|
|
69
|
+
* Record attributes override bound attributes. Binding is O(1) amortized: the
|
|
70
|
+
* merge happens once here, never per record.
|
|
71
|
+
*/
|
|
72
|
+
with(attributes: LogAttributesInput): Logger;
|
|
73
|
+
|
|
74
|
+
/** Drain every attached sink. Bounded by the caller; see §10.5. */
|
|
75
|
+
flush(): Promise<void>;
|
|
76
|
+
|
|
77
|
+
trace(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
78
|
+
debug(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
79
|
+
info(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
80
|
+
warn(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
81
|
+
error(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
82
|
+
/**
|
|
83
|
+
* Emits at severity 21. Severity never implies control flow (§5, D5): `fatal`
|
|
84
|
+
* does **not** terminate the process, exit, or panic — it triggers an
|
|
85
|
+
* immediate flush, synchronous on every sink that supports it and best-effort
|
|
86
|
+
* on the rest (§10.5).
|
|
87
|
+
*/
|
|
88
|
+
fatal(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Resolve a {@link LogValuer} if the value is one, else pass it through. */
|
|
92
|
+
export function isLogValuer(value: unknown): value is LogValuer {
|
|
93
|
+
return (
|
|
94
|
+
typeof value === "object" &&
|
|
95
|
+
value !== null &&
|
|
96
|
+
typeof (value as LogValuer).toLogValue === "function"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A logger that discards everything. Used where a logger is structurally
|
|
101
|
+
* required before one is available, and by tests that assert silence. */
|
|
102
|
+
export const NOOP_LOGGER: Logger = {
|
|
103
|
+
enabled: () => false,
|
|
104
|
+
log: () => {},
|
|
105
|
+
with: () => NOOP_LOGGER,
|
|
106
|
+
flush: async () => {},
|
|
107
|
+
trace: () => {},
|
|
108
|
+
debug: () => {},
|
|
109
|
+
info: () => {},
|
|
110
|
+
warn: () => {},
|
|
111
|
+
error: () => {},
|
|
112
|
+
fatal: () => {},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The bound-attribute merge of {@link Logger.with}, exposed so a runtime's
|
|
116
|
+
* child-logger implementation and its conformance vectors share one definition
|
|
117
|
+
* of "record attributes win". */
|
|
118
|
+
export function mergeBoundAttributes(
|
|
119
|
+
bound: LogAttributes | undefined,
|
|
120
|
+
record: LogAttributes | undefined,
|
|
121
|
+
): LogAttributes | undefined {
|
|
122
|
+
if (!bound) return record;
|
|
123
|
+
if (!record) return bound;
|
|
124
|
+
return { ...bound, ...record };
|
|
125
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ModuleContext } from "./module-context.js";
|
|
2
|
+
import type { KindRef } from "./ref.js";
|
|
3
|
+
import { RuntimeError } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/** The slice of `ResourceContext` needed to resolve a reference. */
|
|
6
|
+
export interface RefResolveContext {
|
|
7
|
+
readonly moduleContext: ModuleContext;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a `!ref` config field to a live instance of `T`. Controllers reach
|
|
12
|
+
* this as `ctx.resolveRef(value, guard, describe, expects)`; the standalone form
|
|
13
|
+
* is for callers holding only a `{ moduleContext }` slice rather than a full
|
|
14
|
+
* `ResourceContext`.
|
|
15
|
+
*
|
|
16
|
+
* Phase 5 injection normally replaces the slot with the live `ResourceInstance`
|
|
17
|
+
* before `init()` — local and cross-module refs alike, since injection resolves
|
|
18
|
+
* an aliased ref through the import's export table (and defers, rather than
|
|
19
|
+
* leaving a raw ref, when the import hasn't published its exports yet). So the
|
|
20
|
+
* common path here is the guard short-circuit.
|
|
21
|
+
*
|
|
22
|
+
* A raw {@link KindRef} still reaches a controller where injection does not
|
|
23
|
+
* reach the slot: a kind whose definition yields no field map, or a ref the
|
|
24
|
+
* controller obtained itself via `ctx.ensureKindRef`. Both are gaps worth
|
|
25
|
+
* closing in the kernel — until they are, both shapes must be accepted here, and
|
|
26
|
+
* an aliased ref routes through the import's exported scope because a bare local
|
|
27
|
+
* lookup would miss it.
|
|
28
|
+
*
|
|
29
|
+
* `guard` decides what counts as the right kind of instance — a duck-type check
|
|
30
|
+
* on the methods the caller will actually invoke, so a mis-wired ref fails with a
|
|
31
|
+
* clear message here rather than as `undefined is not a function` later.
|
|
32
|
+
* `describe` labels the owning resource and slot; `expects` names the contract
|
|
33
|
+
* the slot wants — the slot's own `x-telo-ref` string (`std/cache#Store`) — so
|
|
34
|
+
* the message says what was missing, not just that something was.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const store = resolveRefInstance(
|
|
38
|
+
* this.resource.store, this.ctx, isKvStore,
|
|
39
|
+
* () => `Idempotency.Once "${name}": 'store'`, "std/kv-store#Store",
|
|
40
|
+
* );
|
|
41
|
+
*/
|
|
42
|
+
export function resolveRefInstance<T>(
|
|
43
|
+
value: unknown,
|
|
44
|
+
ctx: RefResolveContext,
|
|
45
|
+
guard: (candidate: unknown) => candidate is T,
|
|
46
|
+
describe: () => string,
|
|
47
|
+
expects?: string,
|
|
48
|
+
): T {
|
|
49
|
+
// Phase-5-injected: already the instance.
|
|
50
|
+
if (guard(value)) return value;
|
|
51
|
+
|
|
52
|
+
const target = expects ? `resource satisfying \`${expects}\`` : "resource";
|
|
53
|
+
if (value === undefined || value === null) {
|
|
54
|
+
throw new RuntimeError("ERR_REF_REQUIRED", `${describe()} is required — reference a ${target}.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ref = value as Partial<KindRef<T>>;
|
|
58
|
+
if (typeof ref.name !== "string") {
|
|
59
|
+
throw new RuntimeError(
|
|
60
|
+
"ERR_REF_UNRESOLVED",
|
|
61
|
+
`${describe()} must be a \`!ref\` to a ${target}.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// `Self` names the declaring library's own scope, so it resolves locally —
|
|
66
|
+
// it is an alias that crosses no import boundary.
|
|
67
|
+
const instance =
|
|
68
|
+
ref.alias && ref.alias !== "Self"
|
|
69
|
+
? ctx.moduleContext.resolveImportedInstance(ref.alias, ref.name)
|
|
70
|
+
: ctx.moduleContext.getInstance(ref.name);
|
|
71
|
+
|
|
72
|
+
if (!guard(instance)) {
|
|
73
|
+
const label = ref.alias ? `${ref.alias}.${ref.name}` : ref.name;
|
|
74
|
+
throw new RuntimeError(
|
|
75
|
+
"ERR_REF_UNRESOLVED",
|
|
76
|
+
`${describe()} reference '${label}' did not resolve to a ${target}` +
|
|
77
|
+
`${instance === undefined ? " (nothing is registered under that name)" : ""}.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return instance;
|
|
81
|
+
}
|
package/src/resource-context.ts
CHANGED
|
@@ -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";
|
|
@@ -84,7 +87,35 @@ export interface ResourceContext extends ControllerContext {
|
|
|
84
87
|
spawnChildContext(): EvaluationContext;
|
|
85
88
|
transientChild(context: Record<string, any>): EvaluationContext;
|
|
86
89
|
withManifests<T>(manifests: any[], fn: () => T): T;
|
|
90
|
+
/**
|
|
91
|
+
* Normalize a nested slot value to a {@link KindRef}. The value is an inline
|
|
92
|
+
* definition (`{ kind, …config }`), an already-normalized `{ kind, name }`
|
|
93
|
+
* ref, or a `!ref` sentinel. An inline definition is *registered* into this
|
|
94
|
+
* module's scope first — minting `resourceName` (or a generated one) as its
|
|
95
|
+
* name — so the returned ref always points at a resource the kernel knows.
|
|
96
|
+
*
|
|
97
|
+
* The inverse of {@link resolveRef}: this goes slot value → ref, that goes
|
|
98
|
+
* ref → live instance. Controllers that dispatch through
|
|
99
|
+
* `invokeResolved(kind, name, …)` want the ref, so the invocation keeps its
|
|
100
|
+
* identity for tracing and error wrapping.
|
|
101
|
+
*/
|
|
102
|
+
ensureKindRef(value: any, resourceName?: string): KindRef;
|
|
103
|
+
/** @deprecated Renamed to {@link ensureKindRef} — it produces a reference
|
|
104
|
+
* (registering an inline definition on the way), it does not resolve one. */
|
|
87
105
|
resolveChildren(resource: any, resourceName?: string): { kind: string; name: string };
|
|
106
|
+
/**
|
|
107
|
+
* Resolve a `!ref` config field to a live instance of `T`. See
|
|
108
|
+
* {@link resolveRefInstance} — this is the same resolution, reached through
|
|
109
|
+
* the context a controller already holds. `expects` names the contract the
|
|
110
|
+
* slot wants — its `x-telo-ref` string (`std/cache#Store`) — so a mis-wire
|
|
111
|
+
* says what was missing.
|
|
112
|
+
*/
|
|
113
|
+
resolveRef<T>(
|
|
114
|
+
value: unknown,
|
|
115
|
+
guard: (candidate: unknown) => candidate is T,
|
|
116
|
+
describe: () => string,
|
|
117
|
+
expects?: string,
|
|
118
|
+
): T;
|
|
88
119
|
validateSchema(value: any, schema: any): void;
|
|
89
120
|
createSchemaValidator(schema: any): DataValidator;
|
|
90
121
|
registerSchema(name: string, schema: object): void;
|
|
@@ -131,6 +162,28 @@ export interface ResourceContext extends ControllerContext {
|
|
|
131
162
|
* manifests. Use this when you need the full kind surface area visible from
|
|
132
163
|
* the module. */
|
|
133
164
|
loadManifests(url: string): Promise<ResourceManifest[]>;
|
|
165
|
+
/**
|
|
166
|
+
* The structured logger for this resource — `kernel/specs/logging.md` §13.2.
|
|
167
|
+
*
|
|
168
|
+
* Ambient rather than a resource (D3), because it must work before any
|
|
169
|
+
* resource initializes. Records are automatically stamped with this
|
|
170
|
+
* resource's identity, its module, its import-alias scope, and the active
|
|
171
|
+
* dispatch span's trace and span ids — a controller never passes those.
|
|
172
|
+
*
|
|
173
|
+
* A controller emits diagnostics **only** through this. Writing to
|
|
174
|
+
* stdout/stderr for diagnostic purposes is forbidden; writing to stdout as
|
|
175
|
+
* *data* (as the `Console` module does) is a separate, legitimate concern and
|
|
176
|
+
* is unaffected.
|
|
177
|
+
*/
|
|
178
|
+
readonly log: Logger;
|
|
179
|
+
/**
|
|
180
|
+
* Sink attach/detach and drop accounting — the surface a `Telo.Sink`
|
|
181
|
+
* controller needs and nothing else. §10.2 keeps the sink set open to the
|
|
182
|
+
* ecosystem, so a third-party sink module reaches the pipeline through this
|
|
183
|
+
* rather than through a kernel-internal import. Ordinary controllers use
|
|
184
|
+
* {@link log}.
|
|
185
|
+
*/
|
|
186
|
+
readonly logging: LoggingHost;
|
|
134
187
|
readonly moduleContext: ModuleContext;
|
|
135
188
|
readonly env: Record<string, string | undefined>;
|
|
136
189
|
readonly stdin: NodeJS.ReadableStream;
|
package/src/resource-instance.ts
CHANGED
|
@@ -11,8 +11,26 @@ export type ResourceInstance<TInput = Record<string, any>, TOutput = any> = Part
|
|
|
11
11
|
init?(ctx?: ResourceContext): Promise<void>;
|
|
12
12
|
teardown?(): void | Promise<void>;
|
|
13
13
|
snapshot?(): Record<string, any> | Promise<Record<string, any>>;
|
|
14
|
+
/**
|
|
15
|
+
* Teardown ordering hint. Instances tear down in ascending priority — a
|
|
16
|
+
* higher number means *later*. Default `0`; within one priority the base
|
|
17
|
+
* order (reverse init) is preserved.
|
|
18
|
+
*
|
|
19
|
+
* This exists because the base order is reverse *insertion* order, which the
|
|
20
|
+
* multi-pass init retry can perturb, so a resource that must reliably outlive
|
|
21
|
+
* the rest at shutdown cannot express that through the dependency graph. Log
|
|
22
|
+
* sinks set {@link TEARDOWN_LAST} so they flush after every resource that
|
|
23
|
+
* might log while shutting down — a generic mechanism, not a logging-specific
|
|
24
|
+
* carve-out in the teardown path.
|
|
25
|
+
*/
|
|
26
|
+
teardownPriority?: number;
|
|
14
27
|
};
|
|
15
28
|
|
|
29
|
+
/** Teardown-last priority (see {@link ResourceInstance.teardownPriority}). Log
|
|
30
|
+
* sinks use it so anything logging during its own teardown still reaches a live
|
|
31
|
+
* destination. */
|
|
32
|
+
export const TEARDOWN_LAST = 1000;
|
|
33
|
+
|
|
16
34
|
/** The kind+name an instance was resolved from. */
|
|
17
35
|
export interface RefIdentity {
|
|
18
36
|
kind: string;
|