@fougere/core 0.2.0-alpha.2 → 0.4.0-alpha.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 (500) hide show
  1. package/README.md +3 -2
  2. package/dist/boot/AppLifecycle.d.ts +18 -0
  3. package/dist/boot/AppLifecycle.d.ts.map +1 -0
  4. package/dist/boot/AppLifecycle.js +42 -0
  5. package/dist/boot/AppLifecycle.js.map +1 -0
  6. package/dist/boot/Emissions.d.ts +126 -0
  7. package/dist/boot/Emissions.d.ts.map +1 -0
  8. package/dist/boot/Emissions.js +233 -0
  9. package/dist/boot/Emissions.js.map +1 -0
  10. package/dist/boot/HandlerFacade.d.ts +72 -0
  11. package/dist/boot/HandlerFacade.d.ts.map +1 -0
  12. package/dist/boot/HandlerFacade.js +174 -0
  13. package/dist/boot/HandlerFacade.js.map +1 -0
  14. package/dist/boot/Lifecycle.d.ts +4 -0
  15. package/dist/boot/Lifecycle.d.ts.map +1 -0
  16. package/dist/boot/Lifecycle.js +3 -0
  17. package/dist/boot/Lifecycle.js.map +1 -0
  18. package/dist/boot/ambient-port.d.ts +38 -0
  19. package/dist/boot/ambient-port.d.ts.map +1 -0
  20. package/dist/boot/ambient-port.js +2 -0
  21. package/dist/boot/ambient-port.js.map +1 -0
  22. package/dist/boot/ambient.als.d.ts +3 -0
  23. package/dist/boot/ambient.als.d.ts.map +1 -0
  24. package/dist/boot/ambient.als.js +34 -0
  25. package/dist/boot/ambient.als.js.map +1 -0
  26. package/dist/boot/ambient.queue.d.ts +10 -0
  27. package/dist/boot/ambient.queue.d.ts.map +1 -0
  28. package/dist/boot/ambient.queue.js +60 -0
  29. package/dist/boot/ambient.queue.js.map +1 -0
  30. package/dist/boot/apply.d.ts +22 -0
  31. package/dist/boot/apply.d.ts.map +1 -0
  32. package/dist/boot/apply.js +38 -0
  33. package/dist/boot/apply.js.map +1 -0
  34. package/dist/boot/auth.d.ts +53 -0
  35. package/dist/boot/auth.d.ts.map +1 -0
  36. package/dist/boot/auth.js +2 -0
  37. package/dist/boot/auth.js.map +1 -0
  38. package/dist/{binding.d.ts → boot/binding.d.ts} +14 -17
  39. package/dist/boot/binding.d.ts.map +1 -0
  40. package/dist/{binding.js → boot/binding.js} +19 -10
  41. package/dist/boot/binding.js.map +1 -0
  42. package/dist/{boot.d.ts → boot/boot.d.ts} +25 -5
  43. package/dist/boot/boot.d.ts.map +1 -0
  44. package/dist/boot/boot.js +70 -0
  45. package/dist/boot/boot.js.map +1 -0
  46. package/dist/boot/bootstrap.d.ts.map +1 -0
  47. package/dist/boot/bootstrap.js +722 -0
  48. package/dist/boot/bootstrap.js.map +1 -0
  49. package/dist/boot/frame.d.ts +44 -0
  50. package/dist/boot/frame.d.ts.map +1 -0
  51. package/dist/boot/frame.js +172 -0
  52. package/dist/boot/frame.js.map +1 -0
  53. package/dist/boot/ownership.d.ts +42 -0
  54. package/dist/boot/ownership.d.ts.map +1 -0
  55. package/dist/boot/ownership.js +127 -0
  56. package/dist/boot/ownership.js.map +1 -0
  57. package/dist/boot/ports.d.ts +17 -0
  58. package/dist/boot/ports.d.ts.map +1 -0
  59. package/dist/boot/ports.js +57 -0
  60. package/dist/boot/ports.js.map +1 -0
  61. package/dist/{remote.d.ts → boot/remote.d.ts} +11 -3
  62. package/dist/boot/remote.d.ts.map +1 -0
  63. package/dist/{remote.js → boot/remote.js} +26 -28
  64. package/dist/boot/remote.js.map +1 -0
  65. package/dist/{seed.d.ts → boot/seed.d.ts} +13 -1
  66. package/dist/boot/seed.d.ts.map +1 -0
  67. package/dist/{seed.js → boot/seed.js} +24 -3
  68. package/dist/boot/seed.js.map +1 -0
  69. package/dist/boot/together.d.ts +26 -0
  70. package/dist/boot/together.d.ts.map +1 -0
  71. package/dist/boot/together.js +163 -0
  72. package/dist/boot/together.js.map +1 -0
  73. package/dist/boot/types.d.ts +260 -0
  74. package/dist/boot/types.d.ts.map +1 -0
  75. package/dist/boot/types.js.map +1 -0
  76. package/dist/builtins/config.d.ts +9 -0
  77. package/dist/builtins/config.d.ts.map +1 -1
  78. package/dist/builtins/config.js +16 -1
  79. package/dist/builtins/config.js.map +1 -1
  80. package/dist/builtins/logger.d.ts +37 -4
  81. package/dist/builtins/logger.d.ts.map +1 -1
  82. package/dist/builtins/logger.js +66 -7
  83. package/dist/builtins/logger.js.map +1 -1
  84. package/dist/config-loader.d.ts +39 -35
  85. package/dist/config-loader.d.ts.map +1 -1
  86. package/dist/config-loader.js +15 -54
  87. package/dist/config-loader.js.map +1 -1
  88. package/dist/contract/Call.d.ts +10 -0
  89. package/dist/contract/Call.d.ts.map +1 -0
  90. package/dist/contract/Call.js +15 -0
  91. package/dist/contract/Call.js.map +1 -0
  92. package/dist/contract/Invocation.d.ts +27 -0
  93. package/dist/contract/Invocation.d.ts.map +1 -0
  94. package/dist/contract/Invocation.js +59 -0
  95. package/dist/contract/Invocation.js.map +1 -0
  96. package/dist/contract/RouteAddress.d.ts +17 -0
  97. package/dist/contract/RouteAddress.d.ts.map +1 -0
  98. package/dist/contract/RouteAddress.js +38 -0
  99. package/dist/contract/RouteAddress.js.map +1 -0
  100. package/dist/contract.d.ts +25 -19
  101. package/dist/contract.d.ts.map +1 -1
  102. package/dist/contract.js +25 -10
  103. package/dist/contract.js.map +1 -1
  104. package/dist/crypto/encoding.d.ts +25 -0
  105. package/dist/crypto/encoding.d.ts.map +1 -0
  106. package/dist/crypto/encoding.js +58 -0
  107. package/dist/crypto/encoding.js.map +1 -0
  108. package/dist/crypto/node.d.ts +4 -0
  109. package/dist/crypto/node.d.ts.map +1 -0
  110. package/dist/crypto/node.js +25 -0
  111. package/dist/crypto/node.js.map +1 -0
  112. package/dist/crypto/port.d.ts +42 -0
  113. package/dist/crypto/port.d.ts.map +1 -0
  114. package/dist/crypto/port.js +19 -0
  115. package/dist/crypto/port.js.map +1 -0
  116. package/dist/crypto/webcrypto.d.ts +4 -0
  117. package/dist/crypto/webcrypto.d.ts.map +1 -0
  118. package/dist/crypto/webcrypto.js +34 -0
  119. package/dist/crypto/webcrypto.js.map +1 -0
  120. package/dist/dispatch/ArgumentResolver.d.ts +10 -0
  121. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -0
  122. package/dist/dispatch/ArgumentResolver.js +12 -0
  123. package/dist/dispatch/ArgumentResolver.js.map +1 -0
  124. package/dist/dispatch/ArrayResult.d.ts +3 -0
  125. package/dist/dispatch/ArrayResult.d.ts.map +1 -0
  126. package/dist/dispatch/ArrayResult.js +11 -0
  127. package/dist/dispatch/ArrayResult.js.map +1 -0
  128. package/dist/dispatch/DispatchEvent.d.ts +18 -0
  129. package/dist/dispatch/DispatchEvent.d.ts.map +1 -0
  130. package/dist/dispatch/DispatchEvent.js +30 -0
  131. package/dist/dispatch/DispatchEvent.js.map +1 -0
  132. package/dist/dispatch/DispatchLifecycle.d.ts +9 -0
  133. package/dist/dispatch/DispatchLifecycle.d.ts.map +1 -0
  134. package/dist/dispatch/DispatchLifecycle.js +25 -0
  135. package/dist/dispatch/DispatchLifecycle.js.map +1 -0
  136. package/dist/dispatch/DispatchPort.d.ts +6 -0
  137. package/dist/dispatch/DispatchPort.d.ts.map +1 -0
  138. package/dist/dispatch/DispatchPort.js +2 -0
  139. package/dist/dispatch/DispatchPort.js.map +1 -0
  140. package/dist/dispatch/Dispatcher.d.ts +16 -0
  141. package/dist/dispatch/Dispatcher.d.ts.map +1 -0
  142. package/dist/dispatch/Dispatcher.js +44 -0
  143. package/dist/dispatch/Dispatcher.js.map +1 -0
  144. package/dist/dispatch/InFlight.d.ts +12 -0
  145. package/dist/dispatch/InFlight.d.ts.map +1 -0
  146. package/dist/dispatch/InFlight.js +46 -0
  147. package/dist/dispatch/InFlight.js.map +1 -0
  148. package/dist/dispatch/InputValidator.d.ts +7 -0
  149. package/dist/dispatch/InputValidator.d.ts.map +1 -0
  150. package/dist/dispatch/InputValidator.js +22 -0
  151. package/dist/dispatch/InputValidator.js.map +1 -0
  152. package/dist/dispatch/LocalRoutePolicy.d.ts +11 -0
  153. package/dist/dispatch/LocalRoutePolicy.d.ts.map +1 -0
  154. package/dist/dispatch/LocalRoutePolicy.js +32 -0
  155. package/dist/dispatch/LocalRoutePolicy.js.map +1 -0
  156. package/dist/dispatch/OperationExecutor.d.ts +25 -0
  157. package/dist/dispatch/OperationExecutor.d.ts.map +1 -0
  158. package/dist/dispatch/OperationExecutor.js +32 -0
  159. package/dist/dispatch/OperationExecutor.js.map +1 -0
  160. package/dist/dispatch/OperationRoute.d.ts +13 -0
  161. package/dist/dispatch/OperationRoute.d.ts.map +1 -0
  162. package/dist/dispatch/OperationRoute.js +15 -0
  163. package/dist/dispatch/OperationRoute.js.map +1 -0
  164. package/dist/dispatch/OutputProjector.d.ts +12 -0
  165. package/dist/dispatch/OutputProjector.d.ts.map +1 -0
  166. package/dist/dispatch/OutputProjector.js +30 -0
  167. package/dist/dispatch/OutputProjector.js.map +1 -0
  168. package/dist/dispatch/OutputView.d.ts +8 -0
  169. package/dist/dispatch/OutputView.d.ts.map +1 -0
  170. package/dist/dispatch/OutputView.js +10 -0
  171. package/dist/dispatch/OutputView.js.map +1 -0
  172. package/dist/dispatch/PresenterArgumentResolver.d.ts +12 -0
  173. package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -0
  174. package/dist/dispatch/PresenterArgumentResolver.js +20 -0
  175. package/dist/dispatch/PresenterArgumentResolver.js.map +1 -0
  176. package/dist/dispatch/PresenterExecutor.d.ts +11 -0
  177. package/dist/dispatch/PresenterExecutor.d.ts.map +1 -0
  178. package/dist/dispatch/PresenterExecutor.js +56 -0
  179. package/dist/dispatch/PresenterExecutor.js.map +1 -0
  180. package/dist/dispatch/RemoteRouteResolver.d.ts +13 -0
  181. package/dist/dispatch/RemoteRouteResolver.d.ts.map +1 -0
  182. package/dist/dispatch/RemoteRouteResolver.js +18 -0
  183. package/dist/dispatch/RemoteRouteResolver.js.map +1 -0
  184. package/dist/dispatch/Route.d.ts +10 -0
  185. package/dist/dispatch/Route.d.ts.map +1 -0
  186. package/dist/dispatch/Route.js +2 -0
  187. package/dist/dispatch/Route.js.map +1 -0
  188. package/dist/dispatch/RouteNotFoundError.d.ts +10 -0
  189. package/dist/dispatch/RouteNotFoundError.d.ts.map +1 -0
  190. package/dist/dispatch/RouteNotFoundError.js +24 -0
  191. package/dist/dispatch/RouteNotFoundError.js.map +1 -0
  192. package/dist/dispatch/RoutePolicy.d.ts +8 -0
  193. package/dist/dispatch/RoutePolicy.d.ts.map +1 -0
  194. package/dist/dispatch/RoutePolicy.js +2 -0
  195. package/dist/dispatch/RoutePolicy.js.map +1 -0
  196. package/dist/dispatch/RouteRegistry.d.ts +18 -0
  197. package/dist/dispatch/RouteRegistry.d.ts.map +1 -0
  198. package/dist/dispatch/RouteRegistry.js +62 -0
  199. package/dist/dispatch/RouteRegistry.js.map +1 -0
  200. package/dist/dispatch/RouteResolver.d.ts +7 -0
  201. package/dist/dispatch/RouteResolver.d.ts.map +1 -0
  202. package/dist/dispatch/RouteResolver.js +2 -0
  203. package/dist/dispatch/RouteResolver.js.map +1 -0
  204. package/dist/dispatch/StorageGuard.d.ts +15 -0
  205. package/dist/dispatch/StorageGuard.d.ts.map +1 -0
  206. package/dist/dispatch/StorageGuard.js +63 -0
  207. package/dist/dispatch/StorageGuard.js.map +1 -0
  208. package/dist/effective-operation.d.ts +90 -0
  209. package/dist/effective-operation.d.ts.map +1 -0
  210. package/dist/effective-operation.js +449 -0
  211. package/dist/effective-operation.js.map +1 -0
  212. package/dist/emit.d.ts +2 -2
  213. package/dist/emit.d.ts.map +1 -1
  214. package/dist/emit.js +2 -2
  215. package/dist/emit.js.map +1 -1
  216. package/dist/entry/DynamicFacade.d.ts +10 -0
  217. package/dist/entry/DynamicFacade.d.ts.map +1 -0
  218. package/dist/entry/DynamicFacade.js +27 -0
  219. package/dist/entry/DynamicFacade.js.map +1 -0
  220. package/dist/entry/FacadeEntry.d.ts +11 -0
  221. package/dist/entry/FacadeEntry.d.ts.map +1 -0
  222. package/dist/entry/FacadeEntry.js +26 -0
  223. package/dist/entry/FacadeEntry.js.map +1 -0
  224. package/dist/entry/TransportEntry.d.ts +13 -0
  225. package/dist/entry/TransportEntry.d.ts.map +1 -0
  226. package/dist/entry/TransportEntry.js +24 -0
  227. package/dist/entry/TransportEntry.js.map +1 -0
  228. package/dist/frond-config.d.ts +24 -7
  229. package/dist/frond-config.d.ts.map +1 -1
  230. package/dist/frond-config.js.map +1 -1
  231. package/dist/graph.d.ts +1 -1
  232. package/dist/graph.d.ts.map +1 -1
  233. package/dist/graph.js +3 -2
  234. package/dist/graph.js.map +1 -1
  235. package/dist/identity-keys.d.ts +14 -0
  236. package/dist/identity-keys.d.ts.map +1 -0
  237. package/dist/identity-keys.js +37 -0
  238. package/dist/identity-keys.js.map +1 -0
  239. package/dist/identity.d.ts +58 -0
  240. package/dist/identity.d.ts.map +1 -0
  241. package/dist/identity.js +182 -0
  242. package/dist/identity.js.map +1 -0
  243. package/dist/imports.d.ts +2 -2
  244. package/dist/imports.d.ts.map +1 -1
  245. package/dist/imports.js +7 -3
  246. package/dist/imports.js.map +1 -1
  247. package/dist/index.d.ts +57 -38
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +38 -32
  250. package/dist/index.js.map +1 -1
  251. package/dist/loader.d.ts +16 -0
  252. package/dist/loader.d.ts.map +1 -0
  253. package/dist/loader.js +25 -0
  254. package/dist/loader.js.map +1 -0
  255. package/dist/node.d.ts +21 -0
  256. package/dist/node.d.ts.map +1 -0
  257. package/dist/node.js +24 -0
  258. package/dist/node.js.map +1 -0
  259. package/dist/orm.d.ts +69 -8
  260. package/dist/orm.d.ts.map +1 -1
  261. package/dist/orm.js +35 -2
  262. package/dist/orm.js.map +1 -1
  263. package/dist/prefab/collector.d.ts +29 -0
  264. package/dist/prefab/collector.d.ts.map +1 -0
  265. package/dist/prefab/collector.js +34 -0
  266. package/dist/prefab/collector.js.map +1 -0
  267. package/dist/{crud.d.ts → prefab/crud.d.ts} +7 -8
  268. package/dist/prefab/crud.d.ts.map +1 -0
  269. package/dist/{crud.js → prefab/crud.js} +39 -5
  270. package/dist/prefab/crud.js.map +1 -0
  271. package/dist/prefab/mirror.d.ts +52 -0
  272. package/dist/prefab/mirror.d.ts.map +1 -0
  273. package/dist/prefab/mirror.js +66 -0
  274. package/dist/prefab/mirror.js.map +1 -0
  275. package/dist/prefab/prefab.d.ts +15 -0
  276. package/dist/prefab/prefab.d.ts.map +1 -0
  277. package/dist/prefab/prefab.js +19 -0
  278. package/dist/prefab/prefab.js.map +1 -0
  279. package/dist/{presenter.d.ts → prefab/presenter.d.ts} +6 -17
  280. package/dist/prefab/presenter.d.ts.map +1 -0
  281. package/dist/{presenter.js → prefab/presenter.js} +5 -23
  282. package/dist/prefab/presenter.js.map +1 -0
  283. package/dist/prefab/repository.d.ts +104 -0
  284. package/dist/prefab/repository.d.ts.map +1 -0
  285. package/dist/prefab/repository.js +62 -0
  286. package/dist/prefab/repository.js.map +1 -0
  287. package/dist/scan/Fronds.d.ts +45 -0
  288. package/dist/scan/Fronds.d.ts.map +1 -0
  289. package/dist/scan/Fronds.js +66 -0
  290. package/dist/scan/Fronds.js.map +1 -0
  291. package/dist/scan/conventions.d.ts +74 -0
  292. package/dist/scan/conventions.d.ts.map +1 -0
  293. package/dist/scan/conventions.js +82 -0
  294. package/dist/scan/conventions.js.map +1 -0
  295. package/dist/scan/emit.d.ts +14 -0
  296. package/dist/scan/emit.d.ts.map +1 -0
  297. package/dist/scan/emit.js +212 -0
  298. package/dist/scan/emit.js.map +1 -0
  299. package/dist/scan/frond.d.ts +224 -0
  300. package/dist/scan/frond.d.ts.map +1 -0
  301. package/dist/scan/frond.js +2 -0
  302. package/dist/scan/frond.js.map +1 -0
  303. package/dist/{handler-parser.d.ts → scan/handler-parser.d.ts} +20 -9
  304. package/dist/scan/handler-parser.d.ts.map +1 -0
  305. package/dist/scan/handler-parser.js +665 -0
  306. package/dist/scan/handler-parser.js.map +1 -0
  307. package/dist/scan/scan-cache.d.ts +16 -0
  308. package/dist/scan/scan-cache.d.ts.map +1 -0
  309. package/dist/{scan-cache.js → scan/scan-cache.js} +27 -20
  310. package/dist/scan/scan-cache.js.map +1 -0
  311. package/dist/scan/scanner.d.ts +20 -0
  312. package/dist/scan/scanner.d.ts.map +1 -0
  313. package/dist/{scanner.js → scan/scanner.js} +212 -167
  314. package/dist/scan/scanner.js.map +1 -0
  315. package/dist/verify.d.ts +16 -3
  316. package/dist/verify.d.ts.map +1 -1
  317. package/dist/verify.js +21 -18
  318. package/dist/verify.js.map +1 -1
  319. package/dist/{call.d.ts → wire/call.d.ts} +76 -7
  320. package/dist/wire/call.d.ts.map +1 -0
  321. package/dist/{call.js → wire/call.js} +53 -80
  322. package/dist/wire/call.js.map +1 -0
  323. package/dist/{middleware.d.ts → wire/errors.d.ts} +18 -19
  324. package/dist/wire/errors.d.ts.map +1 -0
  325. package/dist/{middleware.js → wire/errors.js} +26 -14
  326. package/dist/wire/errors.js.map +1 -0
  327. package/dist/{errors.d.ts → wire/http-error.d.ts} +5 -6
  328. package/dist/wire/http-error.d.ts.map +1 -0
  329. package/dist/{errors.js → wire/http-error.js} +7 -6
  330. package/dist/wire/http-error.js.map +1 -0
  331. package/dist/wire/invocation.d.ts +4 -0
  332. package/dist/wire/invocation.d.ts.map +1 -0
  333. package/dist/wire/invocation.js +3 -0
  334. package/dist/wire/invocation.js.map +1 -0
  335. package/dist/{middleware/logger.d.ts → wire/loggerMiddleware.d.ts} +3 -3
  336. package/dist/wire/loggerMiddleware.d.ts.map +1 -0
  337. package/dist/{middleware/logger.js → wire/loggerMiddleware.js} +2 -2
  338. package/dist/wire/loggerMiddleware.js.map +1 -0
  339. package/dist/wire/middleware.d.ts +29 -0
  340. package/dist/wire/middleware.d.ts.map +1 -0
  341. package/dist/wire/middleware.js +20 -0
  342. package/dist/wire/middleware.js.map +1 -0
  343. package/dist/{operation.d.ts → wire/operation.d.ts} +39 -10
  344. package/dist/wire/operation.d.ts.map +1 -0
  345. package/dist/wire/operation.js +167 -0
  346. package/dist/wire/operation.js.map +1 -0
  347. package/package.json +27 -8
  348. package/src/boot/AppLifecycle.ts +50 -0
  349. package/src/boot/Emissions.ts +263 -0
  350. package/src/boot/HandlerFacade.ts +242 -0
  351. package/src/boot/Lifecycle.ts +3 -0
  352. package/src/boot/ambient-port.ts +41 -0
  353. package/src/boot/ambient.als.ts +43 -0
  354. package/src/boot/ambient.queue.ts +75 -0
  355. package/src/boot/apply.ts +47 -0
  356. package/src/boot/auth.ts +55 -0
  357. package/src/boot/binding.ts +182 -0
  358. package/src/boot/boot.ts +125 -0
  359. package/src/boot/bootstrap.ts +839 -0
  360. package/src/boot/frame.ts +210 -0
  361. package/src/boot/ownership.ts +140 -0
  362. package/src/boot/ports.ts +66 -0
  363. package/src/boot/remote.ts +179 -0
  364. package/src/boot/seed.ts +158 -0
  365. package/src/boot/together.ts +215 -0
  366. package/src/boot/types.ts +261 -0
  367. package/src/builtins/config.ts +19 -0
  368. package/src/builtins/logger.ts +175 -0
  369. package/src/config-loader.ts +146 -0
  370. package/src/contract/Call.ts +18 -0
  371. package/src/contract/Invocation.ts +72 -0
  372. package/src/contract/RouteAddress.ts +49 -0
  373. package/src/contract.ts +74 -0
  374. package/src/crypto/encoding.ts +59 -0
  375. package/src/crypto/node.ts +30 -0
  376. package/src/crypto/port.ts +45 -0
  377. package/src/crypto/webcrypto.ts +55 -0
  378. package/src/define.ts +10 -0
  379. package/src/dispatch/ArgumentResolver.ts +17 -0
  380. package/src/dispatch/ArrayResult.ts +10 -0
  381. package/src/dispatch/DispatchEvent.ts +38 -0
  382. package/src/dispatch/DispatchLifecycle.ts +23 -0
  383. package/src/dispatch/DispatchPort.ts +6 -0
  384. package/src/dispatch/Dispatcher.ts +48 -0
  385. package/src/dispatch/InFlight.ts +49 -0
  386. package/src/dispatch/InputValidator.ts +28 -0
  387. package/src/dispatch/LocalRoutePolicy.ts +34 -0
  388. package/src/dispatch/OperationExecutor.ts +60 -0
  389. package/src/dispatch/OperationRoute.ts +18 -0
  390. package/src/dispatch/OutputProjector.ts +32 -0
  391. package/src/dispatch/OutputView.ts +9 -0
  392. package/src/dispatch/PresenterArgumentResolver.ts +25 -0
  393. package/src/dispatch/PresenterExecutor.ts +55 -0
  394. package/src/dispatch/RemoteRouteResolver.ts +21 -0
  395. package/src/dispatch/Route.ts +11 -0
  396. package/src/dispatch/RouteNotFoundError.ts +29 -0
  397. package/src/dispatch/RoutePolicy.ts +8 -0
  398. package/src/dispatch/RouteRegistry.ts +73 -0
  399. package/src/dispatch/RouteResolver.ts +7 -0
  400. package/src/dispatch/StorageGuard.ts +71 -0
  401. package/src/effective-operation.ts +597 -0
  402. package/src/emit.ts +90 -0
  403. package/src/entry/DynamicFacade.ts +28 -0
  404. package/src/entry/FacadeEntry.ts +32 -0
  405. package/src/entry/TransportEntry.ts +30 -0
  406. package/src/frond-config.ts +138 -0
  407. package/src/graph.ts +180 -0
  408. package/src/identity-keys.ts +43 -0
  409. package/src/identity.ts +235 -0
  410. package/src/imports.ts +123 -0
  411. package/src/index.ts +106 -0
  412. package/src/loader.ts +41 -0
  413. package/src/node.ts +28 -0
  414. package/src/orm.ts +252 -0
  415. package/src/prefab/collector.ts +36 -0
  416. package/src/prefab/crud.ts +188 -0
  417. package/src/prefab/mirror.ts +122 -0
  418. package/src/prefab/prefab.ts +46 -0
  419. package/src/prefab/presenter.ts +63 -0
  420. package/src/prefab/repository.ts +170 -0
  421. package/src/scan/Fronds.ts +73 -0
  422. package/src/scan/conventions.ts +122 -0
  423. package/src/scan/emit.ts +231 -0
  424. package/src/scan/frond.ts +235 -0
  425. package/src/scan/handler-parser.ts +818 -0
  426. package/src/scan/scan-cache.ts +115 -0
  427. package/src/scan/scanner.ts +758 -0
  428. package/src/verify.ts +193 -0
  429. package/src/wire/call.ts +369 -0
  430. package/src/wire/errors.ts +124 -0
  431. package/src/wire/http-error.ts +77 -0
  432. package/src/wire/invocation.ts +10 -0
  433. package/src/wire/loggerMiddleware.ts +29 -0
  434. package/src/wire/middleware.ts +47 -0
  435. package/src/wire/operation.ts +258 -0
  436. package/dist/binding.d.ts.map +0 -1
  437. package/dist/binding.js.map +0 -1
  438. package/dist/boot.d.ts.map +0 -1
  439. package/dist/boot.js +0 -53
  440. package/dist/boot.js.map +0 -1
  441. package/dist/bootstrap.d.ts.map +0 -1
  442. package/dist/bootstrap.js +0 -879
  443. package/dist/bootstrap.js.map +0 -1
  444. package/dist/call.d.ts.map +0 -1
  445. package/dist/call.js.map +0 -1
  446. package/dist/collector.d.ts +0 -28
  447. package/dist/collector.d.ts.map +0 -1
  448. package/dist/collector.js +0 -32
  449. package/dist/collector.js.map +0 -1
  450. package/dist/crud.d.ts.map +0 -1
  451. package/dist/crud.js.map +0 -1
  452. package/dist/egress.d.ts +0 -52
  453. package/dist/egress.d.ts.map +0 -1
  454. package/dist/egress.js +0 -186
  455. package/dist/egress.js.map +0 -1
  456. package/dist/errors.d.ts.map +0 -1
  457. package/dist/errors.js.map +0 -1
  458. package/dist/handler-parser.d.ts.map +0 -1
  459. package/dist/handler-parser.js +0 -505
  460. package/dist/handler-parser.js.map +0 -1
  461. package/dist/invocation.d.ts +0 -20
  462. package/dist/invocation.d.ts.map +0 -1
  463. package/dist/invocation.js +0 -15
  464. package/dist/invocation.js.map +0 -1
  465. package/dist/middleware/error-handler.d.ts +0 -15
  466. package/dist/middleware/error-handler.d.ts.map +0 -1
  467. package/dist/middleware/error-handler.js +0 -29
  468. package/dist/middleware/error-handler.js.map +0 -1
  469. package/dist/middleware/logger.d.ts.map +0 -1
  470. package/dist/middleware/logger.js.map +0 -1
  471. package/dist/middleware.d.ts.map +0 -1
  472. package/dist/middleware.js.map +0 -1
  473. package/dist/mirror.d.ts +0 -93
  474. package/dist/mirror.d.ts.map +0 -1
  475. package/dist/mirror.js +0 -96
  476. package/dist/mirror.js.map +0 -1
  477. package/dist/operation.d.ts.map +0 -1
  478. package/dist/operation.js +0 -95
  479. package/dist/operation.js.map +0 -1
  480. package/dist/presenter.d.ts.map +0 -1
  481. package/dist/presenter.js.map +0 -1
  482. package/dist/remote.d.ts.map +0 -1
  483. package/dist/remote.js.map +0 -1
  484. package/dist/repository.d.ts +0 -55
  485. package/dist/repository.d.ts.map +0 -1
  486. package/dist/repository.js +0 -26
  487. package/dist/repository.js.map +0 -1
  488. package/dist/scan-cache.d.ts +0 -10
  489. package/dist/scan-cache.d.ts.map +0 -1
  490. package/dist/scan-cache.js.map +0 -1
  491. package/dist/scanner.d.ts +0 -34
  492. package/dist/scanner.d.ts.map +0 -1
  493. package/dist/scanner.js.map +0 -1
  494. package/dist/seed.d.ts.map +0 -1
  495. package/dist/seed.js.map +0 -1
  496. package/dist/types.d.ts +0 -421
  497. package/dist/types.d.ts.map +0 -1
  498. package/dist/types.js.map +0 -1
  499. /package/dist/{bootstrap.d.ts → boot/bootstrap.d.ts} +0 -0
  500. /package/dist/{types.js → boot/types.js} +0 -0
