@nwire/forge 0.7.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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/__tests__/actor-methods.test.d.ts +9 -0
  4. package/dist/__tests__/actor-methods.test.d.ts.map +1 -0
  5. package/dist/__tests__/actor-methods.test.js +210 -0
  6. package/dist/__tests__/actor-methods.test.js.map +1 -0
  7. package/dist/__tests__/actor-schema-bound.test.d.ts +6 -0
  8. package/dist/__tests__/actor-schema-bound.test.d.ts.map +1 -0
  9. package/dist/__tests__/actor-schema-bound.test.js +112 -0
  10. package/dist/__tests__/actor-schema-bound.test.js.map +1 -0
  11. package/dist/__tests__/app-capabilities.test.d.ts +19 -0
  12. package/dist/__tests__/app-capabilities.test.d.ts.map +1 -0
  13. package/dist/__tests__/app-capabilities.test.js +57 -0
  14. package/dist/__tests__/app-capabilities.test.js.map +1 -0
  15. package/dist/__tests__/cli-runner.test.d.ts +6 -0
  16. package/dist/__tests__/cli-runner.test.d.ts.map +1 -0
  17. package/dist/__tests__/cli-runner.test.js +158 -0
  18. package/dist/__tests__/cli-runner.test.js.map +1 -0
  19. package/dist/__tests__/create-app.test.d.ts +18 -0
  20. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  21. package/dist/__tests__/create-app.test.js +189 -0
  22. package/dist/__tests__/create-app.test.js.map +1 -0
  23. package/dist/__tests__/cross-service-bus.test.d.ts +8 -0
  24. package/dist/__tests__/cross-service-bus.test.d.ts.map +1 -0
  25. package/dist/__tests__/cross-service-bus.test.js +139 -0
  26. package/dist/__tests__/cross-service-bus.test.js.map +1 -0
  27. package/dist/__tests__/define-schema.test.d.ts +5 -0
  28. package/dist/__tests__/define-schema.test.d.ts.map +1 -0
  29. package/dist/__tests__/define-schema.test.js +83 -0
  30. package/dist/__tests__/define-schema.test.js.map +1 -0
  31. package/dist/__tests__/dev-logger.test.d.ts +9 -0
  32. package/dist/__tests__/dev-logger.test.d.ts.map +1 -0
  33. package/dist/__tests__/dev-logger.test.js +126 -0
  34. package/dist/__tests__/dev-logger.test.js.map +1 -0
  35. package/dist/__tests__/external-call.test.d.ts +14 -0
  36. package/dist/__tests__/external-call.test.d.ts.map +1 -0
  37. package/dist/__tests__/external-call.test.js +99 -0
  38. package/dist/__tests__/external-call.test.js.map +1 -0
  39. package/dist/__tests__/framework-events.test.d.ts +13 -0
  40. package/dist/__tests__/framework-events.test.d.ts.map +1 -0
  41. package/dist/__tests__/framework-events.test.js +204 -0
  42. package/dist/__tests__/framework-events.test.js.map +1 -0
  43. package/dist/__tests__/inline-handler.test.d.ts +8 -0
  44. package/dist/__tests__/inline-handler.test.d.ts.map +1 -0
  45. package/dist/__tests__/inline-handler.test.js +101 -0
  46. package/dist/__tests__/inline-handler.test.js.map +1 -0
  47. package/dist/__tests__/lifecycle-logging.test.d.ts +12 -0
  48. package/dist/__tests__/lifecycle-logging.test.d.ts.map +1 -0
  49. package/dist/__tests__/lifecycle-logging.test.js +112 -0
  50. package/dist/__tests__/lifecycle-logging.test.js.map +1 -0
  51. package/dist/__tests__/middleware.test.d.ts +7 -0
  52. package/dist/__tests__/middleware.test.d.ts.map +1 -0
  53. package/dist/__tests__/middleware.test.js +109 -0
  54. package/dist/__tests__/middleware.test.js.map +1 -0
  55. package/dist/__tests__/module-needs.test.d.ts +10 -0
  56. package/dist/__tests__/module-needs.test.d.ts.map +1 -0
  57. package/dist/__tests__/module-needs.test.js +77 -0
  58. package/dist/__tests__/module-needs.test.js.map +1 -0
  59. package/dist/__tests__/module-topo-sort.test.d.ts +15 -0
  60. package/dist/__tests__/module-topo-sort.test.d.ts.map +1 -0
  61. package/dist/__tests__/module-topo-sort.test.js +105 -0
  62. package/dist/__tests__/module-topo-sort.test.js.map +1 -0
  63. package/dist/__tests__/multi-tenancy.test.d.ts +10 -0
  64. package/dist/__tests__/multi-tenancy.test.d.ts.map +1 -0
  65. package/dist/__tests__/multi-tenancy.test.js +122 -0
  66. package/dist/__tests__/multi-tenancy.test.js.map +1 -0
  67. package/dist/__tests__/needs-topology.test.d.ts +11 -0
  68. package/dist/__tests__/needs-topology.test.d.ts.map +1 -0
  69. package/dist/__tests__/needs-topology.test.js +82 -0
  70. package/dist/__tests__/needs-topology.test.js.map +1 -0
  71. package/dist/__tests__/plugin-closure.test.d.ts +15 -0
  72. package/dist/__tests__/plugin-closure.test.d.ts.map +1 -0
  73. package/dist/__tests__/plugin-closure.test.js +140 -0
  74. package/dist/__tests__/plugin-closure.test.js.map +1 -0
  75. package/dist/__tests__/plugin.test.d.ts +10 -0
  76. package/dist/__tests__/plugin.test.d.ts.map +1 -0
  77. package/dist/__tests__/plugin.test.js +225 -0
  78. package/dist/__tests__/plugin.test.js.map +1 -0
  79. package/dist/__tests__/primitives.test.d.ts +9 -0
  80. package/dist/__tests__/primitives.test.d.ts.map +1 -0
  81. package/dist/__tests__/primitives.test.js +434 -0
  82. package/dist/__tests__/primitives.test.js.map +1 -0
  83. package/dist/__tests__/production-readiness.test.d.ts +22 -0
  84. package/dist/__tests__/production-readiness.test.d.ts.map +1 -0
  85. package/dist/__tests__/production-readiness.test.js +196 -0
  86. package/dist/__tests__/production-readiness.test.js.map +1 -0
  87. package/dist/__tests__/provider.test.d.ts +6 -0
  88. package/dist/__tests__/provider.test.d.ts.map +1 -0
  89. package/dist/__tests__/provider.test.js +122 -0
  90. package/dist/__tests__/provider.test.js.map +1 -0
  91. package/dist/__tests__/public-marker.test.d.ts +7 -0
  92. package/dist/__tests__/public-marker.test.d.ts.map +1 -0
  93. package/dist/__tests__/public-marker.test.js +54 -0
  94. package/dist/__tests__/public-marker.test.js.map +1 -0
  95. package/dist/__tests__/retry-dlq.test.d.ts +6 -0
  96. package/dist/__tests__/retry-dlq.test.d.ts.map +1 -0
  97. package/dist/__tests__/retry-dlq.test.js +68 -0
  98. package/dist/__tests__/retry-dlq.test.js.map +1 -0
  99. package/dist/__tests__/validate.test.d.ts +5 -0
  100. package/dist/__tests__/validate.test.d.ts.map +1 -0
  101. package/dist/__tests__/validate.test.js +53 -0
  102. package/dist/__tests__/validate.test.js.map +1 -0
  103. package/dist/__tests__/workflow-saga.test.d.ts +7 -0
  104. package/dist/__tests__/workflow-saga.test.d.ts.map +1 -0
  105. package/dist/__tests__/workflow-saga.test.js +239 -0
  106. package/dist/__tests__/workflow-saga.test.js.map +1 -0
  107. package/dist/actor-store.d.ts +83 -0
  108. package/dist/actor-store.d.ts.map +1 -0
  109. package/dist/actor-store.js +85 -0
  110. package/dist/actor-store.js.map +1 -0
  111. package/dist/cli-runner.d.ts +46 -0
  112. package/dist/cli-runner.d.ts.map +1 -0
  113. package/dist/cli-runner.js +164 -0
  114. package/dist/cli-runner.js.map +1 -0
  115. package/dist/create-app.d.ts +131 -0
  116. package/dist/create-app.d.ts.map +1 -0
  117. package/dist/create-app.js +593 -0
  118. package/dist/create-app.js.map +1 -0
  119. package/dist/define-action.d.ts +148 -0
  120. package/dist/define-action.d.ts.map +1 -0
  121. package/dist/define-action.js +52 -0
  122. package/dist/define-action.js.map +1 -0
  123. package/dist/define-actor.d.ts +302 -0
  124. package/dist/define-actor.d.ts.map +1 -0
  125. package/dist/define-actor.js +294 -0
  126. package/dist/define-actor.js.map +1 -0
  127. package/dist/define-app.d.ts +104 -0
  128. package/dist/define-app.d.ts.map +1 -0
  129. package/dist/define-app.js +49 -0
  130. package/dist/define-app.js.map +1 -0
  131. package/dist/define-cron.d.ts +50 -0
  132. package/dist/define-cron.d.ts.map +1 -0
  133. package/dist/define-cron.js +34 -0
  134. package/dist/define-cron.js.map +1 -0
  135. package/dist/define-error.d.ts +10 -0
  136. package/dist/define-error.d.ts.map +1 -0
  137. package/dist/define-error.js +10 -0
  138. package/dist/define-error.js.map +1 -0
  139. package/dist/define-external-call.d.ts +85 -0
  140. package/dist/define-external-call.d.ts.map +1 -0
  141. package/dist/define-external-call.js +38 -0
  142. package/dist/define-external-call.js.map +1 -0
  143. package/dist/define-handler.d.ts +98 -0
  144. package/dist/define-handler.d.ts.map +1 -0
  145. package/dist/define-handler.js +29 -0
  146. package/dist/define-handler.js.map +1 -0
  147. package/dist/define-inbound-webhook.d.ts +82 -0
  148. package/dist/define-inbound-webhook.d.ts.map +1 -0
  149. package/dist/define-inbound-webhook.js +42 -0
  150. package/dist/define-inbound-webhook.js.map +1 -0
  151. package/dist/define-inbox.d.ts +40 -0
  152. package/dist/define-inbox.d.ts.map +1 -0
  153. package/dist/define-inbox.js +31 -0
  154. package/dist/define-inbox.js.map +1 -0
  155. package/dist/define-initializer.d.ts +54 -0
  156. package/dist/define-initializer.d.ts.map +1 -0
  157. package/dist/define-initializer.js +38 -0
  158. package/dist/define-initializer.js.map +1 -0
  159. package/dist/define-middleware.d.ts +8 -0
  160. package/dist/define-middleware.d.ts.map +1 -0
  161. package/dist/define-middleware.js +8 -0
  162. package/dist/define-middleware.js.map +1 -0
  163. package/dist/define-model.d.ts +10 -0
  164. package/dist/define-model.d.ts.map +1 -0
  165. package/dist/define-model.js +13 -0
  166. package/dist/define-model.js.map +1 -0
  167. package/dist/define-module.d.ts +157 -0
  168. package/dist/define-module.d.ts.map +1 -0
  169. package/dist/define-module.js +60 -0
  170. package/dist/define-module.js.map +1 -0
  171. package/dist/define-outbox.d.ts +47 -0
  172. package/dist/define-outbox.d.ts.map +1 -0
  173. package/dist/define-outbox.js +36 -0
  174. package/dist/define-outbox.js.map +1 -0
  175. package/dist/define-plugin.d.ts +171 -0
  176. package/dist/define-plugin.d.ts.map +1 -0
  177. package/dist/define-plugin.js +134 -0
  178. package/dist/define-plugin.js.map +1 -0
  179. package/dist/define-projection.d.ts +56 -0
  180. package/dist/define-projection.d.ts.map +1 -0
  181. package/dist/define-projection.js +44 -0
  182. package/dist/define-projection.js.map +1 -0
  183. package/dist/define-provider.d.ts +49 -0
  184. package/dist/define-provider.d.ts.map +1 -0
  185. package/dist/define-provider.js +45 -0
  186. package/dist/define-provider.js.map +1 -0
  187. package/dist/define-query.d.ts +50 -0
  188. package/dist/define-query.d.ts.map +1 -0
  189. package/dist/define-query.js +33 -0
  190. package/dist/define-query.js.map +1 -0
  191. package/dist/define-resolver.d.ts +111 -0
  192. package/dist/define-resolver.d.ts.map +1 -0
  193. package/dist/define-resolver.js +146 -0
  194. package/dist/define-resolver.js.map +1 -0
  195. package/dist/define-schema.d.ts +88 -0
  196. package/dist/define-schema.d.ts.map +1 -0
  197. package/dist/define-schema.js +72 -0
  198. package/dist/define-schema.js.map +1 -0
  199. package/dist/define-workflow.d.ts +193 -0
  200. package/dist/define-workflow.d.ts.map +1 -0
  201. package/dist/define-workflow.js +345 -0
  202. package/dist/define-workflow.js.map +1 -0
  203. package/dist/dev-logger.d.ts +41 -0
  204. package/dist/dev-logger.d.ts.map +1 -0
  205. package/dist/dev-logger.js +135 -0
  206. package/dist/dev-logger.js.map +1 -0
  207. package/dist/event-message.d.ts +37 -0
  208. package/dist/event-message.d.ts.map +1 -0
  209. package/dist/event-message.js +51 -0
  210. package/dist/event-message.js.map +1 -0
  211. package/dist/foundation.d.ts +14 -0
  212. package/dist/foundation.d.ts.map +1 -0
  213. package/dist/foundation.js +14 -0
  214. package/dist/foundation.js.map +1 -0
  215. package/dist/framework-event-bus.d.ts +13 -0
  216. package/dist/framework-event-bus.d.ts.map +1 -0
  217. package/dist/framework-event-bus.js +13 -0
  218. package/dist/framework-event-bus.js.map +1 -0
  219. package/dist/framework-events.d.ts +121 -0
  220. package/dist/framework-events.d.ts.map +1 -0
  221. package/dist/framework-events.js +67 -0
  222. package/dist/framework-events.js.map +1 -0
  223. package/dist/index.d.ts +53 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +61 -0
  226. package/dist/index.js.map +1 -0
  227. package/dist/module-surface.d.ts +47 -0
  228. package/dist/module-surface.d.ts.map +1 -0
  229. package/dist/module-surface.js +65 -0
  230. package/dist/module-surface.js.map +1 -0
  231. package/dist/projection-store.d.ts +26 -0
  232. package/dist/projection-store.d.ts.map +1 -0
  233. package/dist/projection-store.js +28 -0
  234. package/dist/projection-store.js.map +1 -0
  235. package/dist/public-marker.d.ts +35 -0
  236. package/dist/public-marker.d.ts.map +1 -0
  237. package/dist/public-marker.js +45 -0
  238. package/dist/public-marker.js.map +1 -0
  239. package/dist/response.d.ts +8 -0
  240. package/dist/response.d.ts.map +1 -0
  241. package/dist/response.js +8 -0
  242. package/dist/response.js.map +1 -0
  243. package/dist/runtime.d.ts +497 -0
  244. package/dist/runtime.d.ts.map +1 -0
  245. package/dist/runtime.js +1083 -0
  246. package/dist/runtime.js.map +1 -0
  247. package/dist/validate.d.ts +33 -0
  248. package/dist/validate.d.ts.map +1 -0
  249. package/dist/validate.js +48 -0
  250. package/dist/validate.js.map +1 -0
  251. package/dist/when.d.ts +101 -0
  252. package/dist/when.d.ts.map +1 -0
  253. package/dist/when.js +57 -0
  254. package/dist/when.js.map +1 -0
  255. package/dist/workflow-timer-store.d.ts +78 -0
  256. package/dist/workflow-timer-store.d.ts.map +1 -0
  257. package/dist/workflow-timer-store.js +56 -0
  258. package/dist/workflow-timer-store.js.map +1 -0
  259. package/package.json +60 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Multi-tenancy contract — same actor key in two tenants stays fully isolated.
