@nwire/runtime 0.12.1 → 0.13.1

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.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Inbound source chain — the seam between an arriving message (HTTP request,
3
+ * queue job, broker delivery) and the runtime's one execution terminal.
4
+ *
5
+ * Transports hand a raw message to `runtime.receive(msg)`. Every installed
6
+ * stage runs in position order; a stage that returns `{ continue: false }`
7
+ * stops the chain (e.g. a dedup stage dropping a replay). The terminal stage
8
+ * is the router: it lands a command on `runtime.execute` and an event on
9
+ * `runtime.emit`. Exact mirror of the outbound `sink` chain.
10
+ *
11
+ * The runtime knows about the chain but nothing about transports — every
12
+ * source (HTTP, NATS, queue) is installed by an inbound adapter through
13
+ * `endpoint.use(...)` at boot and calls `receive` per message.
14
+ */
15
+ import type { MessageEnvelope } from "@nwire/envelope";
16
+ import type { EventDefinition } from "@nwire/messages";
17
+ import type { HandlerDefinition } from "@nwire/handler";
18
+ import type { StagePosition } from "./sink.js";
19
+ /**
20
+ * A command's dispatch target: a direct handler reference (the HTTP wire
21
+ * already holds one), a plain `(input, ctx)` function, or a registry name the
22
+ * runtime resolves.
23
+ */
24
+ export type InboundTarget = HandlerDefinition<any, any, any> | ((input: any, ctx: any) => any) | string;
25
+ /**
26
+ * A message arriving at the runtime's inbound boundary. `kind` selects the
27
+ * door: a `command` lands on `execute`, an `event` on `emit`.
28
+ */
29
+ export interface InboundMessage {
30
+ readonly kind: "command" | "event";
31
+ /** Logical message name — the route key for telemetry and name-based dispatch. */
32
+ readonly name: string;
33
+ readonly input: unknown;
34
+ /**
35
+ * Command target. Adapters holding the wire pass the handler reference
36
+ * directly; omit to dispatch by `name` through the handler registry.
37
+ */
38
+ readonly target?: InboundTarget;
39
+ /**
40
+ * Event definition — required when `kind === "event"` so `emit` can validate
41
+ * the payload against its schema. (Resolving a broker-delivered event name
42
+ * to its definition is a later step.)
43
+ */
44
+ readonly event?: EventDefinition & {
45
+ readonly name: string;
46
+ readonly schema: {
47
+ parse(input: unknown): unknown;
48
+ };
49
+ };
50
+ /** Idempotency / dedup key, when the transport carries one. */
51
+ readonly id?: string;
52
+ /** Raw transport message (koa ctx, nats msg) — diagnostic / stage use. */
53
+ readonly raw?: unknown;
54
+ }
55
+ /**
56
+ * Per-stage context for the inbound chain. Stages may rewrite `message`
57
+ * (translators / anti-corruption), enrich `envelope` (tenant, user,
58
+ * correlation), or short-circuit. The terminal router reads `message` +
59
+ * `envelope` + `extras` and writes `result`.
60
+ */
61
+ export interface InboundContext {
62
+ message?: InboundMessage;
63
+ /**
64
+ * Envelope seed / overrides for this inbound — tenant, user, correlation,
65
+ * causation. The full `MessageEnvelope` is minted downstream by `execute` /
66
+ * `emit`; at the boundary this is the partial the transport resolved.
67
+ */
68
+ envelope: Partial<MessageEnvelope>;
69
+ /** Transport extras threaded onto the handler ctx (logger, koa, …). */
70
+ extras?: Record<string, unknown>;
71
+ /** Cancellation signal threaded onto the dispatch (queue lock, request abort). */
72
+ signal?: AbortSignal;
73
+ /**
74
+ * Parent envelope to inherit from — the dispatch derives a child (carries
75
+ * correlationId, causationId = parent.messageId). Set by async sources
76
+ * (queue) that replay a stored envelope.
77
+ */
78
+ parent?: MessageEnvelope;
79
+ resolve<T = unknown>(name: string): T;
80
+ /** Acknowledge / negatively-acknowledge, when the transport supports it. */
81
+ ack?(): Promise<void> | void;
82
+ nak?(): Promise<void> | void;
83
+ result?: unknown;
84
+ }
85
+ /**
86
+ * An inbound pipeline stage. Returns nothing to keep going, or
87
+ * `{ continue: false }` to short-circuit (e.g. a dedup stage dropping a
88
+ * replayed message before it reaches the router).
89
+ */
90
+ export interface InboundStage {
91
+ readonly name: string;
92
+ readonly position: StagePosition;
93
+ /**
94
+ * Optional kind tag — `"http" | "nats" | "queue" | ...`. For terminal stages
95
+ * it enforces uniqueness (one per kind); for non-terminal stages it's
96
+ * diagnostic only.
97
+ */
98
+ readonly kind?: string;
99
+ run(ctx: InboundContext): Promise<{
100
+ continue?: boolean;
101
+ } | void> | {
102
+ continue?: boolean;
103
+ } | void;
104
+ }
package/dist/source.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Inbound source chain — the seam between an arriving message (HTTP request,
3
+ * queue job, broker delivery) and the runtime's one execution terminal.
4
+ *
5
+ * Transports hand a raw message to `runtime.receive(msg)`. Every installed
6
+ * stage runs in position order; a stage that returns `{ continue: false }`
7
+ * stops the chain (e.g. a dedup stage dropping a replay). The terminal stage
8
+ * is the router: it lands a command on `runtime.execute` and an event on
9
+ * `runtime.emit`. Exact mirror of the outbound `sink` chain.
10
+ *
11
+ * The runtime knows about the chain but nothing about transports — every
12
+ * source (HTTP, NATS, queue) is installed by an inbound adapter through
13
+ * `endpoint.use(...)` at boot and calls `receive` per message.
14
+ */
15
+ export {};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Telemetry sink — the terminal end of the observability lane.
3
+ *
4
+ * `runtime.onTelemetry` is the SOURCE: every dispatch, hook step, source/sink
5
+ * stage, and event push lands on it. A `TelemetryReporter` is the SINK
6
+ * terminal — it takes each record and does something durable with it (write a
7
+ * JSONL run file, forward to OTLP, ship to a cloud collector). Telemetry
8
+ * records are not domain events, so this is its own lane, parallel to the
9
+ * event sink chain — never an endpoint plugin.
10
+ *
11
+ * Multiple reporters may be installed on one runtime (a local disk reporter
12
+ * for dev history plus a cloud exporter, say). Each `installTelemetryReporter`
13
+ * call subscribes one reporter and returns a detach.
14
+ *
15
+ * This module is deliberately fs-free so `@nwire/runtime` stays portable. The
16
+ * concrete drivers (the local disk file reporter) live in node packages that
17
+ * implement this interface — see `@nwire/telemetry`.
18
+ */
19
+ import type { Runtime } from "./runtime.js";
20
+ /**
21
+ * A telemetry sink terminal. Receives every record pushed onto
22
+ * `runtime.onTelemetry` and persists / forwards it however the driver sees
23
+ * fit. A reporter must never throw out of `report` in a way that breaks the
24
+ * runtime — {@link installTelemetryReporter} guards each call, but well-behaved
25
+ * drivers swallow their own write errors too.
26
+ */
27
+ export interface TelemetryReporter {
28
+ /** Stable identifier — diagnostics, and which reporter is which in logs. */
29
+ readonly name: string;
30
+ /** Called once per `onTelemetry` record. Must be fast + non-throwing. */
31
+ report(record: unknown): void;
32
+ /** Drain any buffered records. Awaited at shutdown / before close. */
33
+ flush?(): Promise<void>;
34
+ /** Release resources (close the file stream, the exporter). Awaited on detach. */
35
+ close?(): Promise<void>;
36
+ }
37
+ /**
38
+ * Subscribe a {@link TelemetryReporter} to `runtime.onTelemetry`.
39
+ *
40
+ * Each pushed record is handed to `reporter.report`. A throwing reporter is
41
+ * caught and logged — telemetry is observation, it never breaks dispatch.
42
+ *
43
+ * Returns a detach: it unsubscribes from the stream, then calls
44
+ * `reporter.close?()` (best-effort; a rejecting close is swallowed so detach
45
+ * is always safe to await in a shutdown sequence). The returned function is
46
+ * synchronous — the close runs fire-and-forget so detach can be wired into
47
+ * sync shutdown paths; await `reporter.flush()` / `reporter.close()` directly
48
+ * when you need the drain to complete.
49
+ */
50
+ export declare function installTelemetryReporter(runtime: Runtime, reporter: TelemetryReporter): () => void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Telemetry sink — the terminal end of the observability lane.
3
+ *
4
+ * `runtime.onTelemetry` is the SOURCE: every dispatch, hook step, source/sink
5
+ * stage, and event push lands on it. A `TelemetryReporter` is the SINK
6
+ * terminal — it takes each record and does something durable with it (write a
7
+ * JSONL run file, forward to OTLP, ship to a cloud collector). Telemetry
8
+ * records are not domain events, so this is its own lane, parallel to the
9
+ * event sink chain — never an endpoint plugin.
10
+ *
11
+ * Multiple reporters may be installed on one runtime (a local disk reporter
12
+ * for dev history plus a cloud exporter, say). Each `installTelemetryReporter`
13
+ * call subscribes one reporter and returns a detach.
14
+ *
15
+ * This module is deliberately fs-free so `@nwire/runtime` stays portable. The
16
+ * concrete drivers (the local disk file reporter) live in node packages that
17
+ * implement this interface — see `@nwire/telemetry`.
18
+ */
19
+ /**
20
+ * Subscribe a {@link TelemetryReporter} to `runtime.onTelemetry`.
21
+ *
22
+ * Each pushed record is handed to `reporter.report`. A throwing reporter is
23
+ * caught and logged — telemetry is observation, it never breaks dispatch.
24
+ *
25
+ * Returns a detach: it unsubscribes from the stream, then calls
26
+ * `reporter.close?()` (best-effort; a rejecting close is swallowed so detach
27
+ * is always safe to await in a shutdown sequence). The returned function is
28
+ * synchronous — the close runs fire-and-forget so detach can be wired into
29
+ * sync shutdown paths; await `reporter.flush()` / `reporter.close()` directly
30
+ * when you need the drain to complete.
31
+ */
32
+ export function installTelemetryReporter(runtime, reporter) {
33
+ const unsubscribe = runtime.onTelemetry((record) => {
34
+ try {
35
+ reporter.report(record);
36
+ }
37
+ catch (err) {
38
+ // Best-effort — a bad reporter must not break the runtime.
39
+ // eslint-disable-next-line no-console
40
+ console.error(`[telemetry] reporter "${reporter.name}" report threw:`, err);
41
+ }
42
+ });
43
+ return () => {
44
+ unsubscribe();
45
+ if (reporter.close) {
46
+ void Promise.resolve()
47
+ .then(() => reporter.close())
48
+ .catch((err) => {
49
+ // eslint-disable-next-line no-console
50
+ console.error(`[telemetry] reporter "${reporter.name}" close threw:`, err);
51
+ });
52
+ }
53
+ };
54
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nwire/runtime",
3
- "version": "0.12.1",
4
- "description": "Nwire — envelope-execution substrate. Dispatch + emit + when + sink + capability registry. Extracted from @nwire/app for 0.11. Standalone usable without the App layer.",
3
+ "version": "0.13.1",
4
+ "description": "Nwire — envelope-execution substrate. Dispatch + emit + when + sink + capability registry. Standalone usable without the App layer.",
5
5
  "keywords": [
6
6
  "capability",
7
7
  "dispatch",
@@ -27,12 +27,12 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@nwire/container": "0.12.1",
31
- "@nwire/envelope": "0.12.1",
32
- "@nwire/handler": "0.12.1",
33
- "@nwire/hooks": "0.12.1",
34
- "@nwire/logger": "0.12.1",
35
- "@nwire/messages": "0.12.1"
30
+ "@nwire/envelope": "0.13.1",
31
+ "@nwire/messages": "0.13.1",
32
+ "@nwire/logger": "0.13.1",
33
+ "@nwire/container": "0.13.1",
34
+ "@nwire/handler": "0.13.1",
35
+ "@nwire/hooks": "0.13.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.19.9",