@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,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operation model after every declaration and convention has been resolved.
|
|
3
|
+
*
|
|
4
|
+
* The scanner produces evidence. This module turns that evidence into the one table
|
|
5
|
+
* boot, check, explain and protocol projections consume. It is deliberately pure: no
|
|
6
|
+
* container, filesystem, migration or handler construction is involved.
|
|
7
|
+
*/
|
|
8
|
+
import { lowerFirst, type SchemaView } from '@fougere/schema';
|
|
9
|
+
import type { BindingPlan } from './boot/binding.js';
|
|
10
|
+
import { targetOf } from './prefab/prefab.js';
|
|
11
|
+
import type {
|
|
12
|
+
CollectorEntry,
|
|
13
|
+
FrondDescriptor,
|
|
14
|
+
HandlerEntry,
|
|
15
|
+
ScanDiagnostic,
|
|
16
|
+
} from './scan/frond.js';
|
|
17
|
+
import { verify } from './verify.js';
|
|
18
|
+
import {
|
|
19
|
+
inferOperationKind,
|
|
20
|
+
resolveContracts,
|
|
21
|
+
type OperationContract,
|
|
22
|
+
type OperationKind,
|
|
23
|
+
type ParsedType,
|
|
24
|
+
knownVerbs,
|
|
25
|
+
} from './wire/operation.js';
|
|
26
|
+
|
|
27
|
+
type Binding = BindingPlan[number];
|
|
28
|
+
|
|
29
|
+
export interface EffectiveParameter {
|
|
30
|
+
/** Position in the TypeScript signature. Never used to infer provenance. */
|
|
31
|
+
position: number;
|
|
32
|
+
name: string;
|
|
33
|
+
type: string | null;
|
|
34
|
+
optional: boolean;
|
|
35
|
+
nullable: boolean;
|
|
36
|
+
/** `?` and `| undefined` both mean canonical absence. */
|
|
37
|
+
undefinable: boolean;
|
|
38
|
+
/** The resolved provenance of this parameter. */
|
|
39
|
+
binding: Binding;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface EffectiveCollector {
|
|
43
|
+
parameter: string;
|
|
44
|
+
typeName: string;
|
|
45
|
+
className: string;
|
|
46
|
+
frond: string;
|
|
47
|
+
filePath: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EffectiveOperation extends OperationContract {
|
|
51
|
+
/** Stable qualified identity: `blog/public/Post.publish`. */
|
|
52
|
+
id: string;
|
|
53
|
+
/** Human-facing identity independent of placement: `Post.publish`. */
|
|
54
|
+
operation: string;
|
|
55
|
+
name: string;
|
|
56
|
+
kind: OperationKind;
|
|
57
|
+
kindSource: 'explicit' | 'convention';
|
|
58
|
+
handler: {
|
|
59
|
+
className: string;
|
|
60
|
+
address: string;
|
|
61
|
+
filePath: string;
|
|
62
|
+
};
|
|
63
|
+
/** The class and method that execute after an optional operation override. */
|
|
64
|
+
implementation: {
|
|
65
|
+
className: string;
|
|
66
|
+
address: string;
|
|
67
|
+
method: string;
|
|
68
|
+
filePath: string;
|
|
69
|
+
};
|
|
70
|
+
/** Every valid operation has a plan, including the empty plan. */
|
|
71
|
+
binding: BindingPlan;
|
|
72
|
+
parameters: EffectiveParameter[];
|
|
73
|
+
collectors: EffectiveCollector[];
|
|
74
|
+
contexts: string[];
|
|
75
|
+
placement: {
|
|
76
|
+
frond: string;
|
|
77
|
+
runtime: 'local' | 'remote';
|
|
78
|
+
remote?: string;
|
|
79
|
+
};
|
|
80
|
+
exposure: {
|
|
81
|
+
surfaces: string[];
|
|
82
|
+
adapters: string[];
|
|
83
|
+
};
|
|
84
|
+
/** Whether output is an explicitly closed per-operation view. */
|
|
85
|
+
outputClosed: boolean;
|
|
86
|
+
semantics: typeof EFFECTIVE_OPERATION_SEMANTICS;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type EffectiveOperationsMap = Map<string, EffectiveOperation>;
|
|
90
|
+
|
|
91
|
+
/** The absence rules every door normalises to before the handler is invoked. */
|
|
92
|
+
export const EFFECTIVE_OPERATION_SEMANTICS = Object.freeze({
|
|
93
|
+
optional: 'undefined' as const,
|
|
94
|
+
undefined: 'absence' as const,
|
|
95
|
+
null: 'explicit-value' as const,
|
|
96
|
+
jsonObjectUndefined: 'omitted' as const,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export interface EffectiveOperationOptions {
|
|
100
|
+
diagnostics?: readonly ScanDiagnostic[];
|
|
101
|
+
remotes?: Record<string, string>;
|
|
102
|
+
adapters?: Record<string, boolean | undefined>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One resolved program. The handler map preserves object identity from the scan, so the
|
|
107
|
+
* boot never has to find a handler again by an order-dependent name lookup.
|
|
108
|
+
*/
|
|
109
|
+
export class EffectiveOperationModel {
|
|
110
|
+
readonly operations: EffectiveOperation[];
|
|
111
|
+
readonly diagnostics: ScanDiagnostic[];
|
|
112
|
+
readonly resolutionDiagnostics: ScanDiagnostic[];
|
|
113
|
+
|
|
114
|
+
constructor(
|
|
115
|
+
operations: EffectiveOperation[],
|
|
116
|
+
diagnostics: ScanDiagnostic[],
|
|
117
|
+
resolutionDiagnostics: ScanDiagnostic[],
|
|
118
|
+
private readonly byHandler: Map<HandlerEntry, EffectiveOperationsMap>,
|
|
119
|
+
) {
|
|
120
|
+
this.operations = operations;
|
|
121
|
+
this.diagnostics = diagnostics;
|
|
122
|
+
this.resolutionDiagnostics = resolutionDiagnostics;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
forHandler(handler: HandlerEntry): EffectiveOperationsMap {
|
|
126
|
+
return this.byHandler.get(handler) ?? new Map();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve the complete operation model without starting the application. */
|
|
131
|
+
export function resolveEffectiveOperations(
|
|
132
|
+
fronds: readonly FrondDescriptor[],
|
|
133
|
+
options: EffectiveOperationOptions = {},
|
|
134
|
+
): EffectiveOperationModel {
|
|
135
|
+
const operations: EffectiveOperation[] = [];
|
|
136
|
+
const byHandler = new Map<HandlerEntry, EffectiveOperationsMap>();
|
|
137
|
+
const resolutionDiagnostics: ScanDiagnostic[] = [];
|
|
138
|
+
const scanDiagnostics = [...(options.diagnostics ?? [])];
|
|
139
|
+
const schemas = new Map(
|
|
140
|
+
fronds.flatMap((frond) => frond.entities.map((entity) => [entity.name, entity.entityClass] as const)),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Input ambiguity is produced where schemas are available (the scanner), but it is a
|
|
144
|
+
// resolution failure. Lift it into the same refusal table as kind/binding/topology.
|
|
145
|
+
resolutionDiagnostics.push(...scanDiagnostics.filter((diagnostic) =>
|
|
146
|
+
diagnostic.code === 'input-contract-ambiguous'));
|
|
147
|
+
|
|
148
|
+
for (const frond of fronds) {
|
|
149
|
+
const collectorsByType = groupedCollectors(frond.collectors);
|
|
150
|
+
for (const [typeName, collectors] of collectorsByType) {
|
|
151
|
+
if (collectors.length < 2) continue;
|
|
152
|
+
resolutionDiagnostics.push({
|
|
153
|
+
severity: 'blocking',
|
|
154
|
+
code: 'collector-ambiguous',
|
|
155
|
+
filePath: collectors[0]!.filePath,
|
|
156
|
+
frond: frond.name,
|
|
157
|
+
subject: typeName,
|
|
158
|
+
message: `Frond '${frond.name}' declares ${collectors.length} collectors for '${typeName}' — `
|
|
159
|
+
+ `${collectors.map((collector) => collector.ctor.name).sort().join(', ')}. `
|
|
160
|
+
+ 'Exactly one collector must own a parameter type.',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const collectorNames = new Set(frond.collectors.map((collector) => collector.typeName));
|
|
165
|
+
for (const handler of frond.handlers) {
|
|
166
|
+
const effective = new Map<string, EffectiveOperation>();
|
|
167
|
+
byHandler.set(handler, effective);
|
|
168
|
+
const contracts = resolveContracts(handler, frond.operationsOverrides, collectorNames);
|
|
169
|
+
|
|
170
|
+
for (const [name, rawContract] of contracts) {
|
|
171
|
+
const subject = `${handler.ctor.name}.${name}`;
|
|
172
|
+
const contract = normalizeBinding(rawContract, handler, frond, name, resolutionDiagnostics);
|
|
173
|
+
if (!contract) continue;
|
|
174
|
+
|
|
175
|
+
const override = frond.operationsOverrides?.[name];
|
|
176
|
+
const inference = inferOperationKind(name);
|
|
177
|
+
const inferredKinds = new Set<OperationKind>();
|
|
178
|
+
if (inference.kind) inferredKinds.add(inference.kind);
|
|
179
|
+
if (inference.queryMatches.length > 0) inferredKinds.add('query');
|
|
180
|
+
if (inference.commandMatches.length > 0) inferredKinds.add('command');
|
|
181
|
+
const kind = override?.kind
|
|
182
|
+
?? (inferredKinds.size === 1 ? [...inferredKinds][0] : undefined);
|
|
183
|
+
if (!kind) {
|
|
184
|
+
const ambiguous = inferredKinds.size > 1;
|
|
185
|
+
resolutionDiagnostics.push({
|
|
186
|
+
severity: 'blocking',
|
|
187
|
+
code: ambiguous ? 'operation-kind-ambiguous' : 'operation-kind-unknown',
|
|
188
|
+
filePath: handler.filePath,
|
|
189
|
+
frond: frond.name,
|
|
190
|
+
subject,
|
|
191
|
+
message: ambiguous
|
|
192
|
+
? `Cannot resolve the kind of ${subject}: its name carries query evidence `
|
|
193
|
+
+ `(${inference.queryMatches.join(', ')}) and command evidence `
|
|
194
|
+
+ `(${inference.commandMatches.join(', ')}). `
|
|
195
|
+
+ `Declare operations.${name}.kind.`
|
|
196
|
+
: `Cannot resolve the kind of ${subject}: '${name}' leads with no known verb. `
|
|
197
|
+
+ `Rename it to lead with one, or declare operations.${name}.kind in `
|
|
198
|
+
+ `frond.config.ts.\n query: ${knownVerbs().query.join(', ')}`
|
|
199
|
+
+ `\n command: ${knownVerbs().command.join(', ')}`,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const implementation = implementationOf(frond, handler, name, resolutionDiagnostics);
|
|
205
|
+
if (!implementation) continue;
|
|
206
|
+
|
|
207
|
+
const params = contract.signature?.params ?? [];
|
|
208
|
+
const parameters = contract.binding.map((binding, position) => {
|
|
209
|
+
const param = params[position];
|
|
210
|
+
const undefinable = binding.optional
|
|
211
|
+
|| param?.optional === true
|
|
212
|
+
|| param?.type.undefined === true;
|
|
213
|
+
return {
|
|
214
|
+
position,
|
|
215
|
+
name: param?.name ?? binding.name,
|
|
216
|
+
type: param?.type.raw ?? null,
|
|
217
|
+
optional: undefinable,
|
|
218
|
+
nullable: param?.type.nullable === true,
|
|
219
|
+
undefinable,
|
|
220
|
+
binding,
|
|
221
|
+
} satisfies EffectiveParameter;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (!validateProvenance(
|
|
225
|
+
fronds,
|
|
226
|
+
frond,
|
|
227
|
+
handler,
|
|
228
|
+
name,
|
|
229
|
+
contract,
|
|
230
|
+
parameters,
|
|
231
|
+
collectorsByType,
|
|
232
|
+
resolutionDiagnostics,
|
|
233
|
+
)) continue;
|
|
234
|
+
|
|
235
|
+
// A fact is judged by the entity it names. This used to be patched into the
|
|
236
|
+
// facade after resolution, leaving check/explain with a different input.
|
|
237
|
+
let input = contract.input;
|
|
238
|
+
if (!input) {
|
|
239
|
+
const fact = contract.binding.find((binding) => binding.source.kind === 'fact');
|
|
240
|
+
if (fact?.source.kind === 'fact') input = schemas.get(fact.source.factName);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const output = effectiveOutput(frond, handler, name, contract);
|
|
244
|
+
const className = handler.ctor.name.endsWith('Handler')
|
|
245
|
+
? handler.ctor.name.slice(0, -'Handler'.length)
|
|
246
|
+
: handler.ctor.name;
|
|
247
|
+
const surface = handler.surface ? `/${handler.surface}` : '/default';
|
|
248
|
+
const remote = options.remotes?.[frond.name];
|
|
249
|
+
const op: EffectiveOperation = {
|
|
250
|
+
...contract,
|
|
251
|
+
...(input ? { input } : {}),
|
|
252
|
+
...(output.schema ? { output: output.schema } : {}),
|
|
253
|
+
id: `${frond.name}${surface}/${className}.${name}`,
|
|
254
|
+
operation: `${className}.${name}`,
|
|
255
|
+
name,
|
|
256
|
+
kind,
|
|
257
|
+
kindSource: override?.kind
|
|
258
|
+
? 'explicit'
|
|
259
|
+
: 'convention',
|
|
260
|
+
handler: {
|
|
261
|
+
className: handler.ctor.name,
|
|
262
|
+
address: handler.address,
|
|
263
|
+
filePath: handler.filePath,
|
|
264
|
+
},
|
|
265
|
+
implementation,
|
|
266
|
+
binding: contract.binding,
|
|
267
|
+
parameters,
|
|
268
|
+
collectors: parameters.flatMap((parameter) => {
|
|
269
|
+
if (parameter.binding.source.kind !== 'collector') return [];
|
|
270
|
+
const collector = collectorsByType.get(parameter.binding.source.typeName)?.[0];
|
|
271
|
+
return collector ? [{
|
|
272
|
+
parameter: parameter.name,
|
|
273
|
+
typeName: collector.typeName,
|
|
274
|
+
className: collector.ctor.name,
|
|
275
|
+
frond: frond.name,
|
|
276
|
+
filePath: collector.filePath,
|
|
277
|
+
}] : [];
|
|
278
|
+
}),
|
|
279
|
+
contexts: parameters
|
|
280
|
+
.filter((parameter) => parameter.binding.source.kind === 'context')
|
|
281
|
+
.map((parameter) => parameter.name),
|
|
282
|
+
placement: {
|
|
283
|
+
frond: frond.name,
|
|
284
|
+
runtime: remote ? 'remote' : 'local',
|
|
285
|
+
...(remote ? { remote } : {}),
|
|
286
|
+
},
|
|
287
|
+
exposure: {
|
|
288
|
+
surfaces: surfacesOf(frond, handler),
|
|
289
|
+
adapters: exposedAdapters(handler, options.adapters),
|
|
290
|
+
},
|
|
291
|
+
outputClosed: output.closed,
|
|
292
|
+
semantics: EFFECTIVE_OPERATION_SEMANTICS,
|
|
293
|
+
};
|
|
294
|
+
effective.set(name, op);
|
|
295
|
+
operations.push(op);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Topology and DI are part of the same effective program. A dependency targeting an
|
|
301
|
+
// actually remote frond is a refusal, not the warning appropriate to a future split.
|
|
302
|
+
for (const violation of verify({ fronds })) {
|
|
303
|
+
const remoteBoundary = options.remotes?.[violation.frond] !== undefined
|
|
304
|
+
|| options.remotes?.[violation.dependsOn.frond] !== undefined;
|
|
305
|
+
resolutionDiagnostics.push({
|
|
306
|
+
severity: remoteBoundary && violation.rule === 'cross-frond-dependency'
|
|
307
|
+
? 'blocking'
|
|
308
|
+
: violation.severity,
|
|
309
|
+
code: violation.rule,
|
|
310
|
+
filePath: violation.filePath,
|
|
311
|
+
frond: violation.frond,
|
|
312
|
+
subject: violation.subject,
|
|
313
|
+
message: violation.message,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const resolution = uniqueDiagnostics(resolutionDiagnostics);
|
|
318
|
+
return new EffectiveOperationModel(
|
|
319
|
+
operations,
|
|
320
|
+
uniqueDiagnostics([...scanDiagnostics, ...resolution]),
|
|
321
|
+
resolution,
|
|
322
|
+
byHandler,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function groupedCollectors(collectors: readonly CollectorEntry[]): Map<string, CollectorEntry[]> {
|
|
327
|
+
const grouped = new Map<string, CollectorEntry[]>();
|
|
328
|
+
for (const collector of collectors) {
|
|
329
|
+
const held = grouped.get(collector.typeName) ?? [];
|
|
330
|
+
held.push(collector);
|
|
331
|
+
grouped.set(collector.typeName, held);
|
|
332
|
+
}
|
|
333
|
+
return grouped;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function normalizeBinding(
|
|
337
|
+
contract: OperationContract,
|
|
338
|
+
handler: HandlerEntry,
|
|
339
|
+
frond: FrondDescriptor,
|
|
340
|
+
name: string,
|
|
341
|
+
diagnostics: ScanDiagnostic[],
|
|
342
|
+
): (OperationContract & { binding: BindingPlan }) | undefined {
|
|
343
|
+
const params = contract.signature?.params ?? [];
|
|
344
|
+
if (!contract.binding) {
|
|
345
|
+
if (params.length === 0) return { ...contract, binding: [] };
|
|
346
|
+
diagnostics.push({
|
|
347
|
+
severity: 'blocking',
|
|
348
|
+
code: 'operation-unbound',
|
|
349
|
+
filePath: handler.filePath,
|
|
350
|
+
frond: frond.name,
|
|
351
|
+
subject: `${handler.ctor.name}.${name}`,
|
|
352
|
+
message: `${handler.ctor.name}.${name} declares ${params.length} parameter(s) and has no binding plan.`,
|
|
353
|
+
});
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
if (params.length === 0) return { ...contract, binding: contract.binding };
|
|
357
|
+
|
|
358
|
+
// Explicit plans are matched by parameter name and then placed in signature order.
|
|
359
|
+
// Array order is never allowed to become semantic authority.
|
|
360
|
+
const byName = new Map<string, Binding[]>();
|
|
361
|
+
for (const binding of contract.binding) {
|
|
362
|
+
const held = byName.get(binding.name) ?? [];
|
|
363
|
+
held.push(binding);
|
|
364
|
+
byName.set(binding.name, held);
|
|
365
|
+
}
|
|
366
|
+
const expected = new Set(params.map((param) => param.name));
|
|
367
|
+
const invalid = params.filter((param) => (byName.get(param.name)?.length ?? 0) !== 1);
|
|
368
|
+
const extras = [...byName.keys()].filter((binding) => !expected.has(binding));
|
|
369
|
+
if (invalid.length > 0 || extras.length > 0) {
|
|
370
|
+
diagnostics.push({
|
|
371
|
+
severity: 'blocking',
|
|
372
|
+
code: 'operation-binding-invalid',
|
|
373
|
+
filePath: handler.filePath,
|
|
374
|
+
frond: frond.name,
|
|
375
|
+
subject: `${handler.ctor.name}.${name}`,
|
|
376
|
+
message: `Binding plan for ${handler.ctor.name}.${name} must bind every parameter exactly once by name. `
|
|
377
|
+
+ `${invalid.length ? `Invalid: ${invalid.map((param) => param.name).join(', ')}. ` : ''}`
|
|
378
|
+
+ `${extras.length ? `Unknown: ${extras.join(', ')}.` : ''}`,
|
|
379
|
+
});
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
...contract,
|
|
384
|
+
binding: params.map((param) => byName.get(param.name)![0]!),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function validateProvenance(
|
|
389
|
+
fronds: readonly FrondDescriptor[],
|
|
390
|
+
frond: FrondDescriptor,
|
|
391
|
+
handler: HandlerEntry,
|
|
392
|
+
name: string,
|
|
393
|
+
contract: OperationContract & { binding: BindingPlan },
|
|
394
|
+
parameters: EffectiveParameter[],
|
|
395
|
+
localCollectors: Map<string, CollectorEntry[]>,
|
|
396
|
+
diagnostics: ScanDiagnostic[],
|
|
397
|
+
): boolean {
|
|
398
|
+
let valid = true;
|
|
399
|
+
const declared = (handler.ctor as { __ops?: Record<string, OperationContract> }).__ops?.[name];
|
|
400
|
+
const explicitBinding = declared?.binding !== undefined
|
|
401
|
+
|| handler.operations.get(name)?.binding !== undefined
|
|
402
|
+
|| frond.operationsOverrides?.[name]?.binding !== undefined;
|
|
403
|
+
const ambiguousInput = diagnostics.some((diagnostic) =>
|
|
404
|
+
diagnostic.code === 'input-contract-ambiguous'
|
|
405
|
+
&& diagnostic.subject === `${handler.ctor.name}.${name}`);
|
|
406
|
+
const body = parameters.filter((parameter) => parameter.binding.source.kind === 'body');
|
|
407
|
+
const inferredBody = new Set<EffectiveParameter>();
|
|
408
|
+
|
|
409
|
+
if (!explicitBinding && !ambiguousInput && contract.input) {
|
|
410
|
+
if (body.length === 1) {
|
|
411
|
+
inferredBody.add(body[0]!);
|
|
412
|
+
} else if (body.length > 1) {
|
|
413
|
+
const matches = body.filter((parameter) =>
|
|
414
|
+
schemaMatches(contract.input!, contract.signature?.params[parameter.position]?.type));
|
|
415
|
+
if (matches.length > 1) {
|
|
416
|
+
diagnostics.push({
|
|
417
|
+
severity: 'blocking',
|
|
418
|
+
code: 'parameter-binding-ambiguous',
|
|
419
|
+
filePath: handler.filePath,
|
|
420
|
+
frond: frond.name,
|
|
421
|
+
subject: `${handler.ctor.name}.${name}`,
|
|
422
|
+
message: `Cannot resolve which parameter of ${handler.ctor.name}.${name} receives the body: `
|
|
423
|
+
+ `${matches.map((parameter) => `${parameter.name}: ${parameter.type ?? 'unknown'}`).join('; ')} `
|
|
424
|
+
+ 'all match the resolved input contract. Declare an explicit binding plan.',
|
|
425
|
+
});
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (matches.length === 1) inferredBody.add(matches[0]!);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// A type written with an inline object literal names nothing a collector can register
|
|
433
|
+
// under, so the parameter IS the body. Only a NAMED type can be the wrong-frond
|
|
434
|
+
// substitution the refusal below exists for.
|
|
435
|
+
for (const parameter of body) {
|
|
436
|
+
if (structural(contract.signature?.params[parameter.position]?.type)) inferredBody.add(parameter);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for (const parameter of parameters) {
|
|
440
|
+
const source = parameter.binding.source;
|
|
441
|
+
if (source.kind === 'collector') {
|
|
442
|
+
const matches = localCollectors.get(source.typeName) ?? [];
|
|
443
|
+
if (matches.length !== 1) {
|
|
444
|
+
valid = false;
|
|
445
|
+
diagnostics.push({
|
|
446
|
+
severity: 'blocking',
|
|
447
|
+
code: matches.length === 0 ? 'collector-unavailable' : 'collector-ambiguous',
|
|
448
|
+
filePath: handler.filePath,
|
|
449
|
+
frond: frond.name,
|
|
450
|
+
subject: `${handler.ctor.name}.${name}(${parameter.name})`,
|
|
451
|
+
message: matches.length === 0
|
|
452
|
+
? `No collector in frond '${frond.name}' resolves ${parameter.name}: ${parameter.type ?? source.typeName}.`
|
|
453
|
+
: `${matches.length} collectors in frond '${frond.name}' resolve '${source.typeName}'.`,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (source.kind !== 'body' || explicitBinding || ambiguousInput) continue;
|
|
459
|
+
|
|
460
|
+
const typeName = lowerFirst(contract.signature?.params[parameter.position]?.type.name ?? '');
|
|
461
|
+
const elsewhere = fronds.flatMap((candidate) => candidate === frond
|
|
462
|
+
? []
|
|
463
|
+
: candidate.collectors.filter((collector) => collector.typeName === typeName));
|
|
464
|
+
if (elsewhere.length > 0) {
|
|
465
|
+
// verify() supplies the more complete topology diagnostic; do not also call this
|
|
466
|
+
// an unknown body.
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// An inferred body must be backed by exactly one resolved interpretation. With one
|
|
471
|
+
// body, the input itself is sufficient evidence; with several, exactly one type may
|
|
472
|
+
// match. Explicit plans are authoritative because they state every provenance.
|
|
473
|
+
const recognized = explicitBinding || inferredBody.has(parameter);
|
|
474
|
+
if (recognized) continue;
|
|
475
|
+
valid = false;
|
|
476
|
+
diagnostics.push({
|
|
477
|
+
severity: 'blocking',
|
|
478
|
+
code: 'parameter-binding-unknown',
|
|
479
|
+
filePath: handler.filePath,
|
|
480
|
+
frond: frond.name,
|
|
481
|
+
subject: `${handler.ctor.name}.${name}(${parameter.name})`,
|
|
482
|
+
message: `Cannot resolve a provenance for ${handler.ctor.name}.${name}(`
|
|
483
|
+
+ `${parameter.name}: ${parameter.type ?? 'unknown'}). It is not a primitive, context, fact, `
|
|
484
|
+
+ 'local collector or resolved input schema. Declare an explicit binding.',
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return valid;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function structural(type: ParsedType | undefined): boolean {
|
|
491
|
+
return type !== undefined && type.raw.includes('{');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function schemaMatches(schema: SchemaView, type: ParsedType | undefined): boolean {
|
|
495
|
+
const names = new Set([schema.name, schema.derivation?.sourceName].filter(Boolean));
|
|
496
|
+
if (type?.name && names.has(type.name)) return true;
|
|
497
|
+
return type?.generics?.some((generic) => generic.name && names.has(generic.name)) ?? false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function implementationOf(
|
|
501
|
+
frond: FrondDescriptor,
|
|
502
|
+
handler: HandlerEntry,
|
|
503
|
+
name: string,
|
|
504
|
+
diagnostics: ScanDiagnostic[],
|
|
505
|
+
): EffectiveOperation['implementation'] | undefined {
|
|
506
|
+
const override = frond.operationsOverrides?.[name];
|
|
507
|
+
const candidates = override?.handlerName
|
|
508
|
+
? frond.handlers.filter((candidate) => candidate.ctor.name === override.handlerName)
|
|
509
|
+
: [handler];
|
|
510
|
+
if (candidates.length !== 1) {
|
|
511
|
+
diagnostics.push({
|
|
512
|
+
severity: 'blocking',
|
|
513
|
+
code: candidates.length === 0 ? 'operation-handler-unresolved' : 'operation-handler-ambiguous',
|
|
514
|
+
filePath: handler.filePath,
|
|
515
|
+
frond: frond.name,
|
|
516
|
+
subject: `${handler.ctor.name}.${name}`,
|
|
517
|
+
message: `Operation ${handler.ctor.name}.${name} names handler '${override?.handlerName}', `
|
|
518
|
+
+ `but ${candidates.length} matching handlers were found.`,
|
|
519
|
+
});
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
const implementation = candidates[0]!;
|
|
523
|
+
const method = override?.method ?? name;
|
|
524
|
+
const declared = implementation.operations.has(method)
|
|
525
|
+
|| typeof (implementation.ctor as { prototype?: Record<string, unknown> }).prototype?.[method] === 'function'
|
|
526
|
+
|| method in ((implementation.ctor as { __ops?: Record<string, unknown> }).__ops ?? {});
|
|
527
|
+
if (!declared) {
|
|
528
|
+
diagnostics.push({
|
|
529
|
+
severity: 'blocking',
|
|
530
|
+
code: 'operation-method-unresolved',
|
|
531
|
+
filePath: handler.filePath,
|
|
532
|
+
frond: frond.name,
|
|
533
|
+
subject: `${handler.ctor.name}.${name}`,
|
|
534
|
+
message: `Operation ${handler.ctor.name}.${name} resolves to `
|
|
535
|
+
+ `${implementation.ctor.name}.${method}, but that method does not exist.`,
|
|
536
|
+
});
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
className: implementation.ctor.name,
|
|
541
|
+
address: implementation.address,
|
|
542
|
+
method,
|
|
543
|
+
filePath: implementation.filePath,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function effectiveOutput(
|
|
548
|
+
frond: FrondDescriptor,
|
|
549
|
+
handler: HandlerEntry,
|
|
550
|
+
name: string,
|
|
551
|
+
contract: OperationContract,
|
|
552
|
+
): { schema?: SchemaView; closed: boolean } {
|
|
553
|
+
const perOperation = (handler.ctor as { __opOutputs?: Record<string, SchemaView> }).__opOutputs?.[name];
|
|
554
|
+
if (perOperation) return { schema: perOperation, closed: true };
|
|
555
|
+
if (contract.output) return { schema: contract.output, closed: false };
|
|
556
|
+
const handlerWide = handler.outputOverride
|
|
557
|
+
?? (handler.ctor as { __output?: SchemaView }).__output;
|
|
558
|
+
if (handlerWide) return { schema: handlerWide, closed: false };
|
|
559
|
+
|
|
560
|
+
const target = targetOf(handler.ctor) as { name?: string } | undefined;
|
|
561
|
+
const address = target?.name ? lowerFirst(target.name) : handler.address;
|
|
562
|
+
const entity = frond.entities.find((candidate) => candidate.name === address);
|
|
563
|
+
return { ...(entity ? { schema: entity.entityClass } : {}), closed: false };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function surfacesOf(frond: FrondDescriptor, handler: HandlerEntry): string[] {
|
|
567
|
+
if (handler.surface) return [handler.surface];
|
|
568
|
+
const surfaces = ['default'];
|
|
569
|
+
for (const [surface, addresses] of Object.entries(frond.surfaces ?? {})) {
|
|
570
|
+
const dedicated = frond.handlers.some((candidate) =>
|
|
571
|
+
candidate.address === handler.address && candidate.surface === surface);
|
|
572
|
+
if (!dedicated && addresses.some((address) =>
|
|
573
|
+
address.toLowerCase() === handler.address.toLowerCase())) surfaces.push(surface);
|
|
574
|
+
}
|
|
575
|
+
return [surfaces[0]!, ...surfaces.slice(1).sort()];
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function exposedAdapters(
|
|
579
|
+
handler: HandlerEntry,
|
|
580
|
+
adapters: Record<string, boolean | undefined> | undefined,
|
|
581
|
+
): string[] {
|
|
582
|
+
if (handler.exposed === false && !handler.surface) return [];
|
|
583
|
+
return Object.entries(adapters ?? {})
|
|
584
|
+
.filter(([, enabled]) => enabled === true)
|
|
585
|
+
.map(([adapter]) => adapter)
|
|
586
|
+
.sort();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function uniqueDiagnostics(diagnostics: readonly ScanDiagnostic[]): ScanDiagnostic[] {
|
|
590
|
+
const seen = new Set<string>();
|
|
591
|
+
return diagnostics.filter((diagnostic) => {
|
|
592
|
+
const key = [diagnostic.code, diagnostic.filePath, diagnostic.subject, diagnostic.message].join('\0');
|
|
593
|
+
if (seen.has(key)) return false;
|
|
594
|
+
seen.add(key);
|
|
595
|
+
return true;
|
|
596
|
+
});
|
|
597
|
+
}
|
package/src/emit.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Announcing a fact — the half of the topology `remotes:` never covered.
|
|
3
|
+
*
|
|
4
|
+
* Every other call in Fougere names **one** recipient: `remotes` names one address per
|
|
5
|
+
* frond, `Facade<T>` resolves one door. `Emit<T>` names a **subject** instead, and the
|
|
6
|
+
* number of readers is not the emitter's business.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* constructor(private published: Emit<PostPublished>) {}
|
|
10
|
+
* await this.published({ id, title }); // I say it happened. I do not know who cares.
|
|
11
|
+
*
|
|
12
|
+
* async reindex(fact: Fact<PostPublished>) {} // any handler, any frond. Nothing to register.
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* **It is a resolver, not a channel.** A bus moves messages — it has a queue, an envelope
|
|
16
|
+
* format, a delivery semantic. This has none of that: it answers *who*, then hands over to
|
|
17
|
+
* the call path that already exists. It is `resolve` returning N things instead of one.
|
|
18
|
+
* That is also why temporal decoupling stays out of reach: a resolver holds nothing. The
|
|
19
|
+
* day at-least-once is wanted, a real channel goes UNDER this, in the transport — the
|
|
20
|
+
* dispatch line becomes a publish and a consumer calls the same door on the far side.
|
|
21
|
+
*/
|
|
22
|
+
import { lowerFirst } from '@fougere/schema';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* What an emitter injects. Resolved by type, like `EntityOrm<Post>` and `Facade<T>`.
|
|
26
|
+
*
|
|
27
|
+
* The returned promise settles when the fact has been **dispatched**, never when it has
|
|
28
|
+
* been handled: a subscriber's failure is its own. The `EventBus` this replaces did
|
|
29
|
+
* `await Promise.all(handlers)` and handed their rejections back, which made a publication
|
|
30
|
+
* hostage to its own indexer.
|
|
31
|
+
*/
|
|
32
|
+
export type Emit<T> = (fact: T) => Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* What a subscriber accepts — and what it PROMISES about itself.
|
|
36
|
+
*
|
|
37
|
+
* Not merely "I subscribe". Push is the strict mode and pull is a special case of it: an
|
|
38
|
+
* op written for push is correct when called directly, the reverse is false. So this
|
|
39
|
+
* wrapper commits the operation to three things its author must honour, because no type
|
|
40
|
+
* can check them:
|
|
41
|
+
*
|
|
42
|
+
* 1. **replayable** — delivery is at-least-once the day a broker sits underneath;
|
|
43
|
+
* 2. **no reader for its return** — nobody receives what it hands back;
|
|
44
|
+
* 3. **owner of its failures** — they reach a log, never the emitter.
|
|
45
|
+
*
|
|
46
|
+
* It is the identity at runtime. The scan reads the written name, which is the whole
|
|
47
|
+
* mechanism: `binding.ts` sees `Fact<X>` and binds the parameter as a fact instead of
|
|
48
|
+
* letting it fall through to "everything else — body".
|
|
49
|
+
*/
|
|
50
|
+
export type Fact<T> = T;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The container key of an emission — THE one place that spells the format.
|
|
54
|
+
*
|
|
55
|
+
* Its dual is right below, because the boot reads the fact back out of a handler's `deps`
|
|
56
|
+
* to know which emissions to register. A key and the way to undo it belong together: the
|
|
57
|
+
* pair that is split is the pair that drifts.
|
|
58
|
+
*/
|
|
59
|
+
export function emitKeyOf(fact: string): string {
|
|
60
|
+
return `${lowerFirst(fact)}Emit`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const SUFFIX = 'Emit';
|
|
64
|
+
|
|
65
|
+
/** The fact behind an emission key, or `undefined` when the key is not one. */
|
|
66
|
+
export function factOfEmitKey(key: string): string | undefined {
|
|
67
|
+
return key.length > SUFFIX.length && key.endsWith(SUFFIX)
|
|
68
|
+
? key.slice(0, -SUFFIX.length)
|
|
69
|
+
: undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies.
|
|
74
|
+
*
|
|
75
|
+
* Two readers, which is exactly why it is a function and not a line: the boot registers one
|
|
76
|
+
* emission value per name, and the identity card publishes the same set. Spelled twice they
|
|
77
|
+
* would drift the day an emission stops being a constructor dependency.
|
|
78
|
+
*
|
|
79
|
+
* The dual — what a handler ACCEPTS — is not here, because it is not read the same way: a
|
|
80
|
+
* `Fact<T>` parameter is a fact about one operation, and `computeBindingPlan` has already
|
|
81
|
+
* written it into the plan (`app.listensTo()`).
|
|
82
|
+
*/
|
|
83
|
+
export function factsAnnouncedBy(handlers: readonly { deps: readonly string[] }[]): string[] {
|
|
84
|
+
return [...new Set(
|
|
85
|
+
handlers
|
|
86
|
+
.flatMap((handler) => handler.deps)
|
|
87
|
+
.map(factOfEmitKey)
|
|
88
|
+
.filter((fact): fact is string => fact !== undefined),
|
|
89
|
+
)];
|
|
90
|
+
}
|