@nwire/forge 0.12.1 → 0.13.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.
Files changed (53) hide show
  1. package/README.md +100 -83
  2. package/dist/framework-events.d.ts +8 -37
  3. package/dist/framework-events.js +7 -3
  4. package/dist/helpers/cli-runner.js +21 -10
  5. package/dist/index.d.ts +8 -7
  6. package/dist/index.js +7 -6
  7. package/dist/plugins/actions-chain.d.ts +39 -22
  8. package/dist/plugins/actions-chain.js +117 -78
  9. package/dist/plugins/actions-plugin.d.ts +26 -23
  10. package/dist/plugins/actions-plugin.js +122 -44
  11. package/dist/plugins/actors-chain.d.ts +9 -2
  12. package/dist/plugins/actors-chain.js +62 -2
  13. package/dist/plugins/actors-plugin.d.ts +1 -1
  14. package/dist/plugins/actors-plugin.js +24 -14
  15. package/dist/plugins/external-calls-plugin.d.ts +28 -0
  16. package/dist/plugins/external-calls-plugin.js +136 -0
  17. package/dist/plugins/idempotency-plugin.d.ts +15 -1
  18. package/dist/plugins/idempotency-plugin.js +56 -11
  19. package/dist/plugins/projections-chain.d.ts +2 -2
  20. package/dist/plugins/projections-chain.js +2 -2
  21. package/dist/plugins/projections-plugin.d.ts +1 -1
  22. package/dist/plugins/projections-plugin.js +4 -13
  23. package/dist/plugins/queries-chain.d.ts +4 -3
  24. package/dist/plugins/queries-chain.js +8 -5
  25. package/dist/plugins/queries-plugin.d.ts +15 -29
  26. package/dist/plugins/queries-plugin.js +36 -49
  27. package/dist/plugins/workflows-chain.d.ts +9 -2
  28. package/dist/plugins/workflows-chain.js +19 -1
  29. package/dist/plugins/workflows-plugin.d.ts +1 -1
  30. package/dist/plugins/workflows-plugin.js +12 -20
  31. package/dist/primitives/define-action.d.ts +80 -115
  32. package/dist/primitives/define-action.js +111 -56
  33. package/dist/primitives/define-actor.d.ts +103 -214
  34. package/dist/primitives/define-actor.js +157 -216
  35. package/dist/primitives/define-handler.d.ts +42 -112
  36. package/dist/primitives/define-handler.js +14 -45
  37. package/dist/primitives/define-projection.d.ts +23 -28
  38. package/dist/primitives/define-projection.js +29 -32
  39. package/dist/primitives/define-query.d.ts +52 -42
  40. package/dist/primitives/define-query.js +65 -28
  41. package/dist/primitives/define-workflow.d.ts +8 -11
  42. package/dist/primitives/define-workflow.js +14 -8
  43. package/dist/runtime/forge-dispatcher.d.ts +30 -12
  44. package/dist/runtime/forge-dispatcher.js +199 -237
  45. package/dist/runtime/forge-plugin.d.ts +8 -0
  46. package/dist/runtime/forge-plugin.js +113 -31
  47. package/dist/runtime/forge-plugins.d.ts +55 -0
  48. package/dist/runtime/forge-plugins.js +57 -0
  49. package/dist/runtime/forge-types.d.ts +8 -2
  50. package/dist/runtime/with-forge.d.ts +8 -11
  51. package/dist/runtime/with-forge.js +9 -11
  52. package/dist/stores/idempotency-store.d.ts +1 -1
  53. 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,59 +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
