@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
@@ -0,0 +1,210 @@
1
+ /**
2
+ * The compensated realization of a frame — what `Together` becomes when its members do
3
+ * not share an engine.
4
+ *
5
+ * A transaction is the engine's own answer and costs nothing to use: the members are
6
+ * rebuilt over it and the engine gives both the unwind and the isolation. Across engines
7
+ * there is no such thing, so the unwind is built here: every write through the port is
8
+ * recorded with the image that preceded it, and a failure replays the inverses in reverse
9
+ * order.
10
+ *
11
+ * **It gives all-or-nothing, never isolation.** Between two writes a reader sees the half,
12
+ * and nothing here can hide it — that is the whole difference between the two realizations
13
+ * and the reason the boot says which one it built.
14
+ *
15
+ * The port is thirteen known gestures, which is what makes the inverse derivable rather
16
+ * than declared: a saga asks its author for an `undo` per step because its steps are
17
+ * arbitrary code. Here they are not.
18
+ */
19
+ import { FieldGroup, FieldSet, Unique, type Fields } from '@fougere/schema';
20
+ import { dequal } from 'dequal';
21
+ import type { Logger } from '../builtins/logger.js';
22
+
23
+ /** One write that landed, and how to take it back. */
24
+ export interface Undo {
25
+ /** What it will undo, in the sentence a failure report needs. */
26
+ what: string;
27
+ run(): Promise<void>;
28
+ }
29
+
30
+ /**
31
+ * The subset of the port a frame has to watch: every gesture that writes, plus the reads
32
+ * that take one back.
33
+ *
34
+ * Not `StorageGuard`'s `Writer`, which is `create` and `update` — all a judge needs. Undoing
35
+ * needs more: the deletes, and the reads that fetch the image to put back.
36
+ */
37
+ interface Undoable {
38
+ create(input: Record<string, unknown>, ...rest: unknown[]): Promise<Record<string, unknown>>;
39
+ update(id: string, patch: Record<string, unknown>, ...rest: unknown[]): Promise<Record<string, unknown>>;
40
+ delete(id: string): Promise<boolean>;
41
+ findById(id: string, ...rest: unknown[]): Promise<Record<string, unknown> | undefined>;
42
+ findByKeys(ids: readonly string[], ...rest: unknown[]): Promise<Map<string, Record<string, unknown>>>;
43
+ upsert?(input: Record<string, unknown>, ...rest: unknown[]): Promise<Record<string, unknown>>;
44
+ upsertAll?(inputs: readonly Record<string, unknown>[], ...rest: unknown[]): Promise<number>;
45
+ }
46
+
47
+ const pick = (row: Record<string, unknown>, keys: readonly string[]): Record<string, unknown> =>
48
+ Object.fromEntries(keys.map((key) => [key, row[key]]));
49
+
50
+ /**
51
+ * Whether an upsert's conflict can be something other than the primary key.
52
+ *
53
+ * The inverse of an upsert is derivable exactly when the conflict is the primary key: read
54
+ * the rows by key first, then restore the ones that were there and delete the ones that
55
+ * were not. `SqlEntityOrm` spells `onConflict(pk)` on SQLite and Postgres, so that holds —
56
+ * but MySQL's `onDuplicateKeyUpdate` fires on ANY unique constraint, and a row that
57
+ * conflicts on a unique email while carrying a different key would be restored under a key
58
+ * the write never touched.
59
+ *
60
+ * So the question is asked of the DECLARATION, not of the engine: an entity whose only
61
+ * unique constraint is its key upserts unambiguously everywhere. This file knows no dialect
62
+ * and should not learn one.
63
+ */
64
+ function mayConflictElsewhere(fields: Fields): boolean {
65
+ const groups = FieldGroup.groupsOf(fields, Unique);
66
+ if (groups.length > 0) return true;
67
+ // A sole `unique()` carries a one-member group, which `groupsOf` filters out.
68
+ return Object.values(fields).some((field) => FieldGroup.on(field, Unique).length > 0);
69
+ }
70
+
71
+ /** Refused where it is used, naming the group that makes the inverse ambiguous. */
72
+ function refuseAmbiguousUpsert(entity: string, gesture: string): never {
73
+ throw new Error(
74
+ `${entity}.${gesture}() cannot be unwound: ${entity} declares a unique constraint besides ` +
75
+ `its key, so an upsert may overwrite a row this frame never read — and there is no ` +
76
+ `transaction here to take it back. Put the members in one source, or write ` +
77
+ `create/update explicitly.`,
78
+ );
79
+ }
80
+
81
+ /**
82
+ * The ORM a member is handed inside a compensated frame: the same port, writing the same
83
+ * rows, leaving an inverse behind each time.
84
+ *
85
+ * `Object.create` for the same reason `StorageGuard` uses it — the ORM keeps every gesture
86
+ * it had, including the ones this knows nothing about.
87
+ */
88
+ export function recording<T extends object>(orm: T, entity: string, fields: Fields, journal: Undo[]): T {
89
+ const base = orm as unknown as Undoable;
90
+ if (typeof base.create !== 'function' || typeof base.update !== 'function') return orm;
91
+
92
+ const key = FieldSet.of(fields).primary;
93
+ if (!key) {
94
+ throw new Error(
95
+ `${entity} declares no primary field, so a write to it cannot be taken back by ` +
96
+ `identity — and this frame has no transaction to do it instead.`,
97
+ );
98
+ }
99
+
100
+ const recorded = Object.create(orm) as T & Undoable;
101
+
102
+ recorded.create = async function (...args) {
103
+ const row = await base.create.apply(this, args);
104
+ const id = String(row[key]);
105
+ journal.push({ what: `create ${entity}#${id}`, run: () => base.delete.call(this, id).then(() => undefined) });
106
+ return row;
107
+ };
108
+
109
+ recorded.update = async function (...args) {
110
+ const [id, patch] = args;
111
+ const before = await base.findById.call(this, id);
112
+ const row = await base.update.apply(this, args);
113
+ if (!before) return row;
114
+
115
+ // Compare against what the WRITE produced, not against the patch: a `update: 'now'`
116
+ // field is stamped by the storage, so the patch is not what the row now holds.
117
+ const touched = Object.keys(patch);
118
+ const wrote = pick(row, touched);
119
+ journal.push({
120
+ what: `update ${entity}#${id} (${touched.join(', ')})`,
121
+ run: async () => {
122
+ const current = await base.findById.call(this, id);
123
+ if (!current) throw new Error(`${entity}#${id} is gone`);
124
+ const moved = touched.filter((field) => !dequal(current[field], wrote[field]));
125
+ if (moved.length > 0) {
126
+ throw new Error(`${entity}#${id} was changed by someone else since (${moved.join(', ')})`);
127
+ }
128
+ await base.update.call(this, id, pick(before, touched));
129
+ },
130
+ });
131
+ return row;
132
+ };
133
+
134
+ recorded.delete = async function (id) {
135
+ const before = await base.findById.call(this, id);
136
+ const removed = await base.delete.call(this, id);
137
+ if (removed && before) {
138
+ journal.push({ what: `delete ${entity}#${id}`, run: () => base.create.call(this, before).then(() => undefined) });
139
+ }
140
+ return removed;
141
+ };
142
+
143
+ // An upsert says neither what it wrote over nor what it inserted, so the frame reads the
144
+ // keys first: what was there is restored, what was not is deleted. One extra query for
145
+ // the page, which is the same bargain `update` already makes for one row.
146
+ const undoUpsert = async function (this: unknown, rows: readonly Record<string, unknown>[]): Promise<Undo> {
147
+ const ids = rows.map((row) => String(row[key]));
148
+ const before = await base.findByKeys.call(this, ids);
149
+ return {
150
+ what: `upsert ${entity} (${ids.length} row(s))`,
151
+ run: async () => {
152
+ for (const id of ids) {
153
+ const was = before.get(id);
154
+ if (was) await base.update.call(this, id, was);
155
+ else await base.delete.call(this, id);
156
+ }
157
+ },
158
+ };
159
+ };
160
+
161
+ if (typeof base.upsert === 'function') {
162
+ (recorded as unknown as Undoable).upsert = async function (input, ...rest) {
163
+ if (mayConflictElsewhere(fields)) refuseAmbiguousUpsert(entity, 'upsert');
164
+ const undo = await undoUpsert.call(this, [input]);
165
+ const row = await base.upsert!.call(this, input, ...rest);
166
+ journal.push(undo);
167
+ return row;
168
+ };
169
+ }
170
+
171
+ if (typeof base.upsertAll === 'function') {
172
+ (recorded as unknown as Undoable).upsertAll = async function (inputs, ...rest) {
173
+ if (mayConflictElsewhere(fields)) refuseAmbiguousUpsert(entity, 'upsertAll');
174
+ const undo = await undoUpsert.call(this, inputs);
175
+ const written = await base.upsertAll!.call(this, inputs, ...rest);
176
+ journal.push(undo);
177
+ return written;
178
+ };
179
+ }
180
+
181
+ return recorded;
182
+ }
183
+
184
+ /**
185
+ * Replay the inverses, most recent first, and report rather than pretend.
186
+ *
187
+ * Every one is attempted even after one fails: stopping at the first would leave writes
188
+ * standing that could have been taken back. What cannot be undone is named — the caller is
189
+ * about to tell a user their transfer failed, and "it failed and here is what is still in
190
+ * the database" is the only honest version of that sentence.
191
+ */
192
+ export async function unwind(journal: readonly Undo[], cause: unknown, log: Logger): Promise<never> {
193
+ const stuck: string[] = [];
194
+ for (const undo of [...journal].reverse()) {
195
+ try {
196
+ await undo.run();
197
+ } catch (failure) {
198
+ stuck.push(`${undo.what} — ${(failure as Error).message}`);
199
+ log.error(`frame could not undo ${undo.what}`, failure);
200
+ }
201
+ }
202
+ if (stuck.length === 0) throw cause;
203
+
204
+ throw new Error(
205
+ `The frame failed and ${stuck.length} of ${journal.length} write(s) could not be taken back: ` +
206
+ `${stuck.join('; ')}. What remains is in the database. Original failure: ` +
207
+ `${(cause as Error)?.message ?? String(cause)}`,
208
+ { cause },
209
+ );
210
+ }
@@ -0,0 +1,140 @@
1
+ import { lowerFirst, type EntityConstructor } from '@fougere/schema';
2
+ import type { FrondDescriptor, ProviderEntry } from '../scan/frond.js';
3
+ import { targetOf } from '../prefab/prefab.js';
4
+ import { ownedBy, repositoryKeyOf } from '../prefab/repository.js';
5
+ import { ormKeyOf } from '../orm.js';
6
+
7
+ /**
8
+ * Who owns an entity's storage.
9
+ *
10
+ * `Repository(Commande, Stock)` states it — an aggregate is the only thing that claims
11
+ * entities it is not named after, and the arity is what says so. A repository of ONE owns
12
+ * nothing: a boundary between one thing and nothing is not a boundary, and the judge is
13
+ * already on that entity's rows.
14
+ */
15
+ export function ownersOf(providers: readonly ProviderEntry[]): Map<string, string> {
16
+ const owners = new Map<string, string>();
17
+ for (const provider of providers) {
18
+ const owned = ownedBy(provider.ctor);
19
+ if (owned.length < 2) continue;
20
+ for (const entity of owned) {
21
+ const name = lowerFirst((entity as { name: string }).name);
22
+ const first = owners.get(name);
23
+ if (first && first !== provider.ctor.name) {
24
+ // Refused rather than settled, for the reason `ports:` refuses two implementations
25
+ // and `remotes` two owners of an entity: whichever won would depend on scan order,
26
+ // and one of the two aggregates would be silently unenforced.
27
+ throw new Error(
28
+ `[aggregate] ${first} and ${provider.ctor.name} both own ${name}. `
29
+ + 'An entity has one owner: merge the two, or take it out of one of them.',
30
+ );
31
+ }
32
+ owners.set(name, provider.ctor.name);
33
+ }
34
+ }
35
+ return owners;
36
+ }
37
+
38
+ /**
39
+ * The entities a class was BUILT ON — what a prefab may legitimately be handed the storage
40
+ * of. Empty for a plain service, which is why one cannot ask for an ORM at all.
41
+ */
42
+ function builtOn(ctor: unknown): string[] {
43
+ const owned = ownedBy(ctor);
44
+ if (owned.length > 0) return owned.map((e) => lowerFirst((e as { name: string }).name));
45
+ const target = targetOf(ctor) as { name?: string } | undefined;
46
+ return target?.name ? [lowerFirst(target.name)] : [];
47
+ }
48
+
49
+ /**
50
+ * `EntityOrm<E>` is not a word of the user's vocabulary — `<E>Repository` is the one way in.
51
+ *
52
+ * A door judges and projects; a HOLDER keeps the storage. So the rule is not about which
53
+ * directory a file sits in but about which of the two a class is: a handler, a presenter and
54
+ * a collector may never name an ORM, and a provider may name only the ORM of what its prefab
55
+ * was built on — which is `Repository` and `Mirror`, without either being named here.
56
+ *
57
+ * What it buys beyond one word instead of two: an entity that becomes owned later needs no
58
+ * call site to move, because none of them ever spelled the storage.
59
+ *
60
+ * Refused at boot rather than reported by `verify`, which nothing calls at startup.
61
+ */
62
+ export function refuseOrmInUserCode(frond: FrondDescriptor, owners: Map<string, string>): void {
63
+ const doors = [
64
+ ...frond.handlers.map((h) => ({ ...h, kind: 'handler' })),
65
+ ...frond.presenters.map((p) => ({ ...p, kind: 'presenter' })),
66
+ ...frond.collectors.map((c) => ({ ...c, kind: 'collector' })),
67
+ ];
68
+ const holders = frond.providers.map((p) => ({ ...p, kind: 'provider' as const }));
69
+
70
+ for (const decl of [...doors, ...holders]) {
71
+ const allowed = decl.kind === 'provider' ? builtOn(decl.ctor) : [];
72
+ for (const dep of decl.deps) {
73
+ const entity = entityOfOrmKey(dep);
74
+ if (!entity) continue;
75
+
76
+ const owner = owners.get(entity);
77
+ if (owner && owner !== decl.ctor.name) {
78
+ throw new Error(
79
+ `[aggregate] ${decl.ctor.name} asks for ${dep}, and ${owner} owns ${entity}.\n`
80
+ + ` Nothing else reaches an owned entity's storage — name the operation on `
81
+ + `${owner} and ask for it:\n`
82
+ + ` constructor(private ${entity}: ${owner}) {}\n`
83
+ + ` ${decl.filePath}`,
84
+ );
85
+ }
86
+ if (allowed.includes(entity)) continue;
87
+
88
+ throw new Error(
89
+ `${decl.ctor.name} asks for ${dep}. Storage is reached through a repository, `
90
+ + `never through the port:\n`
91
+ + ` constructor(private ${entity}: ${repositoryKeyOf(entity)}) {}\n`
92
+ + ` It answers every gesture ${dep} does, whether or not anyone wrote the file — `
93
+ + `and the day ${entity} belongs to an aggregate, this line does not move.\n`
94
+ + ` ${decl.filePath}`,
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * An owned entity has no automatic CRUD — said at boot, not at the first request.
102
+ *
103
+ * `HandlerFacade.deps()` asks for `<E>Repository` when a handler inherits the five gestures
104
+ * and declares no constructor of its own, and an owned entity has no such key. That alone
105
+ * refuses — but LAZILY, at the first call, on the container's own sentence: measured, the app
106
+ * booted clean and answered every request with `'LedgerRepository' is not registered`. A
107
+ * receiver that starts and then rejects everything is found in production, which is the
108
+ * reason `serve()` checks its address at boot rather than per call.
109
+ *
110
+ * Recognized by FORM, like the façade recognizes it: a prototype carrying `list` and
111
+ * `findById` needs storage of its own, whether `Crud()` put them there or an author did.
112
+ */
113
+ export function refuseCrudOnOwned(frond: FrondDescriptor, owners: Map<string, string>): void {
114
+ for (const handler of frond.handlers) {
115
+ const owner = owners.get(handler.address);
116
+ if (!owner || handler.deps.length > 0) continue;
117
+ const proto = (handler.ctor as { prototype?: Record<string, unknown> }).prototype;
118
+ if (typeof proto?.list !== 'function' || typeof proto?.findById !== 'function') continue;
119
+
120
+ throw new Error(
121
+ `[aggregate] ${handler.ctor.name} takes the five gestures on ${handler.address}, `
122
+ + `which ${owner} owns.\n`
123
+ + ` An owned entity has no automatic CRUD: nothing writes it but its owner, so there `
124
+ + `is no storage to hand this handler.\n`
125
+ + ` Name the operations on ${owner} and call them:\n`
126
+ + ` constructor(private ${handler.address}: ${owner}) {}\n`
127
+ + ` ${handler.filePath}`,
128
+ );
129
+ }
130
+ }
131
+
132
+ /** `'PostOrm'` → `'post'`, and nothing for a key that is not one. */
133
+ function entityOfOrmKey(dep: string): string | undefined {
134
+ if (!dep.endsWith('Orm') || dep === 'Orm') return undefined;
135
+ const entity = lowerFirst(dep.slice(0, -'Orm'.length));
136
+ return ormKeyOf(entity) === dep ? entity : undefined;
137
+ }
138
+
139
+ /** Placeholder so the module's imports stay honest if a caller wants the entity list. */
140
+ export type { EntityConstructor };
@@ -0,0 +1,66 @@
1
+ import type { ProviderEntry } from '../scan/frond.js';
2
+
3
+ /**
4
+ * A port is a class something already answers under. Nothing declares one — the
5
+ * prototype chain states it, so `class StripePayment extends Payment` IS the whole
6
+ * registration, and so is `class MyLogger extends Logger` over a builtin.
7
+ *
8
+ * This is the fourth reading of the rule the other three already apply: the type
9
+ * names the SUBJECT (`EntityOrm<E>`, `Emit<F>`, `Facade<H>`) and the container
10
+ * holds the realization. Providers were the one case where the type named the
11
+ * realization instead, so `constructor(private payment: Payment)` resolved to the
12
+ * base class and a handler got an object whose method did not exist.
13
+ *
14
+ * Only the DIRECT base binds. A deeper chain binds each link to its own child, and
15
+ * a case that wants otherwise states it in `ports:` — which wins over all of this.
16
+ */
17
+ export function portBindings(
18
+ providers: ProviderEntry[],
19
+ answers: (name: string) => boolean,
20
+ chosen: Record<string, string> | undefined,
21
+ ): Map<string, ProviderEntry> {
22
+ // port class name → the classes that extend it, in scan order.
23
+ const candidates = new Map<string, ProviderEntry[]>();
24
+ for (const provider of providers) {
25
+ const base = Object.getPrototypeOf(provider.ctor) as { name?: string } | null;
26
+ const port = base?.name;
27
+ // ONE condition: something already answers under that name. Providers are
28
+ // registered into this scope just above, and the builtins sit in its parent, so
29
+ // this covers a neighbour service and `Logger` alike — a framework class is a
30
+ // port like any other, which is what makes a default overridable.
31
+ //
32
+ // It is also what excludes a prefab: a repository extends the class
33
+ // `Repository(Post)` returned (`RepositoryBase`), and no key is ever that name.
34
+ if (!port || !answers(port)) continue;
35
+ candidates.set(port, [...(candidates.get(port) ?? []), provider]);
36
+ }
37
+
38
+ const bound = new Map<string, ProviderEntry>();
39
+ for (const [port, impls] of candidates) {
40
+ const named = chosen?.[port];
41
+ if (named) {
42
+ const pick = impls.find((i) => i.ctor.name === named);
43
+ if (!pick) {
44
+ throw new Error(
45
+ `[ports] ${port}: '${named}' does not extend it. `
46
+ + `What does: ${impls.map((i) => i.ctor.name).join(', ')}.`,
47
+ );
48
+ }
49
+ bound.set(port, pick);
50
+ continue;
51
+ }
52
+ if (impls.length > 1) {
53
+ // Refusing rather than keeping one, for the reason `remotes` refuses two owners
54
+ // of an entity: whichever won would depend on scan order, and the handler would
55
+ // charge the wrong provider without a word.
56
+ throw new Error(
57
+ `[ports] ${impls.map((i) => i.ctor.name).join(' and ')} both extend ${port}, `
58
+ + 'and nothing says which one answers it. Which realization a deployment uses is '
59
+ + `not a fact about the code — state it: ports: { ${port}: '${impls[0].ctor.name}' } `
60
+ + 'in fougere.config.ts.',
61
+ );
62
+ }
63
+ bound.set(port, impls[0]);
64
+ }
65
+ return bound;
66
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Remote façade — what resolve() falls back to.
3
+ *
4
+ * When a handler isn't hosted locally and remotes are declared, resolve()
5
+ * returns a façade-shaped stand-in: same call surface as a local facade,
6
+ * every operation executed through a Transport. Routing is lazy — on the
7
+ * first miss, each declared remote is asked what it hosts (rpc.discover)
8
+ * and the answer is cached. A remote that can't be reached stays pending
9
+ * and is retried on the next miss instead of being cached as absent.
10
+ */
11
+ import type { FrondCall, Transport } from '../wire/call.js';
12
+ import { assertIdentityCard, RPC_ENTITY } from '../wire/call.js';
13
+ import { runMiddlewares, type AppMiddleware, type OperationContext } from '../wire/middleware.js';
14
+ import { EMPTY_INVOCATION, type InvocationContext } from '../wire/invocation.js';
15
+ import { FougereError, ErrorCode } from '../wire/errors.js';
16
+ import { Card, type SchemaView, type SchemaDescriptor } from '@fougere/schema';
17
+ import { DynamicFacade } from '../entry/DynamicFacade.js';
18
+
19
+ interface Route {
20
+ frond: string;
21
+ transport: Transport;
22
+ /**
23
+ * The entity's schema, rebuilt from the identity card — the same
24
+ * `SchemaConstructor` shape `entity({...})` produces, live validation
25
+ * included. Reconstructed once per entity, at discovery time.
26
+ *
27
+ * **Absent when the door stores nothing.** A handler may carry no entity, so the card
28
+ * publishes it with ops and no shape; there is nothing to rebuild and pretending
29
+ * otherwise would hand callers an empty schema that validates everything.
30
+ */
31
+ schema?: SchemaView;
32
+ }
33
+
34
+ export interface RemoteRouter {
35
+ route(entity: string): Promise<Route>;
36
+ }
37
+
38
+ export function createRemoteRouter(
39
+ remotes: Record<string, string>,
40
+ makeTransport: (url: string) => Transport,
41
+ ): RemoteRouter {
42
+ const byEntity = new Map<string, Route>();
43
+ // The remotes config key is a label for the address — the identity card is
44
+ // what decides which entities live behind it.
45
+ const pending = new Map(Object.entries(remotes));
46
+ const transports = new Map<string, Transport>();
47
+
48
+ /** Which remote label claimed a door name — so a second claim can name the first. */
49
+ const claimedBy = new Map<string, string>();
50
+
51
+ const discover = async (): Promise<void> => {
52
+ // Asking is concurrent; INDEXING is not, and is done in the order `remotes` declares.
53
+ // Reading the cards inside the race made "which remote won" depend on who answered
54
+ // first, so the same two remotes could resolve differently between two runs.
55
+ const cards = await Promise.all(
56
+ [...pending].map(async ([label, url]) => {
57
+ const transport = transports.get(url) ?? makeTransport(url);
58
+ transports.set(url, transport);
59
+ try {
60
+ const answer = await transport({ entity: RPC_ENTITY, op: 'discover' }, EMPTY_INVOCATION);
61
+ // Judged below and not here: this catch means "unreachable, retry", and a
62
+ // refusal thrown inside it would be swallowed into another silent retry.
63
+ return { label, url, transport, answer };
64
+ } catch {
65
+ // Unreachable — stays pending, retried on the next miss.
66
+ return undefined;
67
+ }
68
+ }),
69
+ );
70
+
71
+ for (const answered of cards) {
72
+ if (!answered) continue;
73
+ const { label, url, transport, answer } = answered;
74
+ pending.delete(label);
75
+ const card = assertIdentityCard(answer, `Remote '${label}' (${url})`);
76
+
77
+ for (const frond of card.fronds) {
78
+ // Doors only. A fact is not routable — nobody calls it, it arrives — so
79
+ // adding one here would answer a call with a transport to a door that
80
+ // does not exist.
81
+ for (const door of frond.doors) {
82
+ const held = claimedBy.get(door.name);
83
+ /**
84
+ * Two remotes claiming one name is refused, not silently arbitrated.
85
+ *
86
+ * `byEntity` is keyed by the door name alone, so the second claim used to be
87
+ * dropped with `if (!byEntity.has(...))` — no warning, and the winner was
88
+ * whichever remote answered first. The local boot already refuses the same
89
+ * collision (`assertOneOwnerPerKey`) and it refused the OTHER duplicate: in
90
+ * process the last frond loaded won, here the first discovered did. One
91
+ * application, two topologies, two different handlers answering — which is
92
+ * precisely the gradient not holding.
93
+ *
94
+ * Refusing is the honest answer while a call names an entity and not a frond.
95
+ * `FrondCall` already carries an optional `frond`; the day it is required, this
96
+ * becomes a disambiguation instead of a refusal.
97
+ */
98
+ if (held !== undefined && held !== label) {
99
+ throw new FougereError({
100
+ code: ErrorCode.INTERNAL_ERROR,
101
+ message:
102
+ `Two remotes serve '${door.name}': '${held}' and '${label}'.\n`
103
+ + ` A call names an entity, not a frond, so nothing could choose between them.\n`
104
+ + ` - Keep one of the two out of \`remotes:\`, or\n`
105
+ + ` - expose one of them under a different entity name.`,
106
+ entity: door.name,
107
+ });
108
+ }
109
+ claimedBy.set(door.name, label);
110
+ byEntity.set(door.name, {
111
+ frond: frond.name,
112
+ transport,
113
+ ...(door.schema ? { schema: Card.fromDescriptor(door.schema as SchemaDescriptor).toSchema() } : {}),
114
+ });
115
+ }
116
+ }
117
+ }
118
+ };
119
+
120
+ return {
121
+ async route(entity) {
122
+ if (!byEntity.has(entity) && pending.size > 0) await discover();
123
+ const hit = byEntity.get(entity);
124
+ if (hit) return hit;
125
+ if (pending.size > 0) {
126
+ throw new FougereError({
127
+ code: ErrorCode.SERVICE_UNAVAILABLE,
128
+ message: `No reachable remote hosts '${entity}' — unreachable: ${[...pending.keys()].join(', ')}`,
129
+ entity,
130
+ });
131
+ }
132
+ throw new FougereError({
133
+ code: ErrorCode.NOT_FOUND,
134
+ message: `No declared remote hosts '${entity}'`,
135
+ entity,
136
+ });
137
+ },
138
+ };
139
+ }
140
+
141
+ type Facade = Record<string, (invocation?: InvocationContext) => Promise<unknown>>;
142
+
143
+ /**
144
+ * Façade-shaped stand-in — the consumer can't tell it from a local facade.
145
+ *
146
+ * Every trap answers the same question, and that is the point: the runner asks
147
+ * `Object.hasOwn(facade, op)` before calling, so a proxy that only trapped `get`
148
+ * reported *no* operations and every split call came back `Unknown operation` —
149
+ * `get` said yes, `hasOwn` said no, and the runner believed `hasOwn`.
150
+ *
151
+ * The stand-in claims every legal op name because it cannot know better: routing is
152
+ * lazy on purpose (the card is fetched at the first miss), and the remote is the
153
+ * authority on its own surface anyway. An op it does not serve comes back as its
154
+ * NOT_FOUND — judged where it is owned, which is the same answer a local façade gives.
155
+ *
156
+ * It runs the app's middlewares, like `HandlerFacade` does, because "the consumer can't
157
+ * tell it from a local facade" was false of the one thing a consumer installs: measured,
158
+ * `runMiddlewares` had a single caller, so a frond that moved lost every middleware on
159
+ * the calling side — no log, no retry, no cache — from a `remotes:` line alone.
160
+ * The invocation the transport sends is the one the chain leaves behind, so a middleware
161
+ * can still put something on the wire.
162
+ */
163
+ export function createRemoteFacade(
164
+ entity: string,
165
+ router: RemoteRouter,
166
+ middlewaresFor: (address: string) => AppMiddleware[],
167
+ ): Facade {
168
+ const opFn = (op: string) => async (invocation: InvocationContext = EMPTY_INVOCATION) => {
169
+ const { frond, transport } = await router.route(entity);
170
+ const call: FrondCall = { frond, entity, op };
171
+ const ctx: OperationContext = {
172
+ entity, frond, operation: op, args: [], state: invocation.state, invocation,
173
+ };
174
+ return runMiddlewares(middlewaresFor(entity), ctx, () =>
175
+ transport(call, ctx.invocation ?? invocation));
176
+ };
177
+
178
+ return new DynamicFacade(opFn).operations as Facade;
179
+ }