3
+ *
4
+ * The envelope's `tenant` field flows through every store operation. Two
5
+ * tenants can submit, transition, and read with the same id without
6
+ * leakage. Cross-tenant scans require explicit code (the timer scheduler
7
+ * does this; nothing else should).
8
+ */
9
+ import { describe, it, expect, beforeEach } from "vitest";
10
+ import { z } from "zod";
11
+ import { defineEvent } from "@nwire/messages";
12
+ import * as forge from "../foundation.js";
13
+ import { seedEnvelope } from "../foundation.js";
14
+ const { defineAction, defineActor, defineHandler, defineProjection, defineQuery, eventFactory, Runtime, InMemoryActorStore, InMemoryProjectionStore, } = forge;
15
+ const TodoCreatedEventDef = defineEvent({
16
+ name: "todos.created",
17
+ schema: z.object({
18
+ todoId: z.string(),
19
+ title: z.string(),
20
+ }),
21
+ });
22
+ const TodoCreated = eventFactory(TodoCreatedEventDef);
23
+ const TodoActorData = z.object({
24
+ todoId: z.string(),
25
+ title: z.string().optional(),
26
+ status: z.enum(["open", "completed"]).optional(),
27
+ });
28
+ const Todo = defineActor("todo", {
29
+ schema: TodoActorData,
30
+ key: "todoId",
31
+ initial: "open",
32
+ states: {
33
+ open: {
34
+ on: {
35
+ [TodoCreated.name]: {
36
+ assign: (_, event) => {
37
+ const e = event;
38
+ return { todoId: e.todoId, title: e.title, status: "open" };
39
+ },
40
+ },
41
+ },
42
+ },
43
+ completed: { final: true },
44
+ },
45
+ });
46
+ const createTodo = defineAction({
47
+ name: "todos.create",
48
+ schema: z.object({ todoId: z.string(), title: z.string() }),
49
+ });
50
+ const createTodoHandler = defineHandler(createTodo, async (input) => TodoCreated({ todoId: input.todoId, title: input.title }));
51
+ const TodosByOwner = defineProjection("todos-by-owner", {
52
+ listens: [TodoCreatedEventDef],
53
+ initial: () => ({}),
54
+ on: {
55
+ [TodoCreatedEventDef.name]: (state, event) => {
56
+ const e = event;
57
+ return { ...state, [e.todoId]: { todoId: e.todoId, title: e.title } };
58
+ },
59
+ },
60
+ });
61
+ const todosByOwner = defineQuery(TodosByOwner, {
62
+ name: "todos.by-owner",
63
+ schema: z.object({}),
64
+ execute: (state) => Object.values(state),
65
+ });
66
+ describe("multi-tenancy — actor + projection scoping by envelope.tenant", () => {
67
+ let runtime;
68
+ let actorStore;
69
+ let projectionStore;
70
+ beforeEach(() => {
71
+ actorStore = new InMemoryActorStore();
72
+ projectionStore = new InMemoryProjectionStore();
73
+ runtime = new Runtime({ actorStore, projectionStore });
74
+ runtime.registerActor(Todo);
75
+ runtime.registerHandler(createTodoHandler);
76
+ runtime.registerProjection(TodosByOwner);
77
+ runtime.registerQuery(todosByOwner);
78
+ });
79
+ it("the same todoId in two tenants creates two independent actor instances", async () => {
80
+ await runtime.dispatch(createTodo, { todoId: "t-1", title: "Avi: read aleph" }, seedEnvelope({ tenant: "school-tlv" }));
81
+ await runtime.dispatch(createTodo, { todoId: "t-1", title: "Maya: read bet" }, seedEnvelope({ tenant: "school-jlm" }));
82
+ const tlv = await actorStore.load("todo", "t-1", "school-tlv");
83
+ const jlm = await actorStore.load("todo", "t-1", "school-jlm");
84
+ expect(tlv).not.toBeNull();
85
+ expect(jlm).not.toBeNull();
86
+ expect((tlv?.data).title).toBe("Avi: read aleph");
87
+ expect((jlm?.data).title).toBe("Maya: read bet");
88
+ // The default scope ('') sees nothing — explicit tenancy required.
89
+ const def = await actorStore.load("todo", "t-1");
90
+ expect(def).toBeNull();
91
+ });
92
+ it("queries scope to the envelope's tenant — no cross-tenant projection read", async () => {
93
+ await runtime.dispatch(createTodo, { todoId: "t-tlv-1", title: "Avi A" }, seedEnvelope({ tenant: "school-tlv" }));
94
+ await runtime.dispatch(createTodo, { todoId: "t-tlv-2", title: "Avi B" }, seedEnvelope({ tenant: "school-tlv" }));
95
+ await runtime.dispatch(createTodo, { todoId: "t-jlm-1", title: "Maya" }, seedEnvelope({ tenant: "school-jlm" }));
96
+ const tlv = await runtime.query("todos.by-owner", {}, "school-tlv");
97
+ const jlm = await runtime.query("todos.by-owner", {}, "school-jlm");
98
+ expect(tlv).toHaveLength(2);
99
+ expect(tlv.map((t) => t.title).sort()).toEqual(["Avi A", "Avi B"]);
100
+ expect(jlm).toHaveLength(1);
101
+ expect(jlm[0]?.title).toBe("Maya");
102
+ });
103
+ it("listInstances with no tenant returns instances across all tenants", async () => {
104
+ await runtime.dispatch(createTodo, { todoId: "t-1", title: "A" }, seedEnvelope({ tenant: "school-tlv" }));
105
+ await runtime.dispatch(createTodo, { todoId: "t-2", title: "B" }, seedEnvelope({ tenant: "school-jlm" }));
106
+ const all = await actorStore.listInstances("todo");
107
+ const scoped = await actorStore.listInstances("todo", "school-tlv");
108
+ expect(all).toHaveLength(2);
109
+ expect(scoped).toHaveLength(1);
110
+ expect(scoped[0]?.tenant).toBe("school-tlv");
111
+ });
112
+ it("a missing tenant on dispatch falls back to the global scope", async () => {
113
+ // No tenant on envelope → goes to '' scope.
114
+ await runtime.dispatch(createTodo, { todoId: "t-1", title: "global" });
115
+ const global = await actorStore.load("todo", "t-1");
116
+ expect(global).not.toBeNull();
117
+ expect(global?.tenant).toBe("");
118
+ const tlv = await actorStore.load("todo", "t-1", "school-tlv");
119
+ expect(tlv).toBeNull();
120
+ });
121
+ });
122
+ //# sourceMappingURL=multi-tenancy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-tenancy.test.js","sourceRoot":"","sources":["../../src/__tests__/multi-tenancy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `needs.events` is topology-flexible:
3
+ * - If a producer is in the SAME app: in-process apply (no bus needed).
4
+ * - If a producer is in ANOTHER app and a bus is configured: auto-subscribe
5
+ * via the bus, route received messages through `applyExternalEvent`.
6
+ *
7
+ * Same module declaration works in monolith and split topologies. This is
8
+ * what unblocks the 8-services-× -21-bounded-contexts shape.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=needs-topology.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"needs-topology.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/needs-topology.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `needs.events` is topology-flexible:
3
+ * - If a producer is in the SAME app: in-process apply (no bus needed).
4
+ * - If a producer is in ANOTHER app and a bus is configured: auto-subscribe
5
+ * via the bus, route received messages through `applyExternalEvent`.
6
+ *
7
+ * Same module declaration works in monolith and split topologies. This is
8
+ * what unblocks the 8-services-× -21-bounded-contexts shape.
9
+ */
10
+ import { describe, it, expect } from "vitest";
11
+ import { z } from "zod";
12
+ import { defineEvent } from "@nwire/messages";
13
+ import { InMemoryEventBus } from "@nwire/bus";
14
+ import { defineAction, defineModule, createApp, eventFactory, defineWorkflow } from "../foundation.js";
15
+ const StudentEnrolledEvent = defineEvent({
16
+ name: "roster.student-enrolled",
17
+ schema: z.object({ studentId: z.string(), courseId: z.string() }),
18
+ });
19
+ const StudentEnrolled = eventFactory(StudentEnrolledEvent);
20
+ const enrollStudent = defineAction({
21
+ name: "roster.enroll",
22
+ schema: z.object({ studentId: z.string(), courseId: z.string() }),
23
+ handler: async (input) => StudentEnrolled({ studentId: input.studentId, courseId: input.courseId }),
24
+ });
25
+ const rosterModule = defineModule("roster", {
26
+ actions: [enrollStudent],
27
+ events: [StudentEnrolledEvent.public()],
28
+ });
29
+ // "lx" module: REACTS to enrollment events. Same declaration, two topologies.
30
+ function makeConsumerModule() {
31
+ const observed = {};
32
+ const noticeWorkflow = defineWorkflow("lx.notice-enrollment", ({ on }) => {
33
+ on(StudentEnrolledEvent, (payload) => {
34
+ observed.studentId = payload.studentId;
35
+ observed.courseId = payload.courseId;
36
+ });
37
+ });
38
+ const module = defineModule("lx", {
39
+ workflows: [noticeWorkflow],
40
+ needs: { events: [StudentEnrolledEvent] },
41
+ });
42
+ return { module, observed };
43
+ }
44
+ describe("needs.events — topology-flexible", () => {
45
+ it("monolith: roster + lx in one app, no bus → in-process apply", async () => {
46
+ const { module: lxModule, observed } = makeConsumerModule();
47
+ const app = createApp({ modules: [rosterModule, lxModule] });
48
+ await app.runtime.dispatch(enrollStudent, {
49
+ studentId: "avi",
50
+ courseId: "hebrew-letters",
51
+ });
52
+ expect(observed.studentId).toBe("avi");
53
+ expect(observed.courseId).toBe("hebrew-letters");
54
+ });
55
+ it("split: roster in app A, lx in app B, bus between them → bus-subscribed", async () => {
56
+ const bus = new InMemoryEventBus();
57
+ const lmsApp = createApp({
58
+ modules: [rosterModule],
59
+ bus,
60
+ publishToBus: true,
61
+ appName: "lms",
62
+ });
63
+ const { module: lxModule, observed } = makeConsumerModule();
64
+ const lxApp = createApp({
65
+ modules: [lxModule],
66
+ bus,
67
+ appName: "lx",
68
+ });
69
+ await lmsApp.runtime.dispatch(enrollStudent, {
70
+ studentId: "maya",
71
+ courseId: "math-101",
72
+ });
73
+ expect(observed.studentId).toBe("maya");
74
+ expect(observed.courseId).toBe("math-101");
75
+ await bus.stop();
76
+ });
77
+ it("lx without a local roster AND without a bus → boot fails with helpful message", () => {
78
+ const { module: lxModule } = makeConsumerModule();
79
+ expect(() => createApp({ modules: [lxModule] })).toThrow(/no module in this app provides it and no bus is configured/);
80
+ });
81
+ });
82
+ //# sourceMappingURL=needs-topology.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"needs-topology.test.js","sourceRoot":"","sources":["../../src/__tests__/needs-topology.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `definePlugin` closure form — bind / resolve / on / before / after /
3
+ * middleware / actorHook / boot / shutdown bundled into one builder.
4
+ *
5
+ * Verifies:
6
+ * - bind() registers a singleton resolvable via container or api.resolve
7
+ * - on(FrameworkEvent, ...) wires subscriptions
8
+ * - before(action, ...) fires only for that action and can veto
9
+ * - after(action, ...) fires only for that action's completion
10
+ * - middleware() runs onion-style around dispatch
11
+ * - boot() / shutdown() fire in order / reverse-order
12
+ * - object form still works (back-compat)
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=plugin-closure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-closure.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/plugin-closure.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * `definePlugin` closure form — bind / resolve / on / before / after /
3
+ * middleware / actorHook / boot / shutdown bundled into one builder.
4
+ *
5
+ * Verifies:
6
+ * - bind() registers a singleton resolvable via container or api.resolve
7
+ * - on(FrameworkEvent, ...) wires subscriptions
8
+ * - before(action, ...) fires only for that action and can veto
9
+ * - after(action, ...) fires only for that action's completion
10
+ * - middleware() runs onion-style around dispatch
11
+ * - boot() / shutdown() fire in order / reverse-order
12
+ * - object form still works (back-compat)
13
+ */
14
+ import { describe, it, expect } from "vitest";
15
+ import { z } from "zod";
16
+ import { ActionDispatching, createApp, defineAction, defineHandler, defineModule, definePlugin, } from "../foundation.js";
17
+ const Ping = defineAction({
18
+ name: "test/ping",
19
+ schema: z.object({ n: z.number() }),
20
+ });
21
+ const handler = defineHandler(Ping, async () => undefined);
22
+ const mod = defineModule("test", {
23
+ actions: [Ping],
24
+ handlers: [handler],
25
+ });
26
+ describe("definePlugin (closure form)", () => {
27
+ it("bind + resolve registers and reads back a singleton", async () => {
28
+ class Audit {
29
+ messages = [];
30
+ }
31
+ let resolved;
32
+ const plugin = definePlugin("audit", ({ bind, resolve, boot }) => {
33
+ bind("audit", () => new Audit());
34
+ boot(() => {
35
+ resolved = resolve("audit");
36
+ });
37
+ });
38
+ const app = createApp({ modules: [mod], plugins: [plugin] });
39
+ await app.start();
40
+ expect(resolved).toBeInstanceOf(Audit);
41
+ await app.stop();
42
+ });
43
+ it("after(action) fires only for that action, with the result", async () => {
44
+ const seen = [];
45
+ const plugin = definePlugin("watcher", ({ after }) => {
46
+ after("test/ping", (p) => {
47
+ seen.push({ action: p.action.name, n: p.input.n });
48
+ });
49
+ after("test/other", () => {
50
+ seen.push({ action: "OTHER", n: -1 });
51
+ });
52
+ });
53
+ const app = createApp({ modules: [mod], plugins: [plugin] });
54
+ await app.start();
55
+ await app.runtime.dispatch(Ping, { n: 7 });
56
+ // ActionCompleted is parallel/non-awaited; settle.
57
+ await new Promise((r) => setImmediate(r));
58
+ expect(seen).toEqual([{ action: "test/ping", n: 7 }]);
59
+ await app.stop();
60
+ });
61
+ it("before(action) can veto a dispatch by returning false", async () => {
62
+ const plugin = definePlugin("gate", ({ before }) => {
63
+ before("test/ping", () => false);
64
+ });
65
+ const app = createApp({ modules: [mod], plugins: [plugin] });
66
+ await app.start();
67
+ const result = await app.runtime.dispatch(Ping, { n: 1 });
68
+ expect(result).toBeUndefined();
69
+ await app.stop();
70
+ });
71
+ it("middleware() runs onion-style around dispatch", async () => {
72
+ const order = [];
73
+ const plugin = definePlugin("timing", ({ middleware }) => {
74
+ middleware(async (next) => {
75
+ order.push("before");
76
+ const out = await next();
77
+ order.push("after");
78
+ return out;
79
+ });
80
+ });
81
+ const app = createApp({ modules: [mod], plugins: [plugin] });
82
+ await app.start();
83
+ await app.runtime.dispatch(Ping, { n: 1 });
84
+ expect(order).toEqual(["before", "after"]);
85
+ await app.stop();
86
+ });
87
+ it("boot/shutdown fire in registration / reverse order", async () => {
88
+ const order = [];
89
+ const plugin = definePlugin("ordered", ({ boot, shutdown }) => {
90
+ boot(() => {
91
+ order.push("boot-1");
92
+ });
93
+ boot(() => {
94
+ order.push("boot-2");
95
+ });
96
+ shutdown(() => {
97
+ order.push("shutdown-1");
98
+ });
99
+ shutdown(() => {
100
+ order.push("shutdown-2");
101
+ });
102
+ });
103
+ const app = createApp({ modules: [mod], plugins: [plugin] });
104
+ await app.start();
105
+ await app.stop();
106
+ expect(order).toEqual(["boot-1", "boot-2", "shutdown-2", "shutdown-1"]);
107
+ });
108
+ it("on(ActionDispatching) sees every action (not just one)", async () => {
109
+ const seen = [];
110
+ const plugin = definePlugin("watch-all", ({ on }) => {
111
+ on(ActionDispatching, (p) => {
112
+ seen.push(p.action.name);
113
+ });
114
+ });
115
+ const app = createApp({ modules: [mod], plugins: [plugin] });
116
+ await app.start();
117
+ await app.runtime.dispatch(Ping, { n: 1 });
118
+ expect(seen).toEqual(["test/ping"]);
119
+ await app.stop();
120
+ });
121
+ it("object form still works (back-compat)", async () => {
122
+ let registered = false;
123
+ const plugin = definePlugin("legacy", {
124
+ register: () => {
125
+ registered = true;
126
+ },
127
+ });
128
+ const app = createApp({ modules: [], plugins: [plugin] });
129
+ expect(registered).toBe(true); // register runs at createApp construction
130
+ await app.start();
131
+ await app.stop();
132
+ });
133
+ it("rejects an async setup function — must use api.boot for async work", () => {
134
+ const plugin = definePlugin("bad", async () => {
135
+ await Promise.resolve();
136
+ });
137
+ expect(() => createApp({ modules: [], plugins: [plugin] })).toThrow(/synchronous/i);
138
+ });
139
+ });
140
+ //# sourceMappingURL=plugin-closure.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-closure.test.js","sourceRoot":"","sources":["../../src/__tests__/plugin-closure.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `definePlugin` — the only extension primitive.
3
+ *
4
+ * Plugins compose alongside modules. They bind values on the container,
5
+ * subscribe to framework events, contribute middleware + actor hooks, and
6
+ * own boot/shutdown lifecycle. Both authoring forms (object + closure)
7
+ * route through the same registration loop.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=plugin.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/plugin.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * `definePlugin` — the only extension primitive.
3
+ *
4
+ * Plugins compose alongside modules. They bind values on the container,
5
+ * subscribe to framework events, contribute middleware + actor hooks, and
6
+ * own boot/shutdown lifecycle. Both authoring forms (object + closure)
7
+ * route through the same registration loop.
8
+ */
9
+ import { describe, it, expect } from "vitest";
10
+ import { z } from "zod";
11
+ import { defineEvent } from "@nwire/messages";
12
+ import { definePlugin, defineAction, defineActor, defineModule, createApp, eventFactory, } from "../foundation.js";
13
+ describe("definePlugin", () => {
14
+ it("object-form register/boot/shutdown lifecycle fires in correct order", async () => {
15
+ const order = [];
16
+ const plugin = definePlugin("audit", {
17
+ register: (container) => {
18
+ order.push("plugin.register");
19
+ container.register("auditLog", { tag: "audit" });
20
+ },
21
+ boot: async () => {
22
+ order.push("plugin.boot");
23
+ },
24
+ shutdown: async () => {
25
+ order.push("plugin.shutdown");
26
+ },
27
+ });
28
+ const app = createApp({ modules: [], plugins: [plugin] });
29
+ await app.start();
30
+ await app.stop();
31
+ expect(order).toEqual(["plugin.register", "plugin.boot", "plugin.shutdown"]);
32
+ });
33
+ it("closure-form: bind / boot / shutdown / resolve compose end-to-end", async () => {
34
+ const counterPlugin = definePlugin("counter", ({ bind }) => {
35
+ bind("counter", () => {
36
+ const counter = {
37
+ value: 0,
38
+ incr() {
39
+ counter.value += 1;
40
+ },
41
+ };
42
+ return counter;
43
+ });
44
+ });
45
+ let observed = -1;
46
+ const bump = defineAction({
47
+ name: "bump",
48
+ schema: z.object({}),
49
+ handler: async (_input, ctx) => {
50
+ const counter = ctx.resolve("counter");
51
+ counter.incr();
52
+ observed = counter.value;
53
+ return undefined;
54
+ },
55
+ });
56
+ const app = createApp({
57
+ modules: [defineModule("m", { actions: [bump] })],
58
+ plugins: [counterPlugin],
59
+ });
60
+ await app.start();
61
+ await app.runtime.dispatch(bump, {});
62
+ await app.runtime.dispatch(bump, {});
63
+ expect(observed).toBe(2);
64
+ await app.stop();
65
+ });
66
+ it("middleware composes around dispatch", async () => {
67
+ const trace = [];
68
+ const action = defineAction({
69
+ name: "ping",
70
+ schema: z.object({}),
71
+ handler: async () => {
72
+ trace.push("handler");
73
+ return undefined;
74
+ },
75
+ });
76
+ const plugin = definePlugin("trace", {
77
+ middleware: [
78
+ async (next) => {
79
+ trace.push("mw:before");
80
+ const r = await next();
81
+ trace.push("mw:after");
82
+ return r;
83
+ },
84
+ ],
85
+ });
86
+ const app = createApp({
87
+ modules: [defineModule("m", { actions: [action] })],
88
+ plugins: [plugin],
89
+ });
90
+ await app.start();
91
+ await app.runtime.dispatch(action, {});
92
+ await app.stop();
93
+ expect(trace).toEqual(["mw:before", "handler", "mw:after"]);
94
+ });
95
+ it("actorHooks.afterTransition fires AFTER actor state change persists", async () => {
96
+ const TickSchema = z.object({ counter: z.number() });
97
+ const TickedEventDef = defineEvent({
98
+ name: "tick.ticked",
99
+ schema: z.object({ tickId: z.string() }),
100
+ });
101
+ const Ticked = eventFactory(TickedEventDef);
102
+ const Tick = defineActor("tick", {
103
+ schema: TickSchema,
104
+ key: "tickId",
105
+ initial: "fresh",
106
+ states: {
107
+ fresh: {
108
+ on: {
109
+ [TickedEventDef.name]: {
110
+ target: "done",
111
+ assign: (state) => ({ counter: (state.counter ?? 0) + 1 }),
112
+ },
113
+ },
114
+ },
115
+ done: { final: true },
116
+ },
117
+ });
118
+ const tickIt = defineAction({
119
+ name: "tick.it",
120
+ schema: z.object({ tickId: z.string() }),
121
+ handler: async (input) => Ticked({ tickId: input.tickId }),
122
+ });
123
+ const observed = [];
124
+ const observer = definePlugin("observer", {
125
+ actorHooks: {
126
+ afterTransition: (_actor, _key, fromState, toState) => {
127
+ observed.push({ from: fromState, to: toState });
128
+ },
129
+ },
130
+ });
131
+ const app = createApp({
132
+ modules: [
133
+ defineModule("m", {
134
+ actions: [tickIt],
135
+ actors: [Tick],
136
+ events: [TickedEventDef],
137
+ }),
138
+ ],
139
+ plugins: [observer],
140
+ });
141
+ await app.start();
142
+ await app.runtime.dispatch(tickIt, { tickId: "t1" });
143
+ await app.stop();
144
+ expect(observed).toEqual([{ from: "fresh", to: "done" }]);
145
+ });
146
+ it("plugins boot in declaration order; shutdown in REVERSE", async () => {
147
+ const order = [];
148
+ const a = definePlugin("a", {
149
+ boot: async () => {
150
+ order.push("a.boot");
151
+ },
152
+ shutdown: async () => {
153
+ order.push("a.shutdown");
154
+ },
155
+ });
156
+ const b = definePlugin("b", {
157
+ boot: async () => {
158
+ order.push("b.boot");
159
+ },
160
+ shutdown: async () => {
161
+ order.push("b.shutdown");
162
+ },
163
+ });
164
+ const app = createApp({ modules: [], plugins: [a, b] });
165
+ await app.start();
166
+ await app.stop();
167
+ expect(order).toEqual(["a.boot", "b.boot", "b.shutdown", "a.shutdown"]);
168
+ });
169
+ it("shutdown errors propagate (so wires can detect stuck shutdowns)", async () => {
170
+ const flaky = definePlugin("flaky", {
171
+ boot: async () => undefined,
172
+ shutdown: async () => {
173
+ throw new Error("shutdown failed");
174
+ },
175
+ });
176
+ const app = createApp({ modules: [], plugins: [flaky] });
177
+ await app.start();
178
+ await expect(app.stop()).rejects.toThrow(/shutdown failed/);
179
+ });
180
+ it("a bad plugin's shutdown does NOT block subsequent plugins", async () => {
181
+ const order = [];
182
+ const bad = definePlugin("bad", {
183
+ shutdown: async () => {
184
+ order.push("bad.attempted");
185
+ throw new Error("boom");
186
+ },
187
+ });
188
+ const good = definePlugin("good", {
189
+ shutdown: async () => {
190
+ order.push("good.shutdown");
191
+ },
192
+ });
193
+ // Declaration order: bad → good. Reverse shutdown: good → bad. Good
194
+ // must run before bad's failure is surfaced.
195
+ const app = createApp({ modules: [], plugins: [bad, good] });
196
+ await app.start();
197
+ await expect(app.stop()).rejects.toThrow(/boom/);
198
+ expect(order).toEqual(["good.shutdown", "bad.attempted"]);
199
+ });
200
+ it("plugin can register values on the container at register time", async () => {
201
+ const auditPlugin = definePlugin("audit", {
202
+ register: (container) => {
203
+ container.register("auditLogger", { tag: "audit" });
204
+ },
205
+ });
206
+ let seen = null;
207
+ const action = defineAction({
208
+ name: "audited",
209
+ schema: z.object({}),
210
+ handler: async (_input, ctx) => {
211
+ seen = ctx.resolve("auditLogger");
212
+ return undefined;
213
+ },
214
+ });
215
+ const app = createApp({
216
+ modules: [defineModule("m", { actions: [action] })],
217
+ plugins: [auditPlugin],
218
+ });
219
+ await app.start();
220
+ await app.runtime.dispatch(action, {});
221
+ expect(seen).toEqual({ tag: "audit" });
222
+ await app.stop();
223
+ });
224
+ });
225
+ //# sourceMappingURL=plugin.test.js.map