@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,263 @@
|
|
|
1
|
+
import { applyCreate, type SchemaView } from '@fougere/schema';
|
|
2
|
+
import type { Container } from '@fougere/container';
|
|
3
|
+
import { validationErrorsOf } from '../wire/errors.js';
|
|
4
|
+
import { emitKeyOf, factsAnnouncedBy } from '../emit.js';
|
|
5
|
+
import { ambient } from '#ambient';
|
|
6
|
+
import { EMPTY_INVOCATION } from '../wire/invocation.js';
|
|
7
|
+
import type { Logger } from '../builtins/logger.js';
|
|
8
|
+
import type { Fronds } from '../scan/Fronds.js';
|
|
9
|
+
import type { OperationsMap } from '../wire/operation.js';
|
|
10
|
+
|
|
11
|
+
/** A door and the op on it that accepts a fact. */
|
|
12
|
+
interface Listener {
|
|
13
|
+
door: string;
|
|
14
|
+
op: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Carries an announced fact out of this process — see `CreateAppOptions.onEmit`. */
|
|
18
|
+
type Carrier = (fact: string, payload: unknown) => void | Promise<void>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Emissions — the only place in Fougere where an initiator names a SUBJECT.
|
|
22
|
+
*
|
|
23
|
+
* It was eight closures over four collections in the middle of the boot, which is a
|
|
24
|
+
* class that had not been named: the announced set, the subscriber index, the chain
|
|
25
|
+
* carried in async context, and the shapes a fact is judged against.
|
|
26
|
+
*
|
|
27
|
+
* **It is a resolver, not a channel.** It answers *who*, then hands over to the door
|
|
28
|
+
* that already exists — so a subscriber keeps its judge, its binding and its
|
|
29
|
+
* middlewares, and nothing here is durable.
|
|
30
|
+
*/
|
|
31
|
+
export class Emissions {
|
|
32
|
+
/** Who listens to what. Filled as each door's contracts are resolved. */
|
|
33
|
+
private readonly subscribers = new Map<string, Listener[]>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* What is announced here, read from the DEPS and not from the subscribers: a handler
|
|
37
|
+
* declaring `Emit<PostPublished>` must resolve it whether or not anybody listens, and
|
|
38
|
+
* announcing to nobody is legal.
|
|
39
|
+
*/
|
|
40
|
+
private readonly announced: Set<string>;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
fronds: Fronds,
|
|
44
|
+
private readonly shapes: Map<string, SchemaView>,
|
|
45
|
+
private readonly container: Container,
|
|
46
|
+
private readonly log: Logger,
|
|
47
|
+
private readonly carry?: Carrier,
|
|
48
|
+
) {
|
|
49
|
+
this.announced = new Set(fronds.flatMap((frond) => factsAnnouncedBy(frond.handlers)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Who listens to what — read from the PLAN, where `{ kind: 'fact' }` is a sentence
|
|
54
|
+
* `computeBindingPlan` already wrote, so nothing re-derives what a parameter is.
|
|
55
|
+
*
|
|
56
|
+
* It is called for a frond hosted here AND for one declared remote. A remote frond is
|
|
57
|
+
* still scanned — only its hosting is elsewhere — so its subscriptions are known, and
|
|
58
|
+
* its door resolves to a doublure. That is the whole reason an emission crosses a
|
|
59
|
+
* process without a line of transport code: the emitter learned the signature locally
|
|
60
|
+
* and calls the same key. Noting only the hosted ones left the index EMPTY under a
|
|
61
|
+
* split, and a fact announced to a remote listener reached nobody, in silence.
|
|
62
|
+
*/
|
|
63
|
+
note(contracts: OperationsMap, door: string): void {
|
|
64
|
+
for (const [op, contract] of contracts) {
|
|
65
|
+
for (const bound of contract.binding ?? []) {
|
|
66
|
+
if (bound.source.kind !== 'fact') continue;
|
|
67
|
+
const listeners = this.subscribers.get(bound.source.factName) ?? [];
|
|
68
|
+
listeners.push({ door, op });
|
|
69
|
+
this.subscribers.set(bound.source.factName, listeners);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The shape a fact is judged by, when the fact is a declared entity. */
|
|
75
|
+
shapeOf(fact: string): SchemaView | undefined {
|
|
76
|
+
return this.shapes.get(fact);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The facts this process has a listener for — what a carrier subscribes to on its behalf. */
|
|
80
|
+
listensTo(): string[] {
|
|
81
|
+
return [...this.subscribers.keys()];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Register one emission value per fact — announced here, or merely listened to.
|
|
86
|
+
*
|
|
87
|
+
* A process that only subscribes still needs the value, because `deliver` is what a
|
|
88
|
+
* carrier calls and it goes through the same door. Called once every door has been
|
|
89
|
+
* built, so the two sets are complete.
|
|
90
|
+
*/
|
|
91
|
+
register(): void {
|
|
92
|
+
for (const fact of new Set([...this.announced, ...this.subscribers.keys()])) {
|
|
93
|
+
this.container.registerValue(emitKeyOf(fact), (raw: unknown) => this.announce(fact, raw));
|
|
94
|
+
}
|
|
95
|
+
if (ambient.degraded && this.subscribers.size > 0) {
|
|
96
|
+
this.log.warn('no async context on this runtime — an emission ring is not detected');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Announcing. Dispatch, never delivery — the emitter is handed back the moment every
|
|
102
|
+
* subscriber has been HANDED the fact, not when any of them is done.
|
|
103
|
+
*
|
|
104
|
+
* The `EventBus` this replaces did `await Promise.all(handlers)` and passed their
|
|
105
|
+
* rejections up, which made a publication hostage to its own indexer.
|
|
106
|
+
*/
|
|
107
|
+
private async announce(fact: string, raw: unknown): Promise<void> {
|
|
108
|
+
/**
|
|
109
|
+
* A fact announced inside a frame that then rolls back is a lie, and nothing can take
|
|
110
|
+
* it back: announcing is DISPATCH — every subscriber has been handed the fact and the
|
|
111
|
+
* carrier has already put it on the wire — while the frame's writes are still
|
|
112
|
+
* provisional.
|
|
113
|
+
*
|
|
114
|
+
* Refused rather than deferred to the commit. Deferring would make `Emit<T>` behave
|
|
115
|
+
* differently depending on where it is called from, which is the hidden runtime the
|
|
116
|
+
* doctrine refuses; and it opens a window between the commit and the announcement whose
|
|
117
|
+
* only remedy is an outbox — a table, therefore durability, the one thing Fougere says
|
|
118
|
+
* it does not hold.
|
|
119
|
+
*
|
|
120
|
+
* A fact designates something that HAS happened. `run` returning is when that becomes
|
|
121
|
+
* true, so that is where the announcement belongs.
|
|
122
|
+
*/
|
|
123
|
+
await ambient.beforeAnnounce(fact);
|
|
124
|
+
|
|
125
|
+
const payload = this.stamped(fact, raw);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whoever is not in this process — and it is the ONLY way to reach them.
|
|
129
|
+
*
|
|
130
|
+
* The local dispatch finds its listeners by having READ their code, so it stops at
|
|
131
|
+
* the repository boundary: another team's Frond is not on this disk, and the
|
|
132
|
+
* emission reaches nobody. A carrier hands the fact to a name instead, and the far
|
|
133
|
+
* side subscribes to that same name from ITS own code. Neither reads the other.
|
|
134
|
+
*
|
|
135
|
+
* `deliver` deliberately does NOT come here: a hub that resolved this value to hand
|
|
136
|
+
* on an incoming reading echoed it straight back to the whole fleet.
|
|
137
|
+
*/
|
|
138
|
+
const carried = this.carry?.(fact, payload);
|
|
139
|
+
if (carried) void Promise.resolve(carried).catch((cause) => this.log.error(`${fact} — carrier refused it`, cause));
|
|
140
|
+
|
|
141
|
+
for (const { door, op, done } of this.handToListeners(fact, payload)) {
|
|
142
|
+
void done.catch((cause) => this.log.error(`${fact} → ${door}.${op}`, this.describeRefusal(fact, cause) ?? cause));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Receiving. **The opposite rule, deliberately**: this one waits, and it tells.
|
|
148
|
+
*
|
|
149
|
+
* `deliver` is what a CARRIER calls, and a carrier's whole job is to know whether the
|
|
150
|
+
* fact landed — at-least-once is retrying what failed, so a delivery that cannot report
|
|
151
|
+
* makes durability impossible to build on top. It used to be the announcement itself:
|
|
152
|
+
* it resolved before any subscriber had run and swallowed every failure into a log, so a
|
|
153
|
+
* queue calling it could only ever ack blindly.
|
|
154
|
+
*
|
|
155
|
+
* That is not a contradiction of "dispatch is not delivery". That rule protects the
|
|
156
|
+
* EMITTER, which must not become hostage to a subscriber; a carrier is not the emitter,
|
|
157
|
+
* it is precisely the party whose business this is.
|
|
158
|
+
*
|
|
159
|
+
* What it still does not do is HOLD anything. A fact refused here is refused, and the
|
|
160
|
+
* carrier decides whether it comes back — which is the whole of Fougere's position on
|
|
161
|
+
* durability: the channel goes underneath, it is not reimplemented here.
|
|
162
|
+
*/
|
|
163
|
+
async deliver(fact: string, payload: unknown): Promise<void> {
|
|
164
|
+
const handed = this.handToListeners(fact, payload);
|
|
165
|
+
const settled = await Promise.allSettled(handed.map((h) => h.done));
|
|
166
|
+
|
|
167
|
+
const refused = settled.flatMap((result, i) =>
|
|
168
|
+
result.status === 'rejected' ? [{ ...handed[i], reason: result.reason as unknown }] : []);
|
|
169
|
+
for (const { door, op, reason } of refused) {
|
|
170
|
+
this.log.error(`${fact} → ${door}.${op}`, this.describeRefusal(fact, reason) ?? reason);
|
|
171
|
+
}
|
|
172
|
+
if (refused.length > 0) {
|
|
173
|
+
throw new AggregateError(
|
|
174
|
+
refused.map((r) => r.reason),
|
|
175
|
+
`${fact} — ${refused.length} of ${handed.length} listener(s) refused it`
|
|
176
|
+
+ ` (${refused.map((r) => `${r.door}.${r.op}`).join(', ')}).`
|
|
177
|
+
+ ` Nothing here holds it: the carrier decides whether it comes back.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The announcement realizes the fact's own `lifecycle.create` — a `created()` stamped,
|
|
184
|
+
* an id generated, a default applied.
|
|
185
|
+
*
|
|
186
|
+
* `applyCreate` (`schema/src/axis/lifecycle/apply.ts`) is where the split is realized:
|
|
187
|
+
* the judge never fills a hole, the STORAGE does, at the point of persistence. A fact has no storage, so nobody did — the judge declared an
|
|
188
|
+
* absent `created()` legal and omitted it, and a subscriber received a value missing a
|
|
189
|
+
* field its own type promises. Announcing is a fact's point of persistence.
|
|
190
|
+
*
|
|
191
|
+
* Announcing only, never `deliver`: a fact that arrives from elsewhere was stamped by
|
|
192
|
+
* its sender, and stamping it again would give one fact a different identity in every
|
|
193
|
+
* process that relayed it.
|
|
194
|
+
*
|
|
195
|
+
* **A typed emitter cannot reach this yet.** `Emit<T>` names the ROW type, where a
|
|
196
|
+
* `created()` field is present and required, so `announce({ id, title })` is a compile
|
|
197
|
+
* error and the author writes `at: new Date()` anyway. So this runs for a payload built
|
|
198
|
+
* outside the type — a bridge, a replay, a test — and is inert for everyone else.
|
|
199
|
+
*/
|
|
200
|
+
private stamped(fact: string, raw: unknown): unknown {
|
|
201
|
+
const shape = this.shapes.get(fact);
|
|
202
|
+
return shape && raw !== null && typeof raw === 'object' && !Array.isArray(raw)
|
|
203
|
+
? applyCreate(shape.getFields(), raw as Record<string, unknown>)
|
|
204
|
+
: raw;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Hand the fact to every listener in THIS process, and give back one promise each.
|
|
209
|
+
*
|
|
210
|
+
* The call goes THROUGH the door, so a subscriber meets the same judge, the same binding
|
|
211
|
+
* and the same middlewares as any caller. Nothing new answers for correctness — that is
|
|
212
|
+
* the dividend of a subscriber being an ordinary op rather than a special kind.
|
|
213
|
+
*
|
|
214
|
+
* It returns the promises rather than settling them, because the two callers want
|
|
215
|
+
* opposite things and only one of them is wrong to wait.
|
|
216
|
+
*/
|
|
217
|
+
private handToListeners(fact: string, payload: unknown): (Listener & { done: Promise<unknown> })[] {
|
|
218
|
+
const walked = ambient.currentChain();
|
|
219
|
+
if (walked.includes(fact)) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Emission cycle: ${[...walked, fact].join(' → ')}.\n`
|
|
222
|
+
+ ` A fact cannot cause itself. One of the subscribers above announces a fact that leads back here.`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const listeners = this.subscribers.get(fact) ?? [];
|
|
227
|
+
if (listeners.length === 0) {
|
|
228
|
+
this.log.debug(`${fact} — nobody listens in this process`);
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return listeners.map(({ door, op }) => ({
|
|
233
|
+
door,
|
|
234
|
+
op,
|
|
235
|
+
done: ambient.enterChain(fact, async () => {
|
|
236
|
+
let facade: Record<string, Function>;
|
|
237
|
+
try {
|
|
238
|
+
facade = this.container.resolve<Record<string, Function>>(door);
|
|
239
|
+
} catch (cause) {
|
|
240
|
+
throw new Error(`${fact} → ${door} could not be reached`, { cause });
|
|
241
|
+
}
|
|
242
|
+
return facade[op]({ ...EMPTY_INVOCATION, body: payload });
|
|
243
|
+
}),
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A subscriber refusing the SHAPE, said in one line instead of dumped as an error.
|
|
249
|
+
*
|
|
250
|
+
* This is the one refusal nobody else will ever see. A door hands its 400 back to the
|
|
251
|
+
* caller who can fix it; a fact is dispatched, not delivered, so the sender learns
|
|
252
|
+
* nothing and the log is the whole of the evidence. The most likely cause is also the
|
|
253
|
+
* one a stack trace hides worst — this process's copy is older than the sender's — so
|
|
254
|
+
* the line names the fields and the remedy, and hedges because a genuinely bad payload
|
|
255
|
+
* produces the same refusal.
|
|
256
|
+
*/
|
|
257
|
+
private describeRefusal(fact: string, cause: unknown): string | undefined {
|
|
258
|
+
const refusals = validationErrorsOf(cause);
|
|
259
|
+
if (!refusals?.length) return undefined;
|
|
260
|
+
return `refused the shape — ${refusals.map((d) => `${d.path}: ${d.message}`).join(', ')}.`
|
|
261
|
+
+ ` If '${fact}' gained a field, this copy is older than the sender's: re-run \`fougere sync\`.`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { lowerFirst, type Fields } from '@fougere/schema';
|
|
2
|
+
import type { Container } from '@fougere/container';
|
|
3
|
+
import type { AppMiddleware } from '../wire/middleware.js';
|
|
4
|
+
import type { CollectorResolver } from './binding.js';
|
|
5
|
+
import { collectorKeyOf } from '../prefab/collector.js';
|
|
6
|
+
import { presenterKeyOf } from '../prefab/presenter.js';
|
|
7
|
+
import { repositoryKeyOf } from '../prefab/repository.js';
|
|
8
|
+
import { targetOf } from '../prefab/prefab.js';
|
|
9
|
+
import type { OperationContract, OperationsMap } from '../wire/operation.js';
|
|
10
|
+
import type { EffectiveOperation, EffectiveOperationsMap } from '../effective-operation.js';
|
|
11
|
+
import type { InvocationContext } from '../wire/invocation.js';
|
|
12
|
+
import type { Emissions } from './Emissions.js';
|
|
13
|
+
import type { Logger } from '../builtins/logger.js';
|
|
14
|
+
import type { EntityEntry, HandlerEntry, PresenterEntry } from '../scan/frond.js';
|
|
15
|
+
import { InputValidator } from '../dispatch/InputValidator.js';
|
|
16
|
+
import { ArgumentResolver } from '../dispatch/ArgumentResolver.js';
|
|
17
|
+
import { OperationExecutor } from '../dispatch/OperationExecutor.js';
|
|
18
|
+
import { OutputProjector } from '../dispatch/OutputProjector.js';
|
|
19
|
+
import { OutputView } from '../dispatch/OutputView.js';
|
|
20
|
+
import { PresenterExecutor } from '../dispatch/PresenterExecutor.js';
|
|
21
|
+
import { PresenterArgumentResolver } from '../dispatch/PresenterArgumentResolver.js';
|
|
22
|
+
|
|
23
|
+
/** What this door is about: a handler, the entity behind it when there is one, and where it resolves. */
|
|
24
|
+
export interface Doorway {
|
|
25
|
+
handler: HandlerEntry;
|
|
26
|
+
/** Handlers in the owning frond, used to realize a resolved implementation override. */
|
|
27
|
+
handlers: readonly HandlerEntry[];
|
|
28
|
+
/** The canonical operation table resolved before boot performs any side effect. */
|
|
29
|
+
operations: EffectiveOperationsMap;
|
|
30
|
+
/** The subject — absent is ordinary: a health check owns no row. */
|
|
31
|
+
entity: EntityEntry | undefined;
|
|
32
|
+
/** The scope the handler and its collectors resolve in — a surface gets its own. */
|
|
33
|
+
scope: Container;
|
|
34
|
+
/** The container key this door answers under. */
|
|
35
|
+
key: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** What the frond and the boot supply to every door alike. */
|
|
39
|
+
export interface Wiring {
|
|
40
|
+
/** The frond this door belongs to — travels on every OperationContext. */
|
|
41
|
+
frond: string;
|
|
42
|
+
/** The frond's own scope — where presenters live, whatever sub-scope the door uses. */
|
|
43
|
+
frondScope: Container;
|
|
44
|
+
log: Logger;
|
|
45
|
+
/** Entity names this frond has a collector for. */
|
|
46
|
+
collectors: Set<string>;
|
|
47
|
+
presenters: Map<string, PresenterEntry>;
|
|
48
|
+
/** The middlewares that apply to an address, read at call time and never at boot. */
|
|
49
|
+
middlewaresFor: (address: string) => AppMiddleware[];
|
|
50
|
+
emissions: Emissions;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Adapts one handler door to executable operations. */
|
|
54
|
+
export class HandlerFacade {
|
|
55
|
+
/** Rich operation facts shared with check, explain and adapters. */
|
|
56
|
+
readonly effectiveOperations: EffectiveOperationsMap;
|
|
57
|
+
/** Contracts served by this door. */
|
|
58
|
+
readonly contracts: OperationsMap;
|
|
59
|
+
|
|
60
|
+
/** The callable surface: op name → the function a caller reaches. */
|
|
61
|
+
readonly ops: Record<string, Function> = {};
|
|
62
|
+
|
|
63
|
+
private readonly cachedViews = new Map<string, OutputView>();
|
|
64
|
+
private instance: any;
|
|
65
|
+
private readonly implementationKeys = new Map<string, string>();
|
|
66
|
+
private readonly implementationInstances = new Map<string, any>();
|
|
67
|
+
private readonly inputValidator = new InputValidator();
|
|
68
|
+
private readonly argumentResolver = new ArgumentResolver(
|
|
69
|
+
(typeName) => this.collectorResolver(typeName),
|
|
70
|
+
);
|
|
71
|
+
private readonly presenterArguments: PresenterArgumentResolver;
|
|
72
|
+
|
|
73
|
+
constructor(private readonly door: Doorway, private readonly wiring: Wiring) {
|
|
74
|
+
const { handler, entity } = door;
|
|
75
|
+
this.presenterArguments = new PresenterArgumentResolver(
|
|
76
|
+
this.argumentResolver,
|
|
77
|
+
wiring.collectors,
|
|
78
|
+
);
|
|
79
|
+
this.refuseCrudWithoutRepository(handler);
|
|
80
|
+
|
|
81
|
+
door.scope.register(this.handlerKey, handler.ctor, { deps: this.depsOf(handler) });
|
|
82
|
+
|
|
83
|
+
this.effectiveOperations = door.operations;
|
|
84
|
+
this.contracts = new Map(
|
|
85
|
+
[...door.operations].map(([name, operation]) => [name, operation as OperationContract] as const),
|
|
86
|
+
);
|
|
87
|
+
handler.operations = this.contracts;
|
|
88
|
+
|
|
89
|
+
// Register model-selected implementations in the same execution scope.
|
|
90
|
+
for (const [name, operation] of door.operations) {
|
|
91
|
+
if (this.isBaseImplementation(operation)) continue;
|
|
92
|
+
const implementation = this.implementationHandler(name);
|
|
93
|
+
this.refuseCrudWithoutRepository(implementation);
|
|
94
|
+
const key = `${this.handlerKey}:implementation:${operation.implementation.className}`;
|
|
95
|
+
if (!this.implementationKeys.has(operation.implementation.className)) {
|
|
96
|
+
door.scope.register(key, implementation.ctor, { deps: this.depsOf(implementation) });
|
|
97
|
+
this.implementationKeys.set(operation.implementation.className, key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Emissions use the same contracts and execution path as direct calls.
|
|
102
|
+
wiring.emissions.note(this.contracts, door.key);
|
|
103
|
+
// Only declared operations become callable façade members.
|
|
104
|
+
for (const op of this.contracts.keys()) this.ops[op] = this.wrap(op);
|
|
105
|
+
|
|
106
|
+
if (this.inheritsCrud(handler) && !entity) {
|
|
107
|
+
// An installed Crud subject may be absent from the local scan.
|
|
108
|
+
wiring.log.debug(`${handler.ctor.name} extends Crud() and no scanned entity is named `
|
|
109
|
+
+ `'${this.subjectOf(handler)}' — installed entity, or a missing one: no ORM will be injected`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Storage follows the Crud subject, which may differ from the door address. */
|
|
114
|
+
private subjectOf(handler: HandlerEntry): string {
|
|
115
|
+
const target = targetOf(handler.ctor);
|
|
116
|
+
return target?.name ? lowerFirst(target.name) : handler.address;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private inheritsCrud(handler: HandlerEntry): boolean {
|
|
120
|
+
const proto = handler.ctor.prototype;
|
|
121
|
+
return typeof proto?.list === 'function' && typeof proto?.findById === 'function';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private get handlerKey(): string {
|
|
125
|
+
return `_handler:${this.door.key}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private depsOf(handler: HandlerEntry): string[] {
|
|
129
|
+
const { deps } = handler;
|
|
130
|
+
if (deps.length > 0) return deps;
|
|
131
|
+
return this.inheritsCrud(handler) ? [repositoryKeyOf(this.subjectOf(handler))] : [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A custom Crud constructor must explicitly receive its repository. */
|
|
135
|
+
private refuseCrudWithoutRepository(handler: HandlerEntry): void {
|
|
136
|
+
const repoTypeName = repositoryKeyOf(this.subjectOf(handler));
|
|
137
|
+
if (!this.inheritsCrud(handler) || handler.deps.length === 0 || handler.deps.includes(repoTypeName)) return;
|
|
138
|
+
throw new Error(
|
|
139
|
+
`${handler.ctor.name} extends Crud() and declares a constructor, so its storage is no ` +
|
|
140
|
+
`longer injected for it — but it does not take any.\n` +
|
|
141
|
+
` Add it and hand it to super():\n` +
|
|
142
|
+
` constructor(repo: ${repoTypeName}, …) { super(repo); }`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Resolve and cache the output view declared for one operation. */
|
|
147
|
+
private viewOf(op: string): OutputView {
|
|
148
|
+
const known = this.cachedViews.get(op);
|
|
149
|
+
if (known) return known;
|
|
150
|
+
|
|
151
|
+
const operation = this.effectiveOperations.get(op);
|
|
152
|
+
const schema = operation?.output as { getFields?: () => Fields } | undefined;
|
|
153
|
+
const resolved = new OutputView(
|
|
154
|
+
typeof schema?.getFields === 'function' ? schema.getFields() : {},
|
|
155
|
+
operation?.outputClosed ?? false,
|
|
156
|
+
);
|
|
157
|
+
this.cachedViews.set(op, resolved);
|
|
158
|
+
return resolved;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private collectorResolver = (typeName: string): CollectorResolver | undefined => {
|
|
162
|
+
try { return this.door.scope.resolve(collectorKeyOf(typeName)) as CollectorResolver; }
|
|
163
|
+
catch { return undefined; }
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** The handler itself, resolved on first call — never at boot. */
|
|
167
|
+
private resolvedHandler(): any {
|
|
168
|
+
if (!this.instance) this.instance = this.door.scope.resolve(this.handlerKey);
|
|
169
|
+
return this.instance;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private isBaseImplementation(operation: EffectiveOperation): boolean {
|
|
173
|
+
return operation.implementation.className === this.door.handler.ctor.name
|
|
174
|
+
&& operation.implementation.address === this.door.handler.address
|
|
175
|
+
&& operation.implementation.filePath === this.door.handler.filePath;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The exact handler entry the pure model selected; no name-only retry or fallback. */
|
|
179
|
+
private implementationHandler(operationName: string): HandlerEntry {
|
|
180
|
+
const operation = this.effectiveOperations.get(operationName)!;
|
|
181
|
+
const matches = this.door.handlers.filter((handler) =>
|
|
182
|
+
handler.ctor.name === operation.implementation.className
|
|
183
|
+
&& handler.address === operation.implementation.address
|
|
184
|
+
&& handler.filePath === operation.implementation.filePath);
|
|
185
|
+
if (matches.length !== 1) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`EffectiveOperation '${operation.id}' names ${operation.implementation.className}.`
|
|
188
|
+
+ `${operation.implementation.method}, but boot found ${matches.length} matching handlers.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return matches[0]!;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private resolvedImplementation(operationName: string): { instance: any; method: string } {
|
|
195
|
+
const operation = this.effectiveOperations.get(operationName)!;
|
|
196
|
+
if (this.isBaseImplementation(operation)) {
|
|
197
|
+
return { instance: this.resolvedHandler(), method: operation.implementation.method };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const className = operation.implementation.className;
|
|
201
|
+
const key = this.implementationKeys.get(className);
|
|
202
|
+
if (!key) throw new Error(`No registered implementation for EffectiveOperation '${operation.id}'.`);
|
|
203
|
+
let instance = this.implementationInstances.get(key);
|
|
204
|
+
if (!instance) {
|
|
205
|
+
instance = this.door.scope.resolve(key);
|
|
206
|
+
this.implementationInstances.set(key, instance);
|
|
207
|
+
}
|
|
208
|
+
return { instance, method: operation.implementation.method };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private wrap(op: string): (invocation?: InvocationContext) => Promise<unknown> {
|
|
212
|
+
const address = this.door.handler.address;
|
|
213
|
+
const contract = this.contracts.get(op);
|
|
214
|
+
const view = this.viewOf(op);
|
|
215
|
+
const presenter = this.wiring.presenters.get(address);
|
|
216
|
+
const executor = new OperationExecutor({
|
|
217
|
+
entity: address,
|
|
218
|
+
frond: this.wiring.frond,
|
|
219
|
+
operation: op,
|
|
220
|
+
contract,
|
|
221
|
+
middlewares: () => this.wiring.middlewaresFor(address),
|
|
222
|
+
validator: this.inputValidator,
|
|
223
|
+
arguments: this.argumentResolver,
|
|
224
|
+
invoke: async (args) => {
|
|
225
|
+
const implementation = this.resolvedImplementation(op);
|
|
226
|
+
return implementation.instance[implementation.method](...args);
|
|
227
|
+
},
|
|
228
|
+
projector: new OutputProjector(view),
|
|
229
|
+
...(view.closed || !presenter ? {} : {
|
|
230
|
+
present: async (result: unknown, effective: InvocationContext) =>
|
|
231
|
+
new PresenterExecutor(
|
|
232
|
+
this.wiring.frondScope.resolve(presenterKeyOf(address)),
|
|
233
|
+
presenter.fields,
|
|
234
|
+
address,
|
|
235
|
+
op,
|
|
236
|
+
).present(result, await this.presenterArguments.resolve(presenter, effective)),
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return (invocation) => executor.execute(invocation);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a call needs to know about the call it is inside, and the two ways to answer it.
|
|
3
|
+
*
|
|
4
|
+
* One realization reads an async context (`ambient.als.ts`), the other cannot and waits
|
|
5
|
+
* its turn instead (`ambient.queue.ts`). `#ambient` in package.json picks one per runtime,
|
|
6
|
+
* so a bundle for a host without async context never names `node:async_hooks`.
|
|
7
|
+
*/
|
|
8
|
+
export interface Ambient {
|
|
9
|
+
/** True when this runtime has no async context, and the boot says so. */
|
|
10
|
+
readonly degraded: boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Run `fn` as the frame `key`. A frame opened inside another must not proceed.
|
|
14
|
+
*
|
|
15
|
+
* Measured on SQLite: a second transaction on the same connection WAITS for the first,
|
|
16
|
+
* so a nested frame hangs on one engine and returns when split across two.
|
|
17
|
+
*/
|
|
18
|
+
enterFrame<R>(key: string, fn: () => Promise<R>): Promise<R>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Settle before `fact` is dispatched: a fact announced inside a frame that then rolls
|
|
22
|
+
* back is a lie. Refuses when the caller's own frame can be told, waits when it cannot.
|
|
23
|
+
*/
|
|
24
|
+
beforeAnnounce(fact: string): Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The facts already being announced up the stack, so a fact cannot cause itself.
|
|
28
|
+
*
|
|
29
|
+
* A CHAIN and not a depth: `A → B → D` and `A → C → D` is a diamond, perfectly legal,
|
|
30
|
+
* while `A → … → A` never ends. Empty where it cannot be followed.
|
|
31
|
+
*
|
|
32
|
+
* Detecting this at boot was the first idea and it was wrong: `Emit<G>` is a CONSTRUCTOR
|
|
33
|
+
* dependency, so it belongs to the handler and not to one of its methods. A handler that
|
|
34
|
+
* subscribes to `A` in one method and emits `G` from another would have been refused for
|
|
35
|
+
* a cycle it never walks.
|
|
36
|
+
*/
|
|
37
|
+
currentChain(): readonly string[];
|
|
38
|
+
|
|
39
|
+
/** Run `fn` with `fact` appended to the emission chain. */
|
|
40
|
+
enterChain<R>(fact: string, fn: () => Promise<R>): Promise<R>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The realization that can tell whose frame is open: an async context.
|
|
3
|
+
*
|
|
4
|
+
* Chosen by `#ambient` wherever `node:async_hooks` exists — Node, Deno, Bun, and workerd
|
|
5
|
+
* with `nodejs_als`. Every refusal here is immediate and names its cause.
|
|
6
|
+
*/
|
|
7
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
|
+
import type { Ambient } from './ambient-port.js';
|
|
9
|
+
|
|
10
|
+
const frame = new AsyncLocalStorage<string>();
|
|
11
|
+
const chain = new AsyncLocalStorage<readonly string[]>();
|
|
12
|
+
|
|
13
|
+
export const ambient: Ambient = {
|
|
14
|
+
degraded: false,
|
|
15
|
+
|
|
16
|
+
enterFrame<R>(key: string, fn: () => Promise<R>): Promise<R> {
|
|
17
|
+
const held = frame.getStore();
|
|
18
|
+
if (held !== undefined) {
|
|
19
|
+
return Promise.reject(new Error(
|
|
20
|
+
`Together<[…]> (${key}) cannot be opened inside ${held}: on one engine the second `
|
|
21
|
+
+ `transaction waits for the first and the call hangs. Two frames that must both hold `
|
|
22
|
+
+ `are ONE frame — declare a single Together naming every member.`,
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
return frame.run(key, fn);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
beforeAnnounce(fact: string): Promise<void> {
|
|
29
|
+
const held = frame.getStore();
|
|
30
|
+
if (held === undefined) return Promise.resolve();
|
|
31
|
+
return Promise.reject(new Error(
|
|
32
|
+
`${fact} cannot be announced inside Together<[…]> (${held}): announcing is dispatch, `
|
|
33
|
+
+ `so subscribers and the carrier would have it while these writes can still be taken `
|
|
34
|
+
+ `back. Announce after run() returns, when it is true.`,
|
|
35
|
+
));
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
currentChain: () => chain.getStore() ?? [],
|
|
39
|
+
|
|
40
|
+
enterChain<R>(fact: string, fn: () => Promise<R>): Promise<R> {
|
|
41
|
+
return chain.run([...(chain.getStore() ?? []), fact], fn);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The realization for a runtime with no async context: it cannot tell whose frame is
|
|
3
|
+
* open, so it never guesses — it waits its turn.
|
|
4
|
+
*
|
|
5
|
+
* Two consequences the boot reports rather than hides: frames run one at a time, and a
|
|
6
|
+
* frame opened inside another is not refused, it waits for itself and times out.
|
|
7
|
+
*/
|
|
8
|
+
import type { Ambient } from './ambient-port.js';
|
|
9
|
+
|
|
10
|
+
/** What a nested frame waits before it is told what it did — the hang, measured, plus a name. */
|
|
11
|
+
const NESTED_TIMEOUT_MS = 5_000;
|
|
12
|
+
|
|
13
|
+
let busy: Promise<void> | undefined;
|
|
14
|
+
let holder: string | undefined;
|
|
15
|
+
|
|
16
|
+
const settled = () => undefined;
|
|
17
|
+
|
|
18
|
+
/** Resolves when `p` settles or `ms` elapses, whichever comes first, holding no timer after. */
|
|
19
|
+
function raceWith(p: Promise<void>, ms: number): Promise<void> {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const timer = setTimeout(resolve, ms);
|
|
22
|
+
const clear = () => { clearTimeout(timer); resolve(); };
|
|
23
|
+
void p.then(clear, clear);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ambient: Ambient = {
|
|
28
|
+
degraded: true,
|
|
29
|
+
|
|
30
|
+
async enterFrame<R>(key: string, fn: () => Promise<R>): Promise<R> {
|
|
31
|
+
const deadline = Date.now() + NESTED_TIMEOUT_MS;
|
|
32
|
+
for (;;) {
|
|
33
|
+
// Nothing may await between this test and the reservation below, or two waiters
|
|
34
|
+
// woken in the same tick would both take the frame.
|
|
35
|
+
if (busy === undefined) break;
|
|
36
|
+
const left = deadline - Date.now();
|
|
37
|
+
if (left <= 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Together<[…]> (${key}) waited ${NESTED_TIMEOUT_MS}ms for ${holder} and gave up. `
|
|
40
|
+
+ `This runtime has no async context, so a frame opened inside another waits for `
|
|
41
|
+
+ `itself instead of being refused. Two frames that must both hold are ONE frame — `
|
|
42
|
+
+ `declare a single Together naming every member.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
await raceWith(busy, left);
|
|
46
|
+
}
|
|
47
|
+
// Held BEFORE `fn` runs: the first thing a frame's body does may be to open another,
|
|
48
|
+
// and reserving on the returned promise would be one tick too late to see it.
|
|
49
|
+
let release = settled as () => void;
|
|
50
|
+
busy = new Promise<void>((resolve) => { release = resolve; });
|
|
51
|
+
holder = key;
|
|
52
|
+
try {
|
|
53
|
+
return await fn();
|
|
54
|
+
} finally {
|
|
55
|
+
busy = undefined;
|
|
56
|
+
holder = undefined;
|
|
57
|
+
release();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Waits rather than refuses: whose frame is open cannot be told here, and refusing on
|
|
63
|
+
* that would refuse an announcement made by an unrelated call.
|
|
64
|
+
*/
|
|
65
|
+
async beforeAnnounce(_fact: string): Promise<void> {
|
|
66
|
+
while (busy) await raceWith(busy, NESTED_TIMEOUT_MS);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/** No chain can be followed here — an emission ring is not detected. Said at boot. */
|
|
70
|
+
currentChain: () => [],
|
|
71
|
+
|
|
72
|
+
enterChain<R>(_fact: string, fn: () => Promise<R>): Promise<R> {
|
|
73
|
+
return fn();
|
|
74
|
+
},
|
|
75
|
+
};
|