@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 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";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/dist/index.js 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";
@@ -8,7 +8,9 @@
8
8
  export declare class InvokeError extends Error {
9
9
  readonly code: string;
10
10
  readonly data?: unknown;
11
- constructor(code: string, message: string, data?: unknown);
11
+ constructor(code: string, message: string, data?: unknown, options?: {
12
+ cause?: unknown;
13
+ });
12
14
  }
13
15
  export declare function isInvokeError(err: unknown): err is InvokeError;
14
16
  //# sourceMappingURL=invoke-error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"invoke-error.d.ts","sourceRoot":"","sources":["../src/invoke-error.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAiB1D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW,CAM9D"}
1
+ {"version":3,"file":"invoke-error.d.ts","sourceRoot":"","sources":["../src/invoke-error.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CA8BzF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW,CAM9D"}
@@ -9,7 +9,7 @@ const INVOKE_ERROR = Symbol.for("telo.InvokeError");
9
9
  export class InvokeError extends Error {
10
10
  code;
11
11
  data;
12
- constructor(code, message, data) {
12
+ constructor(code, message, data, options) {
13
13
  super(message);
14
14
  this.name = "InvokeError";
15
15
  this.code = code;
@@ -25,6 +25,19 @@ export class InvokeError extends Error {
25
25
  writable: false,
26
26
  configurable: false,
27
27
  });
28
+ // Preserve the error being wrapped. Defined rather than assigned so it is
29
+ // non-enumerable — matching how the Error constructor's own `cause` option
30
+ // behaves, and keeping it out of JSON serialisation / CEL property access
31
+ // for the same reason as the marker above. (`ErrorOptions` is not in this
32
+ // package's TS lib, hence the local option type.)
33
+ if (options && "cause" in options) {
34
+ Object.defineProperty(this, "cause", {
35
+ value: options.cause,
36
+ enumerable: false,
37
+ writable: true,
38
+ configurable: true,
39
+ });
40
+ }
28
41
  }
29
42
  }
30
43
  export function isInvokeError(err) {
@@ -0,0 +1,72 @@
1
+ import type { SeverityNumber } from "./log-severity.js";
2
+ /**
3
+ * The Telo log record model — `kernel/specs/logging.md` §4. Maps 1:1 onto an
4
+ * OpenTelemetry `LogRecord`; encodings (§11) determine spelling and MUST NOT add
5
+ * or remove semantics.
6
+ *
7
+ * The one deliberate deviation from OTel is {@link LogRecord.message}: OTel's
8
+ * `Body` is an `AnyValue` and may be structured, while Telo requires a string and
9
+ * routes structured data to `attributes`. That keeps the console encoding total —
10
+ * every record has a renderable headline — and matches slog, pino, and zap.
11
+ */
12
+ /** The attribute value type (§6.1). `null` is a valid value and is preserved. */
13
+ export type AnyValue = string | boolean | number | bigint | Uint8Array | null | AnyValue[] | {
14
+ [key: string]: AnyValue;
15
+ };
16
+ export type LogAttributes = Record<string, AnyValue>;
17
+ /** Structured error (§4.2). The `cause` chain is bounded per §6.3. */
18
+ export interface ErrorValue {
19
+ /** Error class or code, e.g. `ERR_INVOKE_CANCELLED`. */
20
+ type: string;
21
+ message: string;
22
+ /** Multi-line, unmodified. */
23
+ stack?: string;
24
+ cause?: ErrorValue;
25
+ }
26
+ /** The emitting Telo resource (§7.3). `id` is the full hierarchical id, which is
27
+ * what distinguishes two instances of the same templated kind. */
28
+ export interface ResourceRef {
29
+ kind: string;
30
+ name: string;
31
+ id?: string;
32
+ }
33
+ export interface LogRecord {
34
+ /** Nanoseconds since the Unix epoch, by the origin clock. */
35
+ timestamp: bigint;
36
+ /** When the runtime observed the event, when that differs from `timestamp`
37
+ * (a bridged third-party logger, §13.3). */
38
+ observedTimestamp?: bigint;
39
+ severityNumber: SeverityNumber;
40
+ /** Canonical short name, or the original source spelling when bridging. */
41
+ severityText: string;
42
+ /** May be empty; never absent. */
43
+ message: string;
44
+ attributes?: LogAttributes;
45
+ /** 32 lowercase hex chars. */
46
+ traceId?: string;
47
+ /** 16 lowercase hex chars. Never present without `traceId`. */
48
+ spanId?: string;
49
+ /** Bit 0 = sampled, bit 1 reserved (§7.5), bits 2–7 zero. */
50
+ traceFlags?: number;
51
+ resource?: ResourceRef;
52
+ /** Module name of the emitter. Not unique — see `scope`. */
53
+ module?: string;
54
+ /** Dotted import-alias path identifying which *instance* emitted the record
55
+ * (`Api.Domain.Db`). Absent for the root Application's own resources. */
56
+ scope?: string;
57
+ /** Identifies a class of event; max 256 chars. */
58
+ eventName?: string;
59
+ error?: ErrorValue;
60
+ /** Non-zero when §6.3 limits truncated attributes. */
61
+ droppedAttributesCount?: number;
62
+ }
63
+ export declare function nowUnixNano(): bigint;
64
+ /** Epoch nanoseconds for a millisecond-resolution instant — used when bridging a
65
+ * third-party record that carries a `Date` or epoch-millis timestamp. */
66
+ export declare function unixNanoFromMillis(epochMillis: number): bigint;
67
+ /**
68
+ * RFC 3339, UTC, nanosecond precision, `Z` suffix — the `time` key of the `json`
69
+ * encoding (§11.1).
70
+ */
71
+ export declare function formatUnixNano(timestamp: bigint): string;
72
+ //# sourceMappingURL=log-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-record.d.ts","sourceRoot":"","sources":["../src/log-record.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;GASG;AAEH,iFAAiF;AACjF,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,UAAU,GACV,IAAI,GACJ,QAAQ,EAAE,GACV;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAC;AAEhC,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAErD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED;mEACmE;AACnE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB;iDAC6C;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;8EAC0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAoBD,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,34 @@
1
+ // Written as `BigInt(...)` rather than as `1_000_000n` literals: this module is
2
+ // consumed from source by the browser-targeted editor, whose tsconfig targets
3
+ // below ES2020 and cannot parse the literal syntax.
4
+ const NANOS_PER_MS = BigInt(1_000_000);
5
+ const NANOS_PER_SECOND = BigInt(1_000_000_000);
6
+ /**
7
+ * Node has no true nanosecond wall clock: `Date` is millisecond-resolution and
8
+ * `hrtime.bigint()` is monotonic rather than epoch-anchored. The best available
9
+ * is the performance origin plus the monotonic offset, which yields microsecond
10
+ * resolution zero-padded to nine digits. Format-conformant with §11.1; the extra
11
+ * three digits are always zero.
12
+ *
13
+ * The origin is captured once as a bigint so the addition never routes a
14
+ * 16-significant-digit value through a float64 and loses the low microseconds.
15
+ */
16
+ const ORIGIN_NANOS = BigInt(Math.round(performance.timeOrigin * 1e6));
17
+ export function nowUnixNano() {
18
+ return ORIGIN_NANOS + BigInt(Math.round(performance.now() * 1e6));
19
+ }
20
+ /** Epoch nanoseconds for a millisecond-resolution instant — used when bridging a
21
+ * third-party record that carries a `Date` or epoch-millis timestamp. */
22
+ export function unixNanoFromMillis(epochMillis) {
23
+ return BigInt(Math.round(epochMillis)) * NANOS_PER_MS;
24
+ }
25
+ /**
26
+ * RFC 3339, UTC, nanosecond precision, `Z` suffix — the `time` key of the `json`
27
+ * encoding (§11.1).
28
+ */
29
+ export function formatUnixNano(timestamp) {
30
+ const seconds = timestamp / NANOS_PER_SECOND;
31
+ const nanos = timestamp - seconds * NANOS_PER_SECOND;
32
+ const isoSeconds = new Date(Number(seconds) * 1000).toISOString().slice(0, 19);
33
+ return `${isoSeconds}.${nanos.toString().padStart(9, "0")}Z`;
34
+ }
@@ -0,0 +1,55 @@
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
+ /** An OTel SeverityNumber. `0` (UNSPECIFIED) is never emitted by a Telo runtime. */
11
+ export type SeverityNumber = number;
12
+ /** The six levels Telo names. Each is the floor of its four-value OTel range. */
13
+ export declare const SEVERITY: {
14
+ readonly trace: 1;
15
+ readonly debug: 5;
16
+ readonly info: 9;
17
+ readonly warn: 13;
18
+ readonly error: 17;
19
+ readonly fatal: 21;
20
+ };
21
+ export type LevelName = keyof typeof SEVERITY;
22
+ export declare const LEVEL_NAMES: readonly LevelName[];
23
+ /** The severity at or above which a record describes an error (§5.1). This is
24
+ * the portable error predicate; runtimes expose it rather than re-deriving it. */
25
+ export declare const ERROR_SEVERITY_FLOOR = 17;
26
+ /**
27
+ * The range floor for a severity number — the canonical level a value maps onto.
28
+ * Out-of-range values clamp into 1–24 rather than producing `0`, which §5.1
29
+ * forbids emitting.
30
+ */
31
+ export declare function severityFloor(severity: SeverityNumber): number;
32
+ /** Canonical short name (`TRACE`…`FATAL`) for a severity number. */
33
+ export declare function severityText(severity: SeverityNumber): string;
34
+ /** `true` when the record describes an error (§5.1). */
35
+ export declare function isErrorSeverity(severity: SeverityNumber): boolean;
36
+ /** Resolve a manifest `level:` name to its severity number. */
37
+ export declare function severityForLevel(level: LevelName): number;
38
+ /**
39
+ * Map a level name of unknown provenance onto the scale. A name Telo does not
40
+ * recognize yields `undefined` so the caller can preserve the original spelling
41
+ * in `severity_text` while landing the number on a range floor (§5.1).
42
+ */
43
+ export declare function parseLevelName(name: string): number | undefined;
44
+ /**
45
+ * Go's `log/slog` documents that subtracting 9 from an OTel severity converts it
46
+ * to the slog range — an exact, officially sanctioned relation, so a Go runtime
47
+ * uses arithmetic rather than a table (§5.2). Exposed here so the conformance
48
+ * vectors can assert the relation from the Node side too.
49
+ */
50
+ export declare const SLOG_OFFSET = 9;
51
+ export declare function pinoLevelForSeverity(severity: SeverityNumber): number;
52
+ /** `undefined` for a pino level Telo does not name, so the caller preserves the
53
+ * source spelling and falls back to the nearest floor. */
54
+ export declare function severityForPinoLevel(level: number): number | undefined;
55
+ //# sourceMappingURL=log-severity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-severity.d.ts","sourceRoot":"","sources":["../src/log-severity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,oFAAoF;AACpF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,iFAAiF;AACjF,eAAO,MAAM,QAAQ;;;;;;;CAOX,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,QAAQ,CAAC;AAE9C,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAyD,CAAC;AAEtG;mFACmF;AACnF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAavC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAQ9D;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAE7D;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAEjE;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU/D;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,IAAI,CAAC;AAwB7B,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAErE;AAED;2DAC2D;AAC3D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtE"}
@@ -0,0 +1,110 @@
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
+ /** The six levels Telo names. Each is the floor of its four-value OTel range. */
11
+ export const SEVERITY = {
12
+ trace: 1,
13
+ debug: 5,
14
+ info: 9,
15
+ warn: 13,
16
+ error: 17,
17
+ fatal: 21,
18
+ };
19
+ export const LEVEL_NAMES = ["trace", "debug", "info", "warn", "error", "fatal"];
20
+ /** The severity at or above which a record describes an error (§5.1). This is
21
+ * the portable error predicate; runtimes expose it rather than re-deriving it. */
22
+ export const ERROR_SEVERITY_FLOOR = 17;
23
+ const FLOORS = [1, 5, 9, 13, 17, 21];
24
+ const TEXT_BY_FLOOR = {
25
+ 1: "TRACE",
26
+ 5: "DEBUG",
27
+ 9: "INFO",
28
+ 13: "WARN",
29
+ 17: "ERROR",
30
+ 21: "FATAL",
31
+ };
32
+ /**
33
+ * The range floor for a severity number — the canonical level a value maps onto.
34
+ * Out-of-range values clamp into 1–24 rather than producing `0`, which §5.1
35
+ * forbids emitting.
36
+ */
37
+ export function severityFloor(severity) {
38
+ const clamped = severity < 1 ? 1 : severity > 24 ? 24 : Math.trunc(severity);
39
+ let floor = FLOORS[0];
40
+ for (const candidate of FLOORS) {
41
+ if (candidate <= clamped)
42
+ floor = candidate;
43
+ else
44
+ break;
45
+ }
46
+ return floor;
47
+ }
48
+ /** Canonical short name (`TRACE`…`FATAL`) for a severity number. */
49
+ export function severityText(severity) {
50
+ return TEXT_BY_FLOOR[severityFloor(severity)];
51
+ }
52
+ /** `true` when the record describes an error (§5.1). */
53
+ export function isErrorSeverity(severity) {
54
+ return severity >= ERROR_SEVERITY_FLOOR;
55
+ }
56
+ /** Resolve a manifest `level:` name to its severity number. */
57
+ export function severityForLevel(level) {
58
+ return SEVERITY[level];
59
+ }
60
+ /**
61
+ * Map a level name of unknown provenance onto the scale. A name Telo does not
62
+ * recognize yields `undefined` so the caller can preserve the original spelling
63
+ * in `severity_text` while landing the number on a range floor (§5.1).
64
+ */
65
+ export function parseLevelName(name) {
66
+ const key = name.trim().toLowerCase();
67
+ // Own-property check, not `in`: `in` also matches inherited members, so
68
+ // `parseLevelName("toString")` would otherwise return a Function and defeat
69
+ // the `?? fallback` at every call site. Written as `hasOwnProperty.call`
70
+ // rather than `Object.hasOwn` because this module is consumed from source by
71
+ // the browser-targeted editor, whose tsconfig targets below ES2022.
72
+ return Object.prototype.hasOwnProperty.call(SEVERITY, key)
73
+ ? SEVERITY[key]
74
+ : undefined;
75
+ }
76
+ /**
77
+ * Go's `log/slog` documents that subtracting 9 from an OTel severity converts it
78
+ * to the slog range — an exact, officially sanctioned relation, so a Go runtime
79
+ * uses arithmetic rather than a table (§5.2). Exposed here so the conformance
80
+ * vectors can assert the relation from the Node side too.
81
+ */
82
+ export const SLOG_OFFSET = 9;
83
+ /**
84
+ * pino's scale is 10× and offset, with no arithmetic relation to OTel, so §5.2
85
+ * requires a table. Used by the Fastify logger replacement (§13.3).
86
+ */
87
+ const PINO_BY_SEVERITY = {
88
+ 1: 10,
89
+ 5: 20,
90
+ 9: 30,
91
+ 13: 40,
92
+ 17: 50,
93
+ 21: 60,
94
+ };
95
+ const SEVERITY_BY_PINO = {
96
+ 10: 1,
97
+ 20: 5,
98
+ 30: 9,
99
+ 40: 13,
100
+ 50: 17,
101
+ 60: 21,
102
+ };
103
+ export function pinoLevelForSeverity(severity) {
104
+ return PINO_BY_SEVERITY[severityFloor(severity)];
105
+ }
106
+ /** `undefined` for a pino level Telo does not name, so the caller preserves the
107
+ * source spelling and falls back to the nearest floor. */
108
+ export function severityForPinoLevel(level) {
109
+ return SEVERITY_BY_PINO[level];
110
+ }
@@ -0,0 +1,91 @@
1
+ import type { LogRecord } from "./log-record.js";
2
+ /**
3
+ * The `Telo.Sink` capability contract — `kernel/specs/logging.md` §10.
4
+ *
5
+ * This lives in the SDK rather than the kernel because §10.2 makes the sink set
6
+ * **open to the ecosystem**: a third party ships a sink by publishing a module
7
+ * whose kind extends `Telo.LogSink`. That module is an ordinary module author's
8
+ * artifact, so the contract it implements belongs on the module-author surface,
9
+ * not behind a kernel-internal import.
10
+ *
11
+ * The logger writes to a sink through this contract directly and **never**
12
+ * through `ctx.invoke`: per-record dispatch is far too slow for a logging hot
13
+ * path, and the dispatch chokepoint emits trace events, so logging through it
14
+ * would generate telemetry from inside the telemetry path.
15
+ *
16
+ * The contract is deliberately payload-opaque — no filtering, no encoding —
17
+ * which is what lets a future `Telo.TraceSink` reuse the capability with a
18
+ * different record type. Log-specific configuration lives on the
19
+ * `Telo.LogSink` abstract instead.
20
+ */
21
+ export type DropCause = "buffer_full" | "sampled" | "encode_failure" | "sink_error";
22
+ /** Policy for a saturated buffer (§10.3). A runtime that cannot honour `block`
23
+ * rejects the manifest at load rather than silently substituting a dropping
24
+ * policy. */
25
+ export type OnFull = "block" | "drop_new" | "drop_old";
26
+ export interface SinkBufferPolicy {
27
+ /** Bounded. Never unbounded. */
28
+ buffer: number;
29
+ onFull: OnFull;
30
+ /** Max time a record may sit buffered, in milliseconds. */
31
+ flushIntervalMs: number;
32
+ }
33
+ export declare const DEFAULT_BUFFER_POLICY: SinkBufferPolicy;
34
+ export interface LogSinkInstance {
35
+ /** Identity for drop accounting (§10.4): the resource name for a `!ref`, or
36
+ * kind plus position for an inline definition. */
37
+ readonly sinkId: string;
38
+ /** This sink's own fan-out filter, applied *after* the record is created. It
39
+ * never decides whether a record is created at all — that is the pipeline's
40
+ * minimum-level gate. */
41
+ readonly level: number;
42
+ /**
43
+ * Whether the sink can be drained to its destination from inside a
44
+ * synchronous call, with no scheduler turn. A file descriptor write can; a
45
+ * network round-trip cannot, and neither can a transport living on another
46
+ * thread — the producer cannot drain a queue it does not own.
47
+ *
48
+ * A capability tier, not a language carve-out: the same rule makes an OTLP
49
+ * sink best-effort in Rust and Go, where blocking a producer thread is
50
+ * possible but still would not make a round-trip synchronous.
51
+ */
52
+ readonly syncFlushable: boolean;
53
+ /** Accept a record. MUST NOT throw — a sink failure is reported out-of-band
54
+ * and counted, never propagated to the caller (§8.4). */
55
+ write(record: LogRecord): void;
56
+ /** Drain asynchronously. */
57
+ flush(): Promise<void>;
58
+ /** Drain to completion before returning. A no-op when {@link syncFlushable}
59
+ * is `false`; the `fatal` path initiates those sinks' flushes without
60
+ * waiting, because blocking on a sink it cannot synchronously drain is a
61
+ * deadlock on an event loop, not durability. */
62
+ flushSync(): void;
63
+ /** Release the destination. Called during teardown, after the final flush. */
64
+ close(): Promise<void>;
65
+ }
66
+ /**
67
+ * The pipeline surface a sink controller reaches for — attach, detach, resolve a
68
+ * level, count a drop. Deliberately narrow: everything else about the pipeline
69
+ * stays private to the runtime, so a third-party sink depends on this and
70
+ * nothing deeper.
71
+ */
72
+ export interface LoggingHost {
73
+ attach(sink: LogSinkInstance): void;
74
+ detach(sink: LogSinkInstance): void;
75
+ /** Resolve a sink's declared `level:` to a severity number, falling back to
76
+ * the effective scope threshold when the sink declares none (§12.1). */
77
+ levelFor(level: string | undefined): number;
78
+ /** Count `count` dropped records against this sink so §10.4's accounting stays
79
+ * complete. `count` defaults to 1; a sink that loses a whole batch at once
80
+ * (an OTLP export failure) passes the batch size so the total is not
81
+ * undercounted to one-per-failure. */
82
+ recordDrop(sinkId: string, cause: DropCause, count?: number): void;
83
+ }
84
+ /** The diagnostic §10.3 requires when a runtime cannot honour `on_full: block`.
85
+ * Rejecting is deliberate: `on_full` exists so an operator can state durability
86
+ * intent, and silently substituting a dropping policy hands back the opposite
87
+ * guarantee — discovered from a gap in an audit trail rather than from an
88
+ * error. */
89
+ export declare function blockUnsupportedMessage(sinkId: string): string;
90
+ export declare const BLOCK_UNSUPPORTED = "ERR_LOG_SINK_ON_FULL_UNSUPPORTED";
91
+ //# sourceMappingURL=log-sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-sink.d.ts","sourceRoot":"","sources":["../src/log-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAEpF;;cAEc;AACd,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB,EAAE,gBAInC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B;uDACmD;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB;;8BAE0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC;8DAC0D;IAC1D,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IAE/B,4BAA4B;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;qDAGiD;IACjD,SAAS,IAAI,IAAI,CAAC;IAElB,8EAA8E;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC;6EACyE;IACzE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAC5C;;;2CAGuC;IACvC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpE;AAED;;;;aAIa;AACb,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED,eAAO,MAAM,iBAAiB,qCAAqC,CAAC"}
@@ -0,0 +1,16 @@
1
+ export const DEFAULT_BUFFER_POLICY = {
2
+ buffer: 8192,
3
+ onFull: "drop_new",
4
+ flushIntervalMs: 1000,
5
+ };
6
+ /** The diagnostic §10.3 requires when a runtime cannot honour `on_full: block`.
7
+ * Rejecting is deliberate: `on_full` exists so an operator can state durability
8
+ * intent, and silently substituting a dropping policy hands back the opposite
9
+ * guarantee — discovered from a gap in an audit trail rather than from an
10
+ * error. */
11
+ export function blockUnsupportedMessage(sinkId) {
12
+ return (`Sink "${sinkId}": on_full: block is not supported by this runtime ` +
13
+ `(single-threaded event loop — blocking the producer would stall the writer). ` +
14
+ `Use \`drop_new\` or \`drop_old\`, or move this sink to a worker thread.`);
15
+ }
16
+ export const BLOCK_UNSUPPORTED = "ERR_LOG_SINK_ON_FULL_UNSUPPORTED";
@@ -0,0 +1,85 @@
1
+ import type { AnyValue, LogAttributes } from "./log-record.js";
2
+ import type { SeverityNumber } from "./log-severity.js";
3
+ /**
4
+ * The logger surface every Telo runtime exposes — `kernel/specs/logging.md` §8.
5
+ *
6
+ * Reached ambiently as `ctx.log`. The logger is ambient rather than a resource
7
+ * because it must work before any resource initializes; its *sinks* are
8
+ * resources, which is what keeps the destination set open to the ecosystem.
9
+ */
10
+ /**
11
+ * A value resolved only on the emit path (§8.2) — slog's `LogValuer`, zap's
12
+ * `ObjectMarshaler`, `tracing`'s `Value`. A deferred value attached to a
13
+ * suppressed record is never resolved, so an expensive rendering costs nothing
14
+ * below the threshold. This is RECOMMENDED sugar and does **not** substitute for
15
+ * {@link Logger.enabled}, which is the only mechanism that avoids evaluating a
16
+ * call's *arguments*.
17
+ */
18
+ export interface LogValuer {
19
+ toLogValue(): AnyValue;
20
+ }
21
+ export type LogAttributeInput = AnyValue | LogValuer;
22
+ export type LogAttributesInput = Record<string, LogAttributeInput>;
23
+ /** Per-record extras that are top-level record fields rather than attributes.
24
+ * Kept out of the attribute map so they cannot collide with a reserved key. */
25
+ export interface LogOptions {
26
+ /** Any thrown value. Normalized to the record's `error` (§4.2), with the
27
+ * `cause` chain bounded per §6.3. */
28
+ error?: unknown;
29
+ /** Identifies a class of event; max 256 chars. Bridges to the event bus. */
30
+ eventName?: string;
31
+ /** When the event occurred, if earlier than the moment `log()` was called —
32
+ * set by a bridge, which also stamps `observedTimestamp` (§13.3). */
33
+ timestamp?: bigint;
34
+ /** The original source spelling of the level, preserved when bridging a level
35
+ * Telo does not name (§5.1). Defaults to the canonical short name. */
36
+ severityText?: string;
37
+ }
38
+ export interface Logger {
39
+ /**
40
+ * Whether a record at this severity would reach any sink. The load-bearing
41
+ * performance primitive: guard an expensive call with it so the *arguments*
42
+ * are never evaluated.
43
+ *
44
+ * Never blocks, never throws. The result is **not static** — it changes when
45
+ * configuration changes or a sink attaches or detaches (§12.4), so callers
46
+ * re-check per emission rather than caching a boolean.
47
+ */
48
+ enabled(severity: SeverityNumber): boolean;
49
+ /**
50
+ * Emit a record. Never throws, under any condition, including sink failure —
51
+ * a sink failure is reported on the fallback diagnostic stream and counted,
52
+ * never propagated and never swallowed (§8.4).
53
+ */
54
+ log(severity: SeverityNumber, message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
55
+ /**
56
+ * A child logger whose bound attributes are merged into every record it emits.
57
+ * Record attributes override bound attributes. Binding is O(1) amortized: the
58
+ * merge happens once here, never per record.
59
+ */
60
+ with(attributes: LogAttributesInput): Logger;
61
+ /** Drain every attached sink. Bounded by the caller; see §10.5. */
62
+ flush(): Promise<void>;
63
+ trace(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
64
+ debug(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
65
+ info(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
66
+ warn(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
67
+ error(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
68
+ /**
69
+ * Emits at severity 21. Severity never implies control flow (§5, D5): `fatal`
70
+ * does **not** terminate the process, exit, or panic — it triggers an
71
+ * immediate flush, synchronous on every sink that supports it and best-effort
72
+ * on the rest (§10.5).
73
+ */
74
+ fatal(message: string, attributes?: LogAttributesInput, options?: LogOptions): void;
75
+ }
76
+ /** Resolve a {@link LogValuer} if the value is one, else pass it through. */
77
+ export declare function isLogValuer(value: unknown): value is LogValuer;
78
+ /** A logger that discards everything. Used where a logger is structurally
79
+ * required before one is available, and by tests that assert silence. */
80
+ export declare const NOOP_LOGGER: Logger;
81
+ /** The bound-attribute merge of {@link Logger.with}, exposed so a runtime's
82
+ * child-logger implementation and its conformance vectors share one definition
83
+ * of "record attributes win". */
84
+ export declare function mergeBoundAttributes(bound: LogAttributes | undefined, record: LogAttributes | undefined): LogAttributes | undefined;
85
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;GAMG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,IAAI,QAAQ,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,CAAC;AACrD,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAEnE;gFACgF;AAChF,MAAM,WAAW,UAAU;IACzB;0CACsC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;0EACsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;2EACuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,MAAM;IACrB;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IAE3C;;;;OAIG;IACH,GAAG,CACD,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,kBAAkB,EAC/B,OAAO,CAAC,EAAE,UAAU,GACnB,IAAI,CAAC;IAER;;;;OAIG;IACH,IAAI,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAE7C,mEAAmE;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACpF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACpF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACnF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACnF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACpF;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CACrF;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAM9D;AAED;0EAC0E;AAC1E,eAAO,MAAM,WAAW,EAAE,MAWzB,CAAC;AAEF;;kCAEkC;AAClC,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,MAAM,EAAE,aAAa,GAAG,SAAS,GAChC,aAAa,GAAG,SAAS,CAI3B"}
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
+ }