package/dist/bootstrap.js DELETED
@@ -1,879 +0,0 @@
1
- import { Judge } from '@fougere/schema';
2
- import { runMiddlewares, FougereError, ErrorCode } from './middleware.js';
3
- import { scanProject } from './scanner.js';
4
- import { Logger } from './builtins/logger.js';
5
- import { Config } from './builtins/config.js';
6
- import { createRemoteRouter, createRemoteFacade } from './remote.js';
7
- import { AsyncLocalStorage } from 'node:async_hooks';
8
- import { facadeKeyOf } from './call.js';
9
- import { emitKeyOf, factsAnnouncedBy } from './emit.js';
10
- import { repositoryKeyOf } from './repository.js';
11
- // The keys, each read from where its concept is declared — never respelled here.
12
- import { ormKeyOf } from './orm.js';
13
- import { presenterKeyOf } from './presenter.js';
14
- import { collectorKeyOf } from './collector.js';
15
- import { computeBindingPlan, resolveArgs } from './binding.js';
16
- import { resolveContracts } from './operation.js';
17
- import { EMPTY_INVOCATION } from './invocation.js';
18
- import { toRegistrationName } from './contract.js';
19
- import { applyCreate } from '@fougere/schema';
20
- import { projectEgress, presentEgress, guardStorage } from './egress.js';
21
- /**
22
- * The one wording for "nobody hosts this here", with both ways out. Said twice, and the
23
- * second copy (`schemaFor`) had lost the two remedies — the same dead end, strictly less
24
- * useful, for no reason.
25
- */
26
- const notLoaded = (entity) => `Frond for '${entity}' is not loaded.\n` +
27
- ` - Add '${entity}' to --fronds flag\n` +
28
- ` - Or declare a remote: remotes: { ${entity}: 'http://...' }`;
29
- /**
30
- * Two fronds cannot claim one name — said at boot, because nothing else says it.
31
- *
32
- * A door lands on the ROOT container under `facadeKeyOf(address)`, and that key carries
33
- * no frond; a presenter lands there too. `registerValue` is a `Map.set`, so the second
34
- * frond loaded simply replaced the first and every call meant for one went to the other.
35
- * Silent in-process, and worse than silent under a split: `createRemoteRouter` guards its
36
- * index with `if (!byEntity.has(...))`, so THERE the first frond discovered wins. The same
37
- * application answered differently depending on how it was deployed.
38
- *
39
- * Refusing is the honest answer while a key cannot say which frond owns it. It is not the
40
- * last word: qualify the key and this boot can accept both. ORMs and repositories are not
41
- * checked because they are registered in the frond's own scope, where two fronds do not meet.
42
- *
43
- * A frond declared remote registers nothing locally, so it cannot collide here. Two REMOTE
44
- * fronds publishing one entity still shadow each other inside the router — a hole this
45
- * check does not reach.
46
- */
47
- function assertOneOwnerPerKey(fronds, remotes) {
48
- const owner = new Map();
49
- const claim = (key, frond, what) => {
50
- const held = owner.get(key);
51
- if (held !== undefined && held !== frond) {
52
- throw new Error(`Two fronds claim the key '${key}': '${held}' and '${frond}'.\n`
53
- + ` A ${what} is registered under a key that names no frond, so one would silently replace the other.\n`
54
- + ` - Rename one of the two classes, or\n`
55
- + ` - keep one of the two fronds out of this process (--fronds), or declare it in remotes:`);
56
- }
57
- owner.set(key, frond);
58
- };
59
- for (const frond of fronds) {
60
- if (remotes && frond.name in remotes)
61
- continue;
62
- for (const handler of frond.handlers)
63
- claim(facadeKeyOf(handler.address, handler.surface), frond.name, 'door');
64
- for (const presenter of frond.presenters)
65
- claim(presenterKeyOf(presenter.entityName), frond.name, 'presenter');
66
- }
67
- }
68
- /** Bootstrap a fougere application. */
69
- export async function createApp(options) {
70
- const root = options.root ?? process.cwd();
71
- const container = options.createContainer();
72
- // Boot chatter is debug by default; a host (e.g. the CLI) can quiet it.
73
- const log = new Logger('boot:app', { level: process.env.FOUGERE_LOG_LEVEL ?? 'debug' });
74
- // Builtins — registered under class name (PascalCase) for type-based DI
75
- container.registerValue('Logger', new Logger());
76
- container.register('Config', Config, { lifetime: 'singleton' });
77
- log.debug('builtins registered (Logger, Config)');
78
- // Scan (with optional filter)
79
- const scanStart = performance.now();
80
- const { fronds, diagnostics } = await scanProject(root, options.fronds);
81
- const scanMs = (performance.now() - scanStart).toFixed(0);
82
- const blocking = diagnostics.filter((d) => d.severity === 'blocking');
83
- log.info(`scanned ${fronds.length} frond(s) in ${scanMs}ms`
84
- + (diagnostics.length ? ` — ${diagnostics.length} thing(s) the scan could not do` : ''));
85
- /**
86
- * Say what could not be read, at the one line everyone already watches.
87
- *
88
- * Not a refusal: an app whose `presenters/` is unreadable still serves its
89
- * entities, and stopping the boot would trade a partial app for none. But it is
90
- * an ERROR, not a debug line — the app now serves less than its source declares,
91
- * and nothing downstream can tell that from a source that declares less.
92
- */
93
- for (const d of blocking)
94
- log.error(`[${d.code}] ${d.message}`, d.cause);
95
- for (const d of diagnostics)
96
- if (d.severity === 'warning')
97
- log.warn(`[${d.code}] ${d.message}`);
98
- // Auth runtime — built once from the lazy AuthConfig produced by a provider factory
99
- // (e.g. betterAuth({...})) in fougere.config.ts. The provider receives our db +
100
- // ormFactory so all auth writes flow through EntityOrm.
101
- let authRuntime;
102
- if (options.auth) {
103
- if (!options.ormFactory) {
104
- throw new Error('createApp: `auth` is set but `ormFactory` is missing — auth providers need it to back their adapter.');
105
- }
106
- if (options.db === undefined) {
107
- throw new Error('createApp: `auth` is set but `db` is missing — pass the storage handle through CreateAppOptions.db.');
108
- }
109
- log.info('initializing auth runtime');
110
- authRuntime = await options.auth.create({
111
- db: options.db,
112
- ormFactory: options.ormFactory,
113
- });
114
- log.info(`auth ready — mounted at ${authRuntime.basePath}`);
115
- }
116
- // Remote routing — judged at boot: declaring remotes without a transport is a config error.
117
- // A remote declaration wins over local presence: `remotes: { blog: url }` IS
118
- // the topology statement — the frond's code may sit in fronds/**, it runs elsewhere.
119
- const declaredRemotes = Object.entries(options.remotes ?? {});
120
- if (declaredRemotes.length > 0 && !options.remoteTransport) {
121
- throw new Error('createApp: `remotes` is declared but `remoteTransport` is missing — pass one (e.g. from @fougere/transport-http).');
122
- }
123
- const remoteRouter = declaredRemotes.length > 0 && options.remoteTransport
124
- ? createRemoteRouter(Object.fromEntries(declaredRemotes), options.remoteTransport)
125
- : undefined;
126
- // Middleware storage — read at call time, not at boot time
127
- const globalMiddlewares = [];
128
- const scopedMiddlewares = new Map();
129
- function getMiddlewares(entity) {
130
- const scoped = scopedMiddlewares.get(entity) ?? [];
131
- return [...globalMiddlewares, ...scoped];
132
- }
133
- assertOneOwnerPerKey(fronds, options.remotes);
134
- /**
135
- * Emissions — the only place in Fougere where an initiator names a SUBJECT.
136
- *
137
- * The set comes from the DEPS, not from the subscribers: a handler that declares
138
- * `Emit<PostPublished>` must resolve it whether or not anybody listens, and announcing
139
- * to nobody is legal. The index is filled by `buildFacade` as each contract is resolved,
140
- * and the value below closes over it — so no order between the two ever matters.
141
- */
142
- const emitted = new Set(fronds.flatMap((frond) => factsAnnouncedBy(frond.handlers)));
143
- const subscribers = new Map();
144
- /**
145
- * Every entity of every frond, by name — so a fact can be judged where it LANDS.
146
- *
147
- * A fact usually lives in the frond that announces it and is heard in another, so the
148
- * subscriber's own frond does not hold it. Across all fronds and not per-frond for that
149
- * one reason.
150
- */
151
- const entityByName = new Map(fronds.flatMap((f) => f.entities.map((e) => [e.name, e.entityClass])));
152
- /**
153
- * Who listens to what — read from the PLAN, where `{ kind: 'fact' }` is a sentence
154
- * `computeBindingPlan` already wrote, so nothing re-derives what a parameter is.
155
- *
156
- * It runs for a frond hosted here AND for one declared remote. A remote frond is still
157
- * scanned — only its hosting is elsewhere — so its subscriptions are known, and its door
158
- * resolves to a doublure. That is the whole reason an emission crosses a process without
159
- * a line of transport code: the emitter learned the signature locally and calls the same
160
- * key. Filling this inside `buildFacade` alone left the index EMPTY under a split, and a
161
- * fact announced to a remote listener reached nobody, in silence.
162
- */
163
- const noteSubscriptions = (contracts, door) => {
164
- for (const [op, contract] of contracts) {
165
- for (const bound of contract.binding ?? []) {
166
- if (bound.source.kind !== 'fact')
167
- continue;
168
- const listeners = subscribers.get(bound.source.factName) ?? [];
169
- listeners.push({ door, op });
170
- subscribers.set(bound.source.factName, listeners);
171
- }
172
- }
173
- };
174
- /**
175
- * The facts already being announced up the stack, so a fact cannot cause itself.
176
- *
177
- * A CHAIN and not a depth: `A → B → D` and `A → C → D` is a diamond, perfectly legal,
178
- * while `A → … → A` never ends. Carried in async context because a nested emission
179
- * happens inside a subscriber, whose own `Emit` closure never sees the invocation that
180
- * reached it.
181
- *
182
- * Detecting this at boot was the first idea and it was wrong: `Emit<G>` is a CONSTRUCTOR
183
- * dependency, so it belongs to the handler and not to one of its methods. A handler that
184
- * subscribes to `A` in one method and emits `G` from another would have been refused for
185
- * a cycle it never walks. Refusing a correct program is worse than a guard that costs
186
- * one array per emission.
187
- */
188
- const chain = new AsyncLocalStorage();
189
- // Register frond scopes
190
- for (const frond of fronds) {
191
- // Declared remote: keep the scanned metadata (bridges route with it),
192
- // register nothing locally — resolve() falls through to the remote façade.
193
- if (options.remotes && frond.name in options.remotes) {
194
- log.child(frond.name).info('declared remote — not hosted locally');
195
- // Its doors answer elsewhere, but what they LISTEN to was read here.
196
- const remoteCollectors = new Set(frond.collectors.map((c) => c.entityName));
197
- for (const handler of frond.handlers) {
198
- noteSubscriptions(resolveContracts(handler, frond.operationsOverrides, remoteCollectors), facadeKeyOf(handler.address, handler.surface));
199
- }
200
- continue;
201
- }
202
- const scope = container.createScope();
203
- const frondLog = log.child(frond.name);
204
- // `reads:` is what makes a cross-source reader exist here, and the list IS its
205
- // environment — a source holding none of these is never opened. Registered under
206
- // the type's own name, which is the key `depKeyOf` already derives for a plain
207
- // parameter: `constructor(private sources: Sources)` and nothing else to say.
208
- // Declaring `reads:` with nothing to build the reader is a boot that ignores a
209
- // clause: the handler asking for `Sources` then dies at its first call, on a
210
- // container message that names neither the clause nor what is missing.
211
- if (frond.reads?.length && !options.sourcesFactory) {
212
- frondLog.warn(`[reads] ${frond.reads.join(', ')} — declared in frond.config.ts, but this boot passes no `
213
- + '`sourcesFactory`, so no reader is registered and a handler asking for `Sources` will fail '
214
- + 'at its first call. Pass one (`@fougere/adapter-duckdb`), or drop the clause.');
215
- }
216
- if (frond.reads?.length && options.sourcesFactory) {
217
- // Resolved across the WHOLE app, not this frond's own entities: a cross-source
218
- // query joins entities from different fronds by definition — `Progress` here,
219
- // `Book` next door — so restricting the list to its own would make it useless.
220
- // Naming one IS the authorization; that is what the declaration is for.
221
- const hosted = new Map(fronds.flatMap((f) => f.entities.map((e) => [e.name, e.entityClass])));
222
- const named = frond.reads
223
- .map((name) => hosted.get(toRegistrationName(name)))
224
- .filter((entity) => entity !== undefined);
225
- if (named.length !== frond.reads.length) {
226
- const missing = frond.reads.filter((name) => !hosted.has(toRegistrationName(name)));
227
- frondLog.warn(`[reads] ${missing.join(', ')} — named in frond.config.ts but scanned nowhere in this app, `
228
- + 'so a query naming one would find no table. Check the spelling, or the entity file.');
229
- }
230
- scope.registerValue('Sources', await options.sourcesFactory(named, frond.name));
231
- frondLog.debug(`cross-source reader over ${named.length} entit(ies)`);
232
- }
233
- for (const provider of frond.providers) {
234
- scope.register(provider.ctor.name, provider.ctor, { deps: provider.deps });
235
- }
236
- if (frond.providers.length > 0) {
237
- frondLog.debug(`${frond.providers.length} provider(s): ${frond.providers.map((p) => p.ctor.name).join(', ')}`);
238
- }
239
- // Register EntityOrm for each entity — PascalCase type name (e.g. 'PostOrm')
240
- // When a handler declares Crud(Entity, Output), scope the ORM via .output(Output)
241
- if (options.ormFactory) {
242
- for (const entity of frond.entities) {
243
- const ormName = ormKeyOf(entity.name);
244
- const baseOrm = options.ormFactory(entity.entityClass, entity.name);
245
- // Check if the default handler (no surface) declares an output override
246
- const defaultHandler = frond.handlers.find((h) => h.address === entity.name && !h.surface);
247
- const outputSchema = defaultHandler?.outputOverride ?? defaultHandler?.ctor?.__output;
248
- const scoped = outputSchema && outputSchema !== entity.entityClass
249
- ? baseOrm.output(outputSchema)
250
- : baseOrm;
251
- // Storage is a way out like the client surface — see egress.ts.
252
- const guarded = guardStorage(scoped, entity.entityClass.getFields(), entity.name);
253
- scope.registerValue(ormName, guarded);
254
- // The default repository holds the port and adds nothing — the same shape a
255
- // declared one has, so a handler reads `repo.orm.list()` either way. Giving
256
- // the ORM itself as the default was shorter and wrong: `repo.orm` would then
257
- // exist only when someone had written the file, and the two forms would
258
- // differ exactly where the convention promises they do not.
259
- //
260
- // `providers` are registered above, so a declared repository is never
261
- // overwritten by this.
262
- const repoKey = repositoryKeyOf(entity.name);
263
- if (!scope.has(repoKey))
264
- scope.registerValue(repoKey, { orm: guarded });
265
- }
266
- if (frond.entities.length > 0) {
267
- frondLog.debug(`${frond.entities.length} entity ORM(s): ${frond.entities.map((e) => e.name).join(', ')}`);
268
- }
269
- }
270
- // Register presenters in scope — PascalCase type name (e.g. 'PostPresenter')
271
- const presenterMap = new Map(frond.presenters.map((p) => [p.entityName, p]));
272
- for (const presenter of frond.presenters) {
273
- scope.register(presenterKeyOf(presenter.entityName), presenter.ctor, { deps: presenter.deps });
274
- }
275
- if (frond.presenters.length > 0) {
276
- frondLog.debug(`${frond.presenters.length} presenter(s): ${frond.presenters.map((p) => p.entityName).join(', ')}`);
277
- }
278
- // Register collectors in scope — PascalCase type name (e.g. 'UserCollector')
279
- const collectorEntityNames = new Set(frond.collectors.map((c) => c.entityName));
280
- for (const collector of frond.collectors) {
281
- const key = collectorKeyOf(collector.entityName);
282
- scope.register(key, collector.ctor, { deps: collector.deps });
283
- }
284
- if (frond.collectors.length > 0) {
285
- frondLog.debug(`${frond.collectors.length} collector(s): ${frond.collectors.map((c) => c.entityName).join(', ')}`);
286
- }
287
- // Build handler facades → registered in ROOT container (public contract)
288
- const defaultHandlers = frond.handlers.filter((h) => !h.surface);
289
- const surfaceHandlers = frond.handlers.filter((h) => h.surface);
290
- const defaultHandlerMap = new Map(defaultHandlers.map((h) => [h.address, h]));
291
- /**
292
- * Build a facade for a handler and register it in the root container.
293
- *
294
- * A facade is built FROM a handler — that is the whole rule, and the entity is
295
- * OPTIONAL. An entity is a shape, not a surface: on its own it declares no
296
- * operation, so it gets no facade and answers nothing. The converse used to be
297
- * false in fact though true on paper — the loop below walked entities, so a
298
- * handler naming no entity was scanned and then silently never built. An
299
- * operation that is about no stored row (`health.check`, a pure computation) is
300
- * an ordinary case, not a gap to accommodate.
301
- *
302
- * Without an entity a facade loses exactly three things, and nothing else: the
303
- * ORM injected by convention, the output fields to project onto, and the
304
- * presenter. Its result travels as the handler returned it.
305
- */
306
- const buildFacade = (entity, handler, targetScope, facadeKey) => {
307
- const handlerKey = `_handler:${facadeKey}`;
308
- // The ORM belongs to the SUBJECT, never to the address. `StockHandler extends
309
- // Crud(Item)` is called `stock` and reads `Item`; asking for `StockOrm` would be
310
- // asking the address for a table. The two coincide in the ordinary case and that
311
- // is why it went unnoticed.
312
- const ormBase = entity?.name ?? handler.address;
313
- const ormTypeName = ormKeyOf(ormBase);
314
- const inheritsCrud = typeof handler.ctor.prototype?.list === 'function'
315
- && typeof handler.ctor.prototype?.findById === 'function';
316
- const hasCrudInProto = handler.deps.length === 0 && inheritsCrud;
317
- const deps = handler.deps.length > 0
318
- ? handler.deps
319
- : hasCrudInProto ? [ormTypeName] : [];
320
- // Declaring a constructor turns the automatic ORM injection OFF — the handler now
321
- // states what it takes, and that is the whole DI convention. But a Crud handler
322
- // that forgets to state its ORM used to get `this.orm === undefined` and break on
323
- // the FIRST REQUEST, silently: `super()` assigns whatever it was handed. Refuse at
324
- // boot instead, naming the fix — the clause is deducible, so it is stated, not
325
- // configured.
326
- if (inheritsCrud && handler.deps.length > 0 && !handler.deps.includes(ormTypeName)) {
327
- throw new Error(`${handler.ctor.name} extends Crud() and declares a constructor, so the ORM is no ` +
328
- `longer injected for it — but it does not take one.\n` +
329
- ` Add it and hand it to super():\n` +
330
- ` constructor(orm: ${ormTypeName}, …) { super(orm); }`);
331
- }
332
- // A Crud handler whose subject is not among the scanned entities is either broken
333
- // or installed — `Crud(Note)` from a published package is legitimate and the scan
334
- // cannot see it (CLAUDE.md, heritage resolution is workspace-only). Refusing at
335
- // boot would break the second case to catch the first, so it is said, not thrown.
336
- if (inheritsCrud && !entity) {
337
- frondLog.debug(`${handler.ctor.name} extends Crud() and no scanned entity is named `
338
- + `'${ormBase}' — installed entity, or a missing one: no ORM will be injected`);
339
- }
340
- targetScope.register(handlerKey, handler.ctor, { deps });
341
- let instance;
342
- const getInstance = () => {
343
- if (!instance)
344
- instance = targetScope.resolve(handlerKey);
345
- return instance;
346
- };
347
- /**
348
- * The contracts this façade serves — one function, shared with every other
349
- * reader, so nobody re-derives the three producers and drifts.
350
- */
351
- const contracts = resolveContracts(handler, frond.operationsOverrides, collectorEntityNames);
352
- /**
353
- * Who listens to what — read HERE because this is where a contract becomes real,
354
- * and read from the PLAN rather than from the AST: `{ kind: 'fact' }` is a sentence
355
- * `computeBindingPlan` already wrote, so nothing re-derives what a parameter is.
356
- *
357
- * A subscriber is an ordinary op. It keeps its door, its judge and its middlewares —
358
- * an emission and a direct call are the same call, which is why nothing here has to
359
- * build a second path.
360
- */
361
- noteSubscriptions(contracts, facadeKey);
362
- /**
363
- * A fact is judged on arrival, by the entity it IS.
364
- *
365
- * The scan never fills `input` from a parameter type, so a subscriber's payload met
366
- * no judge at all — tolerable while it came from an emitter in this very process,
367
- * false the moment it comes off a wire, from another repository, from an older
368
- * emitter, or out of a queue that held it for three days. A fact is an entity: it
369
- * has a card, `reconstruct` rebuilds it on the far side, so the same judge stands on
370
- * both ends — which is already what a door promises.
371
- *
372
- * A contract that states its own `input` wins: the three producers keep their order.
373
- */
374
- for (const [op, contract] of contracts) {
375
- if (contract.input)
376
- continue;
377
- const bound = contract.binding?.find((b) => b.source.kind === 'fact');
378
- if (!bound || bound.source.kind !== 'fact')
379
- continue;
380
- const shape = entityByName.get(bound.source.factName);
381
- if (shape)
382
- contracts.set(op, { ...contract, input: shape });
383
- }
384
- /**
385
- * The field set an op's result is projected onto — the view declared for THAT op
386
- * (`Crud(Post, { list: PostCard })`), else the handler-wide view
387
- * (`Crud(Post, PostPublic)`), else the entity. Each op is the audience of its own
388
- * view: a public index emits cards while `bySlug` emits the full row, from one
389
- * handler reading one full-row ORM. Resolved once per op, on first call.
390
- */
391
- const cachedOutput = new Map();
392
- const outputFieldsFor = (op) => {
393
- const known = cachedOutput.get(op);
394
- if (known)
395
- return known;
396
- const perOp = handler?.ctor?.__opOutputs?.[op];
397
- const contractOutput = contracts.get(op)?.output;
398
- const schema = (perOp
399
- ?? contractOutput
400
- ?? handler?.outputOverride
401
- ?? handler?.ctor?.__output
402
- // No entity and nothing declared: there is no shape to project onto, so the
403
- // result travels as the handler returned it (`encodeFields({}, r)` is `{…r}`).
404
- ?? entity?.entityClass);
405
- const fields = typeof schema?.getFields === 'function' ? schema.getFields() : {};
406
- // A view named for THIS op is a closed list: the author said what this
407
- // audience gets. The handler-wide forms already narrow at the ORM.
408
- const resolved = { fields, closed: perOp !== undefined };
409
- cachedOutput.set(op, resolved);
410
- return resolved;
411
- };
412
- const collectorResolver = (entityName) => {
413
- const key = collectorKeyOf(entityName);
414
- try {
415
- return targetScope.resolve(key);
416
- }
417
- catch {
418
- return undefined;
419
- }
420
- };
421
- // The name the door answers to. It reaches the presenter map below, which is keyed
422
- // by ENTITY name — they coincide whenever both exist, and when no entity carries
423
- // this name the lookup simply misses, which is the correct answer.
424
- const address = handler.address;
425
- const wrapOp = (op) => (invocation) => {
426
- const inv = invocation ?? EMPTY_INVOCATION;
427
- const ctx = { entity: address, operation: op, args: [], state: inv.state, invocation: inv };
428
- return runMiddlewares(getMiddlewares(address), ctx, async () => {
429
- const contract = contracts.get(op);
430
- const schema = contract?.input;
431
- let effectiveInvocation = inv;
432
- if (schema && inv.body && typeof inv.body === 'object') {
433
- // The view's mode travels with it: a partial() input validates as a
434
- // patch (absent field → untouched), never by forging the fields.
435
- // No field filter: the axes already judge every case — a client id
436
- // at create is accepted ({ generate }), an id re-supplied in a
437
- // patch is 'Immutable', a read-only field 'Read-only', every
438
- // system-stamped absence is legal via its lifecycle rule, and a
439
- // key outside the contract is 'Unknown field' (refused, not stripped).
440
- const result = Judge.row(schema.getFields(), inv.body, { patch: schema.getOpts().patch });
441
- if (!result.success) {
442
- throw new FougereError({
443
- code: ErrorCode.VALIDATION_FAILED,
444
- message: result.errors.map((e) => `${e.path}: ${e.message}`).join(', '),
445
- details: result.errors,
446
- entity: address,
447
- operation: op,
448
- });
449
- }
450
- effectiveInvocation = { ...inv, body: result.data };
451
- ctx.invocation = effectiveInvocation;
452
- }
453
- // No plan means no declared argument — an op receives what its
454
- // contract says it receives, never a guess based on its name.
455
- const resolved = contract?.binding
456
- ? await resolveArgs(contract.binding, effectiveInvocation, collectorResolver)
457
- : [];
458
- // Egress at the boundary: a write-only field never rides the result
459
- // out, exactly as REST and Pothos already guarantee on their own —
460
- // then a presenter's computed fields are added, so every door answers
461
- // the same thing (they used to be applied by the projections alone).
462
- const out = outputFieldsFor(op);
463
- const projected = projectEgress(out.fields, await getInstance()[op](...resolved), out.closed);
464
- if (out.closed)
465
- return projected;
466
- const meta = presenterMap.get(address);
467
- if (!meta)
468
- return projected;
469
- // A computed field is bound like an op: what it declares after the rows is
470
- // resolved from the same invocation, by the same collectors. The plan is
471
- // computed here and not at scan time because the scan meets presenters
472
- // before it meets collectors.
473
- const args = {};
474
- for (const field of meta.fieldMeta) {
475
- if (!field.params?.length)
476
- continue;
477
- args[field.name] = await resolveArgs(computeBindingPlan(field.params, collectorEntityNames), effectiveInvocation, collectorResolver);
478
- }
479
- return presentEgress(projected, scope.resolve(presenterKeyOf(address)), meta.fields, address, op, args);
480
- });
481
- };
482
- // The surface IS the contract table. A method nobody declared is not an
483
- // op: it stays a method, callable from inside, unreachable from the wire.
484
- const facade = {};
485
- for (const op of contracts.keys()) {
486
- facade[op] = wrapOp(op);
487
- }
488
- container.registerValue(facadeKey, facade);
489
- // The terms alongside the door, under the same audience — a surface that
490
- // serves fewer ops describes fewer ops.
491
- container.registerValue(`${facadeKey}:contracts`, contracts);
492
- return facade;
493
- };
494
- // A presenter is about an entity — computed fields sit on a shape — so this walks
495
- // entities. Exposing the instance lazily; the bridge resolves it on first access.
496
- for (const entity of frond.entities) {
497
- if (!presenterMap.has(entity.name))
498
- continue;
499
- const presenterKey = presenterKeyOf(entity.name);
500
- let presenterInstance;
501
- container.registerValue(presenterKey, new Proxy({}, {
502
- get(_target, prop) {
503
- if (!presenterInstance)
504
- presenterInstance = scope.resolve(presenterKey);
505
- return presenterInstance[prop];
506
- },
507
- }));
508
- }
509
- // A facade is about a handler, so this walks HANDLERS. It walked entities before,
510
- // which made an entity a precondition for being callable at all: a handler naming
511
- // none was scanned, then never built, and nothing said so.
512
- for (const handler of defaultHandlers) {
513
- // Two ways to know the subject, and the explicit one wins: `Crud(Item)` names the
514
- // entity it was built on, whatever the handler is called. Otherwise the handler's
515
- // own name is the only thing pointing at one — and pointing at nothing is legal.
516
- //
517
- // By NAME, not by identity: the scanner loads an entity through its own loader and
518
- // the handler imports it through the runtime's, so the same class arrives as two
519
- // objects. `===` compares module instances, which is not the question being asked.
520
- const crudTarget = handler.ctor.__entity;
521
- const subject = crudTarget?.name
522
- ? crudTarget.name[0].toLowerCase() + crudTarget.name.slice(1)
523
- : handler.address;
524
- const entity = frond.entities.find((e) => e.name === subject);
525
- const facadeKey = facadeKeyOf(handler.address);
526
- buildFacade(entity, handler, scope, facadeKey);
527
- frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey)).join(', ')}]`
528
- + (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
529
- }
530
- // The dual, and it stays: a shape that declares no operation answers nothing. Said
531
- // once per entity rather than deduced from a silence.
532
- for (const entity of frond.entities) {
533
- if (!defaultHandlerMap.has(entity.name)) {
534
- frondLog.debug(`${entity.name} — entity only, no handler: exposes nothing`);
535
- }
536
- }
537
- // Surface handlers — create sub-scope per surface handler with scoped ORM
538
- //
539
- // Pointing at nothing is legal HERE TOO. This loop used to `continue` when no entity
540
- // carried the handler's name, so `handlers/public/SearchHandler.ts` with no `Search`
541
- // entity got no door at all and no line saying why — while the very same handler at
542
- // the default surface is built and logged. One rule, both surfaces.
543
- for (const handler of surfaceHandlers) {
544
- const entity = frond.entities.find((e) => e.name === handler.address);
545
- const surfaceScope = scope.createScope();
546
- // Register scoped ORM if output override differs from entity
547
- if (entity && options.ormFactory) {
548
- const ormName = ormKeyOf(entity.name);
549
- const baseOrm = options.ormFactory(entity.entityClass, entity.name);
550
- const outputSchema = handler.outputOverride ?? handler.ctor.__output;
551
- const scoped = outputSchema && outputSchema !== entity.entityClass
552
- ? baseOrm.output(outputSchema)
553
- : baseOrm;
554
- surfaceScope.registerValue(ormName, guardStorage(scoped, entity.entityClass.getFields(), entity.name));
555
- }
556
- const facadeKey = facadeKeyOf(handler.address, handler.surface);
557
- buildFacade(entity, handler, surfaceScope, facadeKey);
558
- frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey)).join(', ')}]`
559
- + (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
560
- }
561
- // A named surface is closed, so what it contains is a fact worth stating.
562
- // Saying it at boot is the difference between a rule and a rule you can
563
- // check: an entity you meant to serve and never wrote a handler for is
564
- // absent HERE, in one line, instead of being discovered missing later.
565
- const surfaceNames = [...new Set(surfaceHandlers.map((h) => h.surface))].sort();
566
- for (const surfaceName of surfaceNames) {
567
- const served = surfaceHandlers
568
- .filter((h) => h.surface === surfaceName)
569
- .map((h) => h.address)
570
- .sort();
571
- const absent = frond.entities.map((e) => e.name).filter((n) => !served.includes(n));
572
- frondLog.info(`surface '${surfaceName}' — ${served.length} entit${served.length === 1 ? 'y' : 'ies'}: ${served.join(', ')}` +
573
- (absent.length > 0 ? ` (not served: ${absent.join(', ')})` : ''));
574
- }
575
- container.registerValue(`frond:${frond.name}`, scope);
576
- frondLog.info(`registered — ${frond.entities.length} entities, ${frond.handlers.length} handlers, ${frond.seeds.length} seeds`);
577
- }
578
- /**
579
- * A subscriber refusing the SHAPE, said in one line instead of dumped as an error.
580
- *
581
- * This is the one refusal nobody else will ever see. A door hands its 400 back to the
582
- * caller who can fix it; a fact is dispatched, not delivered, so the sender learns
583
- * nothing and the log is the whole of the evidence. The most likely cause is also the
584
- * one a stack trace hides worst — this process's copy is older than the sender's — so
585
- * the line names the fields and the remedy, and hedges because a genuinely bad payload
586
- * produces the same refusal.
587
- */
588
- const describeRefusal = (fact, cause) => {
589
- const err = cause;
590
- if (err?.code !== ErrorCode.VALIDATION_FAILED || !err.details?.length)
591
- return undefined;
592
- return `refused the shape — ${err.details.map((d) => `${d.path}: ${d.message}`).join(', ')}.`
593
- + ` If '${fact}' gained a field, this copy is older than the sender's: re-run \`fougere sync\`.`;
594
- };
595
- /**
596
- * Hand the fact to every listener in THIS process, and give back one promise each.
597
- *
598
- * The call goes THROUGH the door, so a subscriber meets the same judge, the same binding
599
- * and the same middlewares as any caller. Nothing new answers for correctness — that is
600
- * the dividend of a subscriber being an ordinary op rather than a special kind.
601
- *
602
- * It returns the promises rather than settling them, because the two callers want
603
- * opposite things and only one of them is wrong to wait. See `deliver`.
604
- */
605
- const handToListeners = (fact, payload) => {
606
- const walked = chain.getStore() ?? [];
607
- if (walked.includes(fact)) {
608
- throw new Error(`Emission cycle: ${[...walked, fact].join(' → ')}.\n`
609
- + ` A fact cannot cause itself. One of the subscribers above announces a fact that leads back here.`);
610
- }
611
- const listeners = subscribers.get(fact) ?? [];
612
- if (listeners.length === 0) {
613
- log.debug(`${fact} — nobody listens in this process`);
614
- return [];
615
- }
616
- const deeper = [...walked, fact];
617
- return listeners.map(({ door, op }) => ({
618
- door,
619
- op,
620
- done: chain.run(deeper, async () => {
621
- let facade;
622
- try {
623
- facade = container.resolve(door);
624
- }
625
- catch (cause) {
626
- throw new Error(`${fact} → ${door} could not be reached`, { cause });
627
- }
628
- return facade[op]({ ...EMPTY_INVOCATION, body: payload });
629
- }),
630
- }));
631
- };
632
- /**
633
- * Announcing. Dispatch, never delivery — the emitter is handed back the moment every
634
- * subscriber has been HANDED the fact, not when any of them is done.
635
- *
636
- * The `EventBus` this replaces did `await Promise.all(handlers)` and passed their
637
- * rejections up, which made a publication hostage to its own indexer.
638
- */
639
- const dispatchLocally = async (fact, payload) => {
640
- for (const { door, op, done } of handToListeners(fact, payload)) {
641
- void done.catch((cause) => log.error(`${fact} → ${door}.${op}`, describeRefusal(fact, cause) ?? cause));
642
- }
643
- };
644
- /**
645
- * Receiving. **The opposite rule, deliberately**: this one waits, and it tells.
646
- *
647
- * `deliver` is what a CARRIER calls, and a carrier's whole job is to know whether the
648
- * fact landed — at-least-once is retrying what failed, so a delivery that cannot report
649
- * makes durability impossible to build on top. It used to be `dispatchLocally` itself:
650
- * it resolved before any subscriber had run and swallowed every failure into a log, so a
651
- * queue calling it could only ever ack blindly.
652
- *
653
- * That is not a contradiction of "dispatch is not delivery". That rule protects the
654
- * EMITTER, which must not become hostage to a subscriber; a carrier is not the emitter,
655
- * it is precisely the party whose business this is.
656
- *
657
- * What it still does not do is HOLD anything. A fact refused here is refused, and the
658
- * carrier decides whether it comes back — which is the whole of Fougere's position on
659
- * durability: the channel goes underneath, it is not reimplemented here.
660
- */
661
- const deliver = async (fact, payload) => {
662
- const handed = handToListeners(fact, payload);
663
- const settled = await Promise.allSettled(handed.map((h) => h.done));
664
- const refused = settled.flatMap((result, i) => result.status === 'rejected' ? [{ ...handed[i], reason: result.reason }] : []);
665
- for (const { door, op, reason } of refused) {
666
- log.error(`${fact} → ${door}.${op}`, describeRefusal(fact, reason) ?? reason);
667
- }
668
- if (refused.length > 0) {
669
- throw new AggregateError(refused.map((r) => r.reason), `${fact} — ${refused.length} of ${handed.length} listener(s) refused it`
670
- + ` (${refused.map((r) => `${r.door}.${r.op}`).join(', ')}).`
671
- + ` Nothing here holds it: the carrier decides whether it comes back.`);
672
- }
673
- };
674
- // Emitted here, or merely listened to: a process that only subscribes still needs the
675
- // value, because `deliver` is what a carrier calls and it goes through the same door.
676
- for (const fact of new Set([...emitted, ...subscribers.keys()])) {
677
- const shape = entityByName.get(fact);
678
- container.registerValue(emitKeyOf(fact), async (raw) => {
679
- /**
680
- * The announcement realizes the fact's own `lifecycle.create` — an `created()` stamped,
681
- * an id generated, a default applied.
682
- *
683
- * `validation.ts` states the split: the judge never fills a hole, the STORAGE does,
684
- * at the point of persistence. A fact has no storage, so nobody did — the judge
685
- * declared an absent `created()` legal and omitted it, and a subscriber received a
686
- * value missing a field its own type promises. Announcing is a fact's point of
687
- * persistence, and `applyCreate` is the one realization every storage already shares.
688
- *
689
- * Here and not in `dispatchLocally`, which is shared with `deliver`: a fact that
690
- * arrives from elsewhere was stamped by its sender, and stamping it again would give
691
- * one fact a different identity in every process that relayed it.
692
- *
693
- * **A typed emitter cannot reach this yet.** `Emit<T>` names the ROW type, where an
694
- * `created()` field is present and required, so `announce({ id, title })` is a
695
- * compile error and the author writes `at: new Date()` anyway. `PartialRow`
696
- * (`schema/src/entity.ts`) is exactly the shape wanted and derives from the FIELDS,
697
- * which the instance type has already thrown away. So this runs for a payload built
698
- * outside the type — a bridge, a replay, a test — and is inert for everyone else.
699
- */
700
- const payload = shape && raw !== null && typeof raw === 'object' && !Array.isArray(raw)
701
- ? applyCreate(shape.getFields(), raw)
702
- : raw;
703
- /**
704
- * Whoever is not in this process — and it is the ONLY way to reach them.
705
- *
706
- * The local dispatch finds its listeners by having READ their code, so it stops at
707
- * the repository boundary: another team's Frond is not on this disk, and the
708
- * emission reaches nobody. A carrier hands the fact to a name instead, and the far
709
- * side subscribes to that same name from ITS own code. Neither reads the other.
710
- *
711
- * `deliver` deliberately does NOT come here: a hub that resolved this value to hand
712
- * on an incoming reading echoed it straight back to the whole fleet.
713
- */
714
- const carried = options.onEmit?.(fact, payload);
715
- if (carried)
716
- void Promise.resolve(carried).catch((cause) => log.error(`${fact} — carrier refused it`, cause));
717
- await dispatchLocally(fact, payload);
718
- });
719
- }
720
- /**
721
- * The last resort, held by the container so every resolution path shares it.
722
- *
723
- * It used to live on `app.resolve` alone, which meant the runner found a remote façade
724
- * and dependency injection did not — a handler asking for a neighbour that had moved
725
- * got `is not registered` while the very same call over the wire worked. One door now.
726
- *
727
- * Surface-scoped keys ('admin:productHandler') stay local: a named surface resolves in
728
- * this container only, so fabricating one for a remote frond would answer NOT_FOUND on
729
- * everything (see Known issues).
730
- */
731
- container.setFallback?.((name) => {
732
- if (!remoteRouter)
733
- return undefined;
734
- if (!name.endsWith('Handler') || name.includes(':'))
735
- return undefined;
736
- // Façade-shaped stand-in; routing happens lazily at the first call.
737
- return createRemoteFacade(name.replace(/Handler$/, ''), remoteRouter);
738
- });
739
- const resolve = (name) => {
740
- try {
741
- return container.resolve(name);
742
- }
743
- catch (err) {
744
- if (name.endsWith('Handler') && !name.includes(':') && !remoteRouter) {
745
- throw new Error(notLoaded(name.replace(/Handler$/, '')));
746
- }
747
- throw err;
748
- }
749
- };
750
- const schemaFor = async (entity) => {
751
- for (const frond of fronds) {
752
- const found = frond.entities.find((e) => e.name === entity);
753
- if (found)
754
- return found.entityClass;
755
- }
756
- if (remoteRouter) {
757
- const route = await remoteRouter.route(entity);
758
- // A remote door that stores nothing publishes ops and no shape. Saying so beats
759
- // handing back an empty schema, which would validate every input it was given.
760
- if (!route.schema) {
761
- throw new Error(`'${entity}' is served by frond '${route.frond}' but stores no rows, so it has no schema. `
762
- + `Call its operations through the façade instead.`);
763
- }
764
- return route.schema;
765
- }
766
- throw new Error(notLoaded(entity));
767
- };
768
- const facadeAt = (key, topology) => {
769
- try {
770
- return topology
771
- ? resolve(key)
772
- : container.resolve(key);
773
- }
774
- catch {
775
- return undefined;
776
- }
777
- };
778
- /**
779
- * THE membership rule, stated once — every projection reads this and nothing
780
- * else. It used to live twice, verbatim, in the REST and GraphQL adapters,
781
- * and two other readers invented their own answer.
782
- *
783
- * Naming an audience closes it. Three ways to name an entity into a surface,
784
- * in precedence order:
785
- * - `surfaces:` in frond.config.ts — when the list exists, it IS the list;
786
- * - a handler under `handlers/<surface>/` — which also restricts the façade;
787
- * - the `@expose` sugar, resolved into the same two by the scan.
788
- * What none of them names is not served. It used to be the reverse: no
789
- * `public:categoryHandler` meant the FULL CategoryHandler rode the public
790
- * door, create/update/delete included (measured on demos/nuxt-blog).
791
- *
792
- * A named entity with no façade of its own falls back to the default one —
793
- * that is not the old widening, it is the author saying "this one, as it is".
794
- * The leak was exposure with no statement behind it.
795
- *
796
- * A surface key stays local: a doublure serves whatever the remote's own door
797
- * serves, which is the remote's business and not ours to re-audience.
798
- */
799
- const facadeFor = (entity, surface) => {
800
- if (!surface)
801
- return facadeAt(facadeKeyOf(entity), true);
802
- const own = facadeAt(facadeKeyOf(entity, surface), false);
803
- const declared = fronds.find((f) => f.entities.some((e) => e.name === entity))?.surfaces?.[surface];
804
- if (!declared)
805
- return own;
806
- return declared.some((n) => n.toLowerCase() === entity.toLowerCase())
807
- ? (own ?? facadeAt(facadeKeyOf(entity), false))
808
- : undefined;
809
- };
810
- /**
811
- * The storage an entity is backed by — the dual of `facadeFor`, which serves its
812
- * client-facing door. Both are ways in; an entity that opens none of them still has rows.
813
- *
814
- * It resolves through the owning frond's scope, because that is where entity ORMs live:
815
- * `resolve('UserOrm')` reads the ROOT container and never finds one, so callers outside
816
- * the frond concluded there was no storage. The seed loop did exactly that, and skipped
817
- * every entity with no façade — the one case its own fallback existed for.
818
- */
819
- const ormFor = (entity) => {
820
- const owner = fronds.find((f) => f.entities.some((e) => e.name === entity));
821
- if (!owner)
822
- return undefined;
823
- const key = ormKeyOf(entity);
824
- try {
825
- return container.resolve(`frond:${owner.name}`).resolve(key);
826
- }
827
- catch {
828
- return undefined;
829
- }
830
- };
831
- /**
832
- * The presenter of an entity, resolved through its owning frond's scope.
833
- *
834
- * Same shape as `ormFor`, and it exists for the same reason: without it an adapter
835
- * has to spell the container key itself. `schema-graphql` did — a hand-written
836
- * `${Name}Presenter` — and a key respelled elsewhere finds nothing and says nothing
837
- * the day the convention moves, which is exactly the failure `verify.ts` refuses.
838
- */
839
- const presenterFor = (entity) => {
840
- const owner = fronds.find((f) => f.entities.some((e) => e.name === entity));
841
- if (!owner)
842
- return undefined;
843
- try {
844
- return container.resolve(`frond:${owner.name}`).resolve(presenterKeyOf(entity));
845
- }
846
- catch {
847
- return undefined;
848
- }
849
- };
850
- return {
851
- container,
852
- fronds,
853
- // What this app publishes, straight from fougere.config.ts — the doors read it,
854
- // so an undeclared adapter serves nothing whatever a host mounted.
855
- adapters: options.adapters ?? {},
856
- resolve,
857
- schemaFor,
858
- facadeFor,
859
- listensTo: () => [...subscribers.keys()],
860
- deliver,
861
- ormFor,
862
- presenterFor,
863
- dispose: () => container.dispose(),
864
- [Symbol.asyncDispose]: () => container.dispose(),
865
- use(...args) {
866
- if (typeof args[0] === 'string') {
867
- const [entity, mw] = args;
868
- const list = scopedMiddlewares.get(entity) ?? [];
869
- list.push(mw);
870
- scopedMiddlewares.set(entity, list);
871
- }
872
- else {
873
- globalMiddlewares.push(args[0]);
874
- }
875
- },
876
- auth: authRuntime,
877
- };
878
- }
879
- //# sourceMappingURL=bootstrap.js.map