@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
@@ -1,34 +1,27 @@
1
1
  import { readdir, readFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join, dirname, basename, resolve as resolvePath } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
4
  import { ANONYMOUS_SCHEMA_NAME } from '@fougere/schema';
6
- import { cardinalityOf } from './operation.js';
7
- import { parseAllHandlerMethods, parsePresenterMethods, parseConstructorParams } from './handler-parser.js';
8
- import { hashFile, getCached, setCached, flushCache, setCacheRoot } from './scan-cache.js';
9
- import { loadFrondConfig } from './frond-config.js';
10
- import { emitKeyOf } from './emit.js';
11
- import { getPresenterTarget, getPresenterFields, getPresenterViews } from './presenter.js';
12
- import { getRepositoryTarget } from './repository.js';
13
- import { ormKeyOf } from './orm.js';
14
- import { getCollectorTarget } from './collector.js';
15
- const defaultLoader = async (filePath) => await import(pathToFileURL(filePath).href);
16
- let activeLoader = defaultLoader;
17
- /** Override the module loader used by the scanner (e.g. for jiti/tsx support). */
18
- export function setModuleLoader(loader) {
19
- activeLoader = loader;
20
- }
21
- /** Used by config-loader to load fougere.config.ts via the same TS-aware loader. */
22
- export function getModuleLoader() {
23
- return activeLoader;
24
- }
5
+ import { cardinalityOf } from '../wire/operation.js';
6
+ import { computeBindingPlan } from '../boot/binding.js';
7
+ import { parseAllHandlerMethods, parsePresenterMethods, parseConstructorParams, resetTypePrograms, seedTypeProgram, } from './handler-parser.js';
8
+ import { loadFrondConfig } from '../frond-config.js';
9
+ import { emitKeyOf } from '../emit.js';
10
+ import { getPresenterFields } from '../prefab/presenter.js';
11
+ import { ormKeyOf, togetherKeyOf } from '../orm.js';
12
+ import { targetOf, viewsOf, outputOf } from '../prefab/prefab.js';
13
+ import { ownedBy, repositoryKeyOf } from '../prefab/repository.js';
14
+ import { lowerFirst } from '@fougere/schema';
15
+ import { Fronds } from './Fronds.js';
16
+ import { getModuleLoader } from '../loader.js';
17
+ import { DEFAULT_CONVENTIONS, resolveConventions, frondPackage, providerDirsOf, frondDirsOf, } from './conventions.js';
25
18
  // FS
26
19
  /**
27
20
  * What this scan run could not do. Reset by {@link scanProject}, which owns a run.
28
21
  *
29
- * Module-scoped like the loader and the cache root above: the scanner already has
30
- * a notion of "the current run", and threading a collector through twenty call
31
- * sites would state the same thing more loudly and less clearly.
22
+ * Module-scoped like the loader and the cache root above: the scanner already has a
23
+ * notion of "the current run". Three sites record measured so a per-run object
24
+ * would carry twenty methods to spare one line of reset.
32
25
  */
33
26
  let diagnostics = [];
