@nwire/runtime 0.12.1 → 0.13.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/capability.d.ts +17 -2
- package/dist/framework-hooks.d.ts +52 -83
- package/dist/framework-hooks.js +12 -19
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -2
- package/dist/runtime.d.ts +197 -41
- package/dist/runtime.js +327 -73
- package/dist/source.d.ts +104 -0
- package/dist/source.js +15 -0
- package/dist/telemetry-sink.d.ts +50 -0
- package/dist/telemetry-sink.js +54 -0
- package/package.json +8 -8
package/dist/source.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"description": "Nwire — envelope-execution substrate. Dispatch + emit + when + sink + capability registry.
|
|
3
|
+
"version": "0.13.0",
|
|
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.
|
|
31
|
-
"@nwire/
|
|
32
|
-
"@nwire/
|
|
33
|
-
"@nwire/
|
|
34
|
-
"@nwire/
|
|
35
|
-
"@nwire/
|
|
30
|
+
"@nwire/container": "0.13.0",
|
|
31
|
+
"@nwire/hooks": "0.13.0",
|
|
32
|
+
"@nwire/logger": "0.13.0",
|
|
33
|
+
"@nwire/messages": "0.13.0",
|
|
34
|
+
"@nwire/envelope": "0.13.0",
|
|
35
|
+
"@nwire/handler": "0.13.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.19.9",
|