@nwire/app 0.9.2 → 0.10.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.
Files changed (46) hide show
  1. package/README.md +81 -95
  2. package/dist/app.d.ts +7 -24
  3. package/dist/app.js +6 -24
  4. package/dist/compose-app.d.ts +35 -0
  5. package/dist/compose-app.js +138 -0
  6. package/dist/create-app.d.ts +38 -66
  7. package/dist/create-app.js +42 -216
  8. package/dist/define-plugin.d.ts +10 -149
  9. package/dist/define-plugin.js +11 -62
  10. package/dist/runtime/framework-hooks.d.ts +110 -0
  11. package/dist/runtime/framework-hooks.js +39 -0
  12. package/dist/runtime/index.d.ts +6 -0
  13. package/dist/runtime/index.js +6 -0
  14. package/dist/runtime/runtime.d.ts +349 -0
  15. package/dist/runtime/runtime.js +642 -0
  16. package/package.json +8 -5
  17. package/dist/__tests__/create-app.test.d.ts +0 -6
  18. package/dist/__tests__/create-app.test.d.ts.map +0 -1
  19. package/dist/__tests__/create-app.test.js +0 -126
  20. package/dist/__tests__/create-app.test.js.map +0 -1
  21. package/dist/__tests__/define-plugin.test.d.ts +0 -16
  22. package/dist/__tests__/define-plugin.test.d.ts.map +0 -1
  23. package/dist/__tests__/define-plugin.test.js +0 -269
  24. package/dist/__tests__/define-plugin.test.js.map +0 -1
  25. package/dist/__tests__/framework-events.test.d.ts +0 -18
  26. package/dist/__tests__/framework-events.test.d.ts.map +0 -1
  27. package/dist/__tests__/framework-events.test.js +0 -156
  28. package/dist/__tests__/framework-events.test.js.map +0 -1
  29. package/dist/app.d.ts.map +0 -1
  30. package/dist/app.js.map +0 -1
  31. package/dist/create-app.d.ts.map +0 -1
  32. package/dist/create-app.js.map +0 -1
  33. package/dist/define-plugin.d.ts.map +0 -1
  34. package/dist/define-plugin.js.map +0 -1
  35. package/dist/framework-event-bus.d.ts +0 -129
  36. package/dist/framework-event-bus.d.ts.map +0 -1
  37. package/dist/framework-event-bus.js +0 -188
  38. package/dist/framework-event-bus.js.map +0 -1
  39. package/dist/framework-events.d.ts +0 -233
  40. package/dist/framework-events.d.ts.map +0 -1
  41. package/dist/framework-events.js +0 -136
  42. package/dist/framework-events.js.map +0 -1
  43. package/dist/runtime.d.ts +0 -185
  44. package/dist/runtime.d.ts.map +0 -1
  45. package/dist/runtime.js +0 -197
  46. package/dist/runtime.js.map +0 -1
package/README.md CHANGED
@@ -1,116 +1,102 @@
1
1
  # @nwire/app
2
2
 
3
- > Plugin primitive, framework-event bus, and the lifecycle event catalog every app fires.
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 example
10
+ ## Quick start
15
11
 
16
12
  ```ts
17
- import {
18
- FrameworkEventBus,
19
- AppBooted,
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
- // A series-bail handler can veto by returning `false`.
33
- bus.on(PluginBooting, ({ pluginName }) => {
34
- if (pluginName === "broken") return false;
35
- });
17
+ const app = createApp({ appName: "api" });
36
18
 
37
- // User-defined framework event — same machinery.
38
- const TenantSwitched = defineFrameworkEvent<{ tenantId: string }>(
39
- "app.tenant-switched",
40
- "parallel",
19
+ app.wire(
20
+ post("/hello", { body: z.object({ name: z.string() }) }),
21
+ async (input) => ({ message: `Hello, ${input.name}!` }),
41
22
  );
42
- bus.on(TenantSwitched, ({ tenantId }) => console.log("→", tenantId));
43
- await bus.fire(TenantSwitched, { tenantId: "acme" });
44
-
45
- // definePlugin narrow primitive (no forge-specific knowledge).
46
- export const tracing = definePlugin("tracing", ({ provide, on, boot }) => {
47
- provide("tracer", {
48
- boot: () => createTracer(),
49
- shutdown: (t) => t.shutdown(),
50
- });
51
- on(AppBooted, ({ appName }) => console.log(`tracing live on ${appName}`));
52
- boot(async () => {
53
- /* warm-up */
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
- ## Surface
50
+ The runtime boots plugins in declaration order and disposes them in
51
+ reverse on shutdown.
59
52
 
60
- | Export | Role |
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
- ## Related
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
- - `@nwire/forge` — wraps this package and adds the action/actor primitives.
111
- - `@nwire/hooks` the dispatch substrate; `FrameworkEventBus` adopts a per-event hook lazily so `listHooks()` and `nwire scan` see every framework event.
112
- - `@nwire/endpoint` — fires `WireMounting` / `WireMounted` / `WireUnmounted` on this bus.
60
+ ```ts
61
+ import { appCompose, createApp } from "@nwire/app";
113
62
 