34
27
  function record(d) {
@@ -72,7 +65,7 @@ async function files(path) {
72
65
  }
73
66
  // Module
74
67
  async function loadModule(filePath) {
75
- return await activeLoader(filePath);
68
+ return await getModuleLoader()(filePath);
76
69
  }
77
70
  async function loadDefault(filePath) {
78
71
  const mod = await loadModule(filePath);
@@ -98,21 +91,6 @@ function findWorkspaceRoot(from) {
98
91
  }
99
92
  return resolvePath(from); // fallback: use project root itself
100
93
  }
101
- // Convention
102
- /** Directories whose classes register as providers — two spellings, one behaviour. */
103
- const PROVIDER_DIRS = ['services', 'repositories'];
104
- /**
105
- * The frond vocabulary — every directory {@link scanFrond} reads. A frond directory says
106
- * nothing else, which is why this list is what a reader needs to bound one: the Nuxt
107
- * module watches these for the root frond (watching the root itself would match every
108
- * write), and a flat app's tsconfig lists them instead of `["."]`, which would swallow
109
- * `app/` and `nuxt.config.ts`.
110
- */
111
- export const FROND_DIRS = [
112
- 'entities', 'handlers', 'presenters', 'collectors', 'seeds', ...PROVIDER_DIRS,
113
- ];
114
- export { toRegistrationName } from './contract.js';
115
- import { toRegistrationName } from './contract.js';
116
94
  /**
117
95
  * Strip the 'Handler' suffix → the name the handler answers to. 'PostHandler' → 'post'.
118
96
  *
@@ -122,7 +100,7 @@ import { toRegistrationName } from './contract.js';
122
100
  */
123
101
  function toAddress(className) {
124
102
  const base = className.endsWith('Handler') ? className.slice(0, -7) : className;
125
- return base[0].toLowerCase() + base.slice(1);
103
+ return lowerFirst(base);
126
104
  }
127
105
  // Scan
128
106
  /**
@@ -146,53 +124,73 @@ function depKeyOf(type) {
146
124
  // façade or a doublure, which is what makes the topology invisible from a signature.
147
125
  const facadeOf = type.name === 'Facade' ? type.generics?.[0]?.name : undefined;
148
126
  if (facadeOf)
149
- return toRegistrationName(facadeOf);
127
+ return lowerFirst(facadeOf);
150
128
  // `Emit<PostPublished>` — the third port, and the only one that names a SUBJECT rather
151
129
  // than an interlocutor. Read like the other two: the type names what arrives, here a
152
130
  // function that announces. Who receives it is not in the signature and never will be.
153
131
  const factOf = type.name === 'Emit' ? type.generics?.[0]?.name : undefined;
154
132
  if (factOf)
155
133
  return emitKeyOf(factOf);
134
+ // `Together<[Account, Ledger], [RateMirror]>` — the fifth reading, and the only one whose
135
+ // argument is a SET rather than one subject. The AST hands each tuple back as a single
136
+ // string, so they are split here, where every other question of "how was it written" lives.
137
+ const frame = type.name === 'Together' ? type.generics?.[0]?.name : undefined;
138
+ if (frame)
139
+ return togetherKeyOf(tupleMembers(frame), tupleMembers(type.generics?.[1]?.name ?? ''));
140
+ // `RepositoryOf<Post>` — what an author writes when no repository file exists. The dual
141
+ // of the line below: one names the port, the other the holder, and both resolve to a key
142
+ // rather than to a class the author would have to invent.
143
+ const held = type.name === 'RepositoryOf' ? type.generics?.[0]?.name : undefined;
144
+ if (held)
145
+ return repositoryKeyOf(held);
156
146
  const target = type.name === 'EntityOrm' ? type.generics?.[0]?.name : undefined;
157
147
  if (!target)
158
148
  return type.name;
159
149
  return ormKeyOf(target);
160
150
  }
161
- /** Cached parseConstructorParams — skips TS loading when cache is warm. */
162
- async function cachedCtorParams(filePath) {
163
- const hash = hashFile(filePath);
164
- const key = filePath + ':ctor';
165
- const cached = getCached(key, hash);
166
- if (cached)
167
- return cached;
168
- const params = await parseConstructorParams(filePath);
169
- setCached(key, hash, params);
170
- return params;
171
- }
172
- /** Cached parsePresenterMethods — skips TS loading when cache is warm. */
173
- async function cachedPresenterMethods(filePath) {
174
- const hash = hashFile(filePath);
175
- const key = filePath + ':presenter';
176
- const cached = getCached(key, hash);
177
- if (cached)
178
- return cached;
179
- const methods = await parsePresenterMethods(filePath);
180
- setCached(key, hash, methods);
181
- return methods;
151
+ /**
152
+ * `'[Account, Ledger]'` → `['Account', 'Ledger']`.
153
+ *
154
+ * The tuple was chosen over the variadic form the parser reads more cleanly, because the
155
+ * variadic one costs arities-with-defaults and a `never` filter on the TypeScript side
156
+ * while the tuple maps to `[EntityOrm<Account>, EntityOrm<Ledger>]` in one line. The
157
+ * parser does not get to decide alone; this split is what that choice costs.
158
+ */
159
+ function tupleMembers(raw) {
160
+ return raw.replace(/^\[|\]$/g, '').split(',').map((member) => member.trim()).filter(Boolean);
182
161
  }
162
+ /**
163
+ * These readings are semantic: an unchanged file can mean something different after an
164
+ * imported alias changes. The parser's TypeScript Program is the cache for one scan; a
165
+ * cache keyed only by this file's bytes would be unsound.
166
+ */
167
+ const ctorParamsOf = (filePath) => parseConstructorParams(filePath);
168
+ const presenterMethodsOf = (filePath) => parsePresenterMethods(filePath);
169
+ const handlerMethodsOf = (filePath, projectRoot) => parseAllHandlerMethods(filePath, projectRoot);
183
170
  async function toProvider(filePath) {
184
171
  const ctor = await loadClass(filePath);
185
- const params = await cachedCtorParams(filePath);
172
+ const params = await ctorParamsOf(filePath);
186
173
  const deps = params.map((p) => depKeyOf(p.type));
187
- // A repository inherits its constructor from `Repository(Entity)`, so the file
188
- // declares none and the scan reads no parameter. The mixin knows which entity it
189
- // was built for and says so at runtime — same escape as `Crud.__ops`, and the same
190
- // reason: what a prefab fabricates, only the prefab can describe.
191
- const target = getRepositoryTarget(ctor);
192
- if (target && deps.length === 0) {
193
- deps.push(`${toRegistrationName(target.name).replace(/^./, (c) => c.toUpperCase())}Orm`);
174
+ // A repository inherits its constructor from `Repository()`, so the file declares none
175
+ // and the scan reads no parameter. The mixin knows what it was built for and says so at
176
+ // runtime — same escape as `Crud.__ops`, and the same reason: what a prefab fabricates,
177
+ // only the prefab can describe.
178
+ //
179
+ // An AGGREGATE owns several, and its base takes them in the declared order. It is handed
180
+ // no frame: the boundary and the unit of work are two statements, and a frame is ASKED FOR
181
+ // like anywhere else — see `prefab/repository.ts`.
182
+ const owned = ownedBy(ctor);
183
+ const target = targetOf(ctor);
184
+ if (owned.length > 1 && deps.length === 0) {
185
+ deps.push(...owned.map((entity) => ormKeyOf(lowerFirst(entity.name))));
194
186
  }
195
- return { name: toRegistrationName(ctor.name), ctor, deps, filePath };
187
+ else if (target && deps.length === 0) {
188
+ deps.push(ormKeyOf(lowerFirst(target.name)));
189
+ }
190
+ // No `name` beside `ctor`: a provider registers under `ctor.name`, which is what
191
+ // `depKeyOf` returns since it reads the type as written. The camelCase field that
192
+ // used to sit here called itself the registration key and was one nowhere.
193
+ return { ctor, deps, filePath };
196
194
  }
197
195
  async function toEntityEntry(filePath) {
198
196
  const exported = await loadDefault(filePath);
@@ -206,7 +204,7 @@ async function toEntityEntry(filePath) {
206
204
  const declaredName = runtimeName && runtimeName !== ANONYMOUS_SCHEMA_NAME
207
205
  ? runtimeName
208
206
  : basename(filePath).replace(/\.[^.]+$/, '');
209
- const name = toRegistrationName(declaredName);
207
+ const name = lowerFirst(declaredName);
210
208
  return { name, entityClass: exported, filePath };
211
209
  }
212
210
  /**
@@ -214,8 +212,8 @@ async function toEntityEntry(filePath) {
214
212
  * Handles arrays, generics (uses base name), and simple references.
215
213
  */
216
214
  function resolveSchema(type, moduleExports) {
217
- // For arrays, resolve the element type
218
- const name = type.array ? type.name : type.name;
215
+ // An array's element type IS `type.name` — the arity rides beside it, so nothing has
216
+ // to be unwrapped here.
219
217
  // For generics like Pagination<Post>, also check inner types
220
218
  if (type.generics) {
221
219
  for (const g of type.generics) {
@@ -231,7 +229,7 @@ function resolveSchema(type, moduleExports) {
231
229
  }
232
230
  }
233
231
  }
234
- const resolved = moduleExports[name];
232
+ const resolved = moduleExports[type.name];
235
233
  if (resolved && typeof resolved === 'function' && 'getFields' in resolved) {
236
234
  return resolved;
237
235
  }
@@ -243,20 +241,11 @@ function resolveSchema(type, moduleExports) {
243
241
  * Resolves schemas for all params (not just the first) and stores
244
242
  * full signatures for the binding algorithm.
245
243
  */
246
- async function inferOperations(filePath, moduleExports, projectRoot) {
244
+ async function inferOperations(filePath, handlerName, moduleExports, collectorTypeNames, explicitInputs, declared, projectRoot) {
247
245
  const map = new Map();
248
246
  let parsed;
249
247
  try {
250
- const hash = hashFile(filePath);
251
- const cacheKey = filePath + ':all' + (projectRoot ? ':inherited' : '');
252
- const cached = getCached(cacheKey, hash);
253
- if (cached) {
254
- parsed = cached;
255
- }
256
- else {
257
- parsed = await parseAllHandlerMethods(filePath, projectRoot);
258
- setCached(cacheKey, hash, parsed);
259
- }
248
+ parsed = await handlerMethodsOf(filePath, projectRoot);
260
249
  }
261
250
  catch (cause) {
262
251
  // The handler still gets a façade — its methods exist at runtime — but with no
@@ -302,11 +291,34 @@ async function inferOperations(filePath, moduleExports, projectRoot) {
302
291
  signature: method,
303
292
  ...(method.description && { description: method.description }),
304
293
  };
305
- // Resolve schemas for all params
306
- for (const param of method.params) {
294
+ // A convention may omit a declaration only when it has one answer. Only values the
295
+ // caller supplies through the body are candidates: a schema-typed collector, fact or
296
+ // context parameter is not input merely because it names an entity. The old loop
297
+ // ignored provenance and assigned the first schema it met, so swapping two parameters
298
+ // silently changed the contract the façade used to judge the request body.
299
+ const binding = computeBindingPlan(method.params, collectorTypeNames);
300
+ const candidates = method.params.flatMap((param, index) => {
301
+ if (binding[index]?.source.kind !== 'body')
302
+ return [];
307
303
  const schema = resolveSchema(param.type, moduleExports);
308
- if (schema && !meta.input)
309
- meta.input = schema;
304
+ return schema ? [{ param, schema }] : [];
305
+ });
306
+ if (candidates.length === 1) {
307
+ meta.input = candidates[0].schema;
308
+ }
309
+ else if (candidates.length > 1
310
+ && declared[method.name]?.input === undefined
311
+ && !explicitInputs.has(method.name)) {
312
+ const subject = `${handlerName}.${method.name}`;
313
+ record({
314
+ severity: 'blocking',
315
+ code: 'input-contract-ambiguous',
316
+ filePath,
317
+ subject,
318
+ message: `Cannot infer the input contract for ${subject}: ${candidates.length} entity `
319
+ + `candidates — ${candidates.map(({ param }) => `${param.name}: ${param.type.raw}`).join('; ')}. `
320
+ + `Declare operations.${method.name}.input in frond.config.ts.`,
321
+ });
310
322
  }
311
323
  if (method.returnType) {
312
324
  meta.output = resolveSchema(method.returnType, moduleExports);
@@ -317,7 +329,7 @@ async function inferOperations(filePath, moduleExports, projectRoot) {
317
329
  }
318
330
  return map;
319
331
  }
320
- async function toHandlerEntry(filePath, entityByClassName, projectRoot, surface) {
332
+ async function toHandlerEntry(filePath, entityByClassName, collectorTypeNames, explicitInputs, projectRoot, surface) {
321
333
  const mod = await loadModule(filePath);
322
334
  const ctor = mod.default;
323
335
  if (typeof ctor !== 'function' || !ctor.prototype)
@@ -330,15 +342,20 @@ async function toHandlerEntry(filePath, entityByClassName, projectRoot, surface)
330
342
  augmented[className] = entityClass;
331
343
  }
332
344
  const address = toAddress(ctor.name);
333
- const operations = await inferOperations(filePath, augmented, projectRoot);
334
- const ctorParams = await cachedCtorParams(filePath);
345
+ const declaredOps = ctor.__ops ?? {};
346
+ const operations = await inferOperations(filePath, ctor.name, augmented, collectorTypeNames, explicitInputs, declaredOps, projectRoot);
347
+ const ctorParams = await ctorParamsOf(filePath);
335
348
  const deps = ctorParams.map((p) => depKeyOf(p.type));
336
349
  // Read output override from Crud(Entity, Output) — static __output property
337
- const __entity = ctor.__entity;
338
- const __output = ctor.__output;
339
- const outputOverride = __output && __entity && __output !== __entity ? __output : undefined;
350
+ // A handler-wide view, when it is not simply the entity — the two are compared by
351
+ // identity, which is why both slots are read through the same door.
352
+ const subject = targetOf(ctor);
353
+ const declared = outputOf(ctor);
354
+ const outputOverride = declared && subject && declared !== subject
355
+ ? declared
356
+ : undefined;
340
357
  return {
341
- name: toRegistrationName(ctor.name),
358
+ name: lowerFirst(ctor.name),
342
359
  address,
343
360
  ctor: ctor,
344
361
  operations,
@@ -351,7 +368,7 @@ async function toHandlerEntry(filePath, entityByClassName, projectRoot, surface)
351
368
  /** Strip '.seed' suffix → entity name. 'Author.seed.ts' → 'author'. */
352
369
  function toSeedEntityName(fileName) {
353
370
  const base = fileName.replace(/\.seed\.(ts|js)$/, '').replace(/\.(ts|js)$/, '');
354
- return base[0].toLowerCase() + base.slice(1);
371
+ return lowerFirst(base);
355
372
  }
356
373
  async function toSeedEntry(filePath) {
357
374
  const data = await loadDefault(filePath);
@@ -363,17 +380,17 @@ async function toSeedEntry(filePath) {
363
380
  }
364
381
  async function toPresenterEntry(filePath) {
365
382
  const ctor = await loadClass(filePath);
366
- const target = getPresenterTarget(ctor);
383
+ const target = targetOf(ctor);
367
384
  if (!target)
368
385
  return null;
369
- const entityName = toRegistrationName(target.name);
386
+ const entityName = lowerFirst(target.name);
370
387
  const fields = getPresenterFields(ctor);
371
- const presenterParams = await cachedCtorParams(filePath);
388
+ const presenterParams = await ctorParamsOf(filePath);
372
389
  const deps = presenterParams.map((p) => depKeyOf(p.type));
373
390
  // Parse method return types from source
374
391
  let fieldMeta = [];
375
392
  try {
376
- const parsed = await cachedPresenterMethods(filePath);
393
+ const parsed = await presenterMethodsOf(filePath);
377
394
  fieldMeta = parsed.map((m) => ({
378
395
  name: m.name,
379
396
  returnType: m.returnType?.name,
@@ -384,61 +401,83 @@ async function toPresenterEntry(filePath) {
384
401
  // Everything after the rows is bound like a handler's argument.
385
402
  params: m.params.slice(1),
386
403
  }));
404
+ // The rows parameter is stripped just above, and nothing used to check its SHAPE.
405
+ // A computed field receives the PAGE and answers one value per row, so a method
406
+ // written `items(order: Order)` compiles and dies at the first call with a 500
407
+ // (`expected 1 value(s) for 1 row(s), got 0`) — while the information sat here, at
408
+ // the scan. Every own method of a presenter IS a computed field
409
+ // (`getPresenterFields`), so there is no private helper to spare.
410
+ //
411
+ // An ERROR at boot, not a refusal: a `blocking` scan diagnostic is logged and the
412
+ // app still starts (`bootstrap.ts`, and the comment above that loop says why). Making
413
+ // it refuse means deciding that a declaration which cannot work is an unresolved
414
+ // CONTRACT — the one thing that does stop the boot — and that is a decision, not a
415
+ // severity.
416
+ for (const method of parsed) {
417
+ const rows = method.params[0];
418
+ if (rows && rows.type.array === true)
419
+ continue;
420
+ record({
421
+ severity: 'blocking',
422
+ code: 'presenter-field-not-page',
423
+ filePath,
424
+ subject: `${ctor.name ?? 'Presenter'}.${method.name}`,
425
+ message: `${method.name}(${rows ? `${rows.name}: ${rows.type.raw}` : ''}) is a computed `
426
+ + `field, so it receives the PAGE and must answer one value per row. Declare `
427
+ + `${method.name}(${rows?.name ?? 'rows'}: ${target.name ?? 'Entity'}[]) `
428
+ + `and return an array of the same length.`,
429
+ });
430
+ }
387
431
  }
388
432
  catch { /* parse failure — fall back to untyped */ }
389
433
  // Declared at runtime on the class, so it survives a scan that resolved nothing.
390
- return { entityName, ctor, fields, fieldMeta, views: getPresenterViews(ctor), deps, filePath };
434
+ return { entityName, ctor, fields, fieldMeta, views: viewsOf(ctor), deps, filePath };
391
435
  }
392
436
  async function toCollectorEntry(filePath) {
393
437
  const ctor = await loadClass(filePath);
394
- const target = getCollectorTarget(ctor);
438
+ const target = targetOf(ctor);
395
439
  if (!target)
396
440
  return null;
397
- const entityName = toRegistrationName(target.name);
398
- const collectorParams = await cachedCtorParams(filePath);
441
+ // The target's NAME and nothing else — a collector reads no fields, so the class it
442
+ // was built on needs no schema.
443
+ const typeName = lowerFirst(target.name);
444
+ const collectorParams = await ctorParamsOf(filePath);
399
445
  const deps = collectorParams.map((p) => depKeyOf(p.type));
400
- return { entityName, ctor, deps, filePath };
446
+ return { typeName, ctor, deps, filePath };
401
447
  }
402
- async function scanFrond(frondPath, name, source, projectRoot) {
403
- const providers = [];
404
- const entities = [];
405
- const handlers = [];
406
- const presenters = [];
407
- const collectors = [];
408
- const seeds = [];
409
- // Scan services/ and repositories/ both land in the same provider list.
410
- for (const dir of PROVIDER_DIRS) {
411
- const paths = await files(join(frondPath, dir));
412
- providers.push(...await Promise.all(paths.map((f) => toProvider(f))));
413
- }
414
- // Scan entities/
415
- const entityPaths = await files(join(frondPath, 'entities'));
416
- const entityEntries = await Promise.all(entityPaths.map((f) => toEntityEntry(f)));
417
- entities.push(...entityEntries.filter((e) => e !== null));
418
- // Build entity class map for schema resolution in handlers (T → entity class)
448
+ async function scanFrond(frondPath, name, source, conventions, projectRoot) {
449
+ const { entities: entitiesDir, handlers: handlersDir, presenters: presentersDir, collectors: collectorsDir, seeds: seedsDir, } = conventions.dirs;
450
+ /**
451
+ * A convention directory, read by whoever knows the shape it holds.
452
+ *
453
+ * The same three lines were written six times — list, read in parallel, drop what the
454
+ * reader refused — which is what made `handlers/` quietly special: it grew a second
455
+ * pass for its surfaces and nothing else could.
456
+ */
457
+ const collect = async (dir, read) => {
458
+ const entries = await Promise.all((await files(join(frondPath, dir))).map(read));
459
+ return entries.filter((entry) => entry !== null);
460
+ };
461
+ // services/ and repositories/ — two spellings, one provider list.
462
+ const providers = (await Promise.all(providerDirsOf(conventions).map((dir) => collect(dir, toProvider)))).flat();
463
+ const entities = await collect(entitiesDir, toEntityEntry);
464
+ const collectors = await collect(collectorsDir, toCollectorEntry);
465
+ const collectorTypeNames = new Set(collectors.map((collector) => collector.typeName));
466
+ const frondConfig = await loadFrondConfig(frondPath);
467
+ const explicitInputs = new Set(Object.entries(frondConfig?.operations ?? {})
468
+ .filter(([, contract]) => contract.input !== undefined)
469
+ .map(([operation]) => operation));
470
+ // Handlers resolve `T` against the entities, so those come first.
419
471
  const entityByClassName = new Map(entities.map((e) => [e.entityClass.name, e.entityClass]));
420
- // Scan handlers/ (root = default surface, subdirectories = named surfaces)
421
- const handlerPaths = await files(join(frondPath, 'handlers'));
422
- handlers.push(...await Promise.all(handlerPaths.map((f) => toHandlerEntry(f, entityByClassName, projectRoot))));
423
- const surfaceDirs = await dirs(join(frondPath, 'handlers'));
424
- for (const surfaceName of surfaceDirs) {
425
- const surfacePaths = await files(join(frondPath, 'handlers', surfaceName));
426
- handlers.push(...await Promise.all(surfacePaths.map((f) => toHandlerEntry(f, entityByClassName, projectRoot, surfaceName))));
472
+ const handlers = await collect(handlersDir, (f) => toHandlerEntry(f, entityByClassName, collectorTypeNames, explicitInputs, projectRoot));
473
+ // A subdirectory of handlers/ is a named surface — the one directory whose CHILDREN
474
+ // are part of the convention too.
475
+ for (const surface of await dirs(join(frondPath, handlersDir))) {
476
+ handlers.push(...await collect(join(handlersDir, surface), (f) => toHandlerEntry(f, entityByClassName, collectorTypeNames, explicitInputs, projectRoot, surface)));
427
477
  }
428
- // Scan presenters/
429
- const presenterPaths = await files(join(frondPath, 'presenters'));
430
- const presenterEntries = await Promise.all(presenterPaths.map((f) => toPresenterEntry(f)));
431
- presenters.push(...presenterEntries.filter((p) => p !== null));
432
- // Scan collectors/
433
- const collectorPaths = await files(join(frondPath, 'collectors'));
434
- const collectorEntries = await Promise.all(collectorPaths.map((f) => toCollectorEntry(f)));
435
- collectors.push(...collectorEntries.filter((c) => c !== null));
436
- // Scan seeds/
437
- const seedPaths = await files(join(frondPath, 'seeds'));
438
- const seedEntries = await Promise.all(seedPaths.map((f) => toSeedEntry(f)));
439
- seeds.push(...seedEntries.filter((s) => s !== null));
478
+ const presenters = await collect(presentersDir, toPresenterEntry);
479
+ const seeds = await collect(seedsDir, toSeedEntry);
440
480
  // Mark exposed entries: frond.config.ts takes precedence, then @expose decorator
441
- const frondConfig = await loadFrondConfig(frondPath);
442
481
  if (frondConfig?.expose) {
443
482
  const exposeSet = new Set(frondConfig.expose);
444
483
  for (const e of entities) {
@@ -490,7 +529,7 @@ async function scanFrond(frondPath, name, source, projectRoot) {
490
529
  * Carrying the convention is what makes a frond; the key never marked anything and
491
530
  * nothing read it. It earns its keep as the one thing the directory cannot say: that
492
531
  * `fronds/blog-v2/` serves the frond still called `blog` — so a rename on disk does not
493
- * rename the entity keys, the `@frond/*` import or a `remotes:` entry.
532
+ * rename the entity keys, the `@fronds/*` import or a `remotes:` entry.
494
533
  */
495
534
  async function frondNameOf(frondPath, dirName) {
496
535
  try {
@@ -513,20 +552,20 @@ async function frondNameOf(frondPath, dirName) {
513
552
  * are ordinary top-level names in projects that never heard of Fougere, and a domain
514
553
  * without a single entity is not a domain.
515
554
  */
516
- async function rootFrondOf(root, workspaceRoot) {
517
- if ((await files(join(root, 'entities'))).length === 0)
555
+ async function rootFrondOf(root, workspaceRoot, conventions) {
556
+ if ((await files(join(root, conventions.dirs.entities))).length === 0)
518
557
  return null;
519
558
  const name = await frondNameOf(root, basename(resolvePath(root)));
520
- return scanFrond(root, name, { path: root, package: `@frond/${name}` }, workspaceRoot);
559
+ return scanFrond(root, name, { path: root, package: frondPackage(name, conventions) }, conventions, workspaceRoot);
521
560
  }
522
561
  /**
523
- * `@frond/<name>` → the directory it names, for every frond of a project.
562
+ * `@fronds/<name>` → the directory it names, for every frond of a project.
524
563
  *
525
564
  * The framework states this convention — `FrondSource.package` has always spelled it, and
526
565
  * `fougere sync` writes it into a consumer's tsconfig — and until now its own reader could
527
566
  * not resolve it. The Nuxt module registered a Vite alias, so a `.vue` page could import
528
- * `@frond/blog/entities/Post`, while the SCAN loaded sources through a bare jiti: a frond
529
- * naming its neighbour got `Cannot find module '@frond/user/entities/User.js'`. So the one
567
+ * `@fronds/blog/entities/Post`, while the SCAN loaded sources through a bare jiti: a frond
568
+ * naming its neighbour got `Cannot find module '@fronds/user/entities/User.js'`. So the one
530
569
  * form that survives a split was the one form that did not run.
531
570
  *
532
571
  * Hand it to the module loader — `createJiti(url, { alias: await frondAliases(root) })` —
@@ -534,23 +573,23 @@ async function rootFrondOf(root, workspaceRoot) {
534
573
  * `package.json` read, no parsing: it is deliberately callable BEFORE the loader exists,
535
574
  * which is what makes the chicken-and-egg go away.
536
575
  */
537
- export async function frondAliases(root) {
538
- const frondsDir = join(root, 'fronds');
576
+ export async function frondAliases(root, conventions = DEFAULT_CONVENTIONS) {
577
+ const frondsDir = join(root, conventions.fronds);
539
578
  const aliases = {};
540
579
  // The root itself is a frond when it carries `entities/` — same rule as the scan.
541
- if ((await files(join(root, 'entities'))).length > 0) {
542
- aliases[`@frond/${await frondNameOf(root, basename(resolvePath(root)))}`] = resolvePath(root);
580
+ if ((await files(join(root, conventions.dirs.entities))).length > 0) {
581
+ aliases[frondPackage(await frondNameOf(root, basename(resolvePath(root))), conventions)] = resolvePath(root);
543
582
  }
544
583
  for (const dir of await dirs(frondsDir)) {
545
584
  const path = join(frondsDir, dir);
546
- aliases[`@frond/${await frondNameOf(path, dir)}`] = resolvePath(path);
585
+ aliases[frondPackage(await frondNameOf(path, dir), conventions)] = resolvePath(path);
547
586
  }
548
587
  /**
549
588
  * A SYNCED frond answers to the same name.
550
589
  *
551
590
  * `fougere sync` writes `.fougere/remotes/<name>/` and registers it in `remotes.json`,
552
- * which the Nuxt module already reads to alias `@frond/<name>`. Doing it here too is
553
- * what makes the convention mean ONE thing: a consumer writes `@frond/blog/entities/Post`
591
+ * which the Nuxt module already reads to alias `@fronds/<name>`. Doing it here too is
592
+ * what makes the convention mean ONE thing: a consumer writes `@fronds/blog/entities/Post`
554
593
  * and never learns whether that frond is on this disk or was fetched from a card.
555
594
  *
556
595
  * A local frond wins a name collision — its source is the truth, a synced copy is a
@@ -559,9 +598,9 @@ export async function frondAliases(root) {
559
598
  try {
560
599
  const registry = JSON.parse(await readFile(join(root, '.fougere', 'remotes.json'), 'utf8'));
561
600
  for (const [name, entry] of Object.entries(registry)) {
562
- if (typeof entry?.path !== 'string' || aliases[`@frond/${name}`])
601
+ if (typeof entry?.path !== 'string' || aliases[frondPackage(name, conventions)])
563
602
  continue;
564
- aliases[`@frond/${name}`] = resolvePath(entry.path);
603
+ aliases[frondPackage(name, conventions)] = resolvePath(entry.path);
565
604
  }
566
605
  }
567
606
  catch {
@@ -570,27 +609,33 @@ export async function frondAliases(root) {
570
609
  }
571
610
  return aliases;
572
611
  }
573
- export async function scanProject(root, filter) {
574
- setCacheRoot(root);
612
+ export async function scanProject(root, filter, conventionsInput) {
613
+ const conventions = resolveConventions(conventionsInput);
614
+ resetTypePrograms();
575
615
  // A run owns its findings: two scans in one process (a test suite, a watcher)
576
616
  // must not inherit each other's.
577
617
  diagnostics = [];
578
- const frondsDir = join(root, 'fronds');
618
+ const frondsDir = join(root, conventions.fronds);
579
619
  const dirNames = await dirs(frondsDir);
580
620
  // Resolve workspace root (parent of packages/) for package import resolution
581
621
  // For monorepo: root is the project dir (e.g. demos/nuxt-blog), workspace root is the repo root
582
622
  const workspaceRoot = findWorkspaceRoot(root);
623
+ // One program for this run. Seeded here because the parser rebuilds on every root it
624
+ // has not seen, and a frond lives outside its project's tsconfig `include`. Both keys
625
+ // are seeded: heritage reads under the workspace root, a constructor under none.
626
+ const declarations = (await Promise.all([root, ...dirNames.map((dir) => join(frondsDir, dir))].flatMap((frondPath) => frondDirsOf(conventions).map((dir) => files(join(frondPath, dir)))))).flat();
627
+ await seedTypeProgram(declarations, workspaceRoot);
628
+ await seedTypeProgram(declarations);
583
629
  const [rootFrond, under] = await Promise.all([
584
- rootFrondOf(root, workspaceRoot),
630
+ rootFrondOf(root, workspaceRoot, conventions),
585
631
  Promise.all(dirNames.map(async (dir) => {
586
632
  const name = await frondNameOf(join(frondsDir, dir), dir);
587
- return scanFrond(join(frondsDir, dir), name, { path: join(frondsDir, dir), package: `@frond/${name}` }, workspaceRoot);
633
+ return scanFrond(join(frondsDir, dir), name, { path: join(frondsDir, dir), package: frondPackage(name, conventions) }, conventions, workspaceRoot);
588
634
  })),
589
635
  ]);
590
636
  // The app's own domain first, then the ones it took in.
591
637
  const all = rootFrond ? [rootFrond, ...under] : under;
592
- const fronds = filter ? all.filter((f) => filter.includes(f.name)) : all;
593
- flushCache();
638
+ const fronds = Fronds.scanned(filter ? all.filter((f) => filter.includes(f.name)) : all);
594
639
  return { fronds, diagnostics };
595
640
  }
596
641
  //# sourceMappingURL=scanner.js.map