- void dispatcher.dispatch(action, input, envelope);
53
- return Promise.resolve();
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 and the
61
- // EventPublishing chain. Apps without forge don't carry these.
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 atomic publish chain to the EventPublishing hook.
73
- // Each step does its work for one event and calls next() to descend
74
- // to the next priority slot. The idempotency step short-circuits
75
- // (skips next) when a duplicate is seen.
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
- // Pin the dispatch chain handler step at priority -Infinity so any
78
- // user `runtime.use()` middleware stays strictly outside the
79
- // handler invocation.
80
- //
81
- // The runtime installs its OWN core step (`__nwire_runtime_core__`,
82
- // also -Infinity) the first time a `runtime.use()` middleware routes
83
- // a dispatch through the chain. Both steps would otherwise call
84
- // `coreFn()`, so a forge action running while any user middleware is
85
- // present would execute its handler twice. Guard the call with a
86
- // per-dispatch sentinel: whichever -Infinity step runs first invokes
87
- // the handler; the rest fall through. Forge keeps its own step
88
- // because it dispatches straight through `dispatchHook$.run`, where
89
- // the runtime's lazily-pinned step may not exist yet.
90
- runtime.dispatchHook$.use(async (hctx, next) => {
91
- const h = hctx;
92
- if (!h.__nwireCoreInvoked) {
93
- h.__nwireCoreInvoked = true;
94
- hctx.result = await hctx.coreFn();
95
- }
96
- await next();
97
- }, { name: "handler", priority: -Infinity });
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
+ });
98
158
  // Domain registration runs at AppBooting — after every plugin's
99
159
  // setup ran (so the dispatcher is bound) but before plugin boot
100
160
  // queues fire (so downstream plugins' boot work can reach domain
@@ -126,8 +186,30 @@ export function createForgePlugin(options = {}) {
126
186
  for (const call of options.externalCalls ?? []) {
127
187
  dispatcher.registerExternalCall(call);
128
188
  }
189
+ for (const eventName of options.externalEvents ?? []) {
190
+ dispatcher.registerExternalEvent(eventName);
191
+ }
129
192
  await next();
130
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
+ }
131
213
  // Cleanup hook — durable adopters with their own dispose schedules
132
214
  // register via `bind(name, store, { dispose })`.
133
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, HookStepTelemetry } from "@nwire/app";
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 = HookStepTelemetry | {
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
- * actions: [placeOrder, sendReceipt],
7
+ * handlers: [placeOrder, sendReceipt],
11
8
  * })(createApp({ appName: "orders" }));
12
9
  *
13
- * Equivalent to writing the chain by hand:
10
+ * Equivalent to spreading the set by hand:
14
11
  *
15
- * const app = createApp({ appName: "orders" })
16
- * .with(createForgePlugin({ actors: [Order], ... }));
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 ForgePluginOptions } from "./forge-plugin.js";
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?: ForgePluginOptions): <TCaps>(app: App<TCaps>) => App<TCaps>;
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
- * actions: [placeOrder, sendReceipt],
7
+ * handlers: [placeOrder, sendReceipt],
11
8
  * })(createApp({ appName: "orders" }));
12
9
  *
13
- * Equivalent to writing the chain by hand:
10
+ * Equivalent to spreading the set by hand:
14
11
  *
15
- * const app = createApp({ appName: "orders" })
16
- * .with(createForgePlugin({ actors: [Order], ... }));
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 { createForgePlugin } from "./forge-plugin.js";
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
- app.with(createForgePlugin(opts));
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 EventPublishing
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.12.1",
4
- "description": "Nwire — the framework's core primitives. defineAction, defineEvent, defineHandler, defineActor, defineProjection, defineQuery, defineWorkflow, defineModule, defineApp, definePlugin, createApp. MessageEnvelope with correlation/causation. The runtime is the bus.",
3
+ "version": "0.13.1",
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/bus": "0.12.1",
38
- "@nwire/container": "0.12.1",
39
- "@nwire/dead-letter": "0.12.1",
40
- "@nwire/app": "0.12.1",
41
- "@nwire/handler": "0.12.1",
42
- "@nwire/envelope": "0.12.1",
43
- "@nwire/hooks": "0.12.1",
44
- "@nwire/logger": "0.12.1",
45
- "@nwire/messages": "0.12.1",
46
- "@nwire/wires": "0.12.1"
37
+ "@nwire/envelope": "0.13.1",
38
+ "@nwire/dead-letter": "0.13.1",
39
+ "@nwire/container": "0.13.1",
40
+ "@nwire/app": "0.13.1",
41
+ "@nwire/logger": "0.13.1",
42
+ "@nwire/wires": "0.13.1",
43
+ "@nwire/bus": "0.13.1",
44
+ "@nwire/hooks": "0.13.1",
45
+ "@nwire/messages": "0.13.1",
46
+ "@nwire/handler": "0.13.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/koa": "^2.15.0",