114
- ## Status
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
- v0.x `definePlugin` closure shape, framework-event catalog, and dispatch modes are locked. `createApp` extraction from forge is a follow-up; imports won't change.
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` — managed Container with plugin lifecycle and framework events.
3
- *
4
- * The "app" layer of the sealed architecture. Composes modules + plugins,
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 { defineFrameworkEvent, AppRegistering, AppBooting, AppBooted, AppReady, AppShuttingDown, AppShutdown, PluginRegistered, PluginBooting, PluginBooted, PluginShuttingDown, PluginShutdown, WireMounting, WireMounted, WireUnmounted, builtInLifecycleEvents, type FrameworkEventDefinition, type FrameworkEventMode, } from "./framework-events.js";
23
- export { FrameworkEventBus, type FrameworkEventHandler, type FrameworkEventObserver, type FrameworkEventObservation, } from "./framework-event-bus.js";
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 { Runtime, serializeError, type DispatchHookCtx, type DispatchMiddleware, type HookStepTelemetry, type LifecycleTelemetry, type RuntimeOptions, type SerializedError, type Telemetry, type TelemetryListener, } from "./runtime.js";
27
- //# sourceMappingURL=app.d.ts.map
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` — managed Container with plugin lifecycle and framework events.
3
- *
4
- * The "app" layer of the sealed architecture. Composes modules + plugins,
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 { defineFrameworkEvent, AppRegistering, AppBooting, AppBooted, AppReady, AppShuttingDown, AppShutdown, PluginRegistered, PluginBooting, PluginBooted, PluginShuttingDown, PluginShutdown, WireMounting, WireMounted, WireUnmounted, builtInLifecycleEvents, } from "./framework-events.js";
23
- export { FrameworkEventBus, } from "./framework-event-bus.js";
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 { Runtime, serializeError, } from "./runtime.js";
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
+ }
@@ -1,87 +1,59 @@
1
1
  /**
2
- * `createApp` — generic plugin-lifecycle composition root.
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 { FrameworkEventBus } from "./framework-event-bus.js";
35
- import { type AppPluginDefinition } from "./define-plugin.js";
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-event payloads + dev logger output. */
10
+ /** App name — surfaces in framework-hook payloads + dev logger output. */
38
11
  readonly appName: string;
39
- /** Plugin definitions to register. */
40
- readonly plugins?: readonly AppPluginDefinition[];
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 framework-event bus — defaults to a fresh `FrameworkEventBus`. */
44
- readonly bus?: FrameworkEventBus;
45
- /** Logger for plugin lifecycle messages + framework-event errors. */
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 bus: FrameworkEventBus;
58
- readonly plugins: readonly AppPluginDefinition[];
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
- /** AppServable conformance — alias for `start()`. */
49
+ /** Endpoint alias. */
62
50
  boot(): Promise<void>;
63
- /** AppServable conformance — alias for `stop()`. */
51
+ /** Endpoint alias. */
64
52
  shutdown(): Promise<void>;
65
53
  /**
66
- * Dispatch a framework lifecycle event on the app's bus by name. The
67
- * endpoint uses this to fire `nwire.wire.mounting / .mounted / .unmounted`
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(eventName: string, payload: unknown): Promise<boolean>;
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