@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/capability.d.ts
CHANGED
|
@@ -19,13 +19,16 @@ import type { Container } from "@nwire/container";
|
|
|
19
19
|
import type { Runtime } from "./runtime.js";
|
|
20
20
|
/**
|
|
21
21
|
* Base shape passed to `provideCtx`. The capability returns a Record that is
|
|
22
|
-
* spread into the handler ctx for one dispatch. `envelope`, `container`,
|
|
23
|
-
* `runtime` are the
|
|
22
|
+
* spread into the handler ctx for one dispatch. `envelope`, `container`,
|
|
23
|
+
* `runtime`, and the per-dispatch `signal` are the references every capability
|
|
24
|
+
* has access to. Thread `signal` into any nested dispatch a contributed verb
|
|
25
|
+
* starts, so caller cancellation propagates.
|
|
24
26
|
*/
|
|
25
27
|
export interface CapabilityBase {
|
|
26
28
|
readonly envelope: MessageEnvelope;
|
|
27
29
|
readonly container: Container;
|
|
28
30
|
readonly runtime: Runtime;
|
|
31
|
+
readonly signal: AbortSignal;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* A Capability contributes to the Runtime by reference.
|
|
@@ -38,6 +41,18 @@ export interface CapabilityBase {
|
|
|
38
41
|
export interface Capability<TMark = unknown> {
|
|
39
42
|
/** Stable identifier — used for diagnostics and duplicate-install detection. */
|
|
40
43
|
readonly name: string;
|
|
44
|
+
/**
|
|
45
|
+
* Handler kinds this capability's ctx applies to. When set, `provideCtx`
|
|
46
|
+
* runs only for dispatches whose handler is one of these kinds — so an
|
|
47
|
+
* action's ctx carries `request`/`send`/`emit` while a query's does not.
|
|
48
|
+
* Omit for a universal contribution (the base every handler gets:
|
|
49
|
+
* `resolve`/`logger`/ambient). The dividing line is what the verb closes
|
|
50
|
+
* over: envelope/container-scoped verbs are kind-tagged here; instance-bound
|
|
51
|
+
* verbs (`assign`/`recordThat`) are layered on by the actor/workflow builder,
|
|
52
|
+
* not the runtime ctx. Values are `HandlerKind` strings ("action" / "query" /
|
|
53
|
+
* "handler" / …).
|
|
54
|
+
*/
|
|
55
|
+
readonly kinds?: readonly string[];
|
|
41
56
|
/**
|
|
42
57
|
* Build the per-request ctx contribution. Called once per dispatch with
|
|
43
58
|
* the current envelope + container scope. The returned object is spread
|
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-runtime
|
|
3
|
-
* accessed as a typed property on `runtime.hooks`.
|
|
4
|
-
*
|
|
2
|
+
* Per-runtime delivery / plugin-extension hook registry. Each slot is a
|
|
3
|
+
* `Hook<TPayload>` accessed as a typed property on `runtime.hooks`. The
|
|
4
|
+
* runtime ships exactly one built-in slot — `LocalDelivery`, the ordered
|
|
5
|
+
* local fan-out chain. Plugins extend the registry via TS module
|
|
6
|
+
* augmentation + `runtime.defineHook(name)` (forge adds its Action* / Event*
|
|
7
|
+
* slots this way).
|
|
8
|
+
*
|
|
9
|
+
* App / plugin LIFECYCLE hooks are an App concern and live in `@nwire/app`'s
|
|
10
|
+
* `AppHooks` registry — not here. The runtime does not know about plugins.
|
|
5
11
|
*/
|
|
6
12
|
import { type Hook } from "@nwire/hooks";
|
|
13
|
+
import type { MessageEnvelope } from "@nwire/envelope";
|
|
7
14
|
/**
|
|
8
15
|
* Plugins can distinguish "module compiled onto plugin lifecycle" from a
|
|
9
16
|
* normal plugin. Lifecycle payloads include this so observers can filter.
|
|
10
17
|
*/
|
|
11
18
|
export type PluginKind = "plugin" | "module";
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
20
|
+
* Payload threaded through the `LocalDelivery` chain — the one
|
|
21
|
+
* incoming-to-all delivery `runtime.emit` runs before fanning out to
|
|
22
|
+
* `when` listeners. Ordered fold steps (forge's idempotency, actors,
|
|
23
|
+
* projections, workflows) attach via `.use()` at their priority and
|
|
24
|
+
* read/mutate `deduped`: the idempotency gate sets it and vetoes (skips
|
|
25
|
+
* `next()`) to short-circuit both the rest of the chain AND the listener
|
|
26
|
+
* fan-out. Core-shaped on purpose — name + payload + envelope, no forge
|
|
27
|
+
* `EventMessage` type — so the runtime owns local delivery with zero forge
|
|
28
|
+
* dependency.
|
|
29
|
+
*/
|
|
30
|
+
export interface LocalDeliveryPayload {
|
|
31
|
+
readonly eventName: string;
|
|
32
|
+
readonly payload: unknown;
|
|
33
|
+
readonly envelope: MessageEnvelope;
|
|
34
|
+
readonly dedupKey: string;
|
|
35
|
+
/** Set by an idempotency step when this event was seen before. */
|
|
36
|
+
deduped: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The typed delivery registry. Plugin packages augment this interface to add
|
|
40
|
+
* their own slots; the runtime contributes only the local-delivery slot.
|
|
15
41
|
*
|
|
16
42
|
* Every slot is `Hook<TPayload>` — no dispatch-mode discriminator.
|
|
17
43
|
* Consumers call `.use()` to participate in the chain (with optional veto
|
|
@@ -19,85 +45,28 @@ export type PluginKind = "plugin" | "module";
|
|
|
19
45
|
* receive step-level telemetry.
|
|
20
46
|
*/
|
|
21
47
|
export interface FrameworkHooks {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
AppShutdown: Hook<{
|
|
41
|
-
readonly appName: string;
|
|
42
|
-
}>;
|
|
43
|
-
PluginRegistered: Hook<{
|
|
44
|
-
readonly appName: string;
|
|
45
|
-
readonly pluginName: string;
|
|
46
|
-
readonly kind?: PluginKind;
|
|
47
|
-
}>;
|
|
48
|
-
PluginBooting: Hook<{
|
|
49
|
-
readonly appName: string;
|
|
50
|
-
readonly pluginName: string;
|
|
51
|
-
readonly kind?: PluginKind;
|
|
52
|
-
}>;
|
|
53
|
-
PluginBooted: Hook<{
|
|
54
|
-
readonly appName: string;
|
|
55
|
-
readonly pluginName: string;
|
|
56
|
-
readonly durationMs: number;
|
|
57
|
-
readonly kind?: PluginKind;
|
|
58
|
-
}>;
|
|
59
|
-
PluginShuttingDown: Hook<{
|
|
60
|
-
readonly appName: string;
|
|
61
|
-
readonly pluginName: string;
|
|
62
|
-
readonly kind?: PluginKind;
|
|
63
|
-
}>;
|
|
64
|
-
PluginShutdown: Hook<{
|
|
65
|
-
readonly appName: string;
|
|
66
|
-
readonly pluginName: string;
|
|
67
|
-
readonly durationMs: number;
|
|
68
|
-
readonly kind?: PluginKind;
|
|
69
|
-
}>;
|
|
70
|
-
WireMounting: Hook<{
|
|
71
|
-
readonly appName: string;
|
|
72
|
-
readonly transport: string;
|
|
73
|
-
readonly manifest: unknown;
|
|
74
|
-
}>;
|
|
75
|
-
WireMounted: Hook<{
|
|
76
|
-
readonly appName: string;
|
|
77
|
-
readonly transport: string;
|
|
78
|
-
readonly manifest: unknown;
|
|
79
|
-
}>;
|
|
80
|
-
WireUnmounted: Hook<{
|
|
81
|
-
readonly appName: string;
|
|
82
|
-
readonly transport: string;
|
|
83
|
-
}>;
|
|
48
|
+
/**
|
|
49
|
+
* The one ordered local-delivery chain. `runtime.emit` runs it (veto via
|
|
50
|
+
* skipping `next()`), then fans out to listeners unless an upstream step
|
|
51
|
+
* deduped. Forge's fold steps (idempotency → actors → projections →
|
|
52
|
+
* workflows) attach here at priority; core ships it empty so a forge-less
|
|
53
|
+
* app's `emit` is just listener fan-out.
|
|
54
|
+
*/
|
|
55
|
+
LocalDelivery: Hook<LocalDeliveryPayload>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* App / plugin LIFECYCLE hook registry — an App concern. The runtime does
|
|
59
|
+
* not own a lifecycle; `@nwire/app` augments this interface with the actual
|
|
60
|
+
* slots (App* / Plugin*) and owns the concrete registry instance (see
|
|
61
|
+
* `AppHooks` / `createAppHooks` in `@nwire/app`). It is declared here only
|
|
62
|
+
* so `PluginContext.on` / `PluginContext.defineHook` — which target app
|
|
63
|
+
* lifecycle hooks — stay typed without core-runtime depending on core-app.
|
|
64
|
+
*/
|
|
65
|
+
export interface AppHooks {
|
|
84
66
|
}
|
|
85
|
-
/** Canonical names for the built-in slots — used to label the
|
|
67
|
+
/** Canonical names for the built-in delivery slots — used to label the Hooks. */
|
|
86
68
|
declare const BUILT_IN_HOOK_NAMES: {
|
|
87
|
-
readonly
|
|
88
|
-
readonly AppBooting: "nwire.app.booting";
|
|
89
|
-
readonly AppBooted: "nwire.app.booted";
|
|
90
|
-
readonly AppReady: "nwire.app.ready";
|
|
91
|
-
readonly AppShuttingDown: "nwire.app.shutting-down";
|
|
92
|
-
readonly AppShutdown: "nwire.app.shutdown";
|
|
93
|
-
readonly PluginRegistered: "nwire.plugin.registered";
|
|
94
|
-
readonly PluginBooting: "nwire.plugin.booting";
|
|
95
|
-
readonly PluginBooted: "nwire.plugin.booted";
|
|
96
|
-
readonly PluginShuttingDown: "nwire.plugin.shutting-down";
|
|
97
|
-
readonly PluginShutdown: "nwire.plugin.shutdown";
|
|
98
|
-
readonly WireMounting: "nwire.wire.mounting";
|
|
99
|
-
readonly WireMounted: "nwire.wire.mounted";
|
|
100
|
-
readonly WireUnmounted: "nwire.wire.unmounted";
|
|
69
|
+
readonly LocalDelivery: "nwire.event.local-delivery";
|
|
101
70
|
};
|
|
102
71
|
/**
|
|
103
72
|
* Construct the per-runtime registry, with every built-in slot
|
|
@@ -105,6 +74,6 @@ declare const BUILT_IN_HOOK_NAMES: {
|
|
|
105
74
|
* land on the same registry instance.
|
|
106
75
|
*/
|
|
107
76
|
export declare function createFrameworkHooks(): FrameworkHooks;
|
|
108
|
-
/** True if `slot` is one of the built-in hook keys. */
|
|
77
|
+
/** True if `slot` is one of the built-in runtime hook keys. */
|
|
109
78
|
export declare function isBuiltInHook(slot: string): slot is keyof typeof BUILT_IN_HOOK_NAMES;
|
|
110
79
|
export {};
|
package/dist/framework-hooks.js
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-runtime
|
|
3
|
-
* accessed as a typed property on `runtime.hooks`.
|
|
4
|
-
*
|
|
2
|
+
* Per-runtime delivery / plugin-extension hook registry. Each slot is a
|
|
3
|
+
* `Hook<TPayload>` accessed as a typed property on `runtime.hooks`. The
|
|
4
|
+
* runtime ships exactly one built-in slot — `LocalDelivery`, the ordered
|
|
5
|
+
* local fan-out chain. Plugins extend the registry via TS module
|
|
6
|
+
* augmentation + `runtime.defineHook(name)` (forge adds its Action* / Event*
|
|
7
|
+
* slots this way).
|
|
8
|
+
*
|
|
9
|
+
* App / plugin LIFECYCLE hooks are an App concern and live in `@nwire/app`'s
|
|
10
|
+
* `AppHooks` registry — not here. The runtime does not know about plugins.
|
|
5
11
|
*/
|
|
6
12
|
import { hook } from "@nwire/hooks";
|
|
7
|
-
/** Canonical names for the built-in slots — used to label the
|
|
13
|
+
/** Canonical names for the built-in delivery slots — used to label the Hooks. */
|
|
8
14
|
const BUILT_IN_HOOK_NAMES = {
|
|
9
|
-
|
|
10
|
-
AppBooting: "nwire.app.booting",
|
|
11
|
-
AppBooted: "nwire.app.booted",
|
|
12
|
-
AppReady: "nwire.app.ready",
|
|
13
|
-
AppShuttingDown: "nwire.app.shutting-down",
|
|
14
|
-
AppShutdown: "nwire.app.shutdown",
|
|
15
|
-
PluginRegistered: "nwire.plugin.registered",
|
|
16
|
-
PluginBooting: "nwire.plugin.booting",
|
|
17
|
-
PluginBooted: "nwire.plugin.booted",
|
|
18
|
-
PluginShuttingDown: "nwire.plugin.shutting-down",
|
|
19
|
-
PluginShutdown: "nwire.plugin.shutdown",
|
|
20
|
-
WireMounting: "nwire.wire.mounting",
|
|
21
|
-
WireMounted: "nwire.wire.mounted",
|
|
22
|
-
WireUnmounted: "nwire.wire.unmounted",
|
|
15
|
+
LocalDelivery: "nwire.event.local-delivery",
|
|
23
16
|
};
|
|
24
17
|
/**
|
|
25
18
|
* Construct the per-runtime registry, with every built-in slot
|
|
@@ -33,7 +26,7 @@ export function createFrameworkHooks() {
|
|
|
33
26
|
}
|
|
34
27
|
return registry;
|
|
35
28
|
}
|
|
36
|
-
/** True if `slot` is one of the built-in hook keys. */
|
|
29
|
+
/** True if `slot` is one of the built-in runtime hook keys. */
|
|
37
30
|
export function isBuiltInHook(slot) {
|
|
38
31
|
return slot in BUILT_IN_HOOK_NAMES;
|
|
39
32
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* plus runtime alone is enough to dispatch handlers with envelopes, install
|
|
8
8
|
* capabilities, install middleware, and react to events without any App.
|
|
9
9
|
*/
|
|
10
|
-
export { Runtime, createRuntime, isBuiltInHook, serializeError, type RuntimeOptions, type Telemetry, type TelemetryListener, type HookStepTelemetry, type DispatchHookCtx, type DispatchMiddleware, type SerializedError, type PluginDefinition, type PluginContext, type ListenerContext, } from "./runtime.js";
|
|
11
|
-
export { createFrameworkHooks, type FrameworkHooks, type PluginKind } from "./framework-hooks.js";
|
|
10
|
+
export { Runtime, createRuntime, isBuiltInHook, serializeError, messageRef, type MessageRef, type RuntimeOptions, type Telemetry, type TelemetryListener, type HookStepTelemetry, type SourceStageTelemetry, type SinkStageTelemetry, type DispatchHookCtx, type DispatchMiddleware, type SerializedError, type PluginDefinition, type PluginContext, type ListenerContext, } from "./runtime.js";
|
|
11
|
+
export { createFrameworkHooks, type FrameworkHooks, type AppHooks, type PluginKind, type LocalDeliveryPayload, } from "./framework-hooks.js";
|
|
12
12
|
export { defineCapability, type Capability, type CapabilityBase } from "./capability.js";
|
|
13
13
|
export type { OutboundStage, StagePosition, StageContext } from "./sink.js";
|
|
14
|
+
export type { InboundStage, InboundMessage, InboundContext, InboundTarget } from "./source.js";
|
|
15
|
+
export { installTelemetryReporter, type TelemetryReporter } from "./telemetry-sink.js";
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* capabilities, install middleware, and react to events without any App.
|
|
9
9
|
*/
|
|
10
10
|
// Runtime core
|
|
11
|
-
export { Runtime, createRuntime, isBuiltInHook, serializeError, } from "./runtime.js";
|
|
12
|
-
export { createFrameworkHooks } from "./framework-hooks.js";
|
|
11
|
+
export { Runtime, createRuntime, isBuiltInHook, serializeError, messageRef, } from "./runtime.js";
|
|
12
|
+
export { createFrameworkHooks, } from "./framework-hooks.js";
|
|
13
13
|
// Capability primitive — by-reference Runtime contribution.
|
|
14
14
|
export { defineCapability } from "./capability.js";
|
|
15
|
+
// Telemetry sink — the terminal end of the onTelemetry source. Reporters are
|
|
16
|
+
// installed on the runtime; the concrete file/cloud drivers live in node
|
|
17
|
+
// packages that implement TelemetryReporter (fs-free here).
|
|
18
|
+
export { installTelemetryReporter } from "./telemetry-sink.js";
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime — Container, dispatch hook,
|
|
3
|
-
*
|
|
2
|
+
* Runtime — Container, dispatch hook, delivery / plugin-extension hook
|
|
3
|
+
* registry, telemetry stream. The dispatch hook composes user middleware
|
|
4
4
|
* via `runtime.use(...)` around an inner pinned step that calls the
|
|
5
|
-
* registered handler. Plugins materialise additional FrameworkHooks
|
|
6
|
-
*
|
|
5
|
+
* registered handler. Plugins materialise additional `FrameworkHooks` slots
|
|
6
|
+
* via `runtime.defineHook(name)` and TS module augmentation. The runtime is
|
|
7
|
+
* a stateless substrate — it has no boot/shutdown lifecycle; the App owns
|
|
8
|
+
* that (see `AppHooks` / `AppLifecycle` in `@nwire/app`).
|
|
7
9
|
*/
|
|
8
10
|
import { type Container } from "@nwire/container/awilix";
|
|
9
11
|
import { type Hook } from "@nwire/hooks";
|
|
@@ -11,9 +13,10 @@ import { type Logger } from "@nwire/logger";
|
|
|
11
13
|
import { type MessageEnvelope } from "@nwire/envelope";
|
|
12
14
|
import type { HandlerDefinition } from "@nwire/handler";
|
|
13
15
|
import type { EventDefinition, EventPayload } from "@nwire/messages";
|
|
14
|
-
import { isBuiltInHook, type FrameworkHooks } from "./framework-hooks.js";
|
|
16
|
+
import { isBuiltInHook, type FrameworkHooks, type AppHooks } from "./framework-hooks.js";
|
|
15
17
|
import type { Capability } from "./capability.js";
|
|
16
|
-
import type { OutboundStage } from "./sink.js";
|
|
18
|
+
import type { OutboundStage, StagePosition } from "./sink.js";
|
|
19
|
+
import type { InboundStage, InboundMessage } from "./source.js";
|
|
17
20
|
/**
|
|
18
21
|
* Serialized form of any thrown value — `Error` instances keep `name` /
|
|
19
22
|
* `message` / `stack` plus any own enumerable properties; non-Error throws
|
|
@@ -47,6 +50,43 @@ export interface HookStepTelemetry {
|
|
|
47
50
|
readonly appName: string;
|
|
48
51
|
readonly ts: string;
|
|
49
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* `source.stage` — one record per installed inbound stage run (the terminal
|
|
55
|
+
* router is not recorded; its landing shows up as the `execute` / `emit` /
|
|
56
|
+
* fold records it triggers). `correlationId` ties the inbound pipeline to the
|
|
57
|
+
* dispatch it feeds, so Studio can draw one trace: inbound → execute → fold →
|
|
58
|
+
* outbound.
|
|
59
|
+
*/
|
|
60
|
+
export interface SourceStageTelemetry {
|
|
61
|
+
readonly kind: "source.stage";
|
|
62
|
+
readonly stage: string;
|
|
63
|
+
readonly position: StagePosition;
|
|
64
|
+
readonly stageKind?: string;
|
|
65
|
+
readonly message?: {
|
|
66
|
+
readonly name?: string;
|
|
67
|
+
readonly kind?: "command" | "event";
|
|
68
|
+
};
|
|
69
|
+
readonly correlationId?: string;
|
|
70
|
+
readonly tenant?: string;
|
|
71
|
+
readonly shortCircuited: boolean;
|
|
72
|
+
readonly durationMs: number;
|
|
73
|
+
readonly appName: string;
|
|
74
|
+
readonly ts: string;
|
|
75
|
+
}
|
|
76
|
+
/** `sink.stage` — one record per outbound stage run during `sinkDrain`. */
|
|
77
|
+
export interface SinkStageTelemetry {
|
|
78
|
+
readonly kind: "sink.stage";
|
|
79
|
+
readonly stage: string;
|
|
80
|
+
readonly position: StagePosition;
|
|
81
|
+
readonly stageKind?: string;
|
|
82
|
+
readonly event: string;
|
|
83
|
+
readonly correlationId?: string;
|
|
84
|
+
readonly tenant?: string;
|
|
85
|
+
readonly shortCircuited: boolean;
|
|
86
|
+
readonly durationMs: number;
|
|
87
|
+
readonly appName: string;
|
|
88
|
+
readonly ts: string;
|
|
89
|
+
}
|
|
50
90
|
/**
|
|
51
91
|
* Base `Telemetry` union — generic kinds only. Subclasses (forge) WIDEN
|
|
52
92
|
* this with their own domain kinds and re-export the wider union as their
|
|
@@ -54,7 +94,7 @@ export interface HookStepTelemetry {
|
|
|
54
94
|
* everything; consumers that care about discriminants narrow with
|
|
55
95
|
* `switch (rec.kind)`.
|
|
56
96
|
*/
|
|
57
|
-
export type Telemetry = HookStepTelemetry;
|
|
97
|
+
export type Telemetry = HookStepTelemetry | SourceStageTelemetry | SinkStageTelemetry;
|
|
58
98
|
export type TelemetryListener<T = Telemetry> = (record: T) => void;
|
|
59
99
|
/**
|
|
60
100
|
* Per-dispatch context passed through the `runtime.dispatch` hook. Subclass
|
|
@@ -74,6 +114,33 @@ export interface DispatchHookCtx {
|
|
|
74
114
|
readonly coreFn: () => Promise<unknown>;
|
|
75
115
|
result?: unknown;
|
|
76
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* A handle to a dispatched message. Returned by the async dispatch verbs
|
|
119
|
+
* (`enqueue`, and forge's `send`) so the caller can correlate and observe a
|
|
120
|
+
* dispatch without blocking on its result. Every identifier comes from the
|
|
121
|
+
* envelope minted at dispatch time, so the ref matches the envelope the
|
|
122
|
+
* handler sees and every telemetry record that dispatch emits.
|
|
123
|
+
*
|
|
124
|
+
* The default is "ref, not result." Helpers that recover a result later
|
|
125
|
+
* (`awaitUntil`, `poll`) wrap a ref; they live outside the runtime so the
|
|
126
|
+
* hot dispatch path stays allocation-light.
|
|
127
|
+
*/
|
|
128
|
+
export interface MessageRef {
|
|
129
|
+
/** The dispatched message's id — this dispatch's `envelope.messageId`. */
|
|
130
|
+
readonly messageId: string;
|
|
131
|
+
/** Correlation id shared across the whole causal chain. */
|
|
132
|
+
readonly correlationId: string;
|
|
133
|
+
/** What caused this dispatch — the parent's messageId (self at chain head). */
|
|
134
|
+
readonly causationId: string;
|
|
135
|
+
/** Logical target name — the handler / action / event name. */
|
|
136
|
+
readonly name: string;
|
|
137
|
+
/** Which door it went through. */
|
|
138
|
+
readonly kind: "command" | "event";
|
|
139
|
+
/** Tenant the dispatch is scoped to, when set. */
|
|
140
|
+
readonly tenant?: string;
|
|
141
|
+
}
|
|
142
|
+
/** Build a {@link MessageRef} from a minted envelope + target metadata. */
|
|
143
|
+
export declare function messageRef(envelope: MessageEnvelope, name: string, kind: "command" | "event"): MessageRef;
|
|
77
144
|
/**
|
|
78
145
|
* Onion-style extension point around action dispatch. Outermost first;
|
|
79
146
|
* registration order is execution order (matches Koa/express semantics).
|
|
@@ -110,9 +177,12 @@ export declare class Runtime {
|
|
|
110
177
|
protected readonly dispatchHook: Hook<DispatchHookCtx>;
|
|
111
178
|
private userMiddlewareCount;
|
|
112
179
|
/**
|
|
113
|
-
* Per-runtime
|
|
114
|
-
*
|
|
115
|
-
* interface and materialise their slots via
|
|
180
|
+
* Per-runtime delivery / plugin-extension hook registry. The one built-in
|
|
181
|
+
* slot (`LocalDelivery`) is pre-instantiated; plugins augment the
|
|
182
|
+
* `FrameworkHooks` interface and materialise their slots via
|
|
183
|
+
* `defineHook(name)` (forge adds its Action* / Event* slots this way).
|
|
184
|
+
* App / plugin lifecycle hooks are an App concern — see `AppHooks` in
|
|
185
|
+
* `@nwire/app`.
|
|
116
186
|
*/
|
|
117
187
|
readonly hooks: FrameworkHooks;
|
|
118
188
|
private readonly telemetryListeners;
|
|
@@ -125,7 +195,7 @@ export declare class Runtime {
|
|
|
125
195
|
private readonly handlers;
|
|
126
196
|
/**
|
|
127
197
|
* Per-event subscriber registry — keyed by `event.name`. Subscribers
|
|
128
|
-
* attach via `runtime.
|
|
198
|
+
* attach via `runtime.when(event, fn)` and fire on `runtime.emit(
|
|
129
199
|
* event, payload)`. Foundation calls this out as the broadcast verb
|
|
130
200
|
* distinct from telemetry-push (`runtime.pushTelemetry(record)`).
|
|
131
201
|
*/
|
|
@@ -150,6 +220,16 @@ export declare class Runtime {
|
|
|
150
220
|
*/
|
|
151
221
|
private readonly sinkStages;
|
|
152
222
|
private readonly terminalKinds;
|
|
223
|
+
/**
|
|
224
|
+
* Inbound source chain — populated by `runtime.source(stage)`, the mirror
|
|
225
|
+
* of the sink chain. `receive` runs them in position order (early → middle →
|
|
226
|
+
* terminal) and then the default `inboundRouter` lands the message on the
|
|
227
|
+
* one execution terminal. Terminal stages with a `kind` are exclusivity-
|
|
228
|
+
* checked, in a namespace independent of sink kinds.
|
|
229
|
+
*/
|
|
230
|
+
private readonly sourceStages;
|
|
231
|
+
private readonly terminalSourceKinds;
|
|
232
|
+
private readonly inboundRouter;
|
|
153
233
|
constructor(options?: RuntimeOptions);
|
|
154
234
|
/**
|
|
155
235
|
* Materialise a slot on `runtime.hooks` for a plugin-defined framework
|
|
@@ -190,12 +270,24 @@ export declare class Runtime {
|
|
|
190
270
|
add(cap: Capability<unknown>): void;
|
|
191
271
|
/** All installed capability names, in install order. Used by tests + Studio. */
|
|
192
272
|
listCapabilities(): readonly string[];
|
|
273
|
+
/**
|
|
274
|
+
* Describe installed capabilities for the manifest/topology — name, the
|
|
275
|
+
* handler `kinds` it scopes to (undefined = universal), and the ctx keys it
|
|
276
|
+
* contributes (discovered by invoking `provideCtx` with a probe envelope).
|
|
277
|
+
* Read-only introspection; a `provideCtx` that throws on a bare probe yields
|
|
278
|
+
* `ctxKeys: []` rather than failing the scan.
|
|
279
|
+
*/
|
|
280
|
+
describeCapabilities(): readonly {
|
|
281
|
+
readonly name: string;
|
|
282
|
+
readonly kinds?: readonly string[];
|
|
283
|
+
readonly ctxKeys: readonly string[];
|
|
284
|
+
}[];
|
|
193
285
|
/**
|
|
194
286
|
* Internal — called by `execute` to build the ctx contribution from every
|
|
195
287
|
* installed capability for one dispatch. Public so subclass dispatchers
|
|
196
288
|
* (forge) can compose; not part of the documented contract.
|
|
197
289
|
*/
|
|
198
|
-
buildCapabilityCtx(envelope: MessageEnvelope): Record<string, unknown>;
|
|
290
|
+
buildCapabilityCtx(envelope: MessageEnvelope, signal: AbortSignal, kind?: string): Record<string, unknown>;
|
|
199
291
|
/**
|
|
200
292
|
* Install an outbound pipeline stage. Position-ordered: early → middle →
|
|
201
293
|
* terminal. Within a position, install order is run order. Terminal stages
|
|
@@ -220,6 +312,48 @@ export declare class Runtime {
|
|
|
220
312
|
}, payload: unknown, envelope: MessageEnvelope): Promise<void>;
|
|
221
313
|
/** Stable position ordering: early → middle → terminal, install-order within. */
|
|
222
314
|
private orderedSinkStages;
|
|
315
|
+
/**
|
|
316
|
+
* Install an inbound source stage — the mirror of `sink(stage)`. Position-
|
|
317
|
+
* ordered: early → middle → terminal; install order within a position.
|
|
318
|
+
* Terminal stages carrying a `kind` are deduplicated (one per kind), in a
|
|
319
|
+
* namespace independent of sink kinds.
|
|
320
|
+
*/
|
|
321
|
+
source(stage: InboundStage): void;
|
|
322
|
+
/** All installed source stages plus the terminal router. Used by tests + Studio. */
|
|
323
|
+
listSourceStages(): readonly InboundStage[];
|
|
324
|
+
/**
|
|
325
|
+
* Inbound entry point — the mirror of `sinkDrain`. A transport (HTTP, queue,
|
|
326
|
+
* broker) hands a raw message here; every source stage runs in position
|
|
327
|
+
* order (early → middle → terminal), then the default router lands it on the
|
|
328
|
+
* one execution terminal. A stage returning `{ continue: false }` short-
|
|
329
|
+
* circuits the rest (e.g. a dedup stage dropping a replay).
|
|
330
|
+
*
|
|
331
|
+
* `opts.envelope` carries the seed / overrides the transport resolved
|
|
332
|
+
* (tenant, user, correlation); `opts.extras` threads transport context
|
|
333
|
+
* (logger, koa) onto the handler ctx. Returns the router's `ctx.result` — a
|
|
334
|
+
* command's handler result; events return nothing.
|
|
335
|
+
*/
|
|
336
|
+
receive(message: InboundMessage, opts?: {
|
|
337
|
+
envelope?: Partial<MessageEnvelope>;
|
|
338
|
+
extras?: Record<string, unknown>;
|
|
339
|
+
/** Cancellation signal threaded onto the dispatch (queue lock, request abort). */
|
|
340
|
+
signal?: AbortSignal;
|
|
341
|
+
/**
|
|
342
|
+
* Parent envelope to inherit from — the message derives a child (carries
|
|
343
|
+
* correlationId, causationId = parent.messageId). Used by async sources
|
|
344
|
+
* (queue) that replay a stored envelope.
|
|
345
|
+
*/
|
|
346
|
+
parent?: MessageEnvelope;
|
|
347
|
+
}): Promise<unknown>;
|
|
348
|
+
/** Stable inbound ordering: early → middle → terminal, router lands last. */
|
|
349
|
+
private orderedSourceStages;
|
|
350
|
+
/**
|
|
351
|
+
* The default terminal source stage — the router. A `command` lands on
|
|
352
|
+
* `execute` (by direct `target` reference, or by `name` through the
|
|
353
|
+
* registry); an `event` lands on `emit`. This is the one place inbound
|
|
354
|
+
* messages reach the execution terminal — no second dispatch path.
|
|
355
|
+
*/
|
|
356
|
+
private buildInboundRouter;
|
|
223
357
|
/**
|
|
224
358
|
* Wire a hook's per-step tap into the canonical telemetry stream. After
|
|
225
359
|
* this call, every `.use()` / `.on()` step on the hook emits a
|
|
@@ -242,8 +376,8 @@ export declare class Runtime {
|
|
|
242
376
|
* subclass-widened records (CQRS kinds in forge) don't need cast
|
|
243
377
|
* gymnastics — listeners narrow with `switch (rec.kind)`.
|
|
244
378
|
*
|
|
245
|
-
* Public so
|
|
246
|
-
*
|
|
379
|
+
* Public so forge's plugins (which ride this runtime rather than
|
|
380
|
+
* subclassing it) can push their own domain records.
|
|
247
381
|
* Misuse risk is low: callers who own a Runtime are by definition
|
|
248
382
|
* inside the trust boundary.
|
|
249
383
|
*
|
|
@@ -264,6 +398,14 @@ export declare class Runtime {
|
|
|
264
398
|
getHandler(name: string): HandlerDefinition<any, any, any>;
|
|
265
399
|
/** All registered handler names — used by adapters to build wire tables. */
|
|
266
400
|
listHandlers(): readonly string[];
|
|
401
|
+
/**
|
|
402
|
+
* Mint the delivery envelope for a dispatch. With a `parent` it derives a
|
|
403
|
+
* child (correlationId carried, causationId = parent.messageId); otherwise
|
|
404
|
+
* it seeds a fresh chain from the supplied overrides. The single place the
|
|
405
|
+
* derive-vs-seed choice lives — `execute`, `emit`, and `enqueue` share it
|
|
406
|
+
* so a ref minted up-front matches the envelope the handler sees.
|
|
407
|
+
*/
|
|
408
|
+
private mintEnvelope;
|
|
267
409
|
/**
|
|
268
410
|
* Canonical sync dispatch verb. Validates input via the handler's input
|
|
269
411
|
* schema, mints a child envelope (or seeds one when no parent is given),
|
|
@@ -277,21 +419,30 @@ export declare class Runtime {
|
|
|
277
419
|
execute<TInput = unknown, TOutput = unknown>(handler: HandlerDefinition<any, TOutput, any> | string | ((input: TInput, ctx: any) => TOutput | Promise<TOutput>), input: TInput, envelopePartial?: Partial<MessageEnvelope> & {
|
|
278
420
|
readonly signal?: AbortSignal;
|
|
279
421
|
readonly parent?: MessageEnvelope;
|
|
422
|
+
/**
|
|
423
|
+
* A fully-minted envelope to use verbatim — skips derive/seed. Set by
|
|
424
|
+
* `enqueue`, which mints up-front so the `MessageRef` it returns names
|
|
425
|
+
* the same message the deferred dispatch runs under.
|
|
426
|
+
*/
|
|
427
|
+
readonly envelope?: MessageEnvelope;
|
|
280
428
|
}, extras?: Record<string, unknown>): Promise<TOutput>;
|
|
281
429
|
/** Innermost dispatch step that calls the actual handler. Pinned once. */
|
|
282
430
|
private dispatchCorePinned;
|
|
283
431
|
private ensureDispatchCorePin;
|
|
284
432
|
/**
|
|
285
|
-
* Fire-and-forget dispatch.
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
433
|
+
* Fire-and-forget dispatch. Mints the delivery envelope up-front, hands
|
|
434
|
+
* the deferred dispatch that exact envelope, and returns a {@link MessageRef}
|
|
435
|
+
* naming it — so the caller can correlate / observe without blocking on the
|
|
436
|
+
* result. Default implementation: `setImmediate` → `execute`. Queue-adapter
|
|
437
|
+
* installation can override this to enqueue onto an external queue (BullMQ,
|
|
438
|
+
* SQS, etc.) while keeping the same up-front envelope + ref contract. Errors
|
|
439
|
+
* surface on the telemetry stream as `kind: "enqueue.failed"` — the caller
|
|
440
|
+
* has already returned.
|
|
290
441
|
*/
|
|
291
442
|
enqueue<TInput = unknown>(handler: HandlerDefinition<any, any, any> | string, input: TInput, envelopePartial?: Partial<MessageEnvelope> & {
|
|
292
443
|
readonly signal?: AbortSignal;
|
|
293
444
|
readonly parent?: MessageEnvelope;
|
|
294
|
-
}): Promise<
|
|
445
|
+
}): Promise<MessageRef>;
|
|
295
446
|
/** Resolve a dependency from the runtime's container. */
|
|
296
447
|
resolve<T = unknown>(name: string): T;
|
|
297
448
|
/**
|
|
@@ -303,13 +454,6 @@ export declare class Runtime {
|
|
|
303
454
|
when<E extends EventDefinition & {
|
|
304
455
|
readonly name: string;
|
|
305
456
|
}>(event: E, listener: (payload: EventPayload<E>, ctx: ListenerContext) => void | Promise<void>): () => void;
|
|
306
|
-
/**
|
|
307
|
-
* Alias for {@link when} — same behavior, same return. Preserved for
|
|
308
|
-
* call sites that prefer the longer name.
|
|
309
|
-
*/
|
|
310
|
-
subscribe<E extends EventDefinition & {
|
|
311
|
-
readonly name: string;
|
|
312
|
-
}>(event: E, listener: (payload: EventPayload<E>, ctx: ListenerContext) => void | Promise<void>): () => void;
|
|
313
457
|
/**
|
|
314
458
|
* Broadcast an event to its subscribers. Validates payload against the
|
|
315
459
|
* event's schema, mints a child envelope, fires every registered
|
|
@@ -317,6 +461,19 @@ export declare class Runtime {
|
|
|
317
461
|
* "event.emitted"` telemetry record. Subscriber throws are captured
|
|
318
462
|
* but do not propagate to the caller.
|
|
319
463
|
*/
|
|
464
|
+
/**
|
|
465
|
+
* The one incoming-to-all LOCAL delivery. Runs the ordered `LocalDelivery`
|
|
466
|
+
* chain (forge's idempotency → actors → projections → workflows attach
|
|
467
|
+
* here); if an upstream step vetoed (`deduped`), stops — otherwise fans
|
|
468
|
+
* out to `when` listeners. Both `emit` (the validated entry)
|
|
469
|
+
* and forge's publish ride this, so there is one local-delivery path and
|
|
470
|
+
* no second fan-out. `envelope` is the already-minted delivery envelope —
|
|
471
|
+
* callers derive/seed it; `deliver` does not. Returns whether the event
|
|
472
|
+
* was deduped so a caller can gate its outbound `publish`.
|
|
473
|
+
*/
|
|
474
|
+
deliver(eventName: string, payload: unknown, envelope: MessageEnvelope, dedupKey: string): Promise<{
|
|
475
|
+
deduped: boolean;
|
|
476
|
+
}>;
|
|
320
477
|
emit<TPayload>(event: EventDefinition & {
|
|
321
478
|
readonly name: string;
|
|
322
479
|
readonly schema: {
|
|
@@ -395,15 +552,18 @@ export interface PluginDefinition<TOptions = void, TMark = unknown> {
|
|
|
395
552
|
* What register/setup closures receive. One context type is used by both
|
|
396
553
|
* phases. Members:
|
|
397
554
|
*
|
|
398
|
-
* - `container` / `runtime` / `hooks` — the layer references
|
|
555
|
+
* - `container` / `runtime` / `hooks` — the layer references. `hooks` is
|
|
556
|
+
* the runtime's delivery / plugin-extension registry; runtime concerns
|
|
557
|
+
* (`add` / `sink`) are reached through `runtime`.
|
|
399
558
|
* - `options` — the typed value the plugin was constructed with
|
|
400
559
|
* - `bind` — write a binding into the container
|
|
401
|
-
* - `
|
|
402
|
-
* - `sink(stage)` — install an outbound pipeline stage
|
|
403
|
-
* - `use(mw)` — wrap dispatch in middleware (via runtime)
|
|
404
|
-
* - `on(hook, fn)` — observe a framework hook
|
|
560
|
+
* - `on(hook, fn)` — observe an app lifecycle hook
|
|
405
561
|
* - `boot(fn)` / `shutdown(fn)` — async lifecycle queues
|
|
406
|
-
* - `defineHook(name)` — materialise a plugin-defined
|
|
562
|
+
* - `defineHook(name)` — materialise a plugin-defined app lifecycle slot
|
|
563
|
+
*
|
|
564
|
+
* Runtime concerns (`runtime.add(cap)`, `runtime.sink(stage)`,
|
|
565
|
+
* `runtime.use(mw)`) live on the `runtime` reference — the context exposes
|
|
566
|
+
* app concerns only.
|
|
407
567
|
*/
|
|
408
568
|
export interface PluginContext<TOptions = void> {
|
|
409
569
|
readonly container: Container;
|
|
@@ -415,21 +575,17 @@ export interface PluginContext<TOptions = void> {
|
|
|
415
575
|
dispose?: (v: T) => void | Promise<void>;
|
|
416
576
|
check?: (v: T) => void | Promise<void>;
|
|
417
577
|
}): void;
|
|
418
|
-
/** Install a capability — same as `runtime.add(cap)` but routed through this plugin. */
|
|
419
|
-
add(cap: Capability<unknown>): void;
|
|
420
|
-
/** Install an outbound pipeline stage. */
|
|
421
|
-
sink(stage: OutboundStage): void;
|
|
422
578
|
/**
|
|
423
|
-
* Observe
|
|
424
|
-
*
|
|
579
|
+
* Observe an app lifecycle hook — calls `appHooks[name].on(fn)` under the
|
|
580
|
+
* hood. The payload is typed from the named hook, so
|
|
425
581
|
* `on("AppBooted", ({ appName, bootedAt }) => …)` is fully inferred.
|
|
426
582
|
*/
|
|
427
|
-
on<K extends keyof
|
|
583
|
+
on<K extends keyof AppHooks>(name: K, fn: (payload: AppHooks[K] extends Hook<infer P> ? P : never) => void): void;
|
|
428
584
|
boot(fn: () => Promise<void> | void): void;
|
|
429
585
|
/** Async cleanup work to run at `runtime.stop()`. */
|
|
430
586
|
dispose(fn: () => Promise<void> | void): void;
|
|
431
587
|
/** Alias for {@link dispose}. */
|
|
432
588
|
shutdown(fn: () => Promise<void> | void): void;
|
|
433
|
-
defineHook<TCtx>(name: keyof
|
|
589
|
+
defineHook<TCtx>(name: keyof AppHooks): Hook<TCtx>;
|
|
434
590
|
}
|
|
435
591
|
export { isBuiltInHook };
|