@nwire/forge 0.9.1 → 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 (291) hide show
  1. package/README.md +133 -155
  2. package/dist/foundation.d.ts +0 -13
  3. package/dist/foundation.js +2 -13
  4. package/dist/framework-events.d.ts +51 -54
  5. package/dist/framework-events.js +18 -65
  6. package/dist/{cli-runner.d.ts → helpers/cli-runner.d.ts} +2 -3
  7. package/dist/{cli-runner.js → helpers/cli-runner.js} +11 -27
  8. package/dist/{public-marker.d.ts → helpers/public-marker.d.ts} +0 -1
  9. package/dist/{public-marker.js → helpers/public-marker.js} +0 -1
  10. package/dist/{response.d.ts → helpers/response.d.ts} +0 -1
  11. package/dist/{response.js → helpers/response.js} +0 -1
  12. package/dist/helpers/retry-helpers.d.ts +22 -0
  13. package/dist/helpers/retry-helpers.js +43 -0
  14. package/dist/{validate.d.ts → helpers/validate.d.ts} +0 -1
  15. package/dist/{validate.js → helpers/validate.js} +0 -1
  16. package/dist/index.d.ts +42 -48
  17. package/dist/index.js +47 -55
  18. package/dist/{event-message.d.ts → messages/event-message.d.ts} +0 -1
  19. package/dist/{event-message.js → messages/event-message.js} +0 -1
  20. package/dist/{dev-logger.d.ts → observability/dev-logger.d.ts} +2 -2
  21. package/dist/{dev-logger.js → observability/dev-logger.js} +0 -30
  22. package/dist/{define-action.d.ts → primitives/define-action.d.ts} +0 -1
  23. package/dist/{define-action.js → primitives/define-action.js} +0 -1
  24. package/dist/{define-actor.d.ts → primitives/define-actor.d.ts} +0 -1
  25. package/dist/{define-actor.js → primitives/define-actor.js} +0 -1
  26. package/dist/{define-cron.d.ts → primitives/define-cron.d.ts} +0 -1
  27. package/dist/{define-cron.js → primitives/define-cron.js} +0 -1
  28. package/dist/{define-error.d.ts → primitives/define-error.d.ts} +0 -1
  29. package/dist/{define-error.js → primitives/define-error.js} +0 -1
  30. package/dist/{define-external-call.d.ts → primitives/define-external-call.d.ts} +0 -1
  31. package/dist/{define-external-call.js → primitives/define-external-call.js} +0 -1
  32. package/dist/{define-handler.d.ts → primitives/define-handler.d.ts} +29 -2
  33. package/dist/{define-handler.js → primitives/define-handler.js} +13 -2
  34. package/dist/{define-inbound-webhook.d.ts → primitives/define-inbound-webhook.d.ts} +1 -2
  35. package/dist/{define-inbound-webhook.js → primitives/define-inbound-webhook.js} +1 -2
  36. package/dist/{define-inbox.d.ts → primitives/define-inbox.d.ts} +0 -1
  37. package/dist/{define-inbox.js → primitives/define-inbox.js} +0 -1
  38. package/dist/{define-outbox.d.ts → primitives/define-outbox.d.ts} +0 -1
  39. package/dist/{define-outbox.js → primitives/define-outbox.js} +0 -1
  40. package/dist/{define-projection.d.ts → primitives/define-projection.d.ts} +0 -1
  41. package/dist/{define-projection.js → primitives/define-projection.js} +0 -1
  42. package/dist/{define-query.d.ts → primitives/define-query.d.ts} +1 -2
  43. package/dist/{define-query.js → primitives/define-query.js} +1 -2
  44. package/dist/{define-schema.d.ts → primitives/define-schema.d.ts} +1 -2
  45. package/dist/{define-schema.js → primitives/define-schema.js} +1 -2
  46. package/dist/{define-upcaster.d.ts → primitives/define-upcaster.d.ts} +0 -1
  47. package/dist/{define-upcaster.js → primitives/define-upcaster.js} +0 -1
  48. package/dist/{define-workflow.d.ts → primitives/define-workflow.d.ts} +2 -3
  49. package/dist/{define-workflow.js → primitives/define-workflow.js} +2 -3
  50. package/dist/runtime/create-forge-app.d.ts +64 -0
  51. package/dist/runtime/create-forge-app.js +78 -0
  52. package/dist/runtime/forge-dispatcher.d.ts +148 -0
  53. package/dist/{runtime.js → runtime/forge-dispatcher.js} +242 -571
  54. package/dist/runtime/forge-plugin.d.ts +59 -0
  55. package/dist/runtime/forge-plugin.js +121 -0
  56. package/dist/runtime/forge-types.d.ts +204 -0
  57. package/dist/runtime/forge-types.js +5 -0
  58. package/dist/{actor-store.d.ts → stores/actor-store.d.ts} +1 -2
  59. package/dist/{actor-store.js → stores/actor-store.js} +0 -1
  60. package/dist/{idempotency-store.d.ts → stores/idempotency-store.d.ts} +0 -1
  61. package/dist/{idempotency-store.js → stores/idempotency-store.js} +0 -1
  62. package/dist/{projection-store.d.ts → stores/projection-store.d.ts} +0 -1
  63. package/dist/{projection-store.js → stores/projection-store.js} +0 -1
  64. package/dist/{workflow-timer-store.d.ts → stores/workflow-timer-store.d.ts} +0 -1
  65. package/dist/{workflow-timer-store.js → stores/workflow-timer-store.js} +0 -1
  66. package/package.json +11 -11
  67. package/dist/__tests__/action-hooks.test.d.ts +0 -8
  68. package/dist/__tests__/action-hooks.test.d.ts.map +0 -1
  69. package/dist/__tests__/action-hooks.test.js +0 -95
  70. package/dist/__tests__/action-hooks.test.js.map +0 -1
  71. package/dist/__tests__/actor-methods.test.d.ts +0 -9
  72. package/dist/__tests__/actor-methods.test.d.ts.map +0 -1
  73. package/dist/__tests__/actor-methods.test.js +0 -210
  74. package/dist/__tests__/actor-methods.test.js.map +0 -1
  75. package/dist/__tests__/actor-schema-bound.test.d.ts +0 -6
  76. package/dist/__tests__/actor-schema-bound.test.d.ts.map +0 -1
  77. package/dist/__tests__/actor-schema-bound.test.js +0 -112
  78. package/dist/__tests__/actor-schema-bound.test.js.map +0 -1
  79. package/dist/__tests__/actor-workflow-hooks.test.d.ts +0 -8
  80. package/dist/__tests__/actor-workflow-hooks.test.d.ts.map +0 -1
  81. package/dist/__tests__/actor-workflow-hooks.test.js +0 -106
  82. package/dist/__tests__/actor-workflow-hooks.test.js.map +0 -1
  83. package/dist/__tests__/app-capabilities.test.d.ts +0 -19
  84. package/dist/__tests__/app-capabilities.test.d.ts.map +0 -1
  85. package/dist/__tests__/app-capabilities.test.js +0 -57
  86. package/dist/__tests__/app-capabilities.test.js.map +0 -1
  87. package/dist/__tests__/cli-runner.test.d.ts +0 -6
  88. package/dist/__tests__/cli-runner.test.d.ts.map +0 -1
  89. package/dist/__tests__/cli-runner.test.js +0 -158
  90. package/dist/__tests__/cli-runner.test.js.map +0 -1
  91. package/dist/__tests__/create-app.test.d.ts +0 -18
  92. package/dist/__tests__/create-app.test.d.ts.map +0 -1
  93. package/dist/__tests__/create-app.test.js +0 -189
  94. package/dist/__tests__/create-app.test.js.map +0 -1
  95. package/dist/__tests__/cross-service-bus.test.d.ts +0 -8
  96. package/dist/__tests__/cross-service-bus.test.d.ts.map +0 -1
  97. package/dist/__tests__/cross-service-bus.test.js +0 -139
  98. package/dist/__tests__/cross-service-bus.test.js.map +0 -1
  99. package/dist/__tests__/define-schema.test.d.ts +0 -5
  100. package/dist/__tests__/define-schema.test.d.ts.map +0 -1
  101. package/dist/__tests__/define-schema.test.js +0 -83
  102. package/dist/__tests__/define-schema.test.js.map +0 -1
  103. package/dist/__tests__/dev-logger.test.d.ts +0 -9
  104. package/dist/__tests__/dev-logger.test.d.ts.map +0 -1
  105. package/dist/__tests__/dev-logger.test.js +0 -126
  106. package/dist/__tests__/dev-logger.test.js.map +0 -1
  107. package/dist/__tests__/external-call.test.d.ts +0 -14
  108. package/dist/__tests__/external-call.test.d.ts.map +0 -1
  109. package/dist/__tests__/external-call.test.js +0 -99
  110. package/dist/__tests__/external-call.test.js.map +0 -1
  111. package/dist/__tests__/framework-events.test.d.ts +0 -13
  112. package/dist/__tests__/framework-events.test.d.ts.map +0 -1
  113. package/dist/__tests__/framework-events.test.js +0 -204
  114. package/dist/__tests__/framework-events.test.js.map +0 -1
  115. package/dist/__tests__/inline-handler.test.d.ts +0 -8
  116. package/dist/__tests__/inline-handler.test.d.ts.map +0 -1
  117. package/dist/__tests__/inline-handler.test.js +0 -101
  118. package/dist/__tests__/inline-handler.test.js.map +0 -1
  119. package/dist/__tests__/lifecycle-logging.test.d.ts +0 -12
  120. package/dist/__tests__/lifecycle-logging.test.d.ts.map +0 -1
  121. package/dist/__tests__/lifecycle-logging.test.js +0 -114
  122. package/dist/__tests__/lifecycle-logging.test.js.map +0 -1
  123. package/dist/__tests__/middleware.test.d.ts +0 -7
  124. package/dist/__tests__/middleware.test.d.ts.map +0 -1
  125. package/dist/__tests__/middleware.test.js +0 -109
  126. package/dist/__tests__/middleware.test.js.map +0 -1
  127. package/dist/__tests__/module-needs.test.d.ts +0 -10
  128. package/dist/__tests__/module-needs.test.d.ts.map +0 -1
  129. package/dist/__tests__/module-needs.test.js +0 -77
  130. package/dist/__tests__/module-needs.test.js.map +0 -1
  131. package/dist/__tests__/module-topo-sort.test.d.ts +0 -15
  132. package/dist/__tests__/module-topo-sort.test.d.ts.map +0 -1
  133. package/dist/__tests__/module-topo-sort.test.js +0 -105
  134. package/dist/__tests__/module-topo-sort.test.js.map +0 -1
  135. package/dist/__tests__/multi-tenancy.test.d.ts +0 -10
  136. package/dist/__tests__/multi-tenancy.test.d.ts.map +0 -1
  137. package/dist/__tests__/multi-tenancy.test.js +0 -122
  138. package/dist/__tests__/multi-tenancy.test.js.map +0 -1
  139. package/dist/__tests__/needs-topology.test.d.ts +0 -11
  140. package/dist/__tests__/needs-topology.test.d.ts.map +0 -1
  141. package/dist/__tests__/needs-topology.test.js +0 -82
  142. package/dist/__tests__/needs-topology.test.js.map +0 -1
  143. package/dist/__tests__/plugin-app-narrow.test.d.ts +0 -12
  144. package/dist/__tests__/plugin-app-narrow.test.d.ts.map +0 -1
  145. package/dist/__tests__/plugin-app-narrow.test.js +0 -77
  146. package/dist/__tests__/plugin-app-narrow.test.js.map +0 -1
  147. package/dist/__tests__/plugin-closure.test.d.ts +0 -15
  148. package/dist/__tests__/plugin-closure.test.d.ts.map +0 -1
  149. package/dist/__tests__/plugin-closure.test.js +0 -140
  150. package/dist/__tests__/plugin-closure.test.js.map +0 -1
  151. package/dist/__tests__/plugin-stress.test.d.ts +0 -21
  152. package/dist/__tests__/plugin-stress.test.d.ts.map +0 -1
  153. package/dist/__tests__/plugin-stress.test.js +0 -203
  154. package/dist/__tests__/plugin-stress.test.js.map +0 -1
  155. package/dist/__tests__/plugin.test.d.ts +0 -10
  156. package/dist/__tests__/plugin.test.d.ts.map +0 -1
  157. package/dist/__tests__/plugin.test.js +0 -225
  158. package/dist/__tests__/plugin.test.js.map +0 -1
  159. package/dist/__tests__/primitives.test.d.ts +0 -9
  160. package/dist/__tests__/primitives.test.d.ts.map +0 -1
  161. package/dist/__tests__/primitives.test.js +0 -434
  162. package/dist/__tests__/primitives.test.js.map +0 -1
  163. package/dist/__tests__/production-readiness.test.d.ts +0 -22
  164. package/dist/__tests__/production-readiness.test.d.ts.map +0 -1
  165. package/dist/__tests__/production-readiness.test.js +0 -196
  166. package/dist/__tests__/production-readiness.test.js.map +0 -1
  167. package/dist/__tests__/provider.test.d.ts +0 -6
  168. package/dist/__tests__/provider.test.d.ts.map +0 -1
  169. package/dist/__tests__/provider.test.js +0 -122
  170. package/dist/__tests__/provider.test.js.map +0 -1
  171. package/dist/__tests__/public-marker.test.d.ts +0 -7
  172. package/dist/__tests__/public-marker.test.d.ts.map +0 -1
  173. package/dist/__tests__/public-marker.test.js +0 -58
  174. package/dist/__tests__/public-marker.test.js.map +0 -1
  175. package/dist/__tests__/retry-dlq.test.d.ts +0 -6
  176. package/dist/__tests__/retry-dlq.test.d.ts.map +0 -1
  177. package/dist/__tests__/retry-dlq.test.js +0 -68
  178. package/dist/__tests__/retry-dlq.test.js.map +0 -1
  179. package/dist/__tests__/validate.test.d.ts +0 -5
  180. package/dist/__tests__/validate.test.d.ts.map +0 -1
  181. package/dist/__tests__/validate.test.js +0 -53
  182. package/dist/__tests__/validate.test.js.map +0 -1
  183. package/dist/__tests__/workflow-saga.test.d.ts +0 -7
  184. package/dist/__tests__/workflow-saga.test.d.ts.map +0 -1
  185. package/dist/__tests__/workflow-saga.test.js +0 -265
  186. package/dist/__tests__/workflow-saga.test.js.map +0 -1
  187. package/dist/actor-store.d.ts.map +0 -1
  188. package/dist/actor-store.js.map +0 -1
  189. package/dist/cli-runner.d.ts.map +0 -1
  190. package/dist/cli-runner.js.map +0 -1
  191. package/dist/create-app.d.ts +0 -146
  192. package/dist/create-app.d.ts.map +0 -1
  193. package/dist/create-app.js +0 -703
  194. package/dist/create-app.js.map +0 -1
  195. package/dist/define-action.d.ts.map +0 -1
  196. package/dist/define-action.js.map +0 -1
  197. package/dist/define-actor.d.ts.map +0 -1
  198. package/dist/define-actor.js.map +0 -1
  199. package/dist/define-app.d.ts +0 -104
  200. package/dist/define-app.d.ts.map +0 -1
  201. package/dist/define-app.js +0 -49
  202. package/dist/define-app.js.map +0 -1
  203. package/dist/define-cron.d.ts.map +0 -1
  204. package/dist/define-cron.js.map +0 -1
  205. package/dist/define-error.d.ts.map +0 -1
  206. package/dist/define-error.js.map +0 -1
  207. package/dist/define-external-call.d.ts.map +0 -1
  208. package/dist/define-external-call.js.map +0 -1
  209. package/dist/define-handler.d.ts.map +0 -1
  210. package/dist/define-handler.js.map +0 -1
  211. package/dist/define-inbound-webhook.d.ts.map +0 -1
  212. package/dist/define-inbound-webhook.js.map +0 -1
  213. package/dist/define-inbox.d.ts.map +0 -1
  214. package/dist/define-inbox.js.map +0 -1
  215. package/dist/define-initializer.d.ts +0 -54
  216. package/dist/define-initializer.d.ts.map +0 -1
  217. package/dist/define-initializer.js +0 -38
  218. package/dist/define-initializer.js.map +0 -1
  219. package/dist/define-middleware.d.ts +0 -8
  220. package/dist/define-middleware.d.ts.map +0 -1
  221. package/dist/define-middleware.js +0 -8
  222. package/dist/define-middleware.js.map +0 -1
  223. package/dist/define-model.d.ts +0 -10
  224. package/dist/define-model.d.ts.map +0 -1
  225. package/dist/define-model.js +0 -13
  226. package/dist/define-model.js.map +0 -1
  227. package/dist/define-module.d.ts +0 -160
  228. package/dist/define-module.d.ts.map +0 -1
  229. package/dist/define-module.js +0 -63
  230. package/dist/define-module.js.map +0 -1
  231. package/dist/define-outbox.d.ts.map +0 -1
  232. package/dist/define-outbox.js.map +0 -1
  233. package/dist/define-plugin.d.ts +0 -195
  234. package/dist/define-plugin.d.ts.map +0 -1
  235. package/dist/define-plugin.js +0 -220
  236. package/dist/define-plugin.js.map +0 -1
  237. package/dist/define-projection.d.ts.map +0 -1
  238. package/dist/define-projection.js.map +0 -1
  239. package/dist/define-provider.d.ts +0 -49
  240. package/dist/define-provider.d.ts.map +0 -1
  241. package/dist/define-provider.js +0 -45
  242. package/dist/define-provider.js.map +0 -1
  243. package/dist/define-query.d.ts.map +0 -1
  244. package/dist/define-query.js.map +0 -1
  245. package/dist/define-resolver.d.ts +0 -111
  246. package/dist/define-resolver.d.ts.map +0 -1
  247. package/dist/define-resolver.js +0 -146
  248. package/dist/define-resolver.js.map +0 -1
  249. package/dist/define-schema.d.ts.map +0 -1
  250. package/dist/define-schema.js.map +0 -1
  251. package/dist/define-upcaster.d.ts.map +0 -1
  252. package/dist/define-upcaster.js.map +0 -1
  253. package/dist/define-workflow.d.ts.map +0 -1
  254. package/dist/define-workflow.js.map +0 -1
  255. package/dist/dev-logger.d.ts.map +0 -1
  256. package/dist/dev-logger.js.map +0 -1
  257. package/dist/event-message.d.ts.map +0 -1
  258. package/dist/event-message.js.map +0 -1
  259. package/dist/foundation.d.ts.map +0 -1
  260. package/dist/foundation.js.map +0 -1
  261. package/dist/framework-event-bus.d.ts +0 -13
  262. package/dist/framework-event-bus.d.ts.map +0 -1
  263. package/dist/framework-event-bus.js +0 -13
  264. package/dist/framework-event-bus.js.map +0 -1
  265. package/dist/framework-events.d.ts.map +0 -1
  266. package/dist/framework-events.js.map +0 -1
  267. package/dist/idempotency-store.d.ts.map +0 -1
  268. package/dist/idempotency-store.js.map +0 -1
  269. package/dist/index.d.ts.map +0 -1
  270. package/dist/index.js.map +0 -1
  271. package/dist/module-surface.d.ts +0 -47
  272. package/dist/module-surface.d.ts.map +0 -1
  273. package/dist/module-surface.js +0 -65
  274. package/dist/module-surface.js.map +0 -1
  275. package/dist/projection-store.d.ts.map +0 -1
  276. package/dist/projection-store.js.map +0 -1
  277. package/dist/public-marker.d.ts.map +0 -1
  278. package/dist/public-marker.js.map +0 -1
  279. package/dist/response.d.ts.map +0 -1
  280. package/dist/response.js.map +0 -1
  281. package/dist/runtime.d.ts +0 -621
  282. package/dist/runtime.d.ts.map +0 -1
  283. package/dist/runtime.js.map +0 -1
  284. package/dist/validate.d.ts.map +0 -1
  285. package/dist/validate.js.map +0 -1
  286. package/dist/when.d.ts +0 -101
  287. package/dist/when.d.ts.map +0 -1
  288. package/dist/when.js +0 -57
  289. package/dist/when.js.map +0 -1
  290. package/dist/workflow-timer-store.d.ts.map +0 -1
  291. package/dist/workflow-timer-store.js.map +0 -1
