@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/README.md
CHANGED
|
@@ -1,116 +1,102 @@
|
|
|
1
1
|
# @nwire/app
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
The "app" layer of the sealed architecture. Ships `definePlugin`, the
|
|
6
|
-
typed `FrameworkEventBus`, and the built-in lifecycle events. The
|
|
7
|
-
composition root (`createApp`) currently lives in `@nwire/forge` and
|
|
8
|
-
re-exports everything here so a single import line keeps working.
|
|
3
|
+
> The composition root. `createApp`, `appCompose`, `definePlugin`,
|
|
4
|
+
> runtime, framework events.
|
|
9
5
|
|
|
10
6
|
```bash
|
|
11
7
|
pnpm add @nwire/app
|
|
12
8
|
```
|
|
13
9
|
|
|
14
|
-
## Quick
|
|
10
|
+
## Quick start
|
|
15
11
|
|
|
16
12
|
```ts
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
PluginBooting,
|
|
21
|
-
defineFrameworkEvent,
|
|
22
|
-
definePlugin,
|
|
23
|
-
} from "@nwire/app";
|
|
24
|
-
import { NoopLogger } from "@nwire/logger";
|
|
25
|
-
|
|
26
|
-
const bus = new FrameworkEventBus(new NoopLogger());
|
|
27
|
-
|
|
28
|
-
bus.on(AppBooted, ({ appName, bootedAt }) => {
|
|
29
|
-
console.log(`${appName} booted at ${bootedAt}`);
|
|
30
|
-
});
|
|
13
|
+
import { createApp } from "@nwire/app";
|
|
14
|
+
import { post } from "@nwire/wires/http";
|
|
15
|
+
import { z } from "zod";
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
bus.on(PluginBooting, ({ pluginName }) => {
|
|
34
|
-
if (pluginName === "broken") return false;
|
|
35
|
-
});
|
|
17
|
+
const app = createApp({ appName: "api" });
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"parallel",
|
|
19
|
+
app.wire(
|
|
20
|
+
post("/hello", { body: z.object({ name: z.string() }) }),
|
|
21
|
+
async (input) => ({ message: `Hello, ${input.name}!` }),
|
|
41
22
|
);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`createApp` builds an **App** — a bounded context with its container,
|
|
26
|
+
plugins, and wires. The App is the unit you `endpoint().mount()` later.
|
|
27
|
+
|
|
28
|
+
## Plugins
|
|
29
|
+
|
|
30
|
+
`definePlugin(name, setup)` declares an installable plugin. Plugins
|
|
31
|
+
receive a `PluginContext` with `bind` (register on the container) and
|
|
32
|
+
`dispose` (run on shutdown):
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { definePlugin } from "@nwire/app";
|
|
36
|
+
|
|
37
|
+
const todoStorePlugin = () =>
|
|
38
|
+
definePlugin("todo-store", ({ bind, dispose }) => {
|
|
39
|
+
const store = new TodoStore();
|
|
40
|
+
bind("todos", store);
|
|
41
|
+
dispose(async () => store.close());
|
|
54
42
|
});
|
|
43
|
+
|
|
44
|
+
const app = createApp({
|
|
45
|
+
appName: "todo",
|
|
46
|
+
plugins: [todoStorePlugin()],
|
|
55
47
|
});
|
|
56
48
|
```
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
The runtime boots plugins in declaration order and disposes them in
|
|
51
|
+
reverse on shutdown.
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
62
|
-
| `FrameworkEventBus` | Typed dispatcher; one per app instance. |
|
|
63
|
-
| `defineFrameworkEvent` | Declare a typed event + its dispatch mode. |
|
|
64
|
-
| `definePlugin(name, setup)` | Narrow plugin: `provide` / `on` / `boot` / `shutdown`. |
|
|
65
|
-
| `isAppPlugin(x)` | Type guard for `definePlugin` output. |
|
|
66
|
-
| `AppRegistering` (series-bail) | Before provider boot — vetoable. |
|
|
67
|
-
| `AppBooting` (series-bail) | After provider boot, before plugin boot — vetoable. |
|
|
68
|
-
| `AppBooted` (parallel) | All plugins booted. |
|
|
69
|
-
| `AppReady` (parallel) | Wire mounted, K8s `/ready` flipped. |
|
|
70
|
-
| `AppShuttingDown` (series-bail) | Vetoable shutdown signal. |
|
|
71
|
-
| `AppShutdown` (parallel) | Everything torn down. |
|
|
72
|
-
| `PluginRegistered` / `PluginBooting` / `PluginBooted` / `PluginShuttingDown` / `PluginShutdown` | Per-plugin lifecycle, all carry an optional `kind: "plugin" \| "module"`. |
|
|
73
|
-
| `WireMounting` (series-bail) / `WireMounted` / `WireUnmounted` | Transport mount lifecycle, fired by `@nwire/endpoint`. |
|
|
74
|
-
| `builtInLifecycleEvents` | Array of every built-in above — useful for observability adapters. |
|
|
75
|
-
|
|
76
|
-
### Dispatch modes (tense convention)
|
|
77
|
-
|
|
78
|
-
| Mode | Tense | Semantics |
|
|
79
|
-
| ------------- | ------- | ------------------------------------------------------------- |
|
|
80
|
-
| `series-bail` | `*-ing` | Sequential await. Return `false` to short-circuit. |
|
|
81
|
-
| `series` | (rare) | Sequential await. Throw stops the chain. |
|
|
82
|
-
| `parallel` | `*-ed` | `Promise.allSettled`; one failing handler logs but continues. |
|
|
83
|
-
|
|
84
|
-
`FrameworkEventBus.fire()` returns `false` only when a `series-bail`
|
|
85
|
-
handler vetoed; `true` otherwise.
|
|
86
|
-
|
|
87
|
-
### Module-as-plugin
|
|
88
|
-
|
|
89
|
-
Modules participate in the same plugin lifecycle. `PluginRegistered` /
|
|
90
|
-
`PluginBooting` / `PluginBooted` payloads carry an optional
|
|
91
|
-
`kind: "plugin" | "module"` field so Studio + observability adapters
|
|
92
|
-
render each with the right affordance.
|
|
93
|
-
|
|
94
|
-
## When to reach for which `definePlugin`
|
|
95
|
-
|
|
96
|
-
| You need… | Use |
|
|
97
|
-
| ---------------------------------------- | -------------- |
|
|
98
|
-
| Bind a DB / cache / SDK to the container | `@nwire/app` |
|
|
99
|
-
| React to lifecycle events | `@nwire/app` |
|
|
100
|
-
| Intercept every action dispatch | `@nwire/forge` |
|
|
101
|
-
| Hook actor transitions | `@nwire/forge` |
|
|
102
|
-
| `before("action.x", ...)` sugar | `@nwire/forge` |
|
|
103
|
-
|
|
104
|
-
`@nwire/forge`'s richer `definePlugin` is a superset — both shapes
|
|
105
|
-
coexist in the same plugin list and the runtime dispatches each through
|
|
106
|
-
the right wiring path.
|
|
53
|
+
## Multi-app composition
|
|
107
54
|
|
|
108
|
-
|
|
55
|
+
Apps don't have to be the whole process. `appCompose(...apps)` merges
|
|
56
|
+
several Apps into one — each keeps its own container and plugins;
|
|
57
|
+
adopters see the merged wire collection but routes back to the source
|
|
58
|
+
app's container per dispatch.
|
|
109
59
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- `@nwire/endpoint` — fires `WireMounting` / `WireMounted` / `WireUnmounted` on this bus.
|
|
60
|
+
```ts
|
|
61
|
+
import { appCompose, createApp } from "@nwire/app";
|
|
113
62
|
|
|
114
|
-
|
|
63
|
+
const ordersApp = createApp({ appName: "orders" /* ... */ });
|
|
64
|
+
const inventoryApp = createApp({ appName: "inventory" /* ... */ });
|
|
65
|
+
|
|
66
|
+
const monolith = appCompose(ordersApp, inventoryApp);
|
|
67
|
+
await endpoint("monolith", { port: 3000 })
|
|
68
|
+
.use(httpKoa())
|
|
69
|
+
.mount(monolith)
|
|
70
|
+
.run();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Framework events
|
|
74
|
+
|
|
75
|
+
The runtime fires typed events at lifecycle transitions —
|
|
76
|
+
`AppBooting`, `AppBooted`, `PluginBooting`, `PluginBooted`,
|
|
77
|
+
`AppStopping`, `AppStopped`. Subscribe via the App's `FrameworkEventBus`
|
|
78
|
+
or register a `FrameworkEventBus` hook from a plugin.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { AppBooted } from "@nwire/app";
|
|
82
|
+
|
|
83
|
+
app.frameworkBus.on(AppBooted, ({ appName, bootedAt }) => {
|
|
84
|
+
console.log(`${appName} booted at ${bootedAt}`);
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Surface
|
|
89
|
+
|
|
90
|
+
- `createApp(opts)` — builds an App
|
|
91
|
+
- `appCompose(...apps)` — merges Apps for monolith composition
|
|
92
|
+
- `definePlugin(name, setup)` — plugin factory
|
|
93
|
+
- `App`, `CreateAppOptions`, `PluginContext`, `PluginDefinition` — core types
|
|
94
|
+
- `Runtime`, `createRuntime` — the per-App runtime
|
|
95
|
+
- `FrameworkEventBus`, lifecycle events — `AppBooting`, `AppBooted`, etc.
|
|
96
|
+
|
|
97
|
+
## Related
|
|
115
98
|
|
|
116
|
-
|
|
99
|
+
- [`@nwire/endpoint`](../core-endpoint) — mounts the App under adopters
|
|
100
|
+
- [`@nwire/wires`](../core-wires) — binding helpers used by `app.wire`
|
|
101
|
+
- [`@nwire/container`](../core-container) — DI binding the App owns
|
|
102
|
+
- [`@nwire/forge`](../core-forge) — `createForgePlugin` for actions/workflows
|
package/dist/app.d.ts
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nwire/app` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* boots them in order, exposes a Container, and fires framework events at
|
|
6
|
-
* every lifecycle transition.
|
|
7
|
-
*
|
|
8
|
-
* What it ships:
|
|
9
|
-
*
|
|
10
|
-
* - `defineFrameworkEvent<TPayload>(name, mode)` — declare a typed
|
|
11
|
-
* lifecycle event (`series-bail` / `parallel` / `series` dispatch).
|
|
12
|
-
* - `FrameworkEventBus` — the dispatcher; one per app instance.
|
|
13
|
-
* - `definePlugin(name, setup)` — the only extension primitive.
|
|
14
|
-
* - `App*` events (`AppRegistering` / `AppBooting` / `AppBooted` /
|
|
15
|
-
* `AppReady` / `AppShuttingDown` / `AppShutdown`) plus their plugin
|
|
16
|
-
* counterparts (`PluginRegistered` / `PluginBooting` / `PluginBooted`).
|
|
17
|
-
*
|
|
18
|
-
* `createApp` (the composition root) currently lives in `@nwire/forge`; it
|
|
19
|
-
* will move here in a follow-up. Forge re-exports the framework-event types
|
|
20
|
-
* so consumers don't need to update imports.
|
|
2
|
+
* `@nwire/app` — composition root. `createApp` constructs a Runtime, runs
|
|
3
|
+
* plugin setup, and drives start/stop. `createRuntime` exposes the raw
|
|
4
|
+
* substrate for advanced cases.
|
|
21
5
|
*/
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export { definePlugin, isAppPlugin, type AppPluginContext, type AppPluginDefinition, type BindingLifecycle, } from "./define-plugin.js";
|
|
6
|
+
export { Runtime, createRuntime, serializeError, type DispatchHookCtx, type DispatchMiddleware, type HookStepTelemetry, type RuntimeOptions, type SerializedError, type Telemetry, type TelemetryListener, type FrameworkHooks, type PluginKind, type PluginDefinition, type PluginContext, } from "./runtime/index.js";
|
|
7
|
+
export { definePlugin, isPlugin } from "./define-plugin.js";
|
|
25
8
|
export { createApp, type App, type CreateAppOptions } from "./create-app.js";
|
|
26
|
-
export {
|
|
27
|
-
|
|
9
|
+
export { appCompose, containerOf, type AppComposeOptions, type CollisionPolicy, } from "./compose-app.js";
|
|
10
|
+
export type { HandlerBaseCtx, Ctx } from "@nwire/handler";
|
package/dist/app.js
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nwire/app` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* boots them in order, exposes a Container, and fires framework events at
|
|
6
|
-
* every lifecycle transition.
|
|
7
|
-
*
|
|
8
|
-
* What it ships:
|
|
9
|
-
*
|
|
10
|
-
* - `defineFrameworkEvent<TPayload>(name, mode)` — declare a typed
|
|
11
|
-
* lifecycle event (`series-bail` / `parallel` / `series` dispatch).
|
|
12
|
-
* - `FrameworkEventBus` — the dispatcher; one per app instance.
|
|
13
|
-
* - `definePlugin(name, setup)` — the only extension primitive.
|
|
14
|
-
* - `App*` events (`AppRegistering` / `AppBooting` / `AppBooted` /
|
|
15
|
-
* `AppReady` / `AppShuttingDown` / `AppShutdown`) plus their plugin
|
|
16
|
-
* counterparts (`PluginRegistered` / `PluginBooting` / `PluginBooted`).
|
|
17
|
-
*
|
|
18
|
-
* `createApp` (the composition root) currently lives in `@nwire/forge`; it
|
|
19
|
-
* will move here in a follow-up. Forge re-exports the framework-event types
|
|
20
|
-
* so consumers don't need to update imports.
|
|
2
|
+
* `@nwire/app` — composition root. `createApp` constructs a Runtime, runs
|
|
3
|
+
* plugin setup, and drives start/stop. `createRuntime` exposes the raw
|
|
4
|
+
* substrate for advanced cases.
|
|
21
5
|
*/
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export { definePlugin, isAppPlugin, } from "./define-plugin.js";
|
|
6
|
+
export { Runtime, createRuntime, serializeError, } from "./runtime/index.js";
|
|
7
|
+
export { definePlugin, isPlugin } from "./define-plugin.js";
|
|
25
8
|
export { createApp } from "./create-app.js";
|
|
26
|
-
export {
|
|
27
|
-
//# sourceMappingURL=app.js.map
|
|
9
|
+
export { appCompose, containerOf, } from "./compose-app.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `appCompose(...apps)` — multi-app composition.
|
|
3
|
+
*
|
|
4
|
+
* Takes one-or-more `App` values and returns ONE composite App whose
|
|
5
|
+
* `interface` merges every child's wires (tagged by the source app), and
|
|
6
|
+
* whose `containerOf(wire)` resolver routes each wire back to its source
|
|
7
|
+
* app's container.
|
|
8
|
+
*
|
|
9
|
+
* const orders = createApp({ appName: "orders", ... }).wire(post("/orders"), placeOrder);
|
|
10
|
+
* const billing = createApp({ appName: "billing", ... }).wire(post("/invoices"), createInvoice);
|
|
11
|
+
*
|
|
12
|
+
* const composite = appCompose(orders, billing);
|
|
13
|
+
*
|
|
14
|
+
* // composite is itself an App: endpoint.use(adopter).mount(composite).run()
|
|
15
|
+
*
|
|
16
|
+
* Optional third-arg overload — pass `{ onCollision }` as the last arg
|
|
17
|
+
* to override the default collision policy (throw on duplicate
|
|
18
|
+
* `(adapter, key)` pairs across child apps).
|
|
19
|
+
*/
|
|
20
|
+
import { type Wire } from "@nwire/wires";
|
|
21
|
+
import type { App } from "./create-app.js";
|
|
22
|
+
export type CollisionPolicy = "throw" | "first" | "last";
|
|
23
|
+
export interface AppComposeOptions {
|
|
24
|
+
readonly onCollision?: CollisionPolicy;
|
|
25
|
+
}
|
|
26
|
+
export declare function appCompose(...args: [...App[], AppComposeOptions]): App;
|
|
27
|
+
export declare function appCompose(...apps: App[]): App;
|
|
28
|
+
/**
|
|
29
|
+
* `containerOf(wire)` — resolves a wire to its source app's container.
|
|
30
|
+
* Endpoint adopters call this when building per-request scopes so the
|
|
31
|
+
* resolve path lands in the right app. For wires that came in via
|
|
32
|
+
* `app.wire()` (not through `appCompose`) the wire carries no source
|
|
33
|
+
* reference, and adopters fall back to the endpoint's default container.
|
|
34
|
+
*/
|
|
35
|
+
export declare function containerOf(wire: Wire): any | undefined;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `appCompose(...apps)` — multi-app composition.
|
|
3
|
+
*
|
|
4
|
+
* Takes one-or-more `App` values and returns ONE composite App whose
|
|
5
|
+
* `interface` merges every child's wires (tagged by the source app), and
|
|
6
|
+
* whose `containerOf(wire)` resolver routes each wire back to its source
|
|
7
|
+
* app's container.
|
|
8
|
+
*
|
|
9
|
+
* const orders = createApp({ appName: "orders", ... }).wire(post("/orders"), placeOrder);
|
|
10
|
+
* const billing = createApp({ appName: "billing", ... }).wire(post("/invoices"), createInvoice);
|
|
11
|
+
*
|
|
12
|
+
* const composite = appCompose(orders, billing);
|
|
13
|
+
*
|
|
14
|
+
* // composite is itself an App: endpoint.use(adopter).mount(composite).run()
|
|
15
|
+
*
|
|
16
|
+
* Optional third-arg overload — pass `{ onCollision }` as the last arg
|
|
17
|
+
* to override the default collision policy (throw on duplicate
|
|
18
|
+
* `(adapter, key)` pairs across child apps).
|
|
19
|
+
*/
|
|
20
|
+
import { createInterface } from "@nwire/wires";
|
|
21
|
+
/**
|
|
22
|
+
* Derive a stable key for collision detection from a wire's binding.
|
|
23
|
+
* Different adopters expose different identifying fields (path / queue /
|
|
24
|
+
* schedule / tool / field); the composer reads them generically.
|
|
25
|
+
*/
|
|
26
|
+
function bindingKey(wire) {
|
|
27
|
+
const b = wire.binding;
|
|
28
|
+
const adapter = b.$adapter;
|
|
29
|
+
// Pick the most-distinctive field per known adapter kind.
|
|
30
|
+
const id = b.path ??
|
|
31
|
+
b.queue ??
|
|
32
|
+
b.schedule ??
|
|
33
|
+
b.tool ??
|
|
34
|
+
b.field ??
|
|
35
|
+
b.uriTemplate ??
|
|
36
|
+
"?";
|
|
37
|
+
const verb = b.verb ?? "";
|
|
38
|
+
return `${adapter}:${verb ? `${verb}:` : ""}${id}`;
|
|
39
|
+
}
|
|
40
|
+
function isComposeOptions(x) {
|
|
41
|
+
return (typeof x === "object" &&
|
|
42
|
+
x !== null &&
|
|
43
|
+
!("interface" in x) &&
|
|
44
|
+
"onCollision" in x);
|
|
45
|
+
}
|
|
46
|
+
export function appCompose(...args) {
|
|
47
|
+
// Trailing options arg?
|
|
48
|
+
const last = args.length > 0 ? args[args.length - 1] : undefined;
|
|
49
|
+
const options = last && isComposeOptions(last) ? last : {};
|
|
50
|
+
const apps = (last && isComposeOptions(last) ? args.slice(0, -1) : args);
|
|
51
|
+
if (apps.length === 0) {
|
|
52
|
+
throw new Error("appCompose: at least one app is required");
|
|
53
|
+
}
|
|
54
|
+
if (apps.length === 1)
|
|
55
|
+
return apps[0];
|
|
56
|
+
const policy = options.onCollision ?? "throw";
|
|
57
|
+
const mergedInterface = createInterface();
|
|
58
|
+
const seen = new Map();
|
|
59
|
+
for (const childApp of apps) {
|
|
60
|
+
for (const wire of childApp.interface.wires) {
|
|
61
|
+
const key = bindingKey(wire);
|
|
62
|
+
const prior = seen.get(key);
|
|
63
|
+
if (prior && prior !== childApp) {
|
|
64
|
+
if (policy === "throw") {
|
|
65
|
+
throw new Error(`appCompose: wire collision on "${key}" — first registered by app "${prior.appName}", colliding from app "${childApp.appName}". ` +
|
|
66
|
+
`Pass { onCollision: "first" | "last" } to override.`);
|
|
67
|
+
}
|
|
68
|
+
if (policy === "first")
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Tag the wire with its source app so containerOf can route correctly.
|
|
72
|
+
mergedInterface.wire(wire.binding, wire.handler);
|
|
73
|
+
// Stash the source-app reference on the last appended wire.
|
|
74
|
+
const appended = mergedInterface.wires[mergedInterface.wires.length - 1];
|
|
75
|
+
appended.app = childApp;
|
|
76
|
+
seen.set(key, childApp);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// The composite presents as an App. It re-uses the FIRST child's
|
|
80
|
+
// runtime / container / start / stop / etc. — the composite is for
|
|
81
|
+
// wire surface, not for runtime fan-out. Multi-runtime composition is
|
|
82
|
+
// not supported in 0.10; if you want it, mount each app under its own
|
|
83
|
+
// endpoint instead.
|
|
84
|
+
const head = apps[0];
|
|
85
|
+
const compositeName = apps.map((a) => a.appName).join("+");
|
|
86
|
+
const composite = {
|
|
87
|
+
$nwireApp: true,
|
|
88
|
+
$kind: "app",
|
|
89
|
+
appName: compositeName,
|
|
90
|
+
name: compositeName,
|
|
91
|
+
container: head.container,
|
|
92
|
+
runtime: head.runtime,
|
|
93
|
+
interface: mergedInterface,
|
|
94
|
+
plugins: apps.flatMap((a) => a.plugins),
|
|
95
|
+
wire(binding, handler) {
|
|
96
|
+
mergedInterface.wire(binding, handler);
|
|
97
|
+
return composite;
|
|
98
|
+
},
|
|
99
|
+
provide(builder) {
|
|
100
|
+
mergedInterface.provide(builder);
|
|
101
|
+
return composite;
|
|
102
|
+
},
|
|
103
|
+
async start() {
|
|
104
|
+
for (const a of apps)
|
|
105
|
+
await a.start();
|
|
106
|
+
},
|
|
107
|
+
async stop(reason) {
|
|
108
|
+
for (const a of [...apps].reverse())
|
|
109
|
+
await a.stop(reason);
|
|
110
|
+
},
|
|
111
|
+
boot() {
|
|
112
|
+
return composite.start();
|
|
113
|
+
},
|
|
114
|
+
shutdown() {
|
|
115
|
+
return composite.stop();
|
|
116
|
+
},
|
|
117
|
+
async dispatchFrameworkEvent(slot, payload) {
|
|
118
|
+
for (const a of apps) {
|
|
119
|
+
const ok = await a.dispatchFrameworkEvent(slot, payload);
|
|
120
|
+
if (!ok)
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
return composite;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `containerOf(wire)` — resolves a wire to its source app's container.
|
|
130
|
+
* Endpoint adopters call this when building per-request scopes so the
|
|
131
|
+
* resolve path lands in the right app. For wires that came in via
|
|
132
|
+
* `app.wire()` (not through `appCompose`) the wire carries no source
|
|
133
|
+
* reference, and adopters fall back to the endpoint's default container.
|
|
134
|
+
*/
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
export function containerOf(wire) {
|
|
137
|
+
return wire.app?.container;
|
|
138
|
+
}
|
package/dist/create-app.d.ts
CHANGED
|
@@ -1,87 +1,59 @@
|
|
|
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 { type Container } from "@nwire/container";
|
|
5
|
+
import { type Container } from "@nwire/container/awilix";
|
|
33
6
|
import { type Logger } from "@nwire/logger";
|
|
34
|
-
import {
|
|
35
|
-
import { type
|
|
7
|
+
import { type Binding, type HandlerDef, type Interface, type WireCtxBuilder } from "@nwire/wires";
|
|
8
|
+
import { type PluginDefinition, type Runtime } from "./runtime/index.js";
|
|
36
9
|
export interface CreateAppOptions {
|
|
37
|
-
/** App name — surfaces in framework-
|
|
10
|
+
/** App name — surfaces in framework-hook payloads + dev logger output. */
|
|
38
11
|
readonly appName: string;
|
|
39
|
-
/**
|
|
40
|
-
readonly plugins?: readonly
|
|
12
|
+
/** Plugins. Setup runs synchronously at construction. */
|
|
13
|
+
readonly plugins?: readonly PluginDefinition[];
|
|
14
|
+
/**
|
|
15
|
+
* Handlers — registered on the runtime at construction so any plugin
|
|
16
|
+
* (forge, queue, …) can pick them up during boot. Each handler must
|
|
17
|
+
* satisfy the structural shape `runtime.registerHandler` accepts
|
|
18
|
+
* (`name`, `input`, `run(ctx, opts?)` returning `{ result }`).
|
|
19
|
+
*/
|
|
20
|
+
readonly handlers?: readonly any[];
|
|
41
21
|
/** Override the container — defaults to a fresh `createContainer()`. */
|
|
42
22
|
readonly container?: Container;
|
|
43
|
-
/** Override the
|
|
44
|
-
readonly
|
|
45
|
-
/** Logger
|
|
23
|
+
/** Override the runtime — defaults to a fresh `createRuntime({...})`. */
|
|
24
|
+
readonly runtime?: Runtime;
|
|
25
|
+
/** Logger override. */
|
|
46
26
|
readonly logger?: Logger;
|
|
47
27
|
}
|
|
48
|
-
/**
|
|
49
|
-
* App handle — structurally `AppServable` for `@nwire/endpoint`. The
|
|
50
|
-
* `runtime` field is `undefined` here (no forge); `@nwire/forge`'s richer
|
|
51
|
-
* createApp returns the same shape with `runtime` populated.
|
|
52
|
-
*/
|
|
53
28
|
export interface App {
|
|
54
29
|
readonly $nwireApp: true;
|
|
30
|
+
readonly $kind: "app";
|
|
55
31
|
readonly appName: string;
|
|
32
|
+
/** Alias for appName — foundation-spec name field. */
|
|
33
|
+
readonly name: string;
|
|
56
34
|
readonly container: Container;
|
|
57
|
-
readonly
|
|
58
|
-
|
|
35
|
+
readonly runtime: Runtime;
|
|
36
|
+
/**
|
|
37
|
+
* Wire collection — the same shape as `createInterface()` standalone.
|
|
38
|
+
* Adopters consume `app.interface.forAdapter(kind)`; foundation-form
|
|
39
|
+
* apps wire bindings via `app.wire(b, h)` sugar.
|
|
40
|
+
*/
|
|
41
|
+
readonly interface: Interface;
|
|
42
|
+
readonly plugins: readonly PluginDefinition[];
|
|
43
|
+
/** Sugar for `app.interface.wire(b, h)`. Returns the app for chaining. */
|
|
44
|
+
wire(binding: Binding, handler: HandlerDef): this;
|
|
45
|
+
/** Sugar for `app.interface.provide(builder)`. Returns the app for chaining. */
|
|
46
|
+
provide<TExtras extends object>(builder: WireCtxBuilder<TExtras>): this;
|
|
59
47
|
start(): Promise<void>;
|
|
60
48
|
stop(reason?: string): Promise<void>;
|
|
61
|
-
/**
|
|
49
|
+
/** Endpoint alias. */
|
|
62
50
|
boot(): Promise<void>;
|
|
63
|
-
/**
|
|
51
|
+
/** Endpoint alias. */
|
|
64
52
|
shutdown(): Promise<void>;
|
|
65
53
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* and `nwire.app.ready` at the right serve-loop points. Returns `false`
|
|
69
|
-
* when a series-bail subscriber prevented; `true` otherwise.
|
|
54
|
+
* Run a built-in framework hook by registry-slot name. Resolves `false`
|
|
55
|
+
* when a chain step vetoed (skipped `next()` or threw); `true` otherwise.
|
|
70
56
|
*/
|
|
71
|
-
dispatchFrameworkEvent(
|
|
57
|
+
dispatchFrameworkEvent(slot: string, payload: unknown): Promise<boolean>;
|
|
72
58
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Build a generic Nwire app from plugin definitions. No forge concepts —
|
|
75
|
-
* use `@nwire/forge`'s `createApp` for the rich CQRS / actor / workflow
|
|
76
|
-
* surface.
|
|
77
|
-
*
|
|
78
|
-
* const app = createApp({
|
|
79
|
-
* appName: "users",
|
|
80
|
-
* plugins: [authPlugin, dbPlugin],
|
|
81
|
-
* });
|
|
82
|
-
* await app.start();
|
|
83
|
-
* // app.container.cradle.db is now resolvable
|
|
84
|
-
* await app.stop();
|
|
85
|
-
*/
|
|
86
59
|
export declare function createApp(options: CreateAppOptions): App;
|
|
87
|
-
//# sourceMappingURL=create-app.d.ts.map
|