@nwire/app 0.9.2 → 0.10.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.
- package/README.md +81 -95
- package/dist/app.d.ts +7 -24
- package/dist/app.js +6 -24
- package/dist/compose-app.d.ts +35 -0
- package/dist/compose-app.js +138 -0
- package/dist/create-app.d.ts +38 -66
- package/dist/create-app.js +42 -216
- package/dist/define-plugin.d.ts +10 -149
- package/dist/define-plugin.js +11 -62
- package/dist/runtime/framework-hooks.d.ts +110 -0
- package/dist/runtime/framework-hooks.js +39 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +6 -0
- package/dist/runtime/runtime.d.ts +349 -0
- package/dist/runtime/runtime.js +642 -0
- package/package.json +8 -5
- package/dist/__tests__/create-app.test.d.ts +0 -6
- package/dist/__tests__/create-app.test.d.ts.map +0 -1
- package/dist/__tests__/create-app.test.js +0 -126
- package/dist/__tests__/create-app.test.js.map +0 -1
- package/dist/__tests__/define-plugin.test.d.ts +0 -16
- package/dist/__tests__/define-plugin.test.d.ts.map +0 -1
- package/dist/__tests__/define-plugin.test.js +0 -269
- package/dist/__tests__/define-plugin.test.js.map +0 -1
- package/dist/__tests__/framework-events.test.d.ts +0 -18
- package/dist/__tests__/framework-events.test.d.ts.map +0 -1
- package/dist/__tests__/framework-events.test.js +0 -156
- package/dist/__tests__/framework-events.test.js.map +0 -1
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js.map +0 -1
- package/dist/create-app.d.ts.map +0 -1
- package/dist/create-app.js.map +0 -1
- package/dist/define-plugin.d.ts.map +0 -1
- package/dist/define-plugin.js.map +0 -1
- package/dist/framework-event-bus.d.ts +0 -129
- package/dist/framework-event-bus.d.ts.map +0 -1
- package/dist/framework-event-bus.js +0 -188
- package/dist/framework-event-bus.js.map +0 -1
- package/dist/framework-events.d.ts +0 -233
- package/dist/framework-events.d.ts.map +0 -1
- package/dist/framework-events.js +0 -136
- package/dist/framework-events.js.map +0 -1
- package/dist/runtime.d.ts +0 -185
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -197
- package/dist/runtime.js.map +0 -1
package/dist/create-app.js
CHANGED
|
@@ -1,231 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `createApp` —
|
|
3
|
-
*
|
|
4
|
-
* Boots a Container + a FrameworkEventBus with the supplied plugins.
|
|
5
|
-
* Lightweight: no forge concepts (no Runtime, no actors, no workflows,
|
|
6
|
-
* no module dep graph). For the full forge experience use `createApp`
|
|
7
|
-
* from `@nwire/forge`, which composes on top of this primitive.
|
|
8
|
-
*
|
|
9
|
-
* What this does, in order, during `start()`:
|
|
10
|
-
*
|
|
11
|
-
* 1. Register every plugin synchronously — `setup(ctx)` runs; bindings
|
|
12
|
-
* declared via `ctx.provide()` are collected; framework subscriptions
|
|
13
|
-
* via `ctx.on()` are wired on the bus; boot/shutdown callbacks are
|
|
14
|
-
* captured into ordered lists.
|
|
15
|
-
* 2. Fire `AppRegistering` (series-bail). A vetoing subscriber aborts.
|
|
16
|
-
* 3. Boot every provided binding: call `lifecycle.boot()`, register the
|
|
17
|
-
* value on the container under its declared name. Failures abort.
|
|
18
|
-
* 4. Fire `AppBooting` (series-bail). A vetoing subscriber aborts.
|
|
19
|
-
* 5. Run each plugin's accumulated `boot` callbacks in registration order.
|
|
20
|
-
* 6. Fire `AppBooted` (parallel).
|
|
21
|
-
*
|
|
22
|
-
* `stop(reason?)`:
|
|
23
|
-
*
|
|
24
|
-
* 1. Fire `AppShuttingDown` (series-bail).
|
|
25
|
-
* 2. Run plugin `shutdown` callbacks in REVERSE registration order.
|
|
26
|
-
* 3. Call `lifecycle.shutdown(value)` on each provided binding (reverse).
|
|
27
|
-
* 4. Fire `AppShutdown` (parallel).
|
|
28
|
-
*
|
|
29
|
-
* The returned `App` satisfies `AppServable` from `@nwire/endpoint`, so
|
|
30
|
-
* `endpoint("api").serve(app).run()` works without any glue.
|
|
2
|
+
* `createApp` — composition root. Constructs a Runtime, registers
|
|
3
|
+
* plugins, and exposes the lifecycle the runtime owns.
|
|
31
4
|
*/
|
|
32
|
-
import { createContainer } from "@nwire/container";
|
|
5
|
+
import { createContainer } from "@nwire/container/awilix";
|
|
33
6
|
import { NoopLogger } from "@nwire/logger";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
/**
|
|
37
|
-
* Build a generic Nwire app from plugin definitions. No forge concepts —
|
|
38
|
-
* use `@nwire/forge`'s `createApp` for the rich CQRS / actor / workflow
|
|
39
|
-
* surface.
|
|
40
|
-
*
|
|
41
|
-
* const app = createApp({
|
|
42
|
-
* appName: "users",
|
|
43
|
-
* plugins: [authPlugin, dbPlugin],
|
|
44
|
-
* });
|
|
45
|
-
* await app.start();
|
|
46
|
-
* // app.container.cradle.db is now resolvable
|
|
47
|
-
* await app.stop();
|
|
48
|
-
*/
|
|
7
|
+
import { createInterface, } from "@nwire/wires";
|
|
8
|
+
import { createRuntime } from "./runtime/index.js";
|
|
49
9
|
export function createApp(options) {
|
|
50
10
|
const appName = options.appName;
|
|
51
11
|
const container = options.container ?? createContainer();
|
|
52
12
|
const logger = options.logger ?? new NoopLogger();
|
|
53
|
-
const
|
|
13
|
+
const runtime = options.runtime ?? createRuntime({ container, logger, appName });
|
|
54
14
|
const plugins = options.plugins ?? [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const bootedValues = new Map();
|
|
64
|
-
// ─── 1. Synchronous register: run each plugin's setup closure now ─────
|
|
65
|
-
// Plugins contribute container bindings, framework subscriptions, and
|
|
66
|
-
// boot/shutdown callbacks via the captured-builder pattern.
|
|
67
|
-
plugins.forEach((plugin, pluginIdx) => {
|
|
68
|
-
const ctx = {
|
|
69
|
-
container,
|
|
70
|
-
bus,
|
|
71
|
-
provide(name, lifecycle) {
|
|
72
|
-
provides.push({
|
|
73
|
-
pluginIdx,
|
|
74
|
-
name,
|
|
75
|
-
lifecycle: lifecycle,
|
|
76
|
-
});
|
|
77
|
-
},
|
|
78
|
-
on(event, handler, priority) {
|
|
79
|
-
bus.on(event, handler, priority);
|
|
80
|
-
},
|
|
81
|
-
boot(fn) {
|
|
82
|
-
boots.push({ pluginIdx, fn });
|
|
83
|
-
},
|
|
84
|
-
shutdown(fn) {
|
|
85
|
-
shutdowns.push({ pluginIdx, fn });
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
// setup may be sync or async — we await during start(), not here, so
|
|
89
|
-
// the constructor returns immediately. Defer the call: setup returns
|
|
90
|
-
// a value/promise that start() awaits.
|
|
91
|
-
plugin.setup(ctx);
|
|
92
|
-
});
|
|
93
|
-
let started = false;
|
|
94
|
-
let stopped = false;
|
|
95
|
-
const start = async () => {
|
|
96
|
-
if (started)
|
|
97
|
-
return;
|
|
98
|
-
started = true;
|
|
99
|
-
// Per-plugin registered event — observable; no veto.
|
|
100
|
-
for (const plugin of plugins) {
|
|
101
|
-
await bus.fire(PluginRegistered, {
|
|
102
|
-
appName,
|
|
103
|
-
pluginName: plugin.name,
|
|
104
|
-
kind: "plugin",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
// AppRegistering — interceptable.
|
|
108
|
-
const okReg = await bus.fire(AppRegistering, { appName });
|
|
109
|
-
if (!okReg) {
|
|
110
|
-
throw new Error(`createApp("${appName}"): AppRegistering vetoed by subscriber`);
|
|
111
|
-
}
|
|
112
|
-
// 2. Boot every provided binding; register the value on the container.
|
|
113
|
-
for (const { name, lifecycle } of provides) {
|
|
114
|
-
const value = await lifecycle.boot();
|
|
115
|
-
container.register(name, value);
|
|
116
|
-
bootedValues.set(name, value);
|
|
117
|
-
}
|
|
118
|
-
// 3. AppBooting — interceptable.
|
|
119
|
-
const okBoot = await bus.fire(AppBooting, { appName });
|
|
120
|
-
if (!okBoot) {
|
|
121
|
-
throw new Error(`createApp("${appName}"): AppBooting vetoed by subscriber`);
|
|
122
|
-
}
|
|
123
|
-
// 4. Per-plugin boot — interceptable + observed per plugin.
|
|
124
|
-
for (const { pluginIdx, fn } of boots) {
|
|
125
|
-
const plugin = plugins[pluginIdx];
|
|
126
|
-
const okPlug = await bus.fire(PluginBooting, {
|
|
127
|
-
appName,
|
|
128
|
-
pluginName: plugin.name,
|
|
129
|
-
kind: "plugin",
|
|
130
|
-
});
|
|
131
|
-
if (!okPlug) {
|
|
132
|
-
throw new Error(`createApp("${appName}"): plugin "${plugin.name}" boot vetoed by subscriber`);
|
|
133
|
-
}
|
|
134
|
-
const startedAt = performance.now();
|
|
135
|
-
await fn();
|
|
136
|
-
await bus.fire(PluginBooted, {
|
|
137
|
-
appName,
|
|
138
|
-
pluginName: plugin.name,
|
|
139
|
-
durationMs: performance.now() - startedAt,
|
|
140
|
-
kind: "plugin",
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
// 5. AppBooted — parallel observation.
|
|
144
|
-
await bus.fire(AppBooted, {
|
|
145
|
-
appName,
|
|
146
|
-
bootedAt: new Date().toISOString(),
|
|
147
|
-
});
|
|
148
|
-
// 6. AppReady — parallel observation. Endpoint fires this after the
|
|
149
|
-
// wire is listening; we fire here too for the standalone case.
|
|
150
|
-
await bus.fire(AppReady, {
|
|
151
|
-
appName,
|
|
152
|
-
readyAt: new Date().toISOString(),
|
|
153
|
-
});
|
|
154
|
-
};
|
|
155
|
-
const stop = async (reason) => {
|
|
156
|
-
if (stopped)
|
|
157
|
-
return;
|
|
158
|
-
stopped = true;
|
|
159
|
-
// AppShuttingDown — interceptable. Throwing fails the shutdown; the
|
|
160
|
-
// caller is responsible for hard-timeout SIGKILL via @nwire/endpoint.
|
|
161
|
-
await bus.fire(AppShuttingDown, { appName, reason });
|
|
162
|
-
// 1. Plugin shutdown callbacks (reverse registration order).
|
|
163
|
-
for (let i = shutdowns.length - 1; i >= 0; i--) {
|
|
164
|
-
const { pluginIdx, fn } = shutdowns[i];
|
|
165
|
-
const plugin = plugins[pluginIdx];
|
|
166
|
-
await bus.fire(PluginShuttingDown, {
|
|
167
|
-
appName,
|
|
168
|
-
pluginName: plugin.name,
|
|
169
|
-
kind: "plugin",
|
|
170
|
-
});
|
|
171
|
-
const startedAt = performance.now();
|
|
172
|
-
try {
|
|
173
|
-
await fn();
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
logger.error?.(`plugin "${plugin.name}" shutdown threw`, {
|
|
177
|
-
error: err?.message ?? String(err),
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
await bus.fire(PluginShutdown, {
|
|
181
|
-
appName,
|
|
182
|
-
pluginName: plugin.name,
|
|
183
|
-
durationMs: performance.now() - startedAt,
|
|
184
|
-
kind: "plugin",
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
// 2. Provided-binding shutdowns (reverse boot order).
|
|
188
|
-
for (let i = provides.length - 1; i >= 0; i--) {
|
|
189
|
-
const { name, lifecycle } = provides[i];
|
|
190
|
-
if (!lifecycle.shutdown)
|
|
191
|
-
continue;
|
|
192
|
-
const value = bootedValues.get(name);
|
|
193
|
-
if (value === undefined)
|
|
194
|
-
continue;
|
|
195
|
-
try {
|
|
196
|
-
await lifecycle.shutdown(value);
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
logger.error?.(`provider "${name}" shutdown threw`, {
|
|
200
|
-
error: err?.message ?? String(err),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
// 3. AppShutdown — parallel observation.
|
|
205
|
-
await bus.fire(AppShutdown, { appName });
|
|
206
|
-
};
|
|
207
|
-
return {
|
|
15
|
+
const iface = createInterface();
|
|
16
|
+
for (const h of options.handlers ?? []) {
|
|
17
|
+
runtime.registerHandler(h);
|
|
18
|
+
}
|
|
19
|
+
for (const p of plugins) {
|
|
20
|
+
runtime.registerPlugin(p);
|
|
21
|
+
}
|
|
22
|
+
const app = {
|
|
208
23
|
$nwireApp: true,
|
|
24
|
+
$kind: "app",
|
|
209
25
|
appName,
|
|
26
|
+
name: appName,
|
|
210
27
|
container,
|
|
211
|
-
|
|
28
|
+
runtime,
|
|
29
|
+
interface: iface,
|
|
212
30
|
plugins,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
31
|
+
wire(binding, handler) {
|
|
32
|
+
iface.wire(binding, handler);
|
|
33
|
+
// Tag the just-appended wire with its source app so adopters can
|
|
34
|
+
// resolve `containerOf(wire)` back to this app's container.
|
|
35
|
+
const appended = iface.wires[iface.wires.length - 1];
|
|
36
|
+
appended.app = app;
|
|
37
|
+
return app;
|
|
38
|
+
},
|
|
39
|
+
provide(builder) {
|
|
40
|
+
iface.provide(builder);
|
|
41
|
+
return app;
|
|
42
|
+
},
|
|
43
|
+
start: () => runtime.start(),
|
|
44
|
+
stop: (reason) => runtime.stop(reason),
|
|
45
|
+
boot: () => runtime.start(),
|
|
46
|
+
shutdown: () => runtime.stop(),
|
|
47
|
+
dispatchFrameworkEvent: async (slot, payload) => {
|
|
48
|
+
const hooks = runtime.hooks;
|
|
49
|
+
const h = hooks[slot];
|
|
50
|
+
if (!h)
|
|
51
|
+
return true;
|
|
52
|
+
const result = await h.runDetailed(payload);
|
|
53
|
+
return result.outcome === "completed";
|
|
228
54
|
},
|
|
229
55
|
};
|
|
56
|
+
return app;
|
|
230
57
|
}
|
|
231
|
-
//# sourceMappingURL=create-app.js.map
|
package/dist/define-plugin.d.ts
CHANGED
|
@@ -1,153 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 1. **Container bindings** via `provide(name, lifecycle)` — bootable
|
|
8
|
-
* values (DB pool, Redis client, HTTP client, anything that needs
|
|
9
|
-
* `boot()` + optional `shutdown()` + optional `healthCheck()`).
|
|
10
|
-
* 2. **Event subscriptions** via `on(FrameworkEvent, handler, priority?)`
|
|
11
|
-
* — react to app lifecycle, custom events, or anything else fired
|
|
12
|
-
* through the FrameworkEventBus.
|
|
13
|
-
* 3. **Lifecycle callbacks** via `boot(fn)` + `shutdown(fn)` — async
|
|
14
|
-
* work at known transitions (post-provider-boot, pre-shutdown).
|
|
15
|
-
*
|
|
16
|
-
* This narrow shape covers 100% of "extend the app" cases that don't
|
|
17
|
-
* need forge-specific knowledge. For plugins that need action middleware,
|
|
18
|
-
* actor lifecycle hooks, or `before("action.name", ...)` sugar, use the
|
|
19
|
-
* richer `definePlugin` in `@nwire/forge` — it's a superset that wraps
|
|
20
|
-
* this one.
|
|
21
|
-
*
|
|
22
|
-
* ## When to reach for this vs forge's
|
|
23
|
-
*
|
|
24
|
-
* - Connecting a database / cache / queue / SDK? → this one.
|
|
25
|
-
* - Tracing every dispatched action? → forge's (needs middleware).
|
|
26
|
-
* - Reacting to a workflow saga timer? → forge's (needs actor hooks).
|
|
27
|
-
* - Multi-tenant request context propagation? → forge's (needs middleware).
|
|
28
|
-
* - Custom framework events fired by your own code? → this one.
|
|
29
|
-
*
|
|
30
|
-
* In other words: if you don't need to peek inside action dispatch or
|
|
31
|
-
* actor transitions, you don't need forge's plugin form. Use this one.
|
|
2
|
+
* Plugin factory — wraps the canonical `PluginDefinition` shape with
|
|
3
|
+
* source-location capture so Studio + scan can render where a plugin
|
|
4
|
+
* was declared. Setup runs synchronously at `runtime.registerPlugin`
|
|
5
|
+
* time and contributes container bindings, hook subscriptions, and
|
|
6
|
+
* boot/dispose queues.
|
|
32
7
|
*/
|
|
33
|
-
import type { Container } from "@nwire/container";
|
|
34
8
|
import { type SourceLocation } from "@nwire/messages";
|
|
35
|
-
import type {
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Describes a value the plugin produces. The framework calls `boot()` to
|
|
39
|
-
* obtain it during app start, registers it on the container under the
|
|
40
|
-
* plugin's chosen name, calls `healthCheck` periodically (if provided),
|
|
41
|
-
* and calls `shutdown(value)` during graceful drain.
|
|
42
|
-
*/
|
|
43
|
-
export interface BindingLifecycle<T> {
|
|
44
|
-
boot(): Promise<T> | T;
|
|
45
|
-
shutdown?(value: T): Promise<void> | void;
|
|
46
|
-
healthCheck?(value: T): Promise<void> | void;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* What the setup closure gets. Every method is a builder — each call
|
|
50
|
-
* accrues into the plugin's contribution to the app's lifecycle. There's
|
|
51
|
-
* no separate "apply" step; the framework collects everything the closure
|
|
52
|
-
* does and threads it into the right lifecycle phases.
|
|
53
|
-
*/
|
|
54
|
-
export interface AppPluginContext {
|
|
55
|
-
/**
|
|
56
|
-
* The app's container. Read-only here; the plugin contributes bindings
|
|
57
|
-
* via `provide()`, not by writing to the container directly. This
|
|
58
|
-
* reference is exposed for the rare case where the plugin needs to
|
|
59
|
-
* inspect existing bindings (e.g., feature detection).
|
|
60
|
-
*/
|
|
61
|
-
readonly container: Container;
|
|
62
|
-
/**
|
|
63
|
-
* The app's framework-event bus. Exposed for advanced cases; most
|
|
64
|
-
* subscriptions go through `on()` for ergonomic registration.
|
|
65
|
-
*/
|
|
66
|
-
readonly bus: FrameworkEventBus;
|
|
67
|
-
/**
|
|
68
|
-
* Register a bootable binding. The framework calls `lifecycle.boot()`
|
|
69
|
-
* during app start, stores the result on the container under `name`,
|
|
70
|
-
* and wires `shutdown` + `healthCheck` into the lifecycle.
|
|
71
|
-
*
|
|
72
|
-
* ```ts
|
|
73
|
-
* provide("db", {
|
|
74
|
-
* boot: () => new Pool(env.DATABASE_URL),
|
|
75
|
-
* shutdown: (pool) => pool.end(),
|
|
76
|
-
* healthCheck: (pool) => pool.query("SELECT 1"),
|
|
77
|
-
* })
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
provide<T>(name: string, lifecycle: BindingLifecycle<T>): void;
|
|
81
|
-
/**
|
|
82
|
-
* Subscribe to a framework event. Higher priorities run earlier in
|
|
83
|
-
* series/series-bail modes (default 0; framework built-ins should use
|
|
84
|
-
* 100+; debug/observer plugins -100).
|
|
85
|
-
*/
|
|
86
|
-
on<TPayload>(event: FrameworkEventDefinition<TPayload>, handler: FrameworkEventHandler<TPayload>, priority?: number): void;
|
|
87
|
-
/**
|
|
88
|
-
* Register a callback that runs once during app boot, after all
|
|
89
|
-
* provided bindings are registered and other plugins' `boot` callbacks
|
|
90
|
-
* have run. Use for warm-up work: prefetching, idempotency caches,
|
|
91
|
-
* pre-flight connectivity checks beyond the basic healthCheck.
|
|
92
|
-
*/
|
|
93
|
-
boot(fn: () => Promise<void> | void): void;
|
|
94
|
-
/**
|
|
95
|
-
* Register a callback that runs during graceful shutdown, BEFORE
|
|
96
|
-
* provided bindings' `shutdown(value)` callbacks. Use for "flush pending
|
|
97
|
-
* work" style cleanup — finalizing analytics events, draining queues
|
|
98
|
-
* the plugin owns directly, etc.
|
|
99
|
-
*/
|
|
100
|
-
shutdown(fn: () => Promise<void> | void): void;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* What the framework receives. Opaque to user code — it carries the setup
|
|
104
|
-
* function the app's lifecycle will invoke during boot, plus identity
|
|
105
|
-
* metadata for diagnostics.
|
|
106
|
-
*
|
|
107
|
-
* Note the `$nwireAppPlugin: true` marker — lets the runtime discriminate
|
|
108
|
-
* this from forge's richer `PluginDefinition` (which has its own marker).
|
|
109
|
-
* Both shapes can be used in the same app's plugin list; the runtime
|
|
110
|
-
* dispatches each through the right wiring path.
|
|
111
|
-
*/
|
|
112
|
-
export interface AppPluginDefinition {
|
|
113
|
-
readonly $nwireAppPlugin: true;
|
|
114
|
-
readonly name: string;
|
|
115
|
-
/**
|
|
116
|
-
* The setup closure. The framework calls this during plugin
|
|
117
|
-
* registration with an `AppPluginContext` bound to the app's container
|
|
118
|
-
* + bus. Returns void or a Promise; if async, the framework awaits
|
|
119
|
-
* before proceeding to provider boot.
|
|
120
|
-
*/
|
|
121
|
-
readonly setup: (ctx: AppPluginContext) => void | Promise<void>;
|
|
122
|
-
/** Call-site of `definePlugin(...)` — Studio + scan render this. */
|
|
9
|
+
import type { PluginContext, PluginDefinition } from "./runtime/index.js";
|
|
10
|
+
export declare function definePlugin(name: string, setup: (ctx: PluginContext) => void): PluginDefinition & {
|
|
123
11
|
readonly $source?: SourceLocation;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
* plugin list. The framework invokes the setup closure once during boot
|
|
128
|
-
* with a context bound to the app's container + bus.
|
|
129
|
-
*
|
|
130
|
-
* ```ts
|
|
131
|
-
* import { definePlugin } from "@nwire/app"
|
|
132
|
-
* import { AppBooted } from "@nwire/app"
|
|
133
|
-
*
|
|
134
|
-
* export const tracingPlugin = definePlugin("tracing", ({ provide, on, boot }) => {
|
|
135
|
-
* provide("tracer", {
|
|
136
|
-
* boot: () => new OtelTracer({ exporter: process.env.OTEL_ENDPOINT }),
|
|
137
|
-
* shutdown: (t) => t.shutdown(),
|
|
138
|
-
* })
|
|
139
|
-
*
|
|
140
|
-
* on(AppBooted, ({ appName }) => {
|
|
141
|
-
* console.log(`tracing started for ${appName}`)
|
|
142
|
-
* })
|
|
143
|
-
*
|
|
144
|
-
* boot(async () => {
|
|
145
|
-
* // warm up — initialize span context, register process exit handlers, …
|
|
146
|
-
* })
|
|
147
|
-
* })
|
|
148
|
-
* ```
|
|
149
|
-
*/
|
|
150
|
-
export declare function definePlugin(name: string, setup: (ctx: AppPluginContext) => void | Promise<void>): AppPluginDefinition;
|
|
151
|
-
/** Type guard — does this look like an app-layer plugin definition? */
|
|
152
|
-
export declare function isAppPlugin(x: unknown): x is AppPluginDefinition;
|
|
153
|
-
//# sourceMappingURL=define-plugin.d.ts.map
|
|
12
|
+
};
|
|
13
|
+
/** Discriminator for runtime-side checks. */
|
|
14
|
+
export declare function isPlugin(x: unknown): x is PluginDefinition;
|
package/dist/define-plugin.js
CHANGED
|
@@ -1,74 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 1. **Container bindings** via `provide(name, lifecycle)` — bootable
|
|
8
|
-
* values (DB pool, Redis client, HTTP client, anything that needs
|
|
9
|
-
* `boot()` + optional `shutdown()` + optional `healthCheck()`).
|
|
10
|
-
* 2. **Event subscriptions** via `on(FrameworkEvent, handler, priority?)`
|
|
11
|
-
* — react to app lifecycle, custom events, or anything else fired
|
|
12
|
-
* through the FrameworkEventBus.
|
|
13
|
-
* 3. **Lifecycle callbacks** via `boot(fn)` + `shutdown(fn)` — async
|
|
14
|
-
* work at known transitions (post-provider-boot, pre-shutdown).
|
|
15
|
-
*
|
|
16
|
-
* This narrow shape covers 100% of "extend the app" cases that don't
|
|
17
|
-
* need forge-specific knowledge. For plugins that need action middleware,
|
|
18
|
-
* actor lifecycle hooks, or `before("action.name", ...)` sugar, use the
|
|
19
|
-
* richer `definePlugin` in `@nwire/forge` — it's a superset that wraps
|
|
20
|
-
* this one.
|
|
21
|
-
*
|
|
22
|
-
* ## When to reach for this vs forge's
|
|
23
|
-
*
|
|
24
|
-
* - Connecting a database / cache / queue / SDK? → this one.
|
|
25
|
-
* - Tracing every dispatched action? → forge's (needs middleware).
|
|
26
|
-
* - Reacting to a workflow saga timer? → forge's (needs actor hooks).
|
|
27
|
-
* - Multi-tenant request context propagation? → forge's (needs middleware).
|
|
28
|
-
* - Custom framework events fired by your own code? → this one.
|
|
29
|
-
*
|
|
30
|
-
* In other words: if you don't need to peek inside action dispatch or
|
|
31
|
-
* actor transitions, you don't need forge's plugin form. Use this one.
|
|
2
|
+
* Plugin factory — wraps the canonical `PluginDefinition` shape with
|
|
3
|
+
* source-location capture so Studio + scan can render where a plugin
|
|
4
|
+
* was declared. Setup runs synchronously at `runtime.registerPlugin`
|
|
5
|
+
* time and contributes container bindings, hook subscriptions, and
|
|
6
|
+
* boot/dispose queues.
|
|
32
7
|
*/
|
|
33
8
|
import { captureSourceLocation } from "@nwire/messages";
|
|
34
|
-
// ─── The factory ──────────────────────────────────────────────────
|
|
35
|
-
/**
|
|
36
|
-
* Build a plugin. The returned value is opaque — pass it to your app's
|
|
37
|
-
* plugin list. The framework invokes the setup closure once during boot
|
|
38
|
-
* with a context bound to the app's container + bus.
|
|
39
|
-
*
|
|
40
|
-
* ```ts
|
|
41
|
-
* import { definePlugin } from "@nwire/app"
|
|
42
|
-
* import { AppBooted } from "@nwire/app"
|
|
43
|
-
*
|
|
44
|
-
* export const tracingPlugin = definePlugin("tracing", ({ provide, on, boot }) => {
|
|
45
|
-
* provide("tracer", {
|
|
46
|
-
* boot: () => new OtelTracer({ exporter: process.env.OTEL_ENDPOINT }),
|
|
47
|
-
* shutdown: (t) => t.shutdown(),
|
|
48
|
-
* })
|
|
49
|
-
*
|
|
50
|
-
* on(AppBooted, ({ appName }) => {
|
|
51
|
-
* console.log(`tracing started for ${appName}`)
|
|
52
|
-
* })
|
|
53
|
-
*
|
|
54
|
-
* boot(async () => {
|
|
55
|
-
* // warm up — initialize span context, register process exit handlers, …
|
|
56
|
-
* })
|
|
57
|
-
* })
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
9
|
export function definePlugin(name, setup) {
|
|
10
|
+
const $source = captureSourceLocation();
|
|
61
11
|
return {
|
|
62
|
-
$nwireAppPlugin: true,
|
|
63
12
|
name,
|
|
64
13
|
setup,
|
|
65
|
-
$source
|
|
14
|
+
$source,
|
|
66
15
|
};
|
|
67
16
|
}
|
|
68
|
-
/**
|
|
69
|
-
export function
|
|
17
|
+
/** Discriminator for runtime-side checks. */
|
|
18
|
+
export function isPlugin(x) {
|
|
70
19
|
return (typeof x === "object" &&
|
|
71
20
|
x !== null &&
|
|
72
|
-
x
|
|
21
|
+
typeof x.name === "string" &&
|
|
22
|
+
typeof x.setup === "function");
|
|
73
23
|
}
|
|
74
|
-
//# sourceMappingURL=define-plugin.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-runtime framework-hook registry. Each slot is a `Hook<TPayload>`
|
|
3
|
+
* accessed as a typed property on `runtime.hooks`. Plugins extend via
|
|
4
|
+
* TS module augmentation + `runtime.defineHook(name)`.
|
|
5
|
+
*/
|
|
6
|
+
import { type Hook } from "@nwire/hooks";
|
|
7
|
+
/**
|
|
8
|
+
* Plugins can distinguish "module compiled onto plugin lifecycle" from a
|
|
9
|
+
* normal plugin. Lifecycle payloads include this so observers can filter.
|
|
10
|
+
*/
|
|
11
|
+
export type PluginKind = "plugin" | "module";
|
|
12
|
+
/**
|
|
13
|
+
* The typed registry. Plugin packages augment this interface to add their
|
|
14
|
+
* own slots; the foundation contributes the lifecycle slots below.
|
|
15
|
+
*
|
|
16
|
+
* Every slot is `Hook<TPayload>` — no dispatch-mode discriminator.
|
|
17
|
+
* Consumers call `.use()` to participate in the chain (with optional veto
|
|
18
|
+
* via not calling `next()`), `.on()` to observe in parallel, `.tap()` to
|
|
19
|
+
* receive step-level telemetry.
|
|
20
|
+
*/
|
|
21
|
+
export interface FrameworkHooks {
|
|
22
|
+
AppRegistering: Hook<{
|
|
23
|
+
readonly appName: string;
|
|
24
|
+
}>;
|
|
25
|
+
AppBooting: Hook<{
|
|
26
|
+
readonly appName: string;
|
|
27
|
+
}>;
|
|
28
|
+
AppBooted: Hook<{
|
|
29
|
+
readonly appName: string;
|
|
30
|
+
readonly bootedAt: string;
|
|
31
|
+
}>;
|
|
32
|
+
AppReady: Hook<{
|
|
33
|
+
readonly appName: string;
|
|
34
|
+
readonly readyAt: string;
|
|
35
|
+
}>;
|
|
36
|
+
AppShuttingDown: Hook<{
|
|
37
|
+
readonly appName: string;
|
|
38
|
+
readonly reason?: string;
|
|
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
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
/** Canonical names for the built-in slots — used to label the underlying Hooks. */
|
|
86
|
+
declare const BUILT_IN_HOOK_NAMES: {
|
|
87
|
+
readonly AppRegistering: "nwire.app.registering";
|
|
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";
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Construct the per-runtime registry, with every built-in slot
|
|
104
|
+
* pre-instantiated. Plugin slots added via `runtime.defineHook(name)`
|
|
105
|
+
* land on the same registry instance.
|
|
106
|
+
*/
|
|
107
|
+
export declare function createFrameworkHooks(): FrameworkHooks;
|
|
108
|
+
/** True if `slot` is one of the built-in hook keys. */
|
|
109
|
+
export declare function isBuiltInHook(slot: string): slot is keyof typeof BUILT_IN_HOOK_NAMES;
|
|
110
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-runtime framework-hook registry. Each slot is a `Hook<TPayload>`
|
|
3
|
+
* accessed as a typed property on `runtime.hooks`. Plugins extend via
|
|
4
|
+
* TS module augmentation + `runtime.defineHook(name)`.
|
|
5
|
+
*/
|
|
6
|
+
import { hook } from "@nwire/hooks";
|
|
7
|
+
/** Canonical names for the built-in slots — used to label the underlying Hooks. */
|
|
8
|
+
const BUILT_IN_HOOK_NAMES = {
|
|
9
|
+
AppRegistering: "nwire.app.registering",
|
|
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",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Construct the per-runtime registry, with every built-in slot
|
|
26
|
+
* pre-instantiated. Plugin slots added via `runtime.defineHook(name)`
|
|
27
|
+
* land on the same registry instance.
|
|
28
|
+
*/
|
|
29
|
+
export function createFrameworkHooks() {
|
|
30
|
+
const registry = {};
|
|
31
|
+
for (const [slot, name] of Object.entries(BUILT_IN_HOOK_NAMES)) {
|
|
32
|
+
registry[slot] = hook(name);
|
|
33
|
+
}
|
|
34
|
+
return registry;
|
|
35
|
+
}
|
|
36
|
+
/** True if `slot` is one of the built-in hook keys. */
|
|
37
|
+
export function isBuiltInHook(slot) {
|
|
38
|
+
return slot in BUILT_IN_HOOK_NAMES;
|
|
39
|
+
}
|