@nwire/forge 0.12.0 → 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/README.md +100 -83
- package/dist/framework-events.d.ts +8 -37
- package/dist/framework-events.js +7 -3
- package/dist/helpers/cli-runner.js +21 -10
- package/dist/index.d.ts +8 -7
- package/dist/index.js +7 -6
- package/dist/plugins/actions-chain.d.ts +39 -22
- package/dist/plugins/actions-chain.js +117 -78
- package/dist/plugins/actions-plugin.d.ts +26 -23
- package/dist/plugins/actions-plugin.js +122 -44
- package/dist/plugins/actors-chain.d.ts +9 -2
- package/dist/plugins/actors-chain.js +62 -2
- package/dist/plugins/actors-plugin.d.ts +1 -1
- package/dist/plugins/actors-plugin.js +24 -14
- package/dist/plugins/external-calls-plugin.d.ts +28 -0
- package/dist/plugins/external-calls-plugin.js +136 -0
- package/dist/plugins/idempotency-plugin.d.ts +15 -1
- package/dist/plugins/idempotency-plugin.js +56 -11
- package/dist/plugins/projections-chain.d.ts +2 -2
- package/dist/plugins/projections-chain.js +2 -2
- package/dist/plugins/projections-plugin.d.ts +1 -1
- package/dist/plugins/projections-plugin.js +4 -13
- package/dist/plugins/queries-chain.d.ts +4 -3
- package/dist/plugins/queries-chain.js +8 -5
- package/dist/plugins/queries-plugin.d.ts +15 -29
- package/dist/plugins/queries-plugin.js +36 -49
- package/dist/plugins/workflows-chain.d.ts +9 -2
- package/dist/plugins/workflows-chain.js +19 -1
- package/dist/plugins/workflows-plugin.d.ts +1 -1
- package/dist/plugins/workflows-plugin.js +12 -20
- package/dist/primitives/define-action.d.ts +80 -115
- package/dist/primitives/define-action.js +111 -56
- package/dist/primitives/define-actor.d.ts +103 -214
- package/dist/primitives/define-actor.js +157 -216
- package/dist/primitives/define-handler.d.ts +42 -112
- package/dist/primitives/define-handler.js +14 -45
- package/dist/primitives/define-projection.d.ts +23 -28
- package/dist/primitives/define-projection.js +29 -32
- package/dist/primitives/define-query.d.ts +52 -42
- package/dist/primitives/define-query.js +65 -28
- package/dist/primitives/define-workflow.d.ts +8 -11
- package/dist/primitives/define-workflow.js +14 -8
- package/dist/runtime/forge-dispatcher.d.ts +30 -12
- package/dist/runtime/forge-dispatcher.js +199 -237
- package/dist/runtime/forge-plugin.d.ts +8 -0
- package/dist/runtime/forge-plugin.js +113 -17
- package/dist/runtime/forge-plugins.d.ts +55 -0
- package/dist/runtime/forge-plugins.js +57 -0
- package/dist/runtime/forge-types.d.ts +8 -2
- package/dist/runtime/with-forge.d.ts +8 -11
- package/dist/runtime/with-forge.js +9 -11
- package/dist/stores/idempotency-store.d.ts +1 -1
- package/package.json +12 -12
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* single-process apps. For custom stores or a cross-service bus, use
|
|
28
28
|
* `createForgePlugin(opts)`.
|
|
29
29
|
*/
|
|
30
|
+
import { loggerForEnvelope } from "@nwire/logger";
|
|
30
31
|
import { ForgeDispatcher } from "./forge-dispatcher.js";
|
|
32
|
+
import { seedEnvelope } from "@nwire/envelope";
|
|
31
33
|
/** Container binding the forge dispatcher is registered under. */
|
|
32
34
|
export const FORGE_DISPATCHER_BINDING = "forge.dispatcher";
|
|
33
35
|
/** Capability bindings consumed by handler / resolver ctx via `ctx.resolve(...)`. */
|
|
@@ -42,45 +44,117 @@ export const FORGE_USE_PROJECTION_BINDING = "useProjection";
|
|
|
42
44
|
export function createForgePlugin(options = {}) {
|
|
43
45
|
return {
|
|
44
46
|
name: "forge",
|
|
45
|
-
setup({ runtime, bind, dispose, hooks }) {
|
|
47
|
+
setup({ runtime, bind, dispose, hooks, add }) {
|
|
46
48
|
const dispatcher = new ForgeDispatcher(runtime, options);
|
|
47
49
|
bind(FORGE_DISPATCHER_BINDING, dispatcher);
|
|
50
|
+
// Forge handler-ctx members as a capability — contributed per dispatch
|
|
51
|
+
// via capCtx (so they ride `runtime.execute`'s one ctx). The dispatcher
|
|
52
|
+
// is resolved per dispatch; `signal` threads cancellation into nested
|
|
53
|
+
// dispatches. `emit`/`publish` come from the core publish capability, not
|
|
54
|
+
// here. Replaces the hand-built members in the old `buildHandlerContext`.
|
|
55
|
+
add({
|
|
56
|
+
name: "forge.ctx",
|
|
57
|
+
provideCtx: ({ envelope, container, signal }) => {
|
|
58
|
+
const d = container.resolve(FORGE_DISPATCHER_BINDING);
|
|
59
|
+
return {
|
|
60
|
+
// Members the old buildHandlerContext put on the forge handler ctx
|
|
61
|
+
// that execute's base ctx doesn't carry.
|
|
62
|
+
logger: loggerForEnvelope(runtime.logger, envelope),
|
|
63
|
+
container,
|
|
64
|
+
signal,
|
|
65
|
+
requestId: envelope.messageId,
|
|
66
|
+
request: (action, input) => d.dispatch(action, input, envelope, { signal }),
|
|
67
|
+
// Fire-and-forget command dispatch. Rides the runtime's `enqueue`
|
|
68
|
+
// so it returns a MessageRef up-front (named for the same message
|
|
69
|
+
// the deferred dispatch runs under) rather than blocking on the
|
|
70
|
+
// handler's result. The handler's `run` is the full forge pipeline,
|
|
71
|
+
// so retry / hooks / event-publish still fire when it lands.
|
|
72
|
+
send: (action, input) => {
|
|
73
|
+
const handler = d.findHandler(action.name);
|
|
74
|
+
if (!handler) {
|
|
75
|
+
throw new Error(`forge ctx.send: no handler registered for action "${action.name}".`);
|
|
76
|
+
}
|
|
77
|
+
return runtime.enqueue(handler, input, { parent: envelope, signal });
|
|
78
|
+
},
|
|
79
|
+
query: (queryDef, input) => d.query(queryDef.name, input, envelope.tenant ?? ""),
|
|
80
|
+
actor: (actor, id) => d.actorView(actor, id, envelope),
|
|
81
|
+
externalCall: (call, request) => d.executeExternalCall(call, request, envelope),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
48
85
|
// Capability factories for handler / HTTP resolver ctx
|
|
49
86
|
// (`ctx.resolve("execute" | "send" | "useProjection")`).
|
|
50
87
|
const execute = (action, input, envelope) => dispatcher.dispatch(action, input, envelope);
|
|
51
88
|
const send = (action, input, envelope) => {
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
const handler = dispatcher.findHandler(action.name);
|
|
90
|
+
if (!handler) {
|
|
91
|
+
throw new Error(`forge send: no handler registered for action "${action.name}".`);
|
|
92
|
+
}
|
|
93
|
+
return runtime.enqueue(handler, input, { parent: envelope });
|
|
54
94
|
};
|
|
55
95
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
96
|
const useProjection = (query, input, tenant = "") => dispatcher.query(query.name, input, tenant);
|
|
57
97
|
bind(FORGE_EXECUTE_BINDING, () => execute);
|
|
58
98
|
bind(FORGE_SEND_BINDING, () => send);
|
|
59
99
|
bind(FORGE_USE_PROJECTION_BINDING, () => useProjection);
|
|
60
|
-
// Materialise forge's Action* / Event* hook slots
|
|
61
|
-
//
|
|
100
|
+
// Materialise forge's Action* / Event* hook slots. Apps without forge
|
|
101
|
+
// don't carry these. (Local event delivery rides the core
|
|
102
|
+
// `LocalDelivery` hook, which is always present.)
|
|
62
103
|
for (const slot of [
|
|
63
104
|
"ActionDispatching",
|
|
64
105
|
"ActionCompleted",
|
|
65
106
|
"ActionFailed",
|
|
66
107
|
"EventRecording",
|
|
67
108
|
"EventRecorded",
|
|
68
|
-
"EventPublishing",
|
|
69
109
|
]) {
|
|
70
110
|
runtime.defineHook(slot);
|
|
71
111
|
}
|
|
72
|
-
// Attach forge's
|
|
73
|
-
//
|
|
74
|
-
// to the next priority slot
|
|
75
|
-
// (skips next)
|
|
112
|
+
// Attach forge's fold steps (idempotency → actors → projections →
|
|
113
|
+
// workflows) to the core `LocalDelivery` hook. Each step does its work
|
|
114
|
+
// for one event and calls next() to descend to the next priority slot;
|
|
115
|
+
// the idempotency step short-circuits (skips next) on a duplicate.
|
|
116
|
+
// Bus + outbound sink are NOT chain steps — `dispatcher.publish`
|
|
117
|
+
// applies them after `runtime.deliver` for public, non-deduped events.
|
|
76
118
|
dispatcher.attachPublishChain();
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
// External / bus-inbound events ride the ONE delivery path. This source
|
|
120
|
+
// stage folds a declared external event through `runtime.deliver` — the
|
|
121
|
+
// same LocalDelivery chain (idempotency → actors → projections →
|
|
122
|
+
// workflows + listeners) that local `emit` uses — deduped by the
|
|
123
|
+
// inbound messageId, then stops the chain so the terminal router does
|
|
124
|
+
// not re-handle it. Non-external events pass through to the router,
|
|
125
|
+
// which validates them against their definition. Replaces the dispatcher
|
|
126
|
+
// `applyExternalEvent` parallel fold.
|
|
127
|
+
runtime.source({
|
|
128
|
+
name: "forge.external-delivery",
|
|
129
|
+
position: "terminal",
|
|
130
|
+
run: async (ctx) => {
|
|
131
|
+
const msg = ctx.message;
|
|
132
|
+
if (!msg || msg.kind !== "event")
|
|
133
|
+
return;
|
|
134
|
+
if (!dispatcher.externalEventNames.has(msg.name))
|
|
135
|
+
return;
|
|
136
|
+
const seed = ctx.envelope;
|
|
137
|
+
const envelope = seed?.messageId
|
|
138
|
+
? seed
|
|
139
|
+
: seedEnvelope({
|
|
140
|
+
tenant: ctx.envelope.tenant,
|
|
141
|
+
userId: ctx.envelope.userId,
|
|
142
|
+
user: ctx.envelope.user,
|
|
143
|
+
correlationId: ctx.envelope.correlationId,
|
|
144
|
+
causationId: ctx.envelope.causationId,
|
|
145
|
+
});
|
|
146
|
+
const { deduped } = await runtime.deliver(msg.name, msg.input, envelope, envelope.messageId);
|
|
147
|
+
runtime.pushTelemetry({
|
|
148
|
+
kind: deduped ? "event.deduped" : "event.published",
|
|
149
|
+
event: { eventName: msg.name, payload: msg.input },
|
|
150
|
+
envelope,
|
|
151
|
+
source: "external",
|
|
152
|
+
appName: runtime.appName,
|
|
153
|
+
ts: new Date().toISOString(),
|
|
154
|
+
});
|
|
155
|
+
return { continue: false };
|
|
156
|
+
},
|
|
157
|
+
});
|
|
84
158
|
// Domain registration runs at AppBooting — after every plugin's
|
|
85
159
|
// setup ran (so the dispatcher is bound) but before plugin boot
|
|
86
160
|
// queues fire (so downstream plugins' boot work can reach domain
|
|
@@ -112,8 +186,30 @@ export function createForgePlugin(options = {}) {
|
|
|
112
186
|
for (const call of options.externalCalls ?? []) {
|
|
113
187
|
dispatcher.registerExternalCall(call);
|
|
114
188
|
}
|
|
189
|
+
for (const eventName of options.externalEvents ?? []) {
|
|
190
|
+
dispatcher.registerExternalEvent(eventName);
|
|
191
|
+
}
|
|
115
192
|
await next();
|
|
116
193
|
}, { name: "forge.register-domain", priority: 1_000_000 });
|
|
194
|
+
// Bus inbound — the symmetric counterpart to outbound `publishToBus`.
|
|
195
|
+
// When a bus is configured, subscribe each declared external event and
|
|
196
|
+
// fold it through `runtime.receive`; the forge external-delivery source
|
|
197
|
+
// stage lands it on the one delivery path, deduped by the inbound
|
|
198
|
+
// messageId. Skips this app's own published echoes by origin. Runs after
|
|
199
|
+
// domain registration so `externalEventNames` is populated.
|
|
200
|
+
if (options.bus) {
|
|
201
|
+
const bus = options.bus;
|
|
202
|
+
hooks.AppBooting.use(async (_, next) => {
|
|
203
|
+
for (const eventName of dispatcher.externalEventNames) {
|
|
204
|
+
bus.subscribe(eventName, async (msg) => {
|
|
205
|
+
if (msg.origin === runtime.appName)
|
|
206
|
+
return;
|
|
207
|
+
await runtime.receive({ kind: "event", name: msg.eventName, input: msg.payload }, { envelope: msg.envelope });
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
await next();
|
|
211
|
+
}, { name: "forge.bus-inbound", priority: 900_000 });
|
|
212
|
+
}
|
|
117
213
|
// Cleanup hook — durable adopters with their own dispose schedules
|
|
118
214
|
// register via `bind(name, store, { dispose })`.
|
|
119
215
|
dispose(async () => {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `forgePlugins(options)` — the batteries-included forge SET.
|
|
3
|
+
*
|
|
4
|
+
* import { createApp } from "@nwire/app";
|
|
5
|
+
* import { forgePlugins } from "@nwire/forge";
|
|
6
|
+
*
|
|
7
|
+
* const app = createApp({
|
|
8
|
+
* appName: "orders",
|
|
9
|
+
* handlers: [placeOrder, sendReceipt, listOrders], // actions + queries
|
|
10
|
+
* plugins: [...forgePlugins({ actors, projections })],
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* Handlers are NOT passed here — registration is the app's job. forge scans the
|
|
14
|
+
* runtime for `kind:"action"` / `kind:"query"` and wires each (command pipeline
|
|
15
|
+
* for actions, the read engine for queries).
|
|
16
|
+
*
|
|
17
|
+
* It is NOT a wrapping plugin — it returns the array of concern plugins so the
|
|
18
|
+
* composition stays a flat set you can see and trim. Install the concern
|
|
19
|
+
* plugins individually for an à-la-carte build; spread `forgePlugins()` for the
|
|
20
|
+
* full set. There is no parallel dispatcher — each plugin registers its
|
|
21
|
+
* primitives on the runtime, attaches its fold step to `LocalDelivery`, and
|
|
22
|
+
* contributes its own ctx verb.
|
|
23
|
+
*/
|
|
24
|
+
import type { PluginDefinition } from "@nwire/app";
|
|
25
|
+
import type { DeadLetterSink } from "@nwire/dead-letter";
|
|
26
|
+
import type { EventBus } from "@nwire/bus";
|
|
27
|
+
import type { ActorDefinition } from "../primitives/define-actor.js";
|
|
28
|
+
import type { ProjectionDefinition } from "../primitives/define-projection.js";
|
|
29
|
+
import type { WorkflowDefinition } from "../primitives/define-workflow.js";
|
|
30
|
+
import type { ExternalCallDefinition } from "../primitives/define-external-call.js";
|
|
31
|
+
import type { ActorStore } from "../stores/actor-store.js";
|
|
32
|
+
import type { ProjectionStore } from "../stores/projection-store.js";
|
|
33
|
+
import type { IdempotencyStore } from "../stores/idempotency-store.js";
|
|
34
|
+
import type { WorkflowTimerStore } from "../stores/workflow-timer-store.js";
|
|
35
|
+
export interface ForgeOptions {
|
|
36
|
+
readonly actors?: readonly ActorDefinition[];
|
|
37
|
+
readonly projections?: readonly ProjectionDefinition<any>[];
|
|
38
|
+
readonly workflows?: readonly WorkflowDefinition[];
|
|
39
|
+
readonly externalCalls?: readonly ExternalCallDefinition<any, any>[];
|
|
40
|
+
/** Event names accepted from the bus / source chain. */
|
|
41
|
+
readonly externalEvents?: readonly string[];
|
|
42
|
+
readonly actorStore?: ActorStore;
|
|
43
|
+
readonly projectionStore?: ProjectionStore;
|
|
44
|
+
readonly idempotencyStore?: IdempotencyStore;
|
|
45
|
+
readonly workflowTimerStore?: WorkflowTimerStore;
|
|
46
|
+
readonly deadLetterSink?: DeadLetterSink;
|
|
47
|
+
readonly bus?: EventBus;
|
|
48
|
+
readonly publishToBus?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the full forge plugin set. Order matters: idempotency (the dedup gate)
|
|
52
|
+
* first, then actions (binds the publish + action-runner other concerns use),
|
|
53
|
+
* then state concerns; queries after projections (it reads the store).
|
|
54
|
+
*/
|
|
55
|
+
export declare function forgePlugins(options?: ForgeOptions): PluginDefinition[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `forgePlugins(options)` — the batteries-included forge SET.
|
|
3
|
+
*
|
|
4
|
+
* import { createApp } from "@nwire/app";
|
|
5
|
+
* import { forgePlugins } from "@nwire/forge";
|
|
6
|
+
*
|
|
7
|
+
* const app = createApp({
|
|
8
|
+
* appName: "orders",
|
|
9
|
+
* handlers: [placeOrder, sendReceipt, listOrders], // actions + queries
|
|
10
|
+
* plugins: [...forgePlugins({ actors, projections })],
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* Handlers are NOT passed here — registration is the app's job. forge scans the
|
|
14
|
+
* runtime for `kind:"action"` / `kind:"query"` and wires each (command pipeline
|
|
15
|
+
* for actions, the read engine for queries).
|
|
16
|
+
*
|
|
17
|
+
* It is NOT a wrapping plugin — it returns the array of concern plugins so the
|
|
18
|
+
* composition stays a flat set you can see and trim. Install the concern
|
|
19
|
+
* plugins individually for an à-la-carte build; spread `forgePlugins()` for the
|
|
20
|
+
* full set. There is no parallel dispatcher — each plugin registers its
|
|
21
|
+
* primitives on the runtime, attaches its fold step to `LocalDelivery`, and
|
|
22
|
+
* contributes its own ctx verb.
|
|
23
|
+
*/
|
|
24
|
+
import { idempotencyPlugin } from "../plugins/idempotency-plugin.js";
|
|
25
|
+
import { actionsPlugin } from "../plugins/actions-plugin.js";
|
|
26
|
+
import { actorsPlugin } from "../plugins/actors-plugin.js";
|
|
27
|
+
import { projectionsPlugin } from "../plugins/projections-plugin.js";
|
|
28
|
+
import { queriesPlugin } from "../plugins/queries-plugin.js";
|
|
29
|
+
import { workflowsPlugin } from "../plugins/workflows-plugin.js";
|
|
30
|
+
import { externalCallsPlugin } from "../plugins/external-calls-plugin.js";
|
|
31
|
+
/**
|
|
32
|
+
* Build the full forge plugin set. Order matters: idempotency (the dedup gate)
|
|
33
|
+
* first, then actions (binds the publish + action-runner other concerns use),
|
|
34
|
+
* then state concerns; queries after projections (it reads the store).
|
|
35
|
+
*/
|
|
36
|
+
export function forgePlugins(options = {}) {
|
|
37
|
+
return [
|
|
38
|
+
idempotencyPlugin({
|
|
39
|
+
idempotencyStore: options.idempotencyStore,
|
|
40
|
+
externalEvents: options.externalEvents,
|
|
41
|
+
bus: options.bus,
|
|
42
|
+
}),
|
|
43
|
+
actionsPlugin({
|
|
44
|
+
deadLetterSink: options.deadLetterSink,
|
|
45
|
+
bus: options.bus,
|
|
46
|
+
publishToBus: options.publishToBus,
|
|
47
|
+
}),
|
|
48
|
+
actorsPlugin(options.actors ?? [], { actorStore: options.actorStore }),
|
|
49
|
+
projectionsPlugin(options.projections ?? [], { projectionStore: options.projectionStore }),
|
|
50
|
+
queriesPlugin(),
|
|
51
|
+
workflowsPlugin(options.workflows ?? [], {
|
|
52
|
+
workflowTimerStore: options.workflowTimerStore,
|
|
53
|
+
bus: options.bus,
|
|
54
|
+
}),
|
|
55
|
+
externalCallsPlugin(options.externalCalls ?? []),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* and the widened telemetry union forge emits.
|
|
4
4
|
*/
|
|
5
5
|
import type { MessageEnvelope } from "@nwire/envelope";
|
|
6
|
-
import type { SerializedError,
|
|
6
|
+
import type { SerializedError, Telemetry } from "@nwire/app";
|
|
7
7
|
import type { ActionDefinition } from "../primitives/define-action.js";
|
|
8
8
|
import type { ActorDefinition } from "../primitives/define-actor.js";
|
|
9
9
|
import type { WorkflowDefinition } from "../primitives/define-workflow.js";
|
|
@@ -11,6 +11,12 @@ import type { HandlerContext } from "../primitives/define-handler.js";
|
|
|
11
11
|
import type { EventMessage } from "../messages/event-message.js";
|
|
12
12
|
export interface DispatchOptions {
|
|
13
13
|
readonly signal?: AbortSignal;
|
|
14
|
+
/**
|
|
15
|
+
* A fully-minted envelope to dispatch under, used verbatim instead of
|
|
16
|
+
* deriving a fresh child. Lets a fire-and-forget `send` mint the child
|
|
17
|
+
* up-front so the `MessageRef` it returns names the same message.
|
|
18
|
+
*/
|
|
19
|
+
readonly envelope?: MessageEnvelope;
|
|
14
20
|
}
|
|
15
21
|
/** `action.before:<name>` hook ctx. Set `vetoed = true` to cleanly skip the handler. */
|
|
16
22
|
export interface ActionBeforeHookCtx {
|
|
@@ -42,7 +48,7 @@ export interface WorkflowFireHookCtx {
|
|
|
42
48
|
readonly envelope: MessageEnvelope;
|
|
43
49
|
readonly correlationKey: string;
|
|
44
50
|
}
|
|
45
|
-
export type ForgeTelemetry =
|
|
51
|
+
export type ForgeTelemetry = Telemetry | {
|
|
46
52
|
kind: "action.dispatched";
|
|
47
53
|
action: string;
|
|
48
54
|
input: unknown;
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `withForge(opts)` — App-transformer that installs forge.
|
|
3
|
-
*
|
|
4
|
-
* A plain function `App → App` that calls `app.with(createForgePlugin(opts))`
|
|
5
|
-
* under the hood. Use it as the canonical install path for forge:
|
|
2
|
+
* `withForge(opts)` — App-transformer that installs the forge plugin set.
|
|
6
3
|
*
|
|
7
4
|
* const app = withForge({
|
|
8
5
|
* actors: [Order],
|
|
9
6
|
* projections: [OrdersDashboard],
|
|
10
|
-
*
|
|
7
|
+
* handlers: [placeOrder, sendReceipt],
|
|
11
8
|
* })(createApp({ appName: "orders" }));
|
|
12
9
|
*
|
|
13
|
-
* Equivalent to
|
|
10
|
+
* Equivalent to spreading the set by hand:
|
|
14
11
|
*
|
|
15
|
-
* const app = createApp({ appName: "orders" })
|
|
16
|
-
*
|
|
12
|
+
* const app = createApp({ appName: "orders" });
|
|
13
|
+
* for (const p of forgePlugins({ actors: [Order], ... })) app.with(p);
|
|
17
14
|
*
|
|
18
15
|
* Use whichever reads better at the call site.
|
|
19
16
|
*/
|
|
20
17
|
import type { App } from "@nwire/app";
|
|
21
|
-
import { type
|
|
18
|
+
import { type ForgeOptions } from "./forge-plugins.js";
|
|
22
19
|
/**
|
|
23
|
-
* Returns an App transformer that installs forge into any App it's
|
|
20
|
+
* Returns an App transformer that installs the forge set into any App it's
|
|
24
21
|
* applied to. The transformer preserves the existing `<TCaps>`.
|
|
25
22
|
*/
|
|
26
|
-
export declare function withForge(opts?:
|
|
23
|
+
export declare function withForge(opts?: ForgeOptions): <TCaps>(app: App<TCaps>) => App<TCaps>;
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `withForge(opts)` — App-transformer that installs forge.
|
|
3
|
-
*
|
|
4
|
-
* A plain function `App → App` that calls `app.with(createForgePlugin(opts))`
|
|
5
|
-
* under the hood. Use it as the canonical install path for forge:
|
|
2
|
+
* `withForge(opts)` — App-transformer that installs the forge plugin set.
|
|
6
3
|
*
|
|
7
4
|
* const app = withForge({
|
|
8
5
|
* actors: [Order],
|
|
9
6
|
* projections: [OrdersDashboard],
|
|
10
|
-
*
|
|
7
|
+
* handlers: [placeOrder, sendReceipt],
|
|
11
8
|
* })(createApp({ appName: "orders" }));
|
|
12
9
|
*
|
|
13
|
-
* Equivalent to
|
|
10
|
+
* Equivalent to spreading the set by hand:
|
|
14
11
|
*
|
|
15
|
-
* const app = createApp({ appName: "orders" })
|
|
16
|
-
*
|
|
12
|
+
* const app = createApp({ appName: "orders" });
|
|
13
|
+
* for (const p of forgePlugins({ actors: [Order], ... })) app.with(p);
|
|
17
14
|
*
|
|
18
15
|
* Use whichever reads better at the call site.
|
|
19
16
|
*/
|
|
20
|
-
import {
|
|
17
|
+
import { forgePlugins } from "./forge-plugins.js";
|
|
21
18
|
/**
|
|
22
|
-
* Returns an App transformer that installs forge into any App it's
|
|
19
|
+
* Returns an App transformer that installs the forge set into any App it's
|
|
23
20
|
* applied to. The transformer preserves the existing `<TCaps>`.
|
|
24
21
|
*/
|
|
25
22
|
export function withForge(opts = {}) {
|
|
26
23
|
return (app) => {
|
|
27
|
-
|
|
24
|
+
for (const plugin of forgePlugins(opts))
|
|
25
|
+
app.with(plugin);
|
|
28
26
|
return app;
|
|
29
27
|
};
|
|
30
28
|
}
|
|
@@ -24,7 +24,7 @@ export interface IdempotencyStore {
|
|
|
24
24
|
}): void | Promise<void>;
|
|
25
25
|
/**
|
|
26
26
|
* Atomic check-and-record. Returns true on the first call for a given
|
|
27
|
-
* key and false on subsequent calls. Used by the
|
|
27
|
+
* key and false on subsequent calls. Used by the LocalDelivery
|
|
28
28
|
* idempotency step to dedup correctly under concurrent publishes.
|
|
29
29
|
*
|
|
30
30
|
* Adapters backed by Redis / Postgres / etc. should implement this as
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwire/forge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nwire — the
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Nwire — the CQRS battery. Actors, projections, workflows, queries, and the action pipeline, plus outbox/inbox/cron, invariants, and DLQ wiring — installed via forgePlugins / withForge. Opt-in; an app boots without it. The runtime is the bus.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"actions",
|
|
7
7
|
"actors",
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"emittery": "1.0.1",
|
|
35
35
|
"koa": "^2.16.4",
|
|
36
36
|
"zod": "^4.0.0",
|
|
37
|
-
"@nwire/
|
|
38
|
-
"@nwire/
|
|
39
|
-
"@nwire/
|
|
40
|
-
"@nwire/
|
|
41
|
-
"@nwire/envelope": "0.
|
|
42
|
-
"@nwire/hooks": "0.
|
|
43
|
-
"@nwire/
|
|
44
|
-
"@nwire/
|
|
45
|
-
"@nwire/wires": "0.
|
|
46
|
-
"@nwire/
|
|
37
|
+
"@nwire/container": "0.13.0",
|
|
38
|
+
"@nwire/dead-letter": "0.13.0",
|
|
39
|
+
"@nwire/app": "0.13.0",
|
|
40
|
+
"@nwire/handler": "0.13.0",
|
|
41
|
+
"@nwire/envelope": "0.13.0",
|
|
42
|
+
"@nwire/hooks": "0.13.0",
|
|
43
|
+
"@nwire/logger": "0.13.0",
|
|
44
|
+
"@nwire/messages": "0.13.0",
|
|
45
|
+
"@nwire/wires": "0.13.0",
|
|
46
|
+
"@nwire/bus": "0.13.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/koa": "^2.15.0",
|