@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.
- package/README.md +3 -2
- package/dist/boot/AppLifecycle.d.ts +18 -0
- package/dist/boot/AppLifecycle.d.ts.map +1 -0
- package/dist/boot/AppLifecycle.js +42 -0
- package/dist/boot/AppLifecycle.js.map +1 -0
- package/dist/boot/Emissions.d.ts +126 -0
- package/dist/boot/Emissions.d.ts.map +1 -0
- package/dist/boot/Emissions.js +233 -0
- package/dist/boot/Emissions.js.map +1 -0
- package/dist/boot/HandlerFacade.d.ts +72 -0
- package/dist/boot/HandlerFacade.d.ts.map +1 -0
- package/dist/boot/HandlerFacade.js +174 -0
- package/dist/boot/HandlerFacade.js.map +1 -0
- package/dist/boot/Lifecycle.d.ts +4 -0
- package/dist/boot/Lifecycle.d.ts.map +1 -0
- package/dist/boot/Lifecycle.js +3 -0
- package/dist/boot/Lifecycle.js.map +1 -0
- package/dist/boot/ambient-port.d.ts +38 -0
- package/dist/boot/ambient-port.d.ts.map +1 -0
- package/dist/boot/ambient-port.js +2 -0
- package/dist/boot/ambient-port.js.map +1 -0
- package/dist/boot/ambient.als.d.ts +3 -0
- package/dist/boot/ambient.als.d.ts.map +1 -0
- package/dist/boot/ambient.als.js +34 -0
- package/dist/boot/ambient.als.js.map +1 -0
- package/dist/boot/ambient.queue.d.ts +10 -0
- package/dist/boot/ambient.queue.d.ts.map +1 -0
- package/dist/boot/ambient.queue.js +60 -0
- package/dist/boot/ambient.queue.js.map +1 -0
- package/dist/boot/apply.d.ts +22 -0
- package/dist/boot/apply.d.ts.map +1 -0
- package/dist/boot/apply.js +38 -0
- package/dist/boot/apply.js.map +1 -0
- package/dist/boot/auth.d.ts +53 -0
- package/dist/boot/auth.d.ts.map +1 -0
- package/dist/boot/auth.js +2 -0
- package/dist/boot/auth.js.map +1 -0
- package/dist/{binding.d.ts → boot/binding.d.ts} +14 -17
- package/dist/boot/binding.d.ts.map +1 -0
- package/dist/{binding.js → boot/binding.js} +19 -10
- package/dist/boot/binding.js.map +1 -0
- package/dist/{boot.d.ts → boot/boot.d.ts} +25 -5
- package/dist/boot/boot.d.ts.map +1 -0
- package/dist/boot/boot.js +70 -0
- package/dist/boot/boot.js.map +1 -0
- package/dist/boot/bootstrap.d.ts.map +1 -0
- package/dist/boot/bootstrap.js +722 -0
- package/dist/boot/bootstrap.js.map +1 -0
- package/dist/boot/frame.d.ts +44 -0
- package/dist/boot/frame.d.ts.map +1 -0
- package/dist/boot/frame.js +172 -0
- package/dist/boot/frame.js.map +1 -0
- package/dist/boot/ownership.d.ts +42 -0
- package/dist/boot/ownership.d.ts.map +1 -0
- package/dist/boot/ownership.js +127 -0
- package/dist/boot/ownership.js.map +1 -0
- package/dist/boot/ports.d.ts +17 -0
- package/dist/boot/ports.d.ts.map +1 -0
- package/dist/boot/ports.js +57 -0
- package/dist/boot/ports.js.map +1 -0
- package/dist/{remote.d.ts → boot/remote.d.ts} +11 -3
- package/dist/boot/remote.d.ts.map +1 -0
- package/dist/{remote.js → boot/remote.js} +26 -28
- package/dist/boot/remote.js.map +1 -0
- package/dist/{seed.d.ts → boot/seed.d.ts} +13 -1
- package/dist/boot/seed.d.ts.map +1 -0
- package/dist/{seed.js → boot/seed.js} +24 -3
- package/dist/boot/seed.js.map +1 -0
- package/dist/boot/together.d.ts +26 -0
- package/dist/boot/together.d.ts.map +1 -0
- package/dist/boot/together.js +163 -0
- package/dist/boot/together.js.map +1 -0
- package/dist/boot/types.d.ts +260 -0
- package/dist/boot/types.d.ts.map +1 -0
- package/dist/boot/types.js.map +1 -0
- package/dist/builtins/config.d.ts +9 -0
- package/dist/builtins/config.d.ts.map +1 -1
- package/dist/builtins/config.js +16 -1
- package/dist/builtins/config.js.map +1 -1
- package/dist/builtins/logger.d.ts +37 -4
- package/dist/builtins/logger.d.ts.map +1 -1
- package/dist/builtins/logger.js +66 -7
- package/dist/builtins/logger.js.map +1 -1
- package/dist/config-loader.d.ts +39 -35
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +15 -54
- package/dist/config-loader.js.map +1 -1
- package/dist/contract/Call.d.ts +10 -0
- package/dist/contract/Call.d.ts.map +1 -0
- package/dist/contract/Call.js +15 -0
- package/dist/contract/Call.js.map +1 -0
- package/dist/contract/Invocation.d.ts +27 -0
- package/dist/contract/Invocation.d.ts.map +1 -0
- package/dist/contract/Invocation.js +59 -0
- package/dist/contract/Invocation.js.map +1 -0
- package/dist/contract/RouteAddress.d.ts +17 -0
- package/dist/contract/RouteAddress.d.ts.map +1 -0
- package/dist/contract/RouteAddress.js +38 -0
- package/dist/contract/RouteAddress.js.map +1 -0
- package/dist/contract.d.ts +25 -19
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +25 -10
- package/dist/contract.js.map +1 -1
- package/dist/crypto/encoding.d.ts +25 -0
- package/dist/crypto/encoding.d.ts.map +1 -0
- package/dist/crypto/encoding.js +58 -0
- package/dist/crypto/encoding.js.map +1 -0
- package/dist/crypto/node.d.ts +4 -0
- package/dist/crypto/node.d.ts.map +1 -0
- package/dist/crypto/node.js +25 -0
- package/dist/crypto/node.js.map +1 -0
- package/dist/crypto/port.d.ts +42 -0
- package/dist/crypto/port.d.ts.map +1 -0
- package/dist/crypto/port.js +19 -0
- package/dist/crypto/port.js.map +1 -0
- package/dist/crypto/webcrypto.d.ts +4 -0
- package/dist/crypto/webcrypto.d.ts.map +1 -0
- package/dist/crypto/webcrypto.js +34 -0
- package/dist/crypto/webcrypto.js.map +1 -0
- package/dist/dispatch/ArgumentResolver.d.ts +10 -0
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -0
- package/dist/dispatch/ArgumentResolver.js +12 -0
- package/dist/dispatch/ArgumentResolver.js.map +1 -0
- package/dist/dispatch/ArrayResult.d.ts +3 -0
- package/dist/dispatch/ArrayResult.d.ts.map +1 -0
- package/dist/dispatch/ArrayResult.js +11 -0
- package/dist/dispatch/ArrayResult.js.map +1 -0
- package/dist/dispatch/DispatchEvent.d.ts +18 -0
- package/dist/dispatch/DispatchEvent.d.ts.map +1 -0
- package/dist/dispatch/DispatchEvent.js +30 -0
- package/dist/dispatch/DispatchEvent.js.map +1 -0
- package/dist/dispatch/DispatchLifecycle.d.ts +9 -0
- package/dist/dispatch/DispatchLifecycle.d.ts.map +1 -0
- package/dist/dispatch/DispatchLifecycle.js +25 -0
- package/dist/dispatch/DispatchLifecycle.js.map +1 -0
- package/dist/dispatch/DispatchPort.d.ts +6 -0
- package/dist/dispatch/DispatchPort.d.ts.map +1 -0
- package/dist/dispatch/DispatchPort.js +2 -0
- package/dist/dispatch/DispatchPort.js.map +1 -0
- package/dist/dispatch/Dispatcher.d.ts +16 -0
- package/dist/dispatch/Dispatcher.d.ts.map +1 -0
- package/dist/dispatch/Dispatcher.js +44 -0
- package/dist/dispatch/Dispatcher.js.map +1 -0
- package/dist/dispatch/InFlight.d.ts +12 -0
- package/dist/dispatch/InFlight.d.ts.map +1 -0
- package/dist/dispatch/InFlight.js +46 -0
- package/dist/dispatch/InFlight.js.map +1 -0
- package/dist/dispatch/InputValidator.d.ts +7 -0
- package/dist/dispatch/InputValidator.d.ts.map +1 -0
- package/dist/dispatch/InputValidator.js +22 -0
- package/dist/dispatch/InputValidator.js.map +1 -0
- package/dist/dispatch/LocalRoutePolicy.d.ts +11 -0
- package/dist/dispatch/LocalRoutePolicy.d.ts.map +1 -0
- package/dist/dispatch/LocalRoutePolicy.js +32 -0
- package/dist/dispatch/LocalRoutePolicy.js.map +1 -0
- package/dist/dispatch/OperationExecutor.d.ts +25 -0
- package/dist/dispatch/OperationExecutor.d.ts.map +1 -0
- package/dist/dispatch/OperationExecutor.js +32 -0
- package/dist/dispatch/OperationExecutor.js.map +1 -0
- package/dist/dispatch/OperationRoute.d.ts +13 -0
- package/dist/dispatch/OperationRoute.d.ts.map +1 -0
- package/dist/dispatch/OperationRoute.js +15 -0
- package/dist/dispatch/OperationRoute.js.map +1 -0
- package/dist/dispatch/OutputProjector.d.ts +12 -0
- package/dist/dispatch/OutputProjector.d.ts.map +1 -0
- package/dist/dispatch/OutputProjector.js +30 -0
- package/dist/dispatch/OutputProjector.js.map +1 -0
- package/dist/dispatch/OutputView.d.ts +8 -0
- package/dist/dispatch/OutputView.d.ts.map +1 -0
- package/dist/dispatch/OutputView.js +10 -0
- package/dist/dispatch/OutputView.js.map +1 -0
- package/dist/dispatch/PresenterArgumentResolver.d.ts +12 -0
- package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -0
- package/dist/dispatch/PresenterArgumentResolver.js +20 -0
- package/dist/dispatch/PresenterArgumentResolver.js.map +1 -0
- package/dist/dispatch/PresenterExecutor.d.ts +11 -0
- package/dist/dispatch/PresenterExecutor.d.ts.map +1 -0
- package/dist/dispatch/PresenterExecutor.js +56 -0
- package/dist/dispatch/PresenterExecutor.js.map +1 -0
- package/dist/dispatch/RemoteRouteResolver.d.ts +13 -0
- package/dist/dispatch/RemoteRouteResolver.d.ts.map +1 -0
- package/dist/dispatch/RemoteRouteResolver.js +18 -0
- package/dist/dispatch/RemoteRouteResolver.js.map +1 -0
- package/dist/dispatch/Route.d.ts +10 -0
- package/dist/dispatch/Route.d.ts.map +1 -0
- package/dist/dispatch/Route.js +2 -0
- package/dist/dispatch/Route.js.map +1 -0
- package/dist/dispatch/RouteNotFoundError.d.ts +10 -0
- package/dist/dispatch/RouteNotFoundError.d.ts.map +1 -0
- package/dist/dispatch/RouteNotFoundError.js +24 -0
- package/dist/dispatch/RouteNotFoundError.js.map +1 -0
- package/dist/dispatch/RoutePolicy.d.ts +8 -0
- package/dist/dispatch/RoutePolicy.d.ts.map +1 -0
- package/dist/dispatch/RoutePolicy.js +2 -0
- package/dist/dispatch/RoutePolicy.js.map +1 -0
- package/dist/dispatch/RouteRegistry.d.ts +18 -0
- package/dist/dispatch/RouteRegistry.d.ts.map +1 -0
- package/dist/dispatch/RouteRegistry.js +62 -0
- package/dist/dispatch/RouteRegistry.js.map +1 -0
- package/dist/dispatch/RouteResolver.d.ts +7 -0
- package/dist/dispatch/RouteResolver.d.ts.map +1 -0
- package/dist/dispatch/RouteResolver.js +2 -0
- package/dist/dispatch/RouteResolver.js.map +1 -0
- package/dist/dispatch/StorageGuard.d.ts +15 -0
- package/dist/dispatch/StorageGuard.d.ts.map +1 -0
- package/dist/dispatch/StorageGuard.js +63 -0
- package/dist/dispatch/StorageGuard.js.map +1 -0
- package/dist/effective-operation.d.ts +90 -0
- package/dist/effective-operation.d.ts.map +1 -0
- package/dist/effective-operation.js +449 -0
- package/dist/effective-operation.js.map +1 -0
- package/dist/emit.d.ts +2 -2
- package/dist/emit.d.ts.map +1 -1
- package/dist/emit.js +2 -2
- package/dist/emit.js.map +1 -1
- package/dist/entry/DynamicFacade.d.ts +10 -0
- package/dist/entry/DynamicFacade.d.ts.map +1 -0
- package/dist/entry/DynamicFacade.js +27 -0
- package/dist/entry/DynamicFacade.js.map +1 -0
- package/dist/entry/FacadeEntry.d.ts +11 -0
- package/dist/entry/FacadeEntry.d.ts.map +1 -0
- package/dist/entry/FacadeEntry.js +26 -0
- package/dist/entry/FacadeEntry.js.map +1 -0
- package/dist/entry/TransportEntry.d.ts +13 -0
- package/dist/entry/TransportEntry.d.ts.map +1 -0
- package/dist/entry/TransportEntry.js +24 -0
- package/dist/entry/TransportEntry.js.map +1 -0
- package/dist/frond-config.d.ts +24 -7
- package/dist/frond-config.d.ts.map +1 -1
- package/dist/frond-config.js.map +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +3 -2
- package/dist/graph.js.map +1 -1
- package/dist/identity-keys.d.ts +14 -0
- package/dist/identity-keys.d.ts.map +1 -0
- package/dist/identity-keys.js +37 -0
- package/dist/identity-keys.js.map +1 -0
- package/dist/identity.d.ts +58 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +182 -0
- package/dist/identity.js.map +1 -0
- package/dist/imports.d.ts +2 -2
- package/dist/imports.d.ts.map +1 -1
- package/dist/imports.js +7 -3
- package/dist/imports.js.map +1 -1
- package/dist/index.d.ts +57 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -32
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +16 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +25 -0
- package/dist/loader.js.map +1 -0
- package/dist/node.d.ts +21 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +24 -0
- package/dist/node.js.map +1 -0
- package/dist/orm.d.ts +69 -8
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +35 -2
- package/dist/orm.js.map +1 -1
- package/dist/prefab/collector.d.ts +29 -0
- package/dist/prefab/collector.d.ts.map +1 -0
- package/dist/prefab/collector.js +34 -0
- package/dist/prefab/collector.js.map +1 -0
- package/dist/{crud.d.ts → prefab/crud.d.ts} +7 -8
- package/dist/prefab/crud.d.ts.map +1 -0
- package/dist/{crud.js → prefab/crud.js} +39 -5
- package/dist/prefab/crud.js.map +1 -0
- package/dist/prefab/mirror.d.ts +52 -0
- package/dist/prefab/mirror.d.ts.map +1 -0
- package/dist/prefab/mirror.js +66 -0
- package/dist/prefab/mirror.js.map +1 -0
- package/dist/prefab/prefab.d.ts +15 -0
- package/dist/prefab/prefab.d.ts.map +1 -0
- package/dist/prefab/prefab.js +19 -0
- package/dist/prefab/prefab.js.map +1 -0
- package/dist/{presenter.d.ts → prefab/presenter.d.ts} +6 -17
- package/dist/prefab/presenter.d.ts.map +1 -0
- package/dist/{presenter.js → prefab/presenter.js} +5 -23
- package/dist/prefab/presenter.js.map +1 -0
- package/dist/prefab/repository.d.ts +104 -0
- package/dist/prefab/repository.d.ts.map +1 -0
- package/dist/prefab/repository.js +62 -0
- package/dist/prefab/repository.js.map +1 -0
- package/dist/scan/Fronds.d.ts +45 -0
- package/dist/scan/Fronds.d.ts.map +1 -0
- package/dist/scan/Fronds.js +66 -0
- package/dist/scan/Fronds.js.map +1 -0
- package/dist/scan/conventions.d.ts +74 -0
- package/dist/scan/conventions.d.ts.map +1 -0
- package/dist/scan/conventions.js +82 -0
- package/dist/scan/conventions.js.map +1 -0
- package/dist/scan/emit.d.ts +14 -0
- package/dist/scan/emit.d.ts.map +1 -0
- package/dist/scan/emit.js +212 -0
- package/dist/scan/emit.js.map +1 -0
- package/dist/scan/frond.d.ts +224 -0
- package/dist/scan/frond.d.ts.map +1 -0
- package/dist/scan/frond.js +2 -0
- package/dist/scan/frond.js.map +1 -0
- package/dist/{handler-parser.d.ts → scan/handler-parser.d.ts} +20 -9
- package/dist/scan/handler-parser.d.ts.map +1 -0
- package/dist/scan/handler-parser.js +665 -0
- package/dist/scan/handler-parser.js.map +1 -0
- package/dist/scan/scan-cache.d.ts +16 -0
- package/dist/scan/scan-cache.d.ts.map +1 -0
- package/dist/{scan-cache.js → scan/scan-cache.js} +27 -20
- package/dist/scan/scan-cache.js.map +1 -0
- package/dist/scan/scanner.d.ts +20 -0
- package/dist/scan/scanner.d.ts.map +1 -0
- package/dist/{scanner.js → scan/scanner.js} +212 -167
- package/dist/scan/scanner.js.map +1 -0
- package/dist/verify.d.ts +16 -3
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +21 -18
- package/dist/verify.js.map +1 -1
- package/dist/{call.d.ts → wire/call.d.ts} +76 -7
- package/dist/wire/call.d.ts.map +1 -0
- package/dist/{call.js → wire/call.js} +53 -80
- package/dist/wire/call.js.map +1 -0
- package/dist/{middleware.d.ts → wire/errors.d.ts} +18 -19
- package/dist/wire/errors.d.ts.map +1 -0
- package/dist/{middleware.js → wire/errors.js} +26 -14
- package/dist/wire/errors.js.map +1 -0
- package/dist/{errors.d.ts → wire/http-error.d.ts} +5 -6
- package/dist/wire/http-error.d.ts.map +1 -0
- package/dist/{errors.js → wire/http-error.js} +7 -6
- package/dist/wire/http-error.js.map +1 -0
- package/dist/wire/invocation.d.ts +4 -0
- package/dist/wire/invocation.d.ts.map +1 -0
- package/dist/wire/invocation.js +3 -0
- package/dist/wire/invocation.js.map +1 -0
- package/dist/{middleware/logger.d.ts → wire/loggerMiddleware.d.ts} +3 -3
- package/dist/wire/loggerMiddleware.d.ts.map +1 -0
- package/dist/{middleware/logger.js → wire/loggerMiddleware.js} +2 -2
- package/dist/wire/loggerMiddleware.js.map +1 -0
- package/dist/wire/middleware.d.ts +29 -0
- package/dist/wire/middleware.d.ts.map +1 -0
- package/dist/wire/middleware.js +20 -0
- package/dist/wire/middleware.js.map +1 -0
- package/dist/{operation.d.ts → wire/operation.d.ts} +39 -10
- package/dist/wire/operation.d.ts.map +1 -0
- package/dist/wire/operation.js +167 -0
- package/dist/wire/operation.js.map +1 -0
- package/package.json +27 -8
- package/src/boot/AppLifecycle.ts +50 -0
- package/src/boot/Emissions.ts +263 -0
- package/src/boot/HandlerFacade.ts +242 -0
- package/src/boot/Lifecycle.ts +3 -0
- package/src/boot/ambient-port.ts +41 -0
- package/src/boot/ambient.als.ts +43 -0
- package/src/boot/ambient.queue.ts +75 -0
- package/src/boot/apply.ts +47 -0
- package/src/boot/auth.ts +55 -0
- package/src/boot/binding.ts +182 -0
- package/src/boot/boot.ts +125 -0
- package/src/boot/bootstrap.ts +839 -0
- package/src/boot/frame.ts +210 -0
- package/src/boot/ownership.ts +140 -0
- package/src/boot/ports.ts +66 -0
- package/src/boot/remote.ts +179 -0
- package/src/boot/seed.ts +158 -0
- package/src/boot/together.ts +215 -0
- package/src/boot/types.ts +261 -0
- package/src/builtins/config.ts +19 -0
- package/src/builtins/logger.ts +175 -0
- package/src/config-loader.ts +146 -0
- package/src/contract/Call.ts +18 -0
- package/src/contract/Invocation.ts +72 -0
- package/src/contract/RouteAddress.ts +49 -0
- package/src/contract.ts +74 -0
- package/src/crypto/encoding.ts +59 -0
- package/src/crypto/node.ts +30 -0
- package/src/crypto/port.ts +45 -0
- package/src/crypto/webcrypto.ts +55 -0
- package/src/define.ts +10 -0
- package/src/dispatch/ArgumentResolver.ts +17 -0
- package/src/dispatch/ArrayResult.ts +10 -0
- package/src/dispatch/DispatchEvent.ts +38 -0
- package/src/dispatch/DispatchLifecycle.ts +23 -0
- package/src/dispatch/DispatchPort.ts +6 -0
- package/src/dispatch/Dispatcher.ts +48 -0
- package/src/dispatch/InFlight.ts +49 -0
- package/src/dispatch/InputValidator.ts +28 -0
- package/src/dispatch/LocalRoutePolicy.ts +34 -0
- package/src/dispatch/OperationExecutor.ts +60 -0
- package/src/dispatch/OperationRoute.ts +18 -0
- package/src/dispatch/OutputProjector.ts +32 -0
- package/src/dispatch/OutputView.ts +9 -0
- package/src/dispatch/PresenterArgumentResolver.ts +25 -0
- package/src/dispatch/PresenterExecutor.ts +55 -0
- package/src/dispatch/RemoteRouteResolver.ts +21 -0
- package/src/dispatch/Route.ts +11 -0
- package/src/dispatch/RouteNotFoundError.ts +29 -0
- package/src/dispatch/RoutePolicy.ts +8 -0
- package/src/dispatch/RouteRegistry.ts +73 -0
- package/src/dispatch/RouteResolver.ts +7 -0
- package/src/dispatch/StorageGuard.ts +71 -0
- package/src/effective-operation.ts +597 -0
- package/src/emit.ts +90 -0
- package/src/entry/DynamicFacade.ts +28 -0
- package/src/entry/FacadeEntry.ts +32 -0
- package/src/entry/TransportEntry.ts +30 -0
- package/src/frond-config.ts +138 -0
- package/src/graph.ts +180 -0
- package/src/identity-keys.ts +43 -0
- package/src/identity.ts +235 -0
- package/src/imports.ts +123 -0
- package/src/index.ts +106 -0
- package/src/loader.ts +41 -0
- package/src/node.ts +28 -0
- package/src/orm.ts +252 -0
- package/src/prefab/collector.ts +36 -0
- package/src/prefab/crud.ts +188 -0
- package/src/prefab/mirror.ts +122 -0
- package/src/prefab/prefab.ts +46 -0
- package/src/prefab/presenter.ts +63 -0
- package/src/prefab/repository.ts +170 -0
- package/src/scan/Fronds.ts +73 -0
- package/src/scan/conventions.ts +122 -0
- package/src/scan/emit.ts +231 -0
- package/src/scan/frond.ts +235 -0
- package/src/scan/handler-parser.ts +818 -0
- package/src/scan/scan-cache.ts +115 -0
- package/src/scan/scanner.ts +758 -0
- package/src/verify.ts +193 -0
- package/src/wire/call.ts +369 -0
- package/src/wire/errors.ts +124 -0
- package/src/wire/http-error.ts +77 -0
- package/src/wire/invocation.ts +10 -0
- package/src/wire/loggerMiddleware.ts +29 -0
- package/src/wire/middleware.ts +47 -0
- package/src/wire/operation.ts +258 -0
- package/dist/binding.d.ts.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/boot.d.ts.map +0 -1
- package/dist/boot.js +0 -53
- package/dist/boot.js.map +0 -1
- package/dist/bootstrap.d.ts.map +0 -1
- package/dist/bootstrap.js +0 -879
- package/dist/bootstrap.js.map +0 -1
- package/dist/call.d.ts.map +0 -1
- package/dist/call.js.map +0 -1
- package/dist/collector.d.ts +0 -28
- package/dist/collector.d.ts.map +0 -1
- package/dist/collector.js +0 -32
- package/dist/collector.js.map +0 -1
- package/dist/crud.d.ts.map +0 -1
- package/dist/crud.js.map +0 -1
- package/dist/egress.d.ts +0 -52
- package/dist/egress.d.ts.map +0 -1
- package/dist/egress.js +0 -186
- package/dist/egress.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/handler-parser.d.ts.map +0 -1
- package/dist/handler-parser.js +0 -505
- package/dist/handler-parser.js.map +0 -1
- package/dist/invocation.d.ts +0 -20
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js +0 -15
- package/dist/invocation.js.map +0 -1
- package/dist/middleware/error-handler.d.ts +0 -15
- package/dist/middleware/error-handler.d.ts.map +0 -1
- package/dist/middleware/error-handler.js +0 -29
- package/dist/middleware/error-handler.js.map +0 -1
- package/dist/middleware/logger.d.ts.map +0 -1
- package/dist/middleware/logger.js.map +0 -1
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js.map +0 -1
- package/dist/mirror.d.ts +0 -93
- package/dist/mirror.d.ts.map +0 -1
- package/dist/mirror.js +0 -96
- package/dist/mirror.js.map +0 -1
- package/dist/operation.d.ts.map +0 -1
- package/dist/operation.js +0 -95
- package/dist/operation.js.map +0 -1
- package/dist/presenter.d.ts.map +0 -1
- package/dist/presenter.js.map +0 -1
- package/dist/remote.d.ts.map +0 -1
- package/dist/remote.js.map +0 -1
- package/dist/repository.d.ts +0 -55
- package/dist/repository.d.ts.map +0 -1
- package/dist/repository.js +0 -26
- package/dist/repository.js.map +0 -1
- package/dist/scan-cache.d.ts +0 -10
- package/dist/scan-cache.d.ts.map +0 -1
- package/dist/scan-cache.js.map +0 -1
- package/dist/scanner.d.ts +0 -34
- package/dist/scanner.d.ts.map +0 -1
- package/dist/scanner.js.map +0 -1
- package/dist/seed.d.ts.map +0 -1
- package/dist/seed.js.map +0 -1
- package/dist/types.d.ts +0 -421
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dist/{bootstrap.d.ts → boot/bootstrap.d.ts} +0 -0
- /package/dist/{types.js → boot/types.js} +0 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, type Dirent } from 'node:fs';
|
|
3
|
+
import { join, dirname, basename, resolve as resolvePath } from 'node:path';
|
|
4
|
+
import type { FrondDescriptor, ProviderEntry, EntityEntry, HandlerEntry, PresenterEntry, CollectorEntry, SeedEntry, ScanResult, ScanDiagnostic } from './frond.js';
|
|
5
|
+
import { ANONYMOUS_SCHEMA_NAME, type SchemaView } from '@fougere/schema';
|
|
6
|
+
import type { OperationContract, OperationsMap } from '../wire/operation.js';
|
|
7
|
+
import { cardinalityOf } from '../wire/operation.js';
|
|
8
|
+
import { computeBindingPlan } from '../boot/binding.js';
|
|
9
|
+
import {
|
|
10
|
+
parseAllHandlerMethods,
|
|
11
|
+
parsePresenterMethods,
|
|
12
|
+
parseConstructorParams,
|
|
13
|
+
resetTypePrograms,
|
|
14
|
+
seedTypeProgram,
|
|
15
|
+
type ParsedType,
|
|
16
|
+
} from './handler-parser.js';
|
|
17
|
+
import { loadFrondConfig } from '../frond-config.js';
|
|
18
|
+
import { emitKeyOf } from '../emit.js';
|
|
19
|
+
import { getPresenterFields } from '../prefab/presenter.js';
|
|
20
|
+
import { ormKeyOf, togetherKeyOf } from '../orm.js';
|
|
21
|
+
import { targetOf, viewsOf, outputOf } from '../prefab/prefab.js';
|
|
22
|
+
import { ownedBy, repositoryKeyOf } from '../prefab/repository.js';
|
|
23
|
+
import { lowerFirst } from '@fougere/schema';
|
|
24
|
+
import { Fronds } from './Fronds.js';
|
|
25
|
+
import { getModuleLoader } from '../loader.js';
|
|
26
|
+
import {
|
|
27
|
+
type Conventions, type ConventionsInput,
|
|
28
|
+
DEFAULT_CONVENTIONS, resolveConventions, frondPackage, providerDirsOf, frondDirsOf,
|
|
29
|
+
} from './conventions.js';
|
|
30
|
+
|
|
31
|
+
// FS
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What this scan run could not do. Reset by {@link scanProject}, which owns a run.
|
|
35
|
+
*
|
|
36
|
+
* Module-scoped like the loader and the cache root above: the scanner already has a
|
|
37
|
+
* notion of "the current run". Three sites record — measured — so a per-run object
|
|
38
|
+
* would carry twenty methods to spare one line of reset.
|
|
39
|
+
*/
|
|
40
|
+
let diagnostics: ScanDiagnostic[] = [];
|
|
41
|
+
|
|
42
|
+
function record(d: ScanDiagnostic): void {
|
|
43
|
+
diagnostics.push(d);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An absent convention directory is the ordinary case — a frond without
|
|
48
|
+
* `presenters/` is not a defect. Anything else (permissions, an I/O error, a path
|
|
49
|
+
* that is not a directory) means the scan did not look, and answering `[]` says
|
|
50
|
+
* it did. One `catch` used to conflate the two.
|
|
51
|
+
*/
|
|
52
|
+
async function readEntries(path: string): Promise<Dirent[]> {
|
|
53
|
+
try {
|
|
54
|
+
return await readdir(path, { withFileTypes: true });
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
if ((cause as NodeJS.ErrnoException)?.code === 'ENOENT') return [];
|
|
57
|
+
record({
|
|
58
|
+
severity: 'blocking',
|
|
59
|
+
code: 'directory-unreadable',
|
|
60
|
+
filePath: path,
|
|
61
|
+
// The path is NOT repeated here: `filePath` carries it, and a renderer that
|
|
62
|
+
// prints both wraps an absolute path twice into an unreadable box.
|
|
63
|
+
message: 'Could not read this directory — anything it declares is missing from '
|
|
64
|
+
+ 'the app, and nothing downstream can tell that from an empty directory.',
|
|
65
|
+
cause,
|
|
66
|
+
});
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function dirs(path: string): Promise<string[]> {
|
|
72
|
+
const entries = await readEntries(path);
|
|
73
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function files(path: string): Promise<string[]> {
|
|
77
|
+
const entries = await readEntries(path);
|
|
78
|
+
return entries
|
|
79
|
+
.filter((e) => e.isFile() && (e.name.endsWith('.ts') || e.name.endsWith('.js')))
|
|
80
|
+
.map((e) => join(path, e.name));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Module
|
|
84
|
+
|
|
85
|
+
async function loadModule(filePath: string): Promise<Record<string, unknown>> {
|
|
86
|
+
return await getModuleLoader()(filePath);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function loadDefault(filePath: string): Promise<unknown> {
|
|
90
|
+
const mod = await loadModule(filePath);
|
|
91
|
+
return mod.default;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function loadClass(filePath: string): Promise<ProviderEntry['ctor']> {
|
|
95
|
+
const ctor = await loadDefault(filePath);
|
|
96
|
+
if (typeof ctor !== 'function' || !ctor.prototype)
|
|
97
|
+
throw new Error(`${filePath}: default export is not a class`);
|
|
98
|
+
return ctor as ProviderEntry['ctor'];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isEntityClass(value: unknown): value is SchemaView {
|
|
102
|
+
return typeof value === 'function' && 'getFields' in (value as any);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Workspace
|
|
106
|
+
|
|
107
|
+
/** Walk up from project root to find the monorepo root (pnpm-workspace.yaml + packages/). */
|
|
108
|
+
function findWorkspaceRoot(from: string): string {
|
|
109
|
+
let dir = resolvePath(from);
|
|
110
|
+
while (dir !== dirname(dir)) {
|
|
111
|
+
if (existsSync(join(dir, 'pnpm-workspace.yaml')) && existsSync(join(dir, 'packages'))) return dir;
|
|
112
|
+
dir = dirname(dir);
|
|
113
|
+
}
|
|
114
|
+
return resolvePath(from); // fallback: use project root itself
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Strip the 'Handler' suffix → the name the handler answers to. 'PostHandler' → 'post'.
|
|
119
|
+
*
|
|
120
|
+
* Nothing here checks that an entity carries the result, and nothing should: a handler
|
|
121
|
+
* about no stored row is ordinary. The old name of this function — `toEntityName` — is
|
|
122
|
+
* what let "one façade per entity" be repeated until it read as a rule.
|
|
123
|
+
*/
|
|
124
|
+
function toAddress(className: string): string {
|
|
125
|
+
const base = className.endsWith('Handler') ? className.slice(0, -7) : className;
|
|
126
|
+
return lowerFirst(base);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Scan
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The container key a constructor parameter asks for — derived from its TYPE, not from
|
|
133
|
+
* how the type was spelled.
|
|
134
|
+
*
|
|
135
|
+
* `deps` used to be `p.type.name`, so the key WAS the alias's name: `type ListOrm =
|
|
136
|
+
* EntityOrm<List>` resolved only because someone had spelled it exactly like the
|
|
137
|
+
* registration key (`ListOrm`), while `type ListRepo = EntityOrm<List>` — the same type —
|
|
138
|
+
* typechecked and died at boot on `'ListRepo' is not registered`. And `EntityOrm<List>`
|
|
139
|
+
* written out in full asked for `'EntityOrm'`, which nothing registers.
|
|
140
|
+
*
|
|
141
|
+
* `EntityOrm<X>` names X's storage, so that is the key. The generic argument was already
|
|
142
|
+
* parsed (`ParsedType.generics`) and thrown away. Anything else keeps its own name: a
|
|
143
|
+
* plain service IS designated by its class name.
|
|
144
|
+
*/
|
|
145
|
+
function depKeyOf(type: ParsedType): string {
|
|
146
|
+
// `Facade<PostHandler>` — the second port, read exactly like the first. The type names
|
|
147
|
+
// what arrives: not the handler (its methods take positional arguments and it is never
|
|
148
|
+
// injected), but the door built in front of it. Same key whether that door is the local
|
|
149
|
+
// façade or a doublure, which is what makes the topology invisible from a signature.
|
|
150
|
+
const facadeOf = type.name === 'Facade' ? type.generics?.[0]?.name : undefined;
|
|
151
|
+
if (facadeOf) return lowerFirst(facadeOf);
|
|
152
|
+
|
|
153
|
+
// `Emit<PostPublished>` — the third port, and the only one that names a SUBJECT rather
|
|
154
|
+
// than an interlocutor. Read like the other two: the type names what arrives, here a
|
|
155
|
+
// function that announces. Who receives it is not in the signature and never will be.
|
|
156
|
+
const factOf = type.name === 'Emit' ? type.generics?.[0]?.name : undefined;
|
|
157
|
+
if (factOf) return emitKeyOf(factOf);
|
|
158
|
+
|
|
159
|
+
// `Together<[Account, Ledger], [RateMirror]>` — the fifth reading, and the only one whose
|
|
160
|
+
// argument is a SET rather than one subject. The AST hands each tuple back as a single
|
|
161
|
+
// string, so they are split here, where every other question of "how was it written" lives.
|
|
162
|
+
const frame = type.name === 'Together' ? type.generics?.[0]?.name : undefined;
|
|
163
|
+
if (frame) return togetherKeyOf(tupleMembers(frame), tupleMembers(type.generics?.[1]?.name ?? ''));
|
|
164
|
+
|
|
165
|
+
// `RepositoryOf<Post>` — what an author writes when no repository file exists. The dual
|
|
166
|
+
// of the line below: one names the port, the other the holder, and both resolve to a key
|
|
167
|
+
// rather than to a class the author would have to invent.
|
|
168
|
+
const held = type.name === 'RepositoryOf' ? type.generics?.[0]?.name : undefined;
|
|
169
|
+
if (held) return repositoryKeyOf(held);
|
|
170
|
+
|
|
171
|
+
const target = type.name === 'EntityOrm' ? type.generics?.[0]?.name : undefined;
|
|
172
|
+
if (!target) return type.name;
|
|
173
|
+
|
|
174
|
+
return ormKeyOf(target);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* `'[Account, Ledger]'` → `['Account', 'Ledger']`.
|
|
179
|
+
*
|
|
180
|
+
* The tuple was chosen over the variadic form the parser reads more cleanly, because the
|
|
181
|
+
* variadic one costs arities-with-defaults and a `never` filter on the TypeScript side
|
|
182
|
+
* while the tuple maps to `[EntityOrm<Account>, EntityOrm<Ledger>]` in one line. The
|
|
183
|
+
* parser does not get to decide alone; this split is what that choice costs.
|
|
184
|
+
*/
|
|
185
|
+
function tupleMembers(raw: string): string[] {
|
|
186
|
+
return raw.replace(/^\[|\]$/g, '').split(',').map((member) => member.trim()).filter(Boolean);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* These readings are semantic: an unchanged file can mean something different after an
|
|
191
|
+
* imported alias changes. The parser's TypeScript Program is the cache for one scan; a
|
|
192
|
+
* cache keyed only by this file's bytes would be unsound.
|
|
193
|
+
*/
|
|
194
|
+
const ctorParamsOf = (filePath: string) =>
|
|
195
|
+
parseConstructorParams(filePath);
|
|
196
|
+
|
|
197
|
+
const presenterMethodsOf = (filePath: string) =>
|
|
198
|
+
parsePresenterMethods(filePath);
|
|
199
|
+
|
|
200
|
+
const handlerMethodsOf = (filePath: string, projectRoot?: string) =>
|
|
201
|
+
parseAllHandlerMethods(filePath, projectRoot);
|
|
202
|
+
|
|
203
|
+
async function toProvider(filePath: string): Promise<ProviderEntry> {
|
|
204
|
+
const ctor = await loadClass(filePath);
|
|
205
|
+
const params = await ctorParamsOf(filePath);
|
|
206
|
+
const deps = params.map((p) => depKeyOf(p.type));
|
|
207
|
+
|
|
208
|
+
// A repository inherits its constructor from `Repository(…)`, so the file declares none
|
|
209
|
+
// and the scan reads no parameter. The mixin knows what it was built for and says so at
|
|
210
|
+
// runtime — same escape as `Crud.__ops`, and the same reason: what a prefab fabricates,
|
|
211
|
+
// only the prefab can describe.
|
|
212
|
+
//
|
|
213
|
+
// An AGGREGATE owns several, and its base takes them in the declared order. It is handed
|
|
214
|
+
// no frame: the boundary and the unit of work are two statements, and a frame is ASKED FOR
|
|
215
|
+
// like anywhere else — see `prefab/repository.ts`.
|
|
216
|
+
const owned = ownedBy(ctor);
|
|
217
|
+
const target = targetOf(ctor);
|
|
218
|
+
if (owned.length > 1 && deps.length === 0) {
|
|
219
|
+
deps.push(...owned.map((entity) => ormKeyOf(lowerFirst((entity as { name: string }).name))));
|
|
220
|
+
} else if (target && deps.length === 0) {
|
|
221
|
+
deps.push(ormKeyOf(lowerFirst((target as { name: string }).name)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// No `name` beside `ctor`: a provider registers under `ctor.name`, which is what
|
|
225
|
+
// `depKeyOf` returns since it reads the type as written. The camelCase field that
|
|
226
|
+
// used to sit here called itself the registration key and was one nowhere.
|
|
227
|
+
return { ctor, deps, filePath };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function toEntityEntry(filePath: string): Promise<EntityEntry | null> {
|
|
231
|
+
const exported = await loadDefault(filePath);
|
|
232
|
+
if (!isEntityClass(exported)) return null;
|
|
233
|
+
const runtimeName = (exported as { name?: string }).name;
|
|
234
|
+
// A derivation returned directly (`export default User.extend(...)`) carries the
|
|
235
|
+
// factory's own name, which the schema package stamps and exports. The file is then
|
|
236
|
+
// the declaration site and therefore the only name the author actually supplied;
|
|
237
|
+
// named classes keep winning.
|
|
238
|
+
const declaredName = runtimeName && runtimeName !== ANONYMOUS_SCHEMA_NAME
|
|
239
|
+
? runtimeName
|
|
240
|
+
: basename(filePath).replace(/\.[^.]+$/, '');
|
|
241
|
+
const name = lowerFirst(declaredName);
|
|
242
|
+
return { name, entityClass: exported, filePath };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Resolve a ParsedType to a runtime schema if available in module exports.
|
|
247
|
+
* Handles arrays, generics (uses base name), and simple references.
|
|
248
|
+
*/
|
|
249
|
+
function resolveSchema(type: ParsedType, moduleExports: Record<string, unknown>): SchemaView | undefined {
|
|
250
|
+
// An array's element type IS `type.name` — the arity rides beside it, so nothing has
|
|
251
|
+
// to be unwrapped here.
|
|
252
|
+
// For generics like Pagination<Post>, also check inner types
|
|
253
|
+
if (type.generics) {
|
|
254
|
+
for (const g of type.generics) {
|
|
255
|
+
const resolved = moduleExports[g.name];
|
|
256
|
+
if (resolved && typeof resolved === 'function' && 'getFields' in resolved) {
|
|
257
|
+
// `Partial<X>` in a signature IS the patch declaration (Crud.update) —
|
|
258
|
+
// project it onto the schema view instead of dropping the wrapper, so
|
|
259
|
+
// the facade validates in patch mode (absent field → untouched).
|
|
260
|
+
if (type.name === 'Partial' && 'partial' in resolved && typeof (resolved as any).partial === 'function') {
|
|
261
|
+
return (resolved as any).partial() as SchemaView;
|
|
262
|
+
}
|
|
263
|
+
return resolved as unknown as SchemaView;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const resolved = moduleExports[type.name];
|
|
268
|
+
if (resolved && typeof resolved === 'function' && 'getFields' in resolved) {
|
|
269
|
+
return resolved as unknown as SchemaView;
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Parse ALL method signatures for unified binding.
|
|
276
|
+
*
|
|
277
|
+
* Resolves schemas for all params (not just the first) and stores
|
|
278
|
+
* full signatures for the binding algorithm.
|
|
279
|
+
*/
|
|
280
|
+
async function inferOperations(
|
|
281
|
+
filePath: string,
|
|
282
|
+
handlerName: string,
|
|
283
|
+
moduleExports: Record<string, unknown>,
|
|
284
|
+
collectorTypeNames: Set<string>,
|
|
285
|
+
explicitInputs: ReadonlySet<string>,
|
|
286
|
+
declared: Record<string, OperationContract>,
|
|
287
|
+
projectRoot?: string,
|
|
288
|
+
): Promise<OperationsMap> {
|
|
289
|
+
const map = new Map<string, OperationContract>();
|
|
290
|
+
let parsed: Awaited<ReturnType<typeof parseAllHandlerMethods>>;
|
|
291
|
+
try {
|
|
292
|
+
parsed = await handlerMethodsOf(filePath, projectRoot);
|
|
293
|
+
} catch (cause) {
|
|
294
|
+
// The handler still gets a façade — its methods exist at runtime — but with no
|
|
295
|
+
// contract: no binding plan, no input schema, no doc sentence. It used to
|
|
296
|
+
// return the empty map, so the app served a stranger's idea of the handler and
|
|
297
|
+
// said nothing. The operations are gone; the sentence saying so is not.
|
|
298
|
+
record({
|
|
299
|
+
severity: 'blocking',
|
|
300
|
+
code: 'handler-parse-failed',
|
|
301
|
+
filePath,
|
|
302
|
+
message: 'Could not parse this handler — its operations carry no contract, so the '
|
|
303
|
+
+ 'façade serves them unbound. Not the same as a handler with no operation.',
|
|
304
|
+
cause,
|
|
305
|
+
});
|
|
306
|
+
return map;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* A base class the parse could not open — an installed package, typically, whose
|
|
311
|
+
* source is not in the workspace. Its operations are missing from this façade, and
|
|
312
|
+
* the scan cannot tell whether there were any.
|
|
313
|
+
*
|
|
314
|
+
* A warning, not a refusal: an installed base class with no operation is perfectly
|
|
315
|
+
* ordinary, and the boot has no way to decide between the two. So it names the
|
|
316
|
+
* clause and stops there. Stating the contract in `frond.config.ts` is the answer
|
|
317
|
+
* — the third producer, which creates an op neither other producer found — and it
|
|
318
|
+
* silences this by making the op exist.
|
|
319
|
+
*/
|
|
320
|
+
for (const base of parsed.unresolvedHeritage) {
|
|
321
|
+
record({
|
|
322
|
+
severity: 'warning',
|
|
323
|
+
code: 'heritage-unresolved',
|
|
324
|
+
filePath,
|
|
325
|
+
message: `Could not resolve 'extends ${base}' — any operation it declares is absent `
|
|
326
|
+
+ `from this façade, and the scan cannot tell that from a base class with none. `
|
|
327
|
+
+ `State the contract in frond.config.ts to put it back.`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (const method of parsed.methods) {
|
|
332
|
+
// The contract is what carries the description; `signature` is the raw material it
|
|
333
|
+
// was read from. Leaving it only on the signature meant every consumer had to know
|
|
334
|
+
// to look one level down, and only the façade did.
|
|
335
|
+
const meta: OperationContract = {
|
|
336
|
+
signature: method,
|
|
337
|
+
...(method.description && { description: method.description }),
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// A convention may omit a declaration only when it has one answer. Only values the
|
|
341
|
+
// caller supplies through the body are candidates: a schema-typed collector, fact or
|
|
342
|
+
// context parameter is not input merely because it names an entity. The old loop
|
|
343
|
+
// ignored provenance and assigned the first schema it met, so swapping two parameters
|
|
344
|
+
// silently changed the contract the façade used to judge the request body.
|
|
345
|
+
const binding = computeBindingPlan(method.params, collectorTypeNames);
|
|
346
|
+
const candidates = method.params.flatMap((param, index) => {
|
|
347
|
+
if (binding[index]?.source.kind !== 'body') return [];
|
|
348
|
+
const schema = resolveSchema(param.type, moduleExports);
|
|
349
|
+
return schema ? [{ param, schema }] : [];
|
|
350
|
+
});
|
|
351
|
+
if (candidates.length === 1) {
|
|
352
|
+
meta.input = candidates[0].schema;
|
|
353
|
+
} else if (
|
|
354
|
+
candidates.length > 1
|
|
355
|
+
&& declared[method.name]?.input === undefined
|
|
356
|
+
&& !explicitInputs.has(method.name)
|
|
357
|
+
) {
|
|
358
|
+
const subject = `${handlerName}.${method.name}`;
|
|
359
|
+
record({
|
|
360
|
+
severity: 'blocking',
|
|
361
|
+
code: 'input-contract-ambiguous',
|
|
362
|
+
filePath,
|
|
363
|
+
subject,
|
|
364
|
+
message: `Cannot infer the input contract for ${subject}: ${candidates.length} entity `
|
|
365
|
+
+ `candidates — ${candidates.map(({ param }) => `${param.name}: ${param.type.raw}`).join('; ')}. `
|
|
366
|
+
+ `Declare operations.${method.name}.input in frond.config.ts.`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (method.returnType) {
|
|
371
|
+
meta.output = resolveSchema(method.returnType, moduleExports);
|
|
372
|
+
// `output` is the shape of one row; this says how many rows come back.
|
|
373
|
+
meta.cardinality = cardinalityOf(method.returnType);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
map.set(method.name, meta);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return map;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function toHandlerEntry(
|
|
383
|
+
filePath: string,
|
|
384
|
+
entityByClassName: Map<string, SchemaView>,
|
|
385
|
+
collectorTypeNames: Set<string>,
|
|
386
|
+
explicitInputs: ReadonlySet<string>,
|
|
387
|
+
projectRoot?: string,
|
|
388
|
+
surface?: string,
|
|
389
|
+
): Promise<HandlerEntry> {
|
|
390
|
+
const mod = await loadModule(filePath);
|
|
391
|
+
const ctor = mod.default;
|
|
392
|
+
if (typeof ctor !== 'function' || !ctor.prototype)
|
|
393
|
+
throw new Error(`${filePath}: default export is not a class`);
|
|
394
|
+
|
|
395
|
+
// Augment module exports with known entity classes so resolveSchema
|
|
396
|
+
// can find them when T is substituted (e.g. T → Post after parsing Crud(Post))
|
|
397
|
+
const augmented: Record<string, unknown> = { ...mod };
|
|
398
|
+
for (const [className, entityClass] of entityByClassName) {
|
|
399
|
+
if (!(className in augmented)) augmented[className] = entityClass;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const address = toAddress(ctor.name);
|
|
403
|
+
const declaredOps = (ctor as { __ops?: Record<string, OperationContract> }).__ops ?? {};
|
|
404
|
+
const operations = await inferOperations(
|
|
405
|
+
filePath,
|
|
406
|
+
ctor.name,
|
|
407
|
+
augmented,
|
|
408
|
+
collectorTypeNames,
|
|
409
|
+
explicitInputs,
|
|
410
|
+
declaredOps,
|
|
411
|
+
projectRoot,
|
|
412
|
+
);
|
|
413
|
+
const ctorParams = await ctorParamsOf(filePath);
|
|
414
|
+
const deps = ctorParams.map((p) => depKeyOf(p.type));
|
|
415
|
+
|
|
416
|
+
// Read output override from Crud(Entity, Output) — static __output property
|
|
417
|
+
// A handler-wide view, when it is not simply the entity — the two are compared by
|
|
418
|
+
// identity, which is why both slots are read through the same door.
|
|
419
|
+
const subject = targetOf(ctor);
|
|
420
|
+
const declared = outputOf(ctor);
|
|
421
|
+
const outputOverride = declared && subject && declared !== subject
|
|
422
|
+
? (declared as unknown as SchemaView)
|
|
423
|
+
: undefined;
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
name: lowerFirst(ctor.name),
|
|
427
|
+
address,
|
|
428
|
+
ctor: ctor as ProviderEntry['ctor'],
|
|
429
|
+
operations,
|
|
430
|
+
deps,
|
|
431
|
+
filePath,
|
|
432
|
+
outputOverride,
|
|
433
|
+
surface,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Strip '.seed' suffix → entity name. 'Author.seed.ts' → 'author'. */
|
|
438
|
+
function toSeedEntityName(fileName: string): string {
|
|
439
|
+
const base = fileName.replace(/\.seed\.(ts|js)$/, '').replace(/\.(ts|js)$/, '');
|
|
440
|
+
return lowerFirst(base);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function toSeedEntry(filePath: string): Promise<SeedEntry | null> {
|
|
444
|
+
const data = await loadDefault(filePath);
|
|
445
|
+
if (Array.isArray(data) || typeof data === 'function') {
|
|
446
|
+
const fileName = filePath.split('/').pop()!;
|
|
447
|
+
return { entityName: toSeedEntityName(fileName), data: data as SeedEntry['data'], filePath };
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function toPresenterEntry(filePath: string): Promise<PresenterEntry | null> {
|
|
453
|
+
const ctor = await loadClass(filePath);
|
|
454
|
+
const target = targetOf(ctor);
|
|
455
|
+
if (!target) return null;
|
|
456
|
+
const entityName = lowerFirst((target as any).name);
|
|
457
|
+
const fields = getPresenterFields(ctor);
|
|
458
|
+
const presenterParams = await ctorParamsOf(filePath);
|
|
459
|
+
const deps = presenterParams.map((p) => depKeyOf(p.type));
|
|
460
|
+
|
|
461
|
+
// Parse method return types from source
|
|
462
|
+
let fieldMeta: PresenterEntry['fieldMeta'] = [];
|
|
463
|
+
try {
|
|
464
|
+
const parsed = await presenterMethodsOf(filePath);
|
|
465
|
+
fieldMeta = parsed.map((m) => ({
|
|
466
|
+
name: m.name,
|
|
467
|
+
returnType: m.returnType?.name,
|
|
468
|
+
// One array level IS the page — the method answers one value per row. What is left
|
|
469
|
+
// over is the field's own arity: `string[]` a scalar per row, `string[][]` a list.
|
|
470
|
+
list: (m.returnType?.arrayDepth ?? 0) > 1,
|
|
471
|
+
nullable: m.returnType?.nullable,
|
|
472
|
+
// Everything after the rows is bound like a handler's argument.
|
|
473
|
+
params: m.params.slice(1),
|
|
474
|
+
}));
|
|
475
|
+
|
|
476
|
+
// The rows parameter is stripped just above, and nothing used to check its SHAPE.
|
|
477
|
+
// A computed field receives the PAGE and answers one value per row, so a method
|
|
478
|
+
// written `items(order: Order)` compiles and dies at the first call with a 500
|
|
479
|
+
// (`expected 1 value(s) for 1 row(s), got 0`) — while the information sat here, at
|
|
480
|
+
// the scan. Every own method of a presenter IS a computed field
|
|
481
|
+
// (`getPresenterFields`), so there is no private helper to spare.
|
|
482
|
+
//
|
|
483
|
+
// An ERROR at boot, not a refusal: a `blocking` scan diagnostic is logged and the
|
|
484
|
+
// app still starts (`bootstrap.ts`, and the comment above that loop says why). Making
|
|
485
|
+
// it refuse means deciding that a declaration which cannot work is an unresolved
|
|
486
|
+
// CONTRACT — the one thing that does stop the boot — and that is a decision, not a
|
|
487
|
+
// severity.
|
|
488
|
+
for (const method of parsed) {
|
|
489
|
+
const rows = method.params[0];
|
|
490
|
+
if (rows && rows.type.array === true) continue;
|
|
491
|
+
record({
|
|
492
|
+
severity: 'blocking',
|
|
493
|
+
code: 'presenter-field-not-page',
|
|
494
|
+
filePath,
|
|
495
|
+
subject: `${(ctor as { name?: string }).name ?? 'Presenter'}.${method.name}`,
|
|
496
|
+
message: `${method.name}(${rows ? `${rows.name}: ${rows.type.raw}` : ''}) is a computed `
|
|
497
|
+
+ `field, so it receives the PAGE and must answer one value per row. Declare `
|
|
498
|
+
+ `${method.name}(${rows?.name ?? 'rows'}: ${(target as { name?: string }).name ?? 'Entity'}[]) `
|
|
499
|
+
+ `and return an array of the same length.`,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
} catch { /* parse failure — fall back to untyped */ }
|
|
503
|
+
|
|
504
|
+
// Declared at runtime on the class, so it survives a scan that resolved nothing.
|
|
505
|
+
return { entityName, ctor, fields, fieldMeta, views: viewsOf(ctor), deps, filePath };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function toCollectorEntry(filePath: string): Promise<CollectorEntry | null> {
|
|
509
|
+
const ctor = await loadClass(filePath);
|
|
510
|
+
const target = targetOf(ctor);
|
|
511
|
+
if (!target) return null;
|
|
512
|
+
// The target's NAME and nothing else — a collector reads no fields, so the class it
|
|
513
|
+
// was built on needs no schema.
|
|
514
|
+
const typeName = lowerFirst((target as any).name);
|
|
515
|
+
const collectorParams = await ctorParamsOf(filePath);
|
|
516
|
+
const deps = collectorParams.map((p) => depKeyOf(p.type));
|
|
517
|
+
return { typeName, ctor, deps, filePath };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function scanFrond(frondPath: string, name: string, source: FrondDescriptor['source'], conventions: Conventions, projectRoot?: string): Promise<FrondDescriptor> {
|
|
521
|
+
const {
|
|
522
|
+
entities: entitiesDir, handlers: handlersDir,
|
|
523
|
+
presenters: presentersDir, collectors: collectorsDir, seeds: seedsDir,
|
|
524
|
+
} = conventions.dirs;
|
|
525
|
+
/**
|
|
526
|
+
* A convention directory, read by whoever knows the shape it holds.
|
|
527
|
+
*
|
|
528
|
+
* The same three lines were written six times — list, read in parallel, drop what the
|
|
529
|
+
* reader refused — which is what made `handlers/` quietly special: it grew a second
|
|
530
|
+
* pass for its surfaces and nothing else could.
|
|
531
|
+
*/
|
|
532
|
+
const collect = async <T extends object>(
|
|
533
|
+
dir: string,
|
|
534
|
+
read: (filePath: string) => Promise<T | null>,
|
|
535
|
+
): Promise<T[]> => {
|
|
536
|
+
const entries: (T | null)[] = await Promise.all((await files(join(frondPath, dir))).map(read));
|
|
537
|
+
return entries.filter((entry): entry is T => entry !== null);
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// services/ and repositories/ — two spellings, one provider list.
|
|
541
|
+
const providers = (await Promise.all(providerDirsOf(conventions).map((dir) => collect(dir, toProvider)))).flat();
|
|
542
|
+
const entities = await collect(entitiesDir, toEntityEntry);
|
|
543
|
+
const collectors = await collect(collectorsDir, toCollectorEntry);
|
|
544
|
+
const collectorTypeNames = new Set(collectors.map((collector) => collector.typeName));
|
|
545
|
+
const frondConfig = await loadFrondConfig(frondPath);
|
|
546
|
+
const explicitInputs = new Set(
|
|
547
|
+
Object.entries(frondConfig?.operations ?? {})
|
|
548
|
+
.filter(([, contract]) => contract.input !== undefined)
|
|
549
|
+
.map(([operation]) => operation),
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
// Handlers resolve `T` against the entities, so those come first.
|
|
553
|
+
const entityByClassName = new Map(
|
|
554
|
+
entities.map((e) => [(e.entityClass as { name: string }).name, e.entityClass]),
|
|
555
|
+
);
|
|
556
|
+
const handlers = await collect(handlersDir, (f) =>
|
|
557
|
+
toHandlerEntry(f, entityByClassName, collectorTypeNames, explicitInputs, projectRoot));
|
|
558
|
+
|
|
559
|
+
// A subdirectory of handlers/ is a named surface — the one directory whose CHILDREN
|
|
560
|
+
// are part of the convention too.
|
|
561
|
+
for (const surface of await dirs(join(frondPath, handlersDir))) {
|
|
562
|
+
handlers.push(...await collect(join(handlersDir, surface), (f) =>
|
|
563
|
+
toHandlerEntry(f, entityByClassName, collectorTypeNames, explicitInputs, projectRoot, surface)));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const presenters = await collect(presentersDir, toPresenterEntry);
|
|
567
|
+
const seeds = await collect(seedsDir, toSeedEntry);
|
|
568
|
+
|
|
569
|
+
// Mark exposed entries: frond.config.ts takes precedence, then @expose decorator
|
|
570
|
+
if (frondConfig?.expose) {
|
|
571
|
+
const exposeSet = new Set(frondConfig.expose);
|
|
572
|
+
for (const e of entities) {
|
|
573
|
+
e.exposed = exposeSet.has((e.entityClass as any).name);
|
|
574
|
+
}
|
|
575
|
+
for (const h of handlers) {
|
|
576
|
+
h.exposed = exposeSet.has(h.ctor.name);
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
// Fallback: check @expose decorator, default to true (expose everything unless explicitly hidden)
|
|
580
|
+
for (const e of entities) {
|
|
581
|
+
e.exposed = (e.entityClass as any).__exposed !== false;
|
|
582
|
+
}
|
|
583
|
+
for (const h of handlers) {
|
|
584
|
+
h.exposed = (h.ctor as any).__exposed !== false;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Flatten per-op config overrides: the only thing that needs flattening is the handler
|
|
589
|
+
// CLASS, which becomes its name (that is the DI key). Everything else — the surface keys
|
|
590
|
+
// AND the contract keys (`input`, `binding`) — travels verbatim, so a slot added to
|
|
591
|
+
// OperationOverride reaches its reader without a stop here. Enumerating keys by hand is
|
|
592
|
+
// what used to silently drop whatever was added last (the same invariant `cloneField`
|
|
593
|
+
// holds one layer down).
|
|
594
|
+
const operationsOverrides = frondConfig?.operations
|
|
595
|
+
? Object.fromEntries(
|
|
596
|
+
Object.entries(frondConfig.operations).map(([opName, { handler, ...rest }]) => [
|
|
597
|
+
opName,
|
|
598
|
+
{ ...rest, handlerName: handler?.name },
|
|
599
|
+
]),
|
|
600
|
+
)
|
|
601
|
+
: undefined;
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
name,
|
|
605
|
+
source,
|
|
606
|
+
providers,
|
|
607
|
+
entities,
|
|
608
|
+
handlers,
|
|
609
|
+
presenters,
|
|
610
|
+
collectors,
|
|
611
|
+
seeds,
|
|
612
|
+
surfaces: frondConfig?.surfaces,
|
|
613
|
+
reads: frondConfig?.reads,
|
|
614
|
+
operationsOverrides,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* The frond's name — the directory, unless its `package.json` renames it. One rule for a
|
|
620
|
+
* frond under `fronds/` and for the root frond alike, so the root needs no second spelling.
|
|
621
|
+
*
|
|
622
|
+
* Carrying the convention is what makes a frond; the key never marked anything and
|
|
623
|
+
* nothing read it. It earns its keep as the one thing the directory cannot say: that
|
|
624
|
+
* `fronds/blog-v2/` serves the frond still called `blog` — so a rename on disk does not
|
|
625
|
+
* rename the entity keys, the `@fronds/*` import or a `remotes:` entry.
|
|
626
|
+
*/
|
|
627
|
+
async function frondNameOf(frondPath: string, dirName: string): Promise<string> {
|
|
628
|
+
try {
|
|
629
|
+
const pkg = JSON.parse(await readFile(join(frondPath, 'package.json'), 'utf8')) as {
|
|
630
|
+
fougere?: { frond?: unknown };
|
|
631
|
+
};
|
|
632
|
+
const declared = pkg.fougere?.frond;
|
|
633
|
+
return typeof declared === 'string' && declared.length > 0 ? declared : dirName;
|
|
634
|
+
} catch {
|
|
635
|
+
return dirName;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* A frond is a directory carrying the convention, and the project root is one such
|
|
641
|
+
* directory — so a single-domain app writes `entities/` next to `app/` and never names
|
|
642
|
+
* anything. `fronds/` is not the definition, it is where the OTHERS live: the root frond
|
|
643
|
+
* stays put when a second domain appears, which is what makes flattening free instead of
|
|
644
|
+
* a deferred move.
|
|
645
|
+
*
|
|
646
|
+
* `entities/` is the test, not "any convention directory". `services/` and `repositories/`
|
|
647
|
+
* are ordinary top-level names in projects that never heard of Fougere, and a domain
|
|
648
|
+
* without a single entity is not a domain.
|
|
649
|
+
*/
|
|
650
|
+
async function rootFrondOf(root: string, workspaceRoot: string, conventions: Conventions): Promise<FrondDescriptor | null> {
|
|
651
|
+
if ((await files(join(root, conventions.dirs.entities))).length === 0) return null;
|
|
652
|
+
const name = await frondNameOf(root, basename(resolvePath(root)));
|
|
653
|
+
return scanFrond(root, name, { path: root, package: frondPackage(name, conventions) }, conventions, workspaceRoot);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* `@fronds/<name>` → the directory it names, for every frond of a project.
|
|
658
|
+
*
|
|
659
|
+
* The framework states this convention — `FrondSource.package` has always spelled it, and
|
|
660
|
+
* `fougere sync` writes it into a consumer's tsconfig — and until now its own reader could
|
|
661
|
+
* not resolve it. The Nuxt module registered a Vite alias, so a `.vue` page could import
|
|
662
|
+
* `@fronds/blog/entities/Post`, while the SCAN loaded sources through a bare jiti: a frond
|
|
663
|
+
* naming its neighbour got `Cannot find module '@fronds/user/entities/User.js'`. So the one
|
|
664
|
+
* form that survives a split was the one form that did not run.
|
|
665
|
+
*
|
|
666
|
+
* Hand it to the module loader — `createJiti(url, { alias: await frondAliases(root) })` —
|
|
667
|
+
* and the named form resolves everywhere the framework reads. A directory listing and a
|
|
668
|
+
* `package.json` read, no parsing: it is deliberately callable BEFORE the loader exists,
|
|
669
|
+
* which is what makes the chicken-and-egg go away.
|
|
670
|
+
*/
|
|
671
|
+
export async function frondAliases(root: string, conventions: Conventions = DEFAULT_CONVENTIONS): Promise<Record<string, string>> {
|
|
672
|
+
const frondsDir = join(root, conventions.fronds);
|
|
673
|
+
const aliases: Record<string, string> = {};
|
|
674
|
+
|
|
675
|
+
// The root itself is a frond when it carries `entities/` — same rule as the scan.
|
|
676
|
+
if ((await files(join(root, conventions.dirs.entities))).length > 0) {
|
|
677
|
+
aliases[frondPackage(await frondNameOf(root, basename(resolvePath(root))), conventions)] = resolvePath(root);
|
|
678
|
+
}
|
|
679
|
+
for (const dir of await dirs(frondsDir)) {
|
|
680
|
+
const path = join(frondsDir, dir);
|
|
681
|
+
aliases[frondPackage(await frondNameOf(path, dir), conventions)] = resolvePath(path);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* A SYNCED frond answers to the same name.
|
|
686
|
+
*
|
|
687
|
+
* `fougere sync` writes `.fougere/remotes/<name>/` and registers it in `remotes.json`,
|
|
688
|
+
* which the Nuxt module already reads to alias `@fronds/<name>`. Doing it here too is
|
|
689
|
+
* what makes the convention mean ONE thing: a consumer writes `@fronds/blog/entities/Post`
|
|
690
|
+
* and never learns whether that frond is on this disk or was fetched from a card.
|
|
691
|
+
*
|
|
692
|
+
* A local frond wins a name collision — its source is the truth, a synced copy is a
|
|
693
|
+
* mirror of somebody else's.
|
|
694
|
+
*/
|
|
695
|
+
try {
|
|
696
|
+
const registry = JSON.parse(
|
|
697
|
+
await readFile(join(root, '.fougere', 'remotes.json'), 'utf8'),
|
|
698
|
+
) as Record<string, { path?: string }>;
|
|
699
|
+
for (const [name, entry] of Object.entries(registry)) {
|
|
700
|
+
if (typeof entry?.path !== 'string' || aliases[frondPackage(name, conventions)]) continue;
|
|
701
|
+
aliases[frondPackage(name, conventions)] = resolvePath(entry.path);
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
// No registry, or an unreadable one: nothing was synced here. Not this function's
|
|
705
|
+
// complaint — `sync` owns that file and reports on it.
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return aliases;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export async function scanProject(
|
|
712
|
+
root: string,
|
|
713
|
+
filter?: string[],
|
|
714
|
+
conventionsInput?: ConventionsInput,
|
|
715
|
+
): Promise<ScanResult> {
|
|
716
|
+
const conventions = resolveConventions(conventionsInput);
|
|
717
|
+
resetTypePrograms();
|
|
718
|
+
// A run owns its findings: two scans in one process (a test suite, a watcher)
|
|
719
|
+
// must not inherit each other's.
|
|
720
|
+
diagnostics = [];
|
|
721
|
+
|
|
722
|
+
const frondsDir = join(root, conventions.fronds);
|
|
723
|
+
const dirNames = await dirs(frondsDir);
|
|
724
|
+
// Resolve workspace root (parent of packages/) for package import resolution
|
|
725
|
+
// For monorepo: root is the project dir (e.g. demos/nuxt-blog), workspace root is the repo root
|
|
726
|
+
const workspaceRoot = findWorkspaceRoot(root);
|
|
727
|
+
|
|
728
|
+
// One program for this run. Seeded here because the parser rebuilds on every root it
|
|
729
|
+
// has not seen, and a frond lives outside its project's tsconfig `include`. Both keys
|
|
730
|
+
// are seeded: heritage reads under the workspace root, a constructor under none.
|
|
731
|
+
const declarations = (await Promise.all(
|
|
732
|
+
[root, ...dirNames.map((dir) => join(frondsDir, dir))].flatMap((frondPath) =>
|
|
733
|
+
frondDirsOf(conventions).map((dir) => files(join(frondPath, dir)))),
|
|
734
|
+
)).flat();
|
|
735
|
+
await seedTypeProgram(declarations, workspaceRoot);
|
|
736
|
+
await seedTypeProgram(declarations);
|
|
737
|
+
|
|
738
|
+
const [rootFrond, under] = await Promise.all([
|
|
739
|
+
rootFrondOf(root, workspaceRoot, conventions),
|
|
740
|
+
Promise.all(
|
|
741
|
+
dirNames.map(async (dir) => {
|
|
742
|
+
const name = await frondNameOf(join(frondsDir, dir), dir);
|
|
743
|
+
return scanFrond(
|
|
744
|
+
join(frondsDir, dir), name,
|
|
745
|
+
{ path: join(frondsDir, dir), package: frondPackage(name, conventions) },
|
|
746
|
+
conventions,
|
|
747
|
+
workspaceRoot,
|
|
748
|
+
);
|
|
749
|
+
}),
|
|
750
|
+
),
|
|
751
|
+
]);
|
|
752
|
+
|
|
753
|
+
// The app's own domain first, then the ones it took in.
|
|
754
|
+
const all = rootFrond ? [rootFrond, ...under] : under;
|
|
755
|
+
const fronds = Fronds.scanned(filter ? all.filter((f) => filter.includes(f.name)) : all);
|
|
756
|
+
|
|
757
|
+
return { fronds, diagnostics };
|
|
758
|
+
}
|