@telorun/sdk 0.48.0 → 0.50.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/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/invoke-error.d.ts +3 -1
- package/dist/invoke-error.d.ts.map +1 -1
- package/dist/invoke-error.js +14 -1
- 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/network-fetch.d.ts +71 -0
- package/dist/network-fetch.d.ts.map +1 -0
- package/dist/network-fetch.js +140 -0
- package/dist/resource-context.d.ts +24 -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/index.ts +5 -0
- package/src/invoke-error.ts +14 -1
- 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/network-fetch.ts +175 -0
- package/src/resource-context.ts +24 -0
- package/src/resource-instance.ts +18 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Raised when a request never reached the peer — DNS, connect, reset, or TLS
|
|
2
|
+
* trust. Distinct from a non-OK HTTP response, which is a reply the caller
|
|
3
|
+
* interprets itself. */
|
|
4
|
+
export declare const ERR_NETWORK_UNREACHABLE = "ERR_NETWORK_UNREACHABLE";
|
|
5
|
+
/** The facts a transport failure carries. Deliberately structured rather than
|
|
6
|
+
* pre-rendered: a controller reports *what happened*, and whoever displays the
|
|
7
|
+
* error turns it into a sentence. Prose baked into a controller would have to
|
|
8
|
+
* be re-typed identically by every language SDK (TS, Rust, Go, …) and would
|
|
9
|
+
* drift; `cause: "ENOTFOUND"` is the same symbol in every language.
|
|
10
|
+
*
|
|
11
|
+
* `message` on the thrown error is a reasonable default for today's renderers;
|
|
12
|
+
* a kernel-side renderer can format from these fields instead. */
|
|
13
|
+
export interface NetworkErrorData {
|
|
14
|
+
/** What was being attempted, e.g. `"Embedding model request"`. */
|
|
15
|
+
operation: string;
|
|
16
|
+
url: string;
|
|
17
|
+
host: string;
|
|
18
|
+
port?: number;
|
|
19
|
+
/** The OS/undici code — `ENOTFOUND`, `ECONNREFUSED`, `CERT_HAS_EXPIRED`, … */
|
|
20
|
+
cause: string;
|
|
21
|
+
/** The underlying error's own message, kept verbatim. Carries detail no code
|
|
22
|
+
* mapping has (`SSL alert number 80`, a resolver's remarks), so wrapping is
|
|
23
|
+
* never a downgrade on an unmapped code. */
|
|
24
|
+
detail?: string;
|
|
25
|
+
/** `metadata.name` of the resource whose configuration produced `url`, so the
|
|
26
|
+
* error names the actual instance rather than its kind. */
|
|
27
|
+
resource?: string;
|
|
28
|
+
/** The setting to change — a manifest field (`baseUrl`) or a CLI/env name
|
|
29
|
+
* (`--registry`). Structured rather than a pre-written sentence: this is the
|
|
30
|
+
* one genuinely actionable part, and prose here would be the thing every
|
|
31
|
+
* other language SDK has to retype and keep in sync. */
|
|
32
|
+
setting?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Walk the cause chain for the first `code` — undici wraps the real error one or
|
|
36
|
+
* more levels down, which is exactly the detail lost when only `message` is
|
|
37
|
+
* reported.
|
|
38
|
+
*
|
|
39
|
+
* Exported because classifying a network failure by substring-matching the
|
|
40
|
+
* message does not work: `fetch` rejects with the literal text `"fetch failed"`
|
|
41
|
+
* for DNS, refusal, and TLS alike, so a `message.includes("enotfound")` test
|
|
42
|
+
* silently never matches and every failure collapses into whichever branch is
|
|
43
|
+
* last. Callers with their own error contract should classify on this code
|
|
44
|
+
* rather than on prose.
|
|
45
|
+
*/
|
|
46
|
+
export declare function networkCauseCode(err: unknown): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* `fetch` that turns a transport-level failure into an {@link InvokeError}
|
|
49
|
+
* carrying {@link NetworkErrorData}, instead of undici's opaque
|
|
50
|
+
* `TypeError: fetch failed` whose real cause sits unread on `error.cause`.
|
|
51
|
+
*
|
|
52
|
+
* Only *transport* failures are wrapped. A non-OK response is returned
|
|
53
|
+
* untouched, because a status code is a reply the caller interprets (and often
|
|
54
|
+
* renders from the provider's own error body) — so this drops into an existing
|
|
55
|
+
* call site without changing status handling. Cancellation is re-thrown as-is:
|
|
56
|
+
* an aborted request is the caller's intent, not a network fault.
|
|
57
|
+
*
|
|
58
|
+
* @param context.operation What is being attempted, for the message.
|
|
59
|
+
* @param context.resource `metadata.name` of the resource whose configuration
|
|
60
|
+
* produced the URL, so the error names the instance, not just its kind.
|
|
61
|
+
* @param context.setting The manifest field or CLI/env name to change. Passed
|
|
62
|
+
* as a bare identifier, never a sentence — the wording is composed here, in
|
|
63
|
+
* one place, so another language's SDK supplies the same two facts rather
|
|
64
|
+
* than retyping the same English.
|
|
65
|
+
*/
|
|
66
|
+
export declare function fetchOrThrow(input: string | URL, init: RequestInit | undefined, context: {
|
|
67
|
+
operation: string;
|
|
68
|
+
resource?: string;
|
|
69
|
+
setting?: string;
|
|
70
|
+
}): Promise<Response>;
|
|
71
|
+
//# sourceMappingURL=network-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-fetch.d.ts","sourceRoot":"","sources":["../src/network-fetch.ts"],"names":[],"mappings":"AAGA;;yBAEyB;AACzB,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE;;;;;;;mEAOmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd;;iDAE6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;6DAGyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,QAAQ,CAAC,CA4CnB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { isCancellationError } from "./cancellation.js";
|
|
2
|
+
import { InvokeError } from "./invoke-error.js";
|
|
3
|
+
/** Raised when a request never reached the peer — DNS, connect, reset, or TLS
|
|
4
|
+
* trust. Distinct from a non-OK HTTP response, which is a reply the caller
|
|
5
|
+
* interprets itself. */
|
|
6
|
+
export const ERR_NETWORK_UNREACHABLE = "ERR_NETWORK_UNREACHABLE";
|
|
7
|
+
/** Human explanation per transport failure code. Interpolates only the facts
|
|
8
|
+
* already in `NetworkErrorData`, so a renderer in another language can produce
|
|
9
|
+
* the same sentence from the same fields. */
|
|
10
|
+
function explain(code, host, port) {
|
|
11
|
+
const target = port ? `${host}:${port}` : host;
|
|
12
|
+
switch (code) {
|
|
13
|
+
case "ENOTFOUND":
|
|
14
|
+
return `DNS lookup failed for host '${host}' — the name does not resolve`;
|
|
15
|
+
case "EAI_AGAIN":
|
|
16
|
+
return `DNS is temporarily unavailable resolving '${host}' — a transient resolver failure`;
|
|
17
|
+
case "ECONNREFUSED":
|
|
18
|
+
return `nothing is listening on ${target}`;
|
|
19
|
+
case "ECONNRESET":
|
|
20
|
+
return `the connection to ${target} was reset by the peer`;
|
|
21
|
+
case "EHOSTUNREACH":
|
|
22
|
+
return `no network route to ${host}`;
|
|
23
|
+
case "ETIMEDOUT":
|
|
24
|
+
case "UND_ERR_CONNECT_TIMEOUT":
|
|
25
|
+
return `the connection to ${target} timed out`;
|
|
26
|
+
case "CERT_HAS_EXPIRED":
|
|
27
|
+
return `the TLS certificate presented by ${host} has expired`;
|
|
28
|
+
case "DEPTH_ZERO_SELF_SIGNED_CERT":
|
|
29
|
+
case "SELF_SIGNED_CERT_IN_CHAIN":
|
|
30
|
+
return `the TLS certificate presented by ${host} is self-signed and not trusted`;
|
|
31
|
+
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
|
|
32
|
+
return `the TLS certificate chain presented by ${host} could not be verified`;
|
|
33
|
+
case "EPROTO":
|
|
34
|
+
return `the TLS handshake with ${host} failed`;
|
|
35
|
+
default:
|
|
36
|
+
return `the request to ${target} failed at the transport layer`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** The deepest `message` in the cause chain — the one carrying detail a code
|
|
40
|
+
* mapping cannot have. Skips undici's own `"fetch failed"`, which is the
|
|
41
|
+
* placeholder this whole module exists to replace. */
|
|
42
|
+
function causeDetail(err) {
|
|
43
|
+
let current = err;
|
|
44
|
+
let detail;
|
|
45
|
+
for (let depth = 0; current && depth < 5; depth++) {
|
|
46
|
+
const message = current.message;
|
|
47
|
+
if (typeof message === "string" && message && message !== "fetch failed")
|
|
48
|
+
detail = message;
|
|
49
|
+
current = current.cause;
|
|
50
|
+
}
|
|
51
|
+
return detail;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Walk the cause chain for the first `code` — undici wraps the real error one or
|
|
55
|
+
* more levels down, which is exactly the detail lost when only `message` is
|
|
56
|
+
* reported.
|
|
57
|
+
*
|
|
58
|
+
* Exported because classifying a network failure by substring-matching the
|
|
59
|
+
* message does not work: `fetch` rejects with the literal text `"fetch failed"`
|
|
60
|
+
* for DNS, refusal, and TLS alike, so a `message.includes("enotfound")` test
|
|
61
|
+
* silently never matches and every failure collapses into whichever branch is
|
|
62
|
+
* last. Callers with their own error contract should classify on this code
|
|
63
|
+
* rather than on prose.
|
|
64
|
+
*/
|
|
65
|
+
export function networkCauseCode(err) {
|
|
66
|
+
let current = err;
|
|
67
|
+
for (let depth = 0; current && depth < 5; depth++) {
|
|
68
|
+
const code = current.code;
|
|
69
|
+
if (typeof code === "string")
|
|
70
|
+
return code;
|
|
71
|
+
current = current.cause;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* `fetch` that turns a transport-level failure into an {@link InvokeError}
|
|
77
|
+
* carrying {@link NetworkErrorData}, instead of undici's opaque
|
|
78
|
+
* `TypeError: fetch failed` whose real cause sits unread on `error.cause`.
|
|
79
|
+
*
|
|
80
|
+
* Only *transport* failures are wrapped. A non-OK response is returned
|
|
81
|
+
* untouched, because a status code is a reply the caller interprets (and often
|
|
82
|
+
* renders from the provider's own error body) — so this drops into an existing
|
|
83
|
+
* call site without changing status handling. Cancellation is re-thrown as-is:
|
|
84
|
+
* an aborted request is the caller's intent, not a network fault.
|
|
85
|
+
*
|
|
86
|
+
* @param context.operation What is being attempted, for the message.
|
|
87
|
+
* @param context.resource `metadata.name` of the resource whose configuration
|
|
88
|
+
* produced the URL, so the error names the instance, not just its kind.
|
|
89
|
+
* @param context.setting The manifest field or CLI/env name to change. Passed
|
|
90
|
+
* as a bare identifier, never a sentence — the wording is composed here, in
|
|
91
|
+
* one place, so another language's SDK supplies the same two facts rather
|
|
92
|
+
* than retyping the same English.
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchOrThrow(input, init, context) {
|
|
95
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
96
|
+
try {
|
|
97
|
+
return await fetch(input, init);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (isCancellationError(err))
|
|
101
|
+
throw err;
|
|
102
|
+
if (err instanceof DOMException && err.name === "AbortError")
|
|
103
|
+
throw err;
|
|
104
|
+
const code = networkCauseCode(err) ?? "UNKNOWN";
|
|
105
|
+
let host = url;
|
|
106
|
+
let port;
|
|
107
|
+
try {
|
|
108
|
+
const parsed = new URL(url);
|
|
109
|
+
host = parsed.hostname;
|
|
110
|
+
if (parsed.port)
|
|
111
|
+
port = Number(parsed.port);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Non-absolute URL — fall back to the raw string as the host label.
|
|
115
|
+
}
|
|
116
|
+
const detail = causeDetail(err);
|
|
117
|
+
const data = { operation: context.operation, url, host, cause: code };
|
|
118
|
+
if (port !== undefined)
|
|
119
|
+
data.port = port;
|
|
120
|
+
if (detail)
|
|
121
|
+
data.detail = detail;
|
|
122
|
+
if (context.resource)
|
|
123
|
+
data.resource = context.resource;
|
|
124
|
+
if (context.setting)
|
|
125
|
+
data.setting = context.setting;
|
|
126
|
+
// `detail` is appended only when the code has no mapping of its own —
|
|
127
|
+
// otherwise the explanation already says it better. Without this, wrapping
|
|
128
|
+
// an unmapped code would *lose* information relative to the raw error.
|
|
129
|
+
const explained = explain(code, host, port);
|
|
130
|
+
const isMapped = !explained.startsWith("the request to");
|
|
131
|
+
const because = isMapped || !detail ? explained : `${explained} (${detail})`;
|
|
132
|
+
const fix = context.setting
|
|
133
|
+
? ` Check \`${context.setting}\`${context.resource ? ` on resource '${context.resource}'` : ""}.`
|
|
134
|
+
: "";
|
|
135
|
+
const message = `${context.operation} failed: cannot reach ${url} — ${code}: ${because}.${fix}`;
|
|
136
|
+
// The wrapped error stays reachable as `cause`: the code mapping is a
|
|
137
|
+
// convenience, never a reason to destroy what was actually thrown.
|
|
138
|
+
throw new InvokeError(ERR_NETWORK_UNREACHABLE, message, data, { cause: err });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
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";
|
|
@@ -119,6 +121,28 @@ export interface ResourceContext extends ControllerContext {
|
|
|
119
121
|
* manifests. Use this when you need the full kind surface area visible from
|
|
120
122
|
* the module. */
|
|
121
123
|
loadManifests(url: string): Promise<ResourceManifest[]>;
|
|
124
|
+
/**
|
|
125
|
+
* The structured logger for this resource — `kernel/specs/logging.md` §13.2.
|
|
126
|
+
*
|
|
127
|
+
* Ambient rather than a resource (D3), because it must work before any
|
|
128
|
+
* resource initializes. Records are automatically stamped with this
|
|
129
|
+
* resource's identity, its module, its import-alias scope, and the active
|
|
130
|
+
* dispatch span's trace and span ids — a controller never passes those.
|
|
131
|
+
*
|
|
132
|
+
* A controller emits diagnostics **only** through this. Writing to
|
|
133
|
+
* stdout/stderr for diagnostic purposes is forbidden; writing to stdout as
|
|
134
|
+
* *data* (as the `Console` module does) is a separate, legitimate concern and
|
|
135
|
+
* is unaffected.
|
|
136
|
+
*/
|
|
137
|
+
readonly log: Logger;
|
|
138
|
+
/**
|
|
139
|
+
* Sink attach/detach and drop accounting — the surface a `Telo.Sink`
|
|
140
|
+
* controller needs and nothing else. §10.2 keeps the sink set open to the
|
|
141
|
+
* ecosystem, so a third-party sink module reaches the pipeline through this
|
|
142
|
+
* rather than through a kernel-internal import. Ordinary controllers use
|
|
143
|
+
* {@link log}.
|
|
144
|
+
*/
|
|
145
|
+
readonly logging: LoggingHost;
|
|
122
146
|
readonly moduleContext: ModuleContext;
|
|
123
147
|
readonly env: Record<string, string | undefined>;
|
|
124
148
|
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
package/src/index.ts
CHANGED
|
@@ -17,6 +17,11 @@ export * from "./resource-context.js";
|
|
|
17
17
|
export * from "./resource-instance.js";
|
|
18
18
|
export * from "./resource-manifest.js";
|
|
19
19
|
export * from "./invoke-error.js";
|
|
20
|
+
export * from "./log-record.js";
|
|
21
|
+
export * from "./log-sink.js";
|
|
22
|
+
export * from "./log-severity.js";
|
|
23
|
+
export * from "./logger.js";
|
|
24
|
+
export * from "./network-fetch.js";
|
|
20
25
|
export * from "./runtime-error.js";
|
|
21
26
|
export * from "./runtime-event.js";
|
|
22
27
|
export * from "./runtime-resource.js";
|
package/src/invoke-error.ts
CHANGED
|
@@ -11,7 +11,7 @@ export class InvokeError extends Error {
|
|
|
11
11
|
readonly code: string;
|
|
12
12
|
readonly data?: unknown;
|
|
13
13
|
|
|
14
|
-
constructor(code: string, message: string, data?: unknown) {
|
|
14
|
+
constructor(code: string, message: string, data?: unknown, options?: { cause?: unknown }) {
|
|
15
15
|
super(message);
|
|
16
16
|
this.name = "InvokeError";
|
|
17
17
|
this.code = code;
|
|
@@ -27,6 +27,19 @@ export class InvokeError extends Error {
|
|
|
27
27
|
writable: false,
|
|
28
28
|
configurable: false,
|
|
29
29
|
});
|
|
30
|
+
// Preserve the error being wrapped. Defined rather than assigned so it is
|
|
31
|
+
// non-enumerable — matching how the Error constructor's own `cause` option
|
|
32
|
+
// behaves, and keeping it out of JSON serialisation / CEL property access
|
|
33
|
+
// for the same reason as the marker above. (`ErrorOptions` is not in this
|
|
34
|
+
// package's TS lib, hence the local option type.)
|
|
35
|
+
if (options && "cause" in options) {
|
|
36
|
+
Object.defineProperty(this, "cause", {
|
|
37
|
+
value: options.cause,
|
|
38
|
+
enumerable: false,
|
|
39
|
+
writable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
45
|
|
|
@@ -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
|
+
}
|