@@ -1,703 +0,0 @@
1
- /**
2
- * `createApp` — declarative app composition.
3
- *
4
- * const app = createApp({
5
- * modules: [submissions, enrollments],
6
- * actorStore: new InMemoryActorStore(),
7
- * projectionStore: new InMemoryProjectionStore()
8
- * })
9
- * await app.start()
10
- *
11
- * `createApp` walks each module's manifest and registers actions, actors,
12
- * handlers, projections, queries, and workflows into a fresh `Runtime`.
13
- * The runtime is exposed for transports (HTTP, queue, cron) to bind to.
14
- *
15
- * Transports are NOT bundled here yet — for now, wires hand-attach Koa
16
- * routers using `createDispatchHttp(app)`. Full `expose: [http(...), worker(...)]`
17
- * shape lands when the second wire (queue worker) needs it.
18
- *
19
- * Lifecycle:
20
- * - `start()` — currently a no-op (registration happens at construction).
21
- * Becomes meaningful when transports + timer scheduler join the surface.
22
- * - `stop()` — drains pending work; reserved.
23
- * - `runtime` — escape hatch for transports and tests.
24
- */
25
- import { seedEnvelope } from "@nwire/envelope";
26
- import { hook } from "@nwire/hooks";
27
- import { Runtime } from "./runtime.js";
28
- import { adaptAppPlugin, buildPluginContext } from "./define-plugin.js";
29
- import { isAppPlugin } from "@nwire/app";
30
- import { AppRegistering, AppBooting, AppBooted, AppShuttingDown, AppShutdown, PluginRegistered, PluginBooting, PluginBooted, PluginShuttingDown, PluginShutdown, builtInLifecycleEvents, } from "./framework-events.js";
31
- export function createApp(options) {
32
- // Normalize the plugin list: callers may pass either the rich forge
33
- // PluginDefinition or the narrow @nwire/app AppPluginDefinition. The
34
- // downstream pipeline (register loop, framework events, shutdown) is
35
- // written against the rich shape, so adapt narrow → rich here. Every
36
- // subsequent use of `options.plugins` reads from `plugins` instead.
37
- const plugins = (options.plugins ?? []).map((p) => isAppPlugin(p) ? adaptAppPlugin(p) : p);
38
- // Index local provides up-front — both validators and the bus-subscription
39
- // loop need to know "is this event satisfiable locally in this app?"
40
- const localEventProviders = indexLocalEventProviders(options.modules);
41
- const localActionProviders = indexLocalActionProviders(options.modules);
42
- validateModuleDepGraph(options.modules, localEventProviders, localActionProviders, !!options.bus);
43
- validateExternalEventDeps(options.modules, localEventProviders, !!options.bus);
44
- /**
45
- * Sort modules topologically by `needs.events / needs.actions`.
46
- * Producers register before consumers. Registration is order-independent
47
- * for correctness today (every reference resolves through the runtime's
48
- * registries which are populated up-front), but the order DOES affect:
49
- *
50
- * - Studio's render order (modules appear in dep order, not source order)
51
- * - Telemetry: PluginRegistered events fire in topological order
52
- * - Future module-as-plugin work: if defineModule ever produces a
53
- * plugin with its own boot phase, the order will become load-bearing.
54
- *
55
- * Topo failures (cycles, unknown refs) throw at boot with a readable
56
- * trace — same error class as validateModuleDepGraph so the cause is
57
- * surfaced before any traffic.
58
- */
59
- const orderedModules = topoSortModules(options.modules, localEventProviders, localActionProviders);
60
- const runtime = new Runtime({
61
- container: options.container,
62
- actorStore: options.actorStore,
63
- projectionStore: options.projectionStore,
64
- logger: options.logger,
65
- deadLetterSink: options.deadLetterSink,
66
- bus: options.bus,
67
- publishToBus: options.publishToBus,
68
- appName: options.appName,
69
- });
70
- /**
71
- * Register app capabilities on the container so handlers in any
72
- * transport (HTTP / queue / CLI) can `ctx.resolve("execute")` etc.
73
- * Each call seeds a fresh envelope — transports running outside an
74
- * existing dispatch chain don't have one to inherit. For HTTP that's
75
- * exactly right: each request is its own causal root.
76
- *
77
- * Share the runtime's container so caller-provided + base-default forms
78
- * both wire to the same instance — separate fallback container creation
79
- * would produce divergent containers and break `ctx.resolve("execute")`.
80
- */
81
- const containerForCaps = runtime.getContainer();
82
- containerForCaps.register("execute", () => {
83
- return async function execute(actionOrCmd, input) {
84
- const { resolveDispatch } = await import("./define-action.js");
85
- const { action, input: resolvedInput } = resolveDispatch(actionOrCmd, input, runtime.findActionByName.bind(runtime));
86
- return await runtime.dispatch(action, resolvedInput, seedEnvelope({}));
87
- };
88
- });
89
- containerForCaps.register("send", () => {
90
- return async function send(actionOrCmd, input) {
91
- // Fire-and-forget — same envelope-per-call story as `execute`.
92
- const { resolveDispatch } = await import("./define-action.js");
93
- const { action, input: resolvedInput } = resolveDispatch(actionOrCmd, input, runtime.findActionByName.bind(runtime));
94
- await runtime.dispatch(action, resolvedInput, seedEnvelope({}));
95
- };
96
- });
97
- containerForCaps.register("useProjection", () => {
98
- return async function useProjection(
99
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
- queryDef, input) {
101
- return runtime.query(queryDef.name, input, "");
102
- };
103
- });
104
- let timerInterval = null;
105
- /** Subscription handle from attachDevLogger — called in stop() to detach. */
106
- let detachDevLogger = null;
107
- /** Subscribe the bus for an external event name once (dedup across modules). */
108
- const subscribedExternal = new Set();
109
- const subscribeExternal = (eventName) => {
110
- if (subscribedExternal.has(eventName))
111
- return;
112
- subscribedExternal.add(eventName);
113
- runtime.registerExternalEvent(eventName);
114
- if (!options.bus)
115
- return; // validators ensured this won't happen
116
- options.bus.subscribe(eventName, async (msg) => {
117
- if (msg.origin && options.appName && msg.origin === options.appName)
118
- return;
119
- await runtime.applyExternalEvent(eventName, msg.payload, msg.envelope);
120
- });
121
- };
122
- for (const module of orderedModules) {
123
- const m = module.manifest;
124
- for (const actor of m.actors ?? [])
125
- runtime.registerActor(actor);
126
- for (const handler of m.handlers ?? [])
127
- runtime.registerHandler(handler);
128
- // Inline handlers — `defineAction({..., handler})` attaches a synthesized
129
- // HandlerDefinition to the action. Register here so authors can pick
130
- // either form (inline action+handler OR separate defineHandler) without
131
- // a module-manifest change.
132
- for (const action of m.actions ?? []) {
133
- if (action.handler)
134
- runtime.registerHandler(action.handler);
135
- }
136
- for (const projection of m.projections ?? [])
137
- runtime.registerProjection(projection);
138
- for (const query of m.queries ?? [])
139
- runtime.registerQuery(query);
140
- for (const workflow of m.workflows ?? []) {
141
- runtime.registerWorkflow(workflow);
142
- }
143
- // Orchestrator primitives — Studio sees them, runtime registers the
144
- // call definitions for executor binding by wires/adapters.
145
- for (const call of m.externalCalls ?? []) {
146
- runtime.registerExternalCall(call);
147
- }
148
- // Track public-event names so the runtime knows which to bus-publish.
149
- for (const e of m.events ?? []) {
150
- if ((e.visibility ?? "public") === "public") {
151
- runtime.registerPublicEvent(e.name);
152
- }
153
- }
154
- // For each declared event need: if a producer lives in THIS app,
155
- // do nothing (the in-process publish pipeline handles it). If not,
156
- // and a bus is configured, auto-subscribe — the same declaration
157
- // works in monolith and split topologies.
158
- for (const e of m.needs?.events ?? []) {
159
- if (!localEventProviders.has(e.name)) {
160
- subscribeExternal(e.name);
161
- }
162
- }
163
- // `externalEvents` is the strict-marker form — requires NON-local.
164
- // The validator enforces that; here we just auto-wire.
165
- for (const e of m.needs?.externalEvents ?? []) {
166
- subscribeExternal(e.name);
167
- }
168
- // Routes are bound by transports, not by the runtime.
169
- }
170
- // Pre-create per-module lifecycle hooks so `listHooks()` + scan see
171
- // them even before start() runs. The hooks are adopted by the runtime
172
- // immediately (so taps are wired); chain steps are attached in start().
173
- const pluginBootHooks = new Map();
174
- const pluginShutdownHooks = new Map();
175
- for (const module of orderedModules) {
176
- const bootH = hook(`plugin.boot:${module.name}`);
177
- const shutdownH = hook(`plugin.shutdown:${module.name}`);
178
- runtime.adoptHook(bootH);
179
- runtime.adoptHook(shutdownH);
180
- pluginBootHooks.set(module.name, bootH);
181
- pluginShutdownHooks.set(module.name, shutdownH);
182
- }
183
- // Same for real plugins.
184
- for (const plugin of plugins) {
185
- const bootH = hook(`plugin.boot:${plugin.name}`);
186
- const shutdownH = hook(`plugin.shutdown:${plugin.name}`);
187
- runtime.adoptHook(bootH);
188
- runtime.adoptHook(shutdownH);
189
- pluginBootHooks.set(plugin.name, bootH);
190
- pluginShutdownHooks.set(plugin.name, shutdownH);
191
- }
192
- const container = runtime.getContainer();
193
- /** Booted provider values, indexed by provider name. Populated in start(). */
194
- // Plugins: register synchronously up-front (before async boot) so they
195
- // can declare container values + framework subscriptions + middleware
196
- // before the first dispatch.
197
- //
198
- // The closure form's `setup` runs here too; its `boot`/`shutdown`
199
- // builder calls accumulate into per-plugin lists that get fired in
200
- // order during start() / stop().
201
- /**
202
- * Per-plugin closure-form boots, keyed by plugin index. Keeping the
203
- * association lets the start() loop wrap each plugin with its own
204
- * PluginBooting/PluginBooted framework events (rather than firing one
205
- * "everyone's boots fired" event for the collapsed list).
206
- */
207
- const closureBoots = new Map();
208
- /** Keyed by plugin index so we can fire reverse on stop in-order. */
209
- const closureShutdowns = new Map();
210
- let pluginIdx = 0;
211
- for (const plugin of plugins) {
212
- if (plugin.register)
213
- plugin.register(container);
214
- if (plugin.setup) {
215
- const { api, boots, shutdowns } = buildPluginContext(container, runtime, options.config ?? {}, plugin.name);
216
- // The setup function may be async; we kick it off and await its
217
- // promise so the closure can use top-level `await` if it wants.
218
- const result = plugin.setup(api);
219
- if (result instanceof Promise) {
220
- // Top-level register loop is sync; we can't await here without
221
- // restructuring createApp. Surface the requirement explicitly.
222
- throw new Error(`definePlugin("${plugin.name}"): closure form must be synchronous. ` +
223
- `Use the api.boot(...) builder for async startup work.`);
224
- }
225
- closureBoots.set(pluginIdx, boots);
226
- closureShutdowns.set(pluginIdx, shutdowns);
227
- }
228
- for (const mw of plugin.middleware ?? [])
229
- runtime.use(mw);
230
- if (plugin.actorHooks?.afterTransition) {
231
- runtime.registerActorTransitionHook(plugin.actorHooks.afterTransition);
232
- }
233
- pluginIdx++;
234
- }
235
- // Name → definition map for `dispatchFrameworkEvent`. Built once from
236
- // the built-in lifecycle catalog; endpoint passes event NAMES (strings)
237
- // so it stays decoupled from `@nwire/app`'s typed event definitions.
238
- const lifecycleEventsByName = new Map();
239
- for (const def of builtInLifecycleEvents) {
240
- lifecycleEventsByName.set(def.name, def);
241
- }
242
- const app = {
243
- $nwireApp: true,
244
- runtime,
245
- modules: options.modules,
246
- plugins,
247
- container,
248
- get appName() {
249
- return runtime.appName;
250
- },
251
- async dispatchFrameworkEvent(eventName, payload) {
252
- const def = lifecycleEventsByName.get(eventName);
253
- if (!def) {
254
- // Unknown event — silently no-op. The endpoint may send events
255
- // a future framework version defines; an older app shouldn't
256
- // crash on receipt. Returning `true` matches "nothing
257
- // prevented" semantics.
258
- return true;
259
- }
260
- return runtime.frameworkEvents.fire(def, payload);
261
- },
262
- async boot() {
263
- return this.start();
264
- },
265
- async shutdown() {
266
- return this.stop();
267
- },
268
- async start() {
269
- const appName = runtime.appName;
270
- // Announce composition BEFORE lifecycle fires. Modules first (in
271
- // topological order — same order they registered into the runtime),
272
- // then plugins. Same `PluginRegistered` event with `kind: "module" |
273
- // "plugin"` so observers can render them differently but read from
274
- // one channel.
275
- for (const module of orderedModules) {
276
- await runtime.frameworkEvents.fire(PluginRegistered, {
277
- appName,
278
- pluginName: module.name,
279
- kind: "module",
280
- });
281
- }
282
- for (const plugin of plugins) {
283
- await runtime.frameworkEvents.fire(PluginRegistered, {
284
- appName,
285
- pluginName: plugin.name,
286
- kind: "plugin",
287
- });
288
- }
289
- // Framework event: AppRegistering — interceptable. Plugins that
290
- // subscribed during their `register` hook can refuse boot here.
291
- const registeringOk = await runtime.frameworkEvents.fire(AppRegistering, { appName });
292
- if (!registeringOk) {
293
- throw new Error(`Boot prevented by AppRegistering subscriber (app: ${appName})`);
294
- }
295
- // Framework event: AppBooting — interceptable. Fired after the sync
296
- // plugin-register phase has populated the container, before any
297
- // async plugin boot runs.
298
- const bootingOk = await runtime.frameworkEvents.fire(AppBooting, { appName });
299
- if (!bootingOk) {
300
- throw new Error(`Boot prevented by AppBooting subscriber (app: ${appName})`);
301
- }
302
- // Modules first — same lifecycle event surface + per-name hooks as
303
- // plugins. The hook was pre-created in createApp body so scan +
304
- // listHooks() see it before start() runs.
305
- for (const module of orderedModules) {
306
- const bootHook = pluginBootHooks.get(module.name);
307
- const t = Date.now();
308
- const okBoot = await runtime.frameworkEvents.fire(PluginBooting, {
309
- appName,
310
- pluginName: module.name,
311
- kind: "module",
312
- });
313
- if (!okBoot) {
314
- throw new Error(`Boot of module "${module.name}" prevented by PluginBooting subscriber`);
315
- }
316
- await bootHook.run({ name: module.name, kind: "module" });
317
- await runtime.frameworkEvents.fire(PluginBooted, {
318
- appName,
319
- pluginName: module.name,
320
- durationMs: Date.now() - t,
321
- kind: "module",
322
- });
323
- }
324
- // Plugins boot in declaration order. Each plugin is wrapped with
325
- // PluginBooting (interceptable) + PluginBooted (observable, with
326
- // duration) so logging/tracing/Studio can show per-plugin boot
327
- // timing.
328
- //
329
- // Object-form plugins run their `boot` callback; closure-form
330
- // plugins additionally fire every `api.boot(...)` they registered
331
- // in setup, in declaration order.
332
- const allPlugins = plugins;
333
- for (let i = 0; i < allPlugins.length; i++) {
334
- const plugin = allPlugins[i];
335
- const pluginName = plugin.name;
336
- const okToBoot = await runtime.frameworkEvents.fire(PluginBooting, {
337
- appName,
338
- pluginName,
339
- kind: "plugin",
340
- });
341
- if (!okToBoot) {
342
- throw new Error(`Boot of plugin "${pluginName}" prevented by PluginBooting subscriber`);
343
- }
344
- // Per-plugin boot hook — pre-created in createApp body. Chain
345
- // steps attach JIT so listHooks() sees an empty hook until start()
346
- // runs (matches Studio's expected view of "what runs at boot").
347
- const bootHook = pluginBootHooks.get(pluginName);
348
- if (plugin.boot) {
349
- bootHook.use(async (_c, next) => {
350
- await plugin.boot(container);
351
- await next();
352
- }, {
353
- name: "boot",
354
- });
355
- }
356
- const boots = closureBoots.get(i);
357
- if (boots) {
358
- for (let k = 0; k < boots.length; k++) {
359
- const fn = boots[k];
360
- bootHook.use(async (_c, next) => {
361
- await fn();
362
- await next();
363
- }, {
364
- name: `closure[${k}]`,
365
- });
366
- }
367
- }
368
- const startedAt = Date.now();
369
- await bootHook.run({ name: pluginName, kind: "plugin" });
370
- await runtime.frameworkEvents.fire(PluginBooted, {
371
- appName,
372
- pluginName,
373
- durationMs: Date.now() - startedAt,
374
- kind: "plugin",
375
- });
376
- }
377
- // Default dev console logger. Off when:
378
- // - `devLogs: false` is explicit
379
- // - NODE_ENV=production (assume structured logger is configured)
380
- // - NODE_ENV=test (vitest output stays clean)
381
- // - a `logger` (pino/winston) is provided (structured wins)
382
- const devLogsOpt = options.devLogs;
383
- const env = process.env.NODE_ENV;
384
- const wantDevLogs = devLogsOpt === undefined
385
- ? env !== "production" && env !== "test" && !options.logger
386
- : devLogsOpt !== false;
387
- if (wantDevLogs) {
388
- const { attachDevLogger } = await import("./dev-logger.js");
389
- detachDevLogger = attachDevLogger(runtime, typeof devLogsOpt === "object" ? devLogsOpt : undefined);
390
- }
391
- // Framework event: AppBooted — observable. Awaited (parallel under
392
- // the hood) so start() returns only after every observer had a fair
393
- // window; useful for diagnostics that want to flush before serving.
394
- await runtime.frameworkEvents.fire(AppBooted, {
395
- appName,
396
- bootedAt: new Date().toISOString(),
397
- });
398
- if (options.timerScheduler) {
399
- const intervalMs = typeof options.timerScheduler === "object"
400
- ? (options.timerScheduler.intervalMs ?? 60_000)
401
- : 60_000;
402
- timerInterval = setInterval(() => {
403
- void runtime.fireDueTimers().catch(() => {
404
- // Swallow per-tick errors so a single bad timer doesn't
405
- // halt the scheduler. Production wires should plug in
406
- // a logger here.
407
- });
408
- }, intervalMs);
409
- // Don't keep the process alive solely on the timer interval —
410
- // the wire's HTTP server is the foreground process.
411
- if (typeof timerInterval.unref === "function") {
412
- timerInterval.unref();
413
- }
414
- }
415
- },
416
- async stop(reason) {
417
- const appName = runtime.appName;
418
- // Framework event: AppShuttingDown — interceptable. Subscribers
419
- // throwing here propagate as a failed shutdown; returning false
420
- // cleanly aborts (the wire can decide to retry or hard-kill).
421
- const shutdownOk = await runtime.frameworkEvents.fire(AppShuttingDown, {
422
- appName,
423
- reason,
424
- });
425
- if (!shutdownOk) {
426
- throw new Error(`Shutdown prevented by AppShuttingDown subscriber (app: ${appName})`);
427
- }
428
- if (timerInterval) {
429
- clearInterval(timerInterval);
430
- timerInterval = null;
431
- }
432
- if (detachDevLogger) {
433
- detachDevLogger();
434
- detachDevLogger = null;
435
- }
436
- // Plugins shut down in REVERSE declaration order. For each
437
- // closure-form plugin, fire every `api.shutdown(...)` it registered
438
- // — also in reverse, so dependencies declared late tear down first.
439
- //
440
- // Shutdown error isolation: a plugin's shutdown that throws MUST
441
- // NOT block subsequent plugins from running. A buggy plugin leaving
442
- // resources leaked is bad; that same bug also skipping every
443
- // downstream plugin's cleanup would compound the outage. Each
444
- // shutdown step is wrapped + logged; we collect errors and re-throw
445
- // the first one only AFTER the full sweep completes.
446
- //
447
- // Two contracts, both important:
448
- // (a) A single bad shutdown must NOT block subsequent plugins from
449
- // cleaning up. (production-readiness)
450
- // (b) Wires must still be able to detect shutdown failure so
451
- // SIGTERM → stop() → exit can surface the problem.
452
- // Resolution: collect errors during the sweep, complete every step,
453
- // then re-throw the first error AFTER the full sweep finishes.
454
- const shutdownErrors = [];
455
- const safeRun = async (label, fn) => {
456
- try {
457
- await fn();
458
- }
459
- catch (err) {
460
- shutdownErrors.push(err);
461
- // eslint-disable-next-line no-console
462
- console.error(`[nwire/shutdown] step "${label}" threw — continuing with remaining steps:`, err);
463
- }
464
- };
465
- for (let i = plugins.length - 1; i >= 0; i--) {
466
- const plugin = plugins[i];
467
- const pluginName = plugin.name;
468
- // PluginShuttingDown is interceptable, but a refusal at shutdown
469
- // is harder to honor cleanly — the app is on its way out and the
470
- // host (lightship) may force-kill us regardless. Honor the signal
471
- // by logging + skipping this plugin's teardown, NOT by throwing,
472
- // so downstream plugins still get a chance to clean up.
473
- const shutdownOkPerPlugin = await runtime.frameworkEvents.fire(PluginShuttingDown, {
474
- appName,
475
- pluginName,
476
- kind: "plugin",
477
- });
478
- if (!shutdownOkPerPlugin) {
479
- // eslint-disable-next-line no-console
480
- console.warn(`[nwire/shutdown] PluginShuttingDown subscriber refused — skipping plugin "${pluginName}"`);
481
- continue;
482
- }
483
- const startedAt = Date.now();
484
- if (plugin.shutdown)
485
- await safeRun(`plugin:${pluginName}`, () => plugin.shutdown(container));
486
- const shutdowns = closureShutdowns.get(i);
487
- if (shutdowns) {
488
- for (let j = shutdowns.length - 1; j >= 0; j--) {
489
- await safeRun(`plugin:${pluginName}:closure[${j}]`, () => shutdowns[j]());
490
- }
491
- }
492
- await runtime.frameworkEvents.fire(PluginShutdown, {
493
- appName,
494
- pluginName,
495
- durationMs: Date.now() - startedAt,
496
- kind: "plugin",
497
- });
498
- }
499
- // Symmetric module shutdown — emit lifecycle events for modules in
500
- // REVERSE order. Modules don't have explicit shutdown callbacks; the
501
- // events exist so observers see the full lifecycle close cleanly.
502
- for (let i = orderedModules.length - 1; i >= 0; i--) {
503
- const module = orderedModules[i];
504
- const t = Date.now();
505
- const ok = await runtime.frameworkEvents.fire(PluginShuttingDown, {
506
- appName,
507
- pluginName: module.name,
508
- kind: "module",
509
- });
510
- if (!ok)
511
- continue;
512
- await runtime.frameworkEvents.fire(PluginShutdown, {
513
- appName,
514
- pluginName: module.name,
515
- durationMs: Date.now() - t,
516
- kind: "module",
517
- });
518
- }
519
- // Framework event: AppShutdown — observable. Awaited so stop()
520
- // doesn't return until subscribers had a fair window.
521
- await runtime.frameworkEvents.fire(AppShutdown, { appName });
522
- // Surface the first shutdown error (if any) AFTER every other step
523
- // has run. Wires (and tests) that need to know shutdown failed will
524
- // see the throw; meanwhile, downstream plugins still got cleaned up.
525
- if (shutdownErrors.length > 0) {
526
- throw shutdownErrors[0];
527
- }
528
- },
529
- };
530
- return app;
531
- }
532
- function indexLocalEventProviders(modules) {
533
- const map = new Map();
534
- for (const mod of modules) {
535
- for (const e of mod.manifest.events ?? []) {
536
- // Visibility: the `.public()` marker in the module manifest is the
537
- // canonical signal. The older `visibility: "public" | "internal"`
538
- // field on the event definition is still honored for back-compat
539
- // until every example migrates to `.public()`.
540
- const markedPublic = mod.publicSurface.events.has(e.name);
541
- const declared = e.visibility;
542
- const visibility = markedPublic ? "public" : (declared ?? "internal");
543
- map.set(e.name, { module: mod.name, visibility });
544
- }
545
- }
546
- return map;
547
- }
548
- function indexLocalActionProviders(modules) {
549
- const map = new Map();
550
- for (const mod of modules) {
551
- for (const a of mod.manifest.actions ?? []) {
552
- const visibility = mod.publicSurface.actions.has(a.name)
553
- ? "public"
554
- : "internal";
555
- map.set(a.name, { module: mod.name, visibility });
556
- }
557
- }
558
- return map;
559
- }
560
- /**
561
- * Validate that every `needs.externalEvents` entry has a bus to flow through
562
- * and is NOT also provided by this same app (you don't go cross-service to
563
- * reach your own publishes).
564
- */
565
- function validateExternalEventDeps(modules, localEventProviders, hasBus) {
566
- for (const mod of modules) {
567
- const externals = mod.manifest.needs?.externalEvents;
568
- if (!externals || externals.length === 0)
569
- continue;
570
- if (!hasBus) {
571
- throw new Error(`Module "${mod.name}" declares needs.externalEvents but no bus is configured. ` +
572
- `Pass a bus instance to createApp({ bus, publishToBus? }).`);
573
- }
574
- for (const e of externals) {
575
- if (localEventProviders.has(e.name)) {
576
- throw new Error(`Module "${mod.name}" declares needs.externalEvents for "${e.name}" ` +
577
- `but this app also provides it. External needs are for events that ` +
578
- `originate in OTHER services. Move "${e.name}" to needs.events instead.`);
579
- }
580
- }
581
- }
582
- }
583
- /**
584
- * Validate the cross-module dep graph for `needs.events` and `needs.actions`.
585
- *
586
- * For `needs.events`: the same declaration must work whether the producer is
587
- * local (in-process apply) or remote (cross-service via bus). So:
588
- * - If a local provider exists: validate visibility (no internal events
589
- * leaking across modules) and reject self-references.
590
- * - If no local provider exists: require a bus — the runtime will
591
- * auto-subscribe at boot. No visibility check (the producer service
592
- * decides what it exports).
593
- *
594
- * For `needs.actions`: must be local (we don't have cross-service action
595
- * dispatch yet — that's a future RPC story over the bus or HTTP).
596
- */
597
- function validateModuleDepGraph(modules, localEventProviders, localActionProviders, hasBus) {
598
- for (const mod of modules) {
599
- const needs = mod.manifest.needs;
600
- if (!needs)
601
- continue;
602
- for (const event of needs.events ?? []) {
603
- const provider = localEventProviders.get(event.name);
604
- if (provider) {
605
- if (provider.module === mod.name) {
606
- throw new Error(`Module "${mod.name}" needs event "${event.name}" but provides it itself. ` +
607
- `A module's needs are its dependencies — drop the self-reference.`);
608
- }
609
- if (provider.visibility === "internal") {
610
- throw new Error(`Module "${mod.name}" needs event "${event.name}" but module "${provider.module}" ` +
611
- `marks it as visibility: 'internal'. Internal events are module-private; ` +
612
- `either change the producer to visibility: 'public' or refactor to avoid the cross-module subscription.`);
613
- }
614
- continue;
615
- }
616
- // No local provider — only satisfiable via the bus.
617
- if (!hasBus) {
618
- throw new Error(`Module "${mod.name}" needs event "${event.name}" but no module in this app provides it ` +
619
- `and no bus is configured. Either include a providing module in this app, configure a bus ` +
620
- `so the event can arrive from another service, or remove "${event.name}" from needs.events.`);
621
- }
622
- // hasBus + non-local: the createApp wiring loop auto-subscribes.
623
- }
624
- for (const action of needs.actions ?? []) {
625
- const provider = localActionProviders.get(action.name);
626
- if (!provider) {
627
- throw new Error(`Module "${mod.name}" needs action "${action.name}" but no module provides it. ` +
628
- `Cross-service action dispatch is not supported yet — actions must be local to the app.`);
629
- }
630
- if (provider.module === mod.name) {
631
- throw new Error(`Module "${mod.name}" needs action "${action.name}" but provides it itself. ` +
632
- `A module's needs are its cross-module dependencies — drop the self-reference.`);
633
- }
634
- if (provider.visibility === "internal") {
635
- throw new Error(`Module "${mod.name}" needs action "${action.name}" but module "${provider.module}" ` +
636
- `did not call .public() on it in its module manifest. Internal actions are ` +
637
- `module-private; mark the action public in the producer's manifest or refactor ` +
638
- `to avoid the cross-module dispatch.`);
639
- }
640
- }
641
- }
642
- }
643
- /**
644
- * Kahn-style topological sort over the `needs` graph.
645
- *
646
- * Two modules form an edge `A → B` when A needs an event or action that B
647
- * provides locally. The returned order satisfies "producers before
648
- * consumers." Modules without any deps preserve their declaration order
649
- * (stable sort), so a no-needs codebase boots in the order the user wrote.
650
- *
651
- * Cycles throw a `Cycle detected` error naming every module on the cycle
652
- * — readable so the offending pair is obvious. Cycles between BCs usually
653
- * indicate the boundary is drawn wrong; the fix is to extract the shared
654
- * concept into a third module, not paper over with externalEvents.
655
- *
656
- * External / unknown refs are ignored here because `validateModuleDepGraph`
657
- * has already proven every `needs.events` either has a local provider OR
658
- * a bus subscription path. Topo sort cares only about LOCAL edges.
659
- */
660
- function topoSortModules(modules, localEventProviders, localActionProviders) {
661
- if (modules.length <= 1)
662
- return modules;
663
- const byName = new Map();
664
- for (const m of modules)
665
- byName.set(m.name, m);
666
- // edges[consumer] = set of producer module names
667
- const edges = new Map();
668
- for (const m of modules)
669
- edges.set(m.name, new Set());
670
- for (const m of modules) {
671
- const needs = m.manifest.needs;
672
- if (!needs)
673
- continue;
674
- for (const e of needs.events ?? []) {
675
- const p = localEventProviders.get(e.name);
676
- if (p && p.module !== m.name)
677
- edges.get(m.name).add(p.module);
678
- }
679
- for (const a of needs.actions ?? []) {
680
- const p = localActionProviders.get(a.name);
681
- if (p && p.module !== m.name)
682
- edges.get(m.name).add(p.module);
683
- }
684
- }
685
- // Kahn: repeatedly emit nodes whose dep set is empty, in stable declaration order.
686
- const remaining = new Set(modules.map((m) => m.name));
687
- const ordered = [];
688
- while (remaining.size > 0) {
689
- const ready = modules.filter((m) => remaining.has(m.name) && [...edges.get(m.name)].every((dep) => !remaining.has(dep)));
690
- if (ready.length === 0) {
691
- const cycle = [...remaining].sort();
692
- throw new Error(`createApp: cycle detected in module needs graph among: ${cycle.join(", ")}. ` +
693
- `Two or more modules need each other (directly or transitively). ` +
694
- `Extract the shared concept into a third module or move one side to externalEvents (bus).`);
695
- }
696
- for (const m of ready) {
697
- ordered.push(m);
698
- remaining.delete(m.name);
699
- }
700
- }
701
- return ordered;
702
- }
703
- //# sourceMappingURL=create-app.js.map