@fougere/core 0.2.0-alpha.2 → 0.3.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 +2 -1
- 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 +48 -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 +721 -0
- package/dist/boot/bootstrap.js.map +1 -0
- package/dist/boot/egress.d.ts +8 -0
- package/dist/boot/egress.d.ts.map +1 -0
- package/dist/boot/egress.js +11 -0
- package/dist/boot/egress.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 +181 -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} +24 -26
- 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 +21 -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 +450 -0
- package/dist/effective-operation.js.map +1 -0
- 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 +1 -1
- 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 +211 -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} +18 -9
- package/dist/scan/handler-parser.d.ts.map +1 -0
- package/dist/scan/handler-parser.js +625 -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} +185 -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} +70 -1
- package/dist/wire/call.d.ts.map +1 -0
- package/dist/{call.js → wire/call.js} +48 -75
- 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 +24 -7
- 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,721 @@
|
|
|
1
|
+
import { registrationKeyOf } from '@fougere/schema';
|
|
2
|
+
import { Logger } from '../builtins/logger.js';
|
|
3
|
+
import { Config } from '../builtins/config.js';
|
|
4
|
+
import { createRemoteRouter, createRemoteFacade } from './remote.js';
|
|
5
|
+
import { registerFrames } from './together.js';
|
|
6
|
+
import { Emissions } from './Emissions.js';
|
|
7
|
+
import { HandlerFacade } from './HandlerFacade.js';
|
|
8
|
+
import { targetOf } from '../prefab/prefab.js';
|
|
9
|
+
import { ownersOf, refuseOrmInUserCode, refuseCrudOnOwned } from './ownership.js';
|
|
10
|
+
import { resolveEffectiveOperations, } from '../effective-operation.js';
|
|
11
|
+
import { guardStorage } from './egress.js';
|
|
12
|
+
import { portBindings } from './ports.js';
|
|
13
|
+
import { InFlight } from '../dispatch/InFlight.js';
|
|
14
|
+
// The keys, each read from where its concept is declared — never respelled here.
|
|
15
|
+
import { facadeKeyOf, contractsKeyOf, identityCardOf } from '../wire/call.js';
|
|
16
|
+
import { AppLifecycle } from './AppLifecycle.js';
|
|
17
|
+
import { repositoryKeyOf } from '../prefab/repository.js';
|
|
18
|
+
import { ormKeyOf } from '../orm.js';
|
|
19
|
+
import { presenterKeyOf } from '../prefab/presenter.js';
|
|
20
|
+
import { collectorKeyOf } from '../prefab/collector.js';
|
|
21
|
+
import { RouteAddress } from '../contract/RouteAddress.js';
|
|
22
|
+
import { DispatchLifecycle } from '../dispatch/DispatchLifecycle.js';
|
|
23
|
+
import { Dispatcher } from '../dispatch/Dispatcher.js';
|
|
24
|
+
import { LocalRoutePolicy } from '../dispatch/LocalRoutePolicy.js';
|
|
25
|
+
import { OperationRoute } from '../dispatch/OperationRoute.js';
|
|
26
|
+
import { RemoteRouteResolver } from '../dispatch/RemoteRouteResolver.js';
|
|
27
|
+
import { RouteRegistry } from '../dispatch/RouteRegistry.js';
|
|
28
|
+
import { FacadeEntry } from '../entry/FacadeEntry.js';
|
|
29
|
+
/**
|
|
30
|
+
* The one wording for "nobody hosts this here", with both ways out. Said twice, and the
|
|
31
|
+
* second copy (`schemaFor`) had lost the two remedies — the same dead end, strictly less
|
|
32
|
+
* useful, for no reason.
|
|
33
|
+
*/
|
|
34
|
+
const notLoaded = (entity) => `Frond for '${entity}' is not loaded.\n` +
|
|
35
|
+
` - Add '${entity}' to --fronds flag\n` +
|
|
36
|
+
` - Or declare a remote: remotes: { ${entity}: 'http://...' }`;
|
|
37
|
+
/**
|
|
38
|
+
* Two fronds cannot claim one name — said at boot, because nothing else says it.
|
|
39
|
+
*
|
|
40
|
+
* A door lands on the ROOT container under `facadeKeyOf(address)`, and that key carries
|
|
41
|
+
* no frond; a presenter lands there too. `registerValue` is a `Map.set`, so the second
|
|
42
|
+
* frond loaded simply replaced the first and every call meant for one went to the other.
|
|
43
|
+
* Silent in-process, and worse than silent under a split: `createRemoteRouter` guards its
|
|
44
|
+
* index with `if (!byEntity.has(...))`, so THERE the first frond discovered wins. The same
|
|
45
|
+
* application answered differently depending on how it was deployed.
|
|
46
|
+
*
|
|
47
|
+
* Refusing is the honest answer while a key cannot say which frond owns it. It is not the
|
|
48
|
+
* last word: qualify the key and this boot can accept both. ORMs and repositories are not
|
|
49
|
+
* checked because they are registered in the frond's own scope, where two fronds do not meet.
|
|
50
|
+
*
|
|
51
|
+
* A frond declared remote registers nothing locally, so it cannot collide here. Two REMOTE
|
|
52
|
+
* fronds publishing one entity still shadow each other inside the router — a hole this
|
|
53
|
+
* check does not reach.
|
|
54
|
+
*/
|
|
55
|
+
function assertOneOwnerPerKey(fronds, remotes) {
|
|
56
|
+
const owner = new Map();
|
|
57
|
+
const claim = (key, frond, what) => {
|
|
58
|
+
const held = owner.get(key);
|
|
59
|
+
if (held !== undefined && held !== frond) {
|
|
60
|
+
throw new Error(`Two fronds claim the key '${key}': '${held}' and '${frond}'.\n`
|
|
61
|
+
+ ` A ${what} is registered under a key that names no frond, so one would silently replace the other.\n`
|
|
62
|
+
+ ` - Rename one of the two classes, or\n`
|
|
63
|
+
+ ` - keep one of the two fronds out of this process (--fronds), or declare it in remotes:`);
|
|
64
|
+
}
|
|
65
|
+
owner.set(key, frond);
|
|
66
|
+
};
|
|
67
|
+
for (const frond of fronds) {
|
|
68
|
+
if (remotes && frond.name in remotes)
|
|
69
|
+
continue;
|
|
70
|
+
for (const handler of frond.handlers)
|
|
71
|
+
claim(facadeKeyOf(handler.address, handler.surface), frond.name, 'door');
|
|
72
|
+
for (const presenter of frond.presenters)
|
|
73
|
+
claim(presenterKeyOf(presenter.entityName), frond.name, 'presenter');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Bootstrap a fougere application. */
|
|
77
|
+
export async function createApp(options) {
|
|
78
|
+
const container = options.createContainer();
|
|
79
|
+
// Boot chatter is debug by default; a host (e.g. the CLI) can quiet it.
|
|
80
|
+
const log = new Logger('boot:app');
|
|
81
|
+
// Builtins — registered under class name (PascalCase) for type-based DI.
|
|
82
|
+
// No level here and none anywhere: a logger consults `setLogLevel`'s value at each
|
|
83
|
+
// emission, so this instance survives a level change and so does every handler that
|
|
84
|
+
// was handed it. A frond declaring `class X extends Logger` takes this key over,
|
|
85
|
+
// like any other port.
|
|
86
|
+
container.registerValue('Logger', new Logger('app'));
|
|
87
|
+
container.register('Config', Config, { lifetime: 'singleton' });
|
|
88
|
+
log.debug('builtins registered (Logger, Config)');
|
|
89
|
+
// The scan is HANDED IN. Producing it reads a disk, consuming it does not — which is
|
|
90
|
+
// the whole reason this file names no builtin and a Worker can run what it builds.
|
|
91
|
+
// `scanProject` (`@fougere/core/node`) is one producer; a module a build wrote is another.
|
|
92
|
+
const scanStart = performance.now();
|
|
93
|
+
const { fronds, diagnostics } = await (typeof options.scan === 'function' ? options.scan() : options.scan);
|
|
94
|
+
const operationModel = resolveEffectiveOperations(fronds, {
|
|
95
|
+
diagnostics,
|
|
96
|
+
remotes: options.remotes,
|
|
97
|
+
adapters: options.adapters,
|
|
98
|
+
});
|
|
99
|
+
const scanMs = (performance.now() - scanStart).toFixed(0);
|
|
100
|
+
const blocking = diagnostics.filter((d) => d.severity === 'blocking');
|
|
101
|
+
log.info(`read ${fronds.length} frond(s) in ${scanMs}ms`
|
|
102
|
+
+ (diagnostics.length ? ` — ${diagnostics.length} thing(s) the scan could not do` : ''));
|
|
103
|
+
/**
|
|
104
|
+
* Say what could not be read, at the one line everyone already watches.
|
|
105
|
+
*
|
|
106
|
+
* Not a refusal: an app whose `presenters/` is unreadable still serves its
|
|
107
|
+
* entities, and stopping the boot would trade a partial app for none. But it is
|
|
108
|
+
* an ERROR, not a debug line — the app now serves less than its source declares,
|
|
109
|
+
* and nothing downstream can tell that from a source that declares less.
|
|
110
|
+
*/
|
|
111
|
+
for (const d of blocking)
|
|
112
|
+
log.error(`[${d.code}] ${d.message}`, d.cause);
|
|
113
|
+
for (const d of diagnostics)
|
|
114
|
+
if (d.severity === 'warning')
|
|
115
|
+
log.warn(`[${d.code}] ${d.message}`);
|
|
116
|
+
/**
|
|
117
|
+
* An ambiguous convention is not a partial scan. Every relevant declaration was read,
|
|
118
|
+
* but more than one executable contract can be built from it. Refuse before auth,
|
|
119
|
+
* storage, migrations or seeds make the boot observable.
|
|
120
|
+
*/
|
|
121
|
+
const invalidOperations = operationModel.resolutionDiagnostics
|
|
122
|
+
.filter((diagnostic) => diagnostic.severity === 'blocking');
|
|
123
|
+
if (invalidOperations.length > 0) {
|
|
124
|
+
const details = invalidOperations.map((d) => ` [${d.code}]${d.subject ? ` ${d.subject}` : ''}\n ${d.message}\n ${d.filePath}`);
|
|
125
|
+
throw new Error(`Fougere boot refused: ${invalidOperations.length} unresolved operation contract(s):\n`
|
|
126
|
+
+ details.join('\n'));
|
|
127
|
+
}
|
|
128
|
+
// Auth runtime — built once from the lazy AuthConfig produced by a provider factory
|
|
129
|
+
// (e.g. betterAuth({...})) in fougere.config.ts. The provider receives our db +
|
|
130
|
+
// ormFactory so all auth writes flow through EntityOrm.
|
|
131
|
+
let authRuntime;
|
|
132
|
+
if (options.auth) {
|
|
133
|
+
if (!options.ormFactory) {
|
|
134
|
+
throw new Error('createApp: `auth` is set but `ormFactory` is missing — auth providers need it to back their adapter.');
|
|
135
|
+
}
|
|
136
|
+
if (options.db === undefined) {
|
|
137
|
+
throw new Error('createApp: `auth` is set but `db` is missing — pass the storage handle through CreateAppOptions.db.');
|
|
138
|
+
}
|
|
139
|
+
log.info('initializing auth runtime');
|
|
140
|
+
authRuntime = await options.auth.create({
|
|
141
|
+
db: options.db,
|
|
142
|
+
ormFactory: options.ormFactory,
|
|
143
|
+
});
|
|
144
|
+
log.info(`auth ready — mounted at ${authRuntime.basePath}`);
|
|
145
|
+
}
|
|
146
|
+
// Remote routing — judged at boot: declaring remotes without a transport is a config error.
|
|
147
|
+
// A remote declaration wins over local presence: `remotes: { blog: url }` IS
|
|
148
|
+
// the topology statement — the frond's code may sit in fronds/**, it runs elsewhere.
|
|
149
|
+
const declaredRemotes = Object.entries(options.remotes ?? {});
|
|
150
|
+
if (declaredRemotes.length > 0 && !options.remoteTransport) {
|
|
151
|
+
throw new Error('createApp: `remotes` is declared but `remoteTransport` is missing — pass one (e.g. from @fougere/transport-http).');
|
|
152
|
+
}
|
|
153
|
+
const remoteRouter = declaredRemotes.length > 0 && options.remoteTransport
|
|
154
|
+
? createRemoteRouter(Object.fromEntries(declaredRemotes), options.remoteTransport)
|
|
155
|
+
: undefined;
|
|
156
|
+
// What is running on this app — counted at the one door every caller goes through,
|
|
157
|
+
// so releasing it can wait for the work instead of pulling the floor out.
|
|
158
|
+
const inflight = new InFlight();
|
|
159
|
+
// Middleware storage — read at call time, not at boot time
|
|
160
|
+
const globalMiddlewares = [];
|
|
161
|
+
const scopedMiddlewares = new Map();
|
|
162
|
+
/** What this app took on beyond its fronds. Its `up` is the last thing the boot does. */
|
|
163
|
+
const appLifecycle = new AppLifecycle().add(...(options.extensions ?? []));
|
|
164
|
+
const routeRegistry = new RouteRegistry();
|
|
165
|
+
const dispatchLifecycle = new DispatchLifecycle(options.dispatchObservers, (error, event) => log.error(`[dispatch-observer] ${event.stage} ${event.call.address.toString()}`, error));
|
|
166
|
+
const dispatcher = new Dispatcher(routeRegistry, inflight, dispatchLifecycle);
|
|
167
|
+
const localDispatcher = new Dispatcher(routeRegistry, inflight, dispatchLifecycle, new LocalRoutePolicy((surface) => fronds.servedNames(surface)));
|
|
168
|
+
function getMiddlewares(entity) {
|
|
169
|
+
const scoped = scopedMiddlewares.get(entity) ?? [];
|
|
170
|
+
return [...globalMiddlewares, ...scoped];
|
|
171
|
+
}
|
|
172
|
+
assertOneOwnerPerKey(fronds, options.remotes);
|
|
173
|
+
// Every entity of every frond, by name — so a fact can be judged where it LANDS, and
|
|
174
|
+
// so a `reads:` clause can name a neighbour's.
|
|
175
|
+
const entityByName = fronds.schemas();
|
|
176
|
+
// Which frond holds an entity — what turns "a member is remote" into a refusal that
|
|
177
|
+
// names the frond rather than the entity, since `remotes:` is declared per frond.
|
|
178
|
+
const frondOf = new Map(fronds.flatMap((f) => f.entities.map((e) => [e.name, f.name])));
|
|
179
|
+
const emissions = new Emissions(fronds, entityByName, container, log, options.onEmit);
|
|
180
|
+
/** Canonical operation tables, indexed by the same audience key as their facades. */
|
|
181
|
+
const effectiveByKey = new Map();
|
|
182
|
+
const contractsOf = (operations) => new Map([...operations].map(([name, operation]) => [name, operation]));
|
|
183
|
+
// Every port an implementation was bound to, so a `ports:` entry that named none
|
|
184
|
+
// can say so rather than look obeyed.
|
|
185
|
+
const boundPorts = new Set();
|
|
186
|
+
// Register frond scopes
|
|
187
|
+
for (const frond of fronds) {
|
|
188
|
+
// Declared remote: keep the scanned metadata (bridges route with it),
|
|
189
|
+
// register nothing locally — resolve() falls through to the remote façade.
|
|
190
|
+
if (options.remotes && frond.name in options.remotes) {
|
|
191
|
+
log.child(frond.name).info('declared remote — not hosted locally');
|
|
192
|
+
// Its doors answer elsewhere, but what they LISTEN to was read here.
|
|
193
|
+
for (const handler of frond.handlers) {
|
|
194
|
+
const key = facadeKeyOf(handler.address, handler.surface);
|
|
195
|
+
const operations = operationModel.forHandler(handler);
|
|
196
|
+
effectiveByKey.set(key, operations);
|
|
197
|
+
emissions.note(contractsOf(operations), key);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const scope = container.createScope();
|
|
202
|
+
const frondLog = log.child(frond.name);
|
|
203
|
+
// `reads:` is what makes a cross-source reader exist here, and the list IS its
|
|
204
|
+
// environment — a source holding none of these is never opened. Registered under
|
|
205
|
+
// the type's own name, which is the key `depKeyOf` already derives for a plain
|
|
206
|
+
// parameter: `constructor(private sources: Sources)` and nothing else to say.
|
|
207
|
+
// Declaring `reads:` with nothing to build the reader is a boot that ignores a
|
|
208
|
+
// clause: the handler asking for `Sources` then dies at its first call, on a
|
|
209
|
+
// container message that names neither the clause nor what is missing.
|
|
210
|
+
if (frond.reads?.length && !options.sourcesFactory) {
|
|
211
|
+
frondLog.warn(`[reads] ${frond.reads.join(', ')} — declared in frond.config.ts, but this boot passes no `
|
|
212
|
+
+ '`sourcesFactory`, so no reader is registered and a handler asking for `Sources` will fail '
|
|
213
|
+
+ 'at its first call. Pass one (`@fougere/adapter-duckdb`), or drop the clause.');
|
|
214
|
+
}
|
|
215
|
+
if (frond.reads?.length && options.sourcesFactory) {
|
|
216
|
+
// Resolved across the WHOLE app, not this frond's own entities: a cross-source
|
|
217
|
+
// query joins entities from different fronds by definition — `Progress` here,
|
|
218
|
+
// `Book` next door — so restricting the list to its own would make it useless.
|
|
219
|
+
// Naming one IS the authorization; that is what the declaration is for.
|
|
220
|
+
const named = frond.reads
|
|
221
|
+
.map((name) => entityByName.get(registrationKeyOf(name)))
|
|
222
|
+
.filter((entity) => entity !== undefined);
|
|
223
|
+
if (named.length !== frond.reads.length) {
|
|
224
|
+
const missing = frond.reads.filter((name) => !entityByName.has(registrationKeyOf(name)));
|
|
225
|
+
frondLog.warn(`[reads] ${missing.join(', ')} — named in frond.config.ts but scanned nowhere in this app, `
|
|
226
|
+
+ 'so a query naming one would find no table. Check the spelling, or the entity file.');
|
|
227
|
+
}
|
|
228
|
+
scope.registerValue('Sources', await options.sourcesFactory(named, frond.name));
|
|
229
|
+
frondLog.debug(`cross-source reader over ${named.length} entit(ies)`);
|
|
230
|
+
}
|
|
231
|
+
// Who owns what, and the rule that makes owning mean something. Before anything is
|
|
232
|
+
// registered, so a bad line is named by this refusal rather than by the container's.
|
|
233
|
+
const owners = ownersOf(frond.providers);
|
|
234
|
+
refuseOrmInUserCode(frond, owners);
|
|
235
|
+
refuseCrudOnOwned(frond, owners);
|
|
236
|
+
for (const provider of frond.providers) {
|
|
237
|
+
scope.register(provider.ctor.name, provider.ctor, { deps: provider.deps });
|
|
238
|
+
}
|
|
239
|
+
// …and again under the port each one extends, so `private payment: Payment`
|
|
240
|
+
// reaches the realization instead of the base class it is declared against.
|
|
241
|
+
// Registered AFTER the loop above so a port key always wins over the base's
|
|
242
|
+
// own registration — same precedence as a declared repository over its default.
|
|
243
|
+
for (const [port, impl] of portBindings(frond.providers, (n) => scope.has(n), options.ports)) {
|
|
244
|
+
scope.register(port, impl.ctor, { deps: impl.deps });
|
|
245
|
+
boundPorts.add(port);
|
|
246
|
+
frondLog.debug(`port ${port} → ${impl.ctor.name}`);
|
|
247
|
+
}
|
|
248
|
+
if (frond.providers.length > 0) {
|
|
249
|
+
frondLog.debug(`${frond.providers.length} provider(s): ${frond.providers.map((p) => p.ctor.name).join(', ')}`);
|
|
250
|
+
}
|
|
251
|
+
// Register EntityOrm for each entity — PascalCase type name (e.g. 'PostOrm')
|
|
252
|
+
// When a handler declares Crud(Entity, Output), scope the ORM via .output(Output)
|
|
253
|
+
if (options.ormFactory) {
|
|
254
|
+
for (const entity of frond.entities) {
|
|
255
|
+
const ormName = ormKeyOf(entity.name);
|
|
256
|
+
const baseOrm = options.ormFactory(entity.entityClass, entity.name);
|
|
257
|
+
// Check if the default handler (no surface) declares an output override
|
|
258
|
+
const defaultHandler = frond.handlers.find((h) => h.address === entity.name && !h.surface);
|
|
259
|
+
const outputSchema = defaultHandler?.outputOverride ?? defaultHandler?.ctor?.__output;
|
|
260
|
+
const scoped = outputSchema && outputSchema !== entity.entityClass
|
|
261
|
+
? baseOrm.output(outputSchema)
|
|
262
|
+
: baseOrm;
|
|
263
|
+
// Storage is a way out like the client surface — see `StorageGuard`.
|
|
264
|
+
const guarded = guardStorage(scoped, entity.entityClass.getFields(), entity.name);
|
|
265
|
+
scope.registerValue(ormName, guarded);
|
|
266
|
+
// The default repository IS the guarded port — it already answers every gesture a
|
|
267
|
+
// declared one forwards, so the two forms have the same shape and a handler reads
|
|
268
|
+
// `repo.list()` either way. The wrapper that used to sit here (`{ orm: guarded }`)
|
|
269
|
+
// existed to make `repo.orm` true in both, back when `.orm` was the way in.
|
|
270
|
+
//
|
|
271
|
+
// Not registered for an OWNED entity: an aggregate's members are reached through it
|
|
272
|
+
// and nowhere else, and the default would be a second door under a name a handler
|
|
273
|
+
// can spell. Every member is skipped, not just the one the key is named after —
|
|
274
|
+
// that asymmetry was the whole hole.
|
|
275
|
+
const repoKey = repositoryKeyOf(entity.name);
|
|
276
|
+
const owner = owners.get(entity.name);
|
|
277
|
+
if (owner) {
|
|
278
|
+
frondLog.debug(`${entity.name} — owned by ${owner}, no default repository`);
|
|
279
|
+
}
|
|
280
|
+
else if (!scope.has(repoKey)) {
|
|
281
|
+
scope.registerValue(repoKey, guarded);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (frond.entities.length > 0) {
|
|
285
|
+
frondLog.debug(`${frond.entities.length} entity ORM(s): ${frond.entities.map((e) => e.name).join(', ')}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Frames, after the ORMs and before anything that may ask for one. A frame is read
|
|
289
|
+
// from the same `deps` every other port is read from — asking for it IS declaring it,
|
|
290
|
+
// so nothing is registered for a frame nobody wants.
|
|
291
|
+
registerFrames(scope, [...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors].flatMap((d) => d.deps), frond.providers, {
|
|
292
|
+
entityByName,
|
|
293
|
+
frondOf,
|
|
294
|
+
hostedHere: (name) => !(options.remotes && name in options.remotes),
|
|
295
|
+
ormFactory: options.ormFactory,
|
|
296
|
+
sourceOf: options.sourceOf,
|
|
297
|
+
transacted: options.transacted,
|
|
298
|
+
log: frondLog,
|
|
299
|
+
});
|
|
300
|
+
// Register presenters in scope — PascalCase type name (e.g. 'PostPresenter')
|
|
301
|
+
const presenterMap = new Map(frond.presenters.map((p) => [p.entityName, p]));
|
|
302
|
+
for (const presenter of frond.presenters) {
|
|
303
|
+
scope.register(presenterKeyOf(presenter.entityName), presenter.ctor, { deps: presenter.deps });
|
|
304
|
+
}
|
|
305
|
+
if (frond.presenters.length > 0) {
|
|
306
|
+
frondLog.debug(`${frond.presenters.length} presenter(s): ${frond.presenters.map((p) => p.entityName).join(', ')}`);
|
|
307
|
+
}
|
|
308
|
+
// Register collectors in scope — PascalCase type name (e.g. 'UserCollector')
|
|
309
|
+
const collectorTypeNames = new Set(frond.collectors.map((c) => c.typeName));
|
|
310
|
+
for (const collector of frond.collectors) {
|
|
311
|
+
const key = collectorKeyOf(collector.typeName);
|
|
312
|
+
scope.register(key, collector.ctor, { deps: collector.deps });
|
|
313
|
+
}
|
|
314
|
+
if (frond.collectors.length > 0) {
|
|
315
|
+
frondLog.debug(`${frond.collectors.length} collector(s): ${frond.collectors.map((c) => c.typeName).join(', ')}`);
|
|
316
|
+
}
|
|
317
|
+
// Build handler facades → registered in ROOT container (public contract)
|
|
318
|
+
const defaultHandlers = frond.handlers.filter((h) => !h.surface);
|
|
319
|
+
const surfaceHandlers = frond.handlers.filter((h) => h.surface);
|
|
320
|
+
const defaultHandlerMap = new Map(defaultHandlers.map((h) => [h.address, h]));
|
|
321
|
+
/** Build the door of a handler and register it under the audience it serves. */
|
|
322
|
+
const buildFacade = (entity, handler, targetScope, facadeKey) => {
|
|
323
|
+
const facade = new HandlerFacade({
|
|
324
|
+
handler,
|
|
325
|
+
handlers: frond.handlers,
|
|
326
|
+
entity,
|
|
327
|
+
scope: targetScope,
|
|
328
|
+
key: facadeKey,
|
|
329
|
+
operations: operationModel.forHandler(handler),
|
|
330
|
+
}, {
|
|
331
|
+
frond: frond.name,
|
|
332
|
+
frondScope: scope,
|
|
333
|
+
log: frondLog,
|
|
334
|
+
collectors: collectorTypeNames,
|
|
335
|
+
presenters: presenterMap,
|
|
336
|
+
middlewaresFor: getMiddlewares,
|
|
337
|
+
emissions,
|
|
338
|
+
});
|
|
339
|
+
// The terms alongside the door, under the same audience — a surface that serves
|
|
340
|
+
// fewer ops describes fewer ops.
|
|
341
|
+
container.registerValue(contractsKeyOf(handler.address, handler.surface), facade.contracts);
|
|
342
|
+
effectiveByKey.set(facadeKey, facade.effectiveOperations);
|
|
343
|
+
const surfaces = new Set([handler.surface]);
|
|
344
|
+
if (!handler.surface) {
|
|
345
|
+
for (const [surface, names] of Object.entries(frond.surfaces ?? {})) {
|
|
346
|
+
const isDeclared = names.some((name) => name.toLowerCase() === handler.address.toLowerCase());
|
|
347
|
+
const hasOwnDoor = surfaceHandlers.some((candidate) => candidate.surface === surface && candidate.address === handler.address);
|
|
348
|
+
if (isDeclared && !hasOwnDoor)
|
|
349
|
+
surfaces.add(surface);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const operation of facade.contracts.keys()) {
|
|
353
|
+
for (const surface of surfaces) {
|
|
354
|
+
const address = new RouteAddress({
|
|
355
|
+
entity: handler.address,
|
|
356
|
+
operation,
|
|
357
|
+
...(surface !== undefined ? { surface } : {}),
|
|
358
|
+
});
|
|
359
|
+
routeRegistry.register(new OperationRoute('local', address, (call) => facade.ops[operation](call.invocation)));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const entry = new FacadeEntry(handler.surface ? localDispatcher : dispatcher, handler.address, routeRegistry.operationNames(handler.address, handler.surface), handler.surface);
|
|
363
|
+
container.registerValue(facadeKey, entry.operations);
|
|
364
|
+
};
|
|
365
|
+
// A presenter is about an entity — computed fields sit on a shape — so this walks
|
|
366
|
+
// entities. Exposing the instance lazily; the bridge resolves it on first access.
|
|
367
|
+
for (const entity of frond.entities) {
|
|
368
|
+
if (!presenterMap.has(entity.name))
|
|
369
|
+
continue;
|
|
370
|
+
const presenterKey = presenterKeyOf(entity.name);
|
|
371
|
+
let presenterInstance;
|
|
372
|
+
container.registerValue(presenterKey, new Proxy({}, {
|
|
373
|
+
get(_target, prop) {
|
|
374
|
+
if (!presenterInstance)
|
|
375
|
+
presenterInstance = scope.resolve(presenterKey);
|
|
376
|
+
return presenterInstance[prop];
|
|
377
|
+
},
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
// A facade is about a handler, so this walks HANDLERS. It walked entities before,
|
|
381
|
+
// which made an entity a precondition for being callable at all: a handler naming
|
|
382
|
+
// none was scanned, then never built, and nothing said so.
|
|
383
|
+
for (const handler of defaultHandlers) {
|
|
384
|
+
// Two ways to know the subject, and the explicit one wins: `Crud(Item)` names the
|
|
385
|
+
// entity it was built on, whatever the handler is called. Otherwise the handler's
|
|
386
|
+
// own name is the only thing pointing at one — and pointing at nothing is legal.
|
|
387
|
+
//
|
|
388
|
+
// By NAME, not by identity: the scanner loads an entity through its own loader and
|
|
389
|
+
// the handler imports it through the runtime's, so the same class arrives as two
|
|
390
|
+
// objects. `===` compares module instances, which is not the question being asked.
|
|
391
|
+
const crudTarget = targetOf(handler.ctor);
|
|
392
|
+
const subject = crudTarget?.name ? registrationKeyOf(crudTarget.name) : handler.address;
|
|
393
|
+
const entity = frond.entities.find((e) => e.name === subject);
|
|
394
|
+
const facadeKey = facadeKeyOf(handler.address);
|
|
395
|
+
buildFacade(entity, handler, scope, facadeKey);
|
|
396
|
+
frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey)).join(', ')}]`
|
|
397
|
+
+ (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
|
|
398
|
+
}
|
|
399
|
+
// The dual, and it stays: a shape that declares no operation answers nothing. Said
|
|
400
|
+
// once per entity rather than deduced from a silence.
|
|
401
|
+
for (const entity of frond.entities) {
|
|
402
|
+
if (!defaultHandlerMap.has(entity.name)) {
|
|
403
|
+
frondLog.debug(`${entity.name} — entity only, no handler: exposes nothing`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Surface handlers — create sub-scope per surface handler with scoped ORM
|
|
407
|
+
//
|
|
408
|
+
// Pointing at nothing is legal HERE TOO. This loop used to `continue` when no entity
|
|
409
|
+
// carried the handler's name, so `handlers/public/SearchHandler.ts` with no `Search`
|
|
410
|
+
// entity got no door at all and no line saying why — while the very same handler at
|
|
411
|
+
// the default surface is built and logged. One rule, both surfaces.
|
|
412
|
+
for (const handler of surfaceHandlers) {
|
|
413
|
+
const entity = frond.entities.find((e) => e.name === handler.address);
|
|
414
|
+
const surfaceScope = scope.createScope();
|
|
415
|
+
// Register scoped ORM if output override differs from entity — under the REPOSITORY
|
|
416
|
+
// key, which is what a Crud handler asks for, and under the port's own for a holder
|
|
417
|
+
// that legitimately names it. Registering only the latter left a named surface with
|
|
418
|
+
// no door at all once the façade stopped spelling the storage.
|
|
419
|
+
if (entity && options.ormFactory) {
|
|
420
|
+
const baseOrm = options.ormFactory(entity.entityClass, entity.name);
|
|
421
|
+
const outputSchema = handler.outputOverride ?? handler.ctor.__output;
|
|
422
|
+
const scoped = outputSchema && outputSchema !== entity.entityClass
|
|
423
|
+
? baseOrm.output(outputSchema)
|
|
424
|
+
: baseOrm;
|
|
425
|
+
const guarded = guardStorage(scoped, entity.entityClass.getFields(), entity.name);
|
|
426
|
+
surfaceScope.registerValue(ormKeyOf(entity.name), guarded);
|
|
427
|
+
surfaceScope.registerValue(repositoryKeyOf(entity.name), guarded);
|
|
428
|
+
}
|
|
429
|
+
const facadeKey = facadeKeyOf(handler.address, handler.surface);
|
|
430
|
+
buildFacade(entity, handler, surfaceScope, facadeKey);
|
|
431
|
+
frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey)).join(', ')}]`
|
|
432
|
+
+ (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
|
|
433
|
+
}
|
|
434
|
+
// A named surface is closed, so what it contains is a fact worth stating.
|
|
435
|
+
// Saying it at boot is the difference between a rule and a rule you can
|
|
436
|
+
// check: an entity you meant to serve and never wrote a handler for is
|
|
437
|
+
// absent HERE, in one line, instead of being discovered missing later.
|
|
438
|
+
const surfaceNames = [...new Set(surfaceHandlers.map((h) => h.surface))].sort();
|
|
439
|
+
for (const surfaceName of surfaceNames) {
|
|
440
|
+
const served = surfaceHandlers
|
|
441
|
+
.filter((h) => h.surface === surfaceName)
|
|
442
|
+
.map((h) => h.address)
|
|
443
|
+
.sort();
|
|
444
|
+
const absent = frond.entities.map((e) => e.name).filter((n) => !served.includes(n));
|
|
445
|
+
frondLog.info(`surface '${surfaceName}' — ${served.length} entit${served.length === 1 ? 'y' : 'ies'}: ${served.join(', ')}` +
|
|
446
|
+
(absent.length > 0 ? ` (not served: ${absent.join(', ')})` : ''));
|
|
447
|
+
}
|
|
448
|
+
container.registerValue(`frond:${frond.name}`, scope);
|
|
449
|
+
frondLog.info(`registered — ${frond.entities.length} entities, ${frond.handlers.length} handlers, ${frond.seeds.length} seeds`);
|
|
450
|
+
}
|
|
451
|
+
// A `ports:` key that matched no port anywhere reads as a choice that was made, and
|
|
452
|
+
// was not. Said once, at the end, because the entry is app-wide while a port is a
|
|
453
|
+
// frond's — no single frond can tell whether a key is a typo or a neighbour's.
|
|
454
|
+
const unused = Object.keys(options.ports ?? {}).filter((port) => !boundPorts.has(port));
|
|
455
|
+
if (unused.length > 0) {
|
|
456
|
+
log.warn(`[ports] ${unused.join(', ')} — named in fougere.config.ts, but no scanned class extends `
|
|
457
|
+
+ 'them, so nothing was chosen. Check the spelling, or drop the entry.');
|
|
458
|
+
}
|
|
459
|
+
// Once every door exists: what is announced here and what is listened to are both known.
|
|
460
|
+
emissions.register();
|
|
461
|
+
/**
|
|
462
|
+
* The last resort, held by the container so every resolution path shares it.
|
|
463
|
+
*
|
|
464
|
+
* It used to live on `app.resolve` alone, which meant the runner found a remote façade
|
|
465
|
+
* and dependency injection did not — a handler asking for a neighbour that had moved
|
|
466
|
+
* got `is not registered` while the very same call over the wire worked. One door now.
|
|
467
|
+
*
|
|
468
|
+
* Surface-scoped keys ('admin:productHandler') stay local: a named surface resolves in
|
|
469
|
+
* this container only, so fabricating one for a remote frond would answer NOT_FOUND on
|
|
470
|
+
* everything (see Known issues).
|
|
471
|
+
*/
|
|
472
|
+
container.setFallback?.((name) => {
|
|
473
|
+
if (!remoteRouter)
|
|
474
|
+
return undefined;
|
|
475
|
+
if (!name.endsWith('Handler') || name.includes(':'))
|
|
476
|
+
return undefined;
|
|
477
|
+
// Façade-shaped stand-in; routing happens lazily at the first call. Through
|
|
478
|
+
// `registrationKeyOf` because a DEPENDENCY names the type as written — `ProductHandler`,
|
|
479
|
+
// PascalCase — while a card declares `product`, so the raw strip asked the router for
|
|
480
|
+
// 'Product' and every by-type dependency on a remote handler answered NOT_FOUND.
|
|
481
|
+
return new FacadeEntry(dispatcher, registrationKeyOf(name.replace(/Handler$/, ''))).operations;
|
|
482
|
+
});
|
|
483
|
+
/**
|
|
484
|
+
* Everything this app holds, let go in reverse of how it was taken: what an extension
|
|
485
|
+
* took on last, then the container's own, then whoever handed a resource in.
|
|
486
|
+
*
|
|
487
|
+
* It does NOT wait for running calls — `drain()` is that, and it is a separate gesture
|
|
488
|
+
* because waiting and releasing do not have the same owner: a test releases at once, a
|
|
489
|
+
* host turning the ring wants the work finished first.
|
|
490
|
+
*/
|
|
491
|
+
const release = async () => {
|
|
492
|
+
// Every level is told to close even when one refuses, and the refusals leave together —
|
|
493
|
+
// the rule `Lifecycle.down` applies INSIDE its list, applied ACROSS the three. Stated
|
|
494
|
+
// there and broken here, a refusing extension took the container and the connection
|
|
495
|
+
// down with it, which is the leak this gesture exists to prevent.
|
|
496
|
+
const refused = [];
|
|
497
|
+
for (const level of [() => appLifecycle.down(app), () => container.dispose(), () => options.onDispose?.()]) {
|
|
498
|
+
try {
|
|
499
|
+
await level();
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
// Flattened one level: an extension's refusals are already an AggregateError, and
|
|
503
|
+
// nesting them would make the caller unwrap twice to read one list.
|
|
504
|
+
if (error instanceof AggregateError)
|
|
505
|
+
refused.push(...error.errors);
|
|
506
|
+
else
|
|
507
|
+
refused.push(error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (refused.length > 0) {
|
|
511
|
+
throw new AggregateError(refused, `${refused.length} refusal(s) while releasing the app`);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
/**
|
|
515
|
+
* Stop taking calls, and resolve once the ones already running are done.
|
|
516
|
+
*
|
|
517
|
+
* Refuses on `timeoutMs` rather than resolving anyway: a drain that gives up quietly
|
|
518
|
+
* reads exactly like a drain that succeeded, and the caller — who is about to release
|
|
519
|
+
* a storage connection under whatever is left — is the one who must decide.
|
|
520
|
+
*/
|
|
521
|
+
const drain = async (timeoutMs) => {
|
|
522
|
+
inflight.close();
|
|
523
|
+
if (timeoutMs === undefined)
|
|
524
|
+
return inflight.whenIdle();
|
|
525
|
+
let timer;
|
|
526
|
+
await Promise.race([
|
|
527
|
+
inflight.whenIdle().then(() => clearTimeout(timer)),
|
|
528
|
+
new Promise((_, reject) => {
|
|
529
|
+
timer = setTimeout(() => reject(new Error(`[drain] ${inflight.count} call(s) still running after ${timeoutMs}ms`)), timeoutMs);
|
|
530
|
+
}),
|
|
531
|
+
]);
|
|
532
|
+
};
|
|
533
|
+
const resolve = (name) => {
|
|
534
|
+
try {
|
|
535
|
+
return container.resolve(name);
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
if (name.endsWith('Handler') && !name.includes(':') && !remoteRouter) {
|
|
539
|
+
throw new Error(notLoaded(name.replace(/Handler$/, '')));
|
|
540
|
+
}
|
|
541
|
+
throw err;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const schemaFor = async (entity) => {
|
|
545
|
+
const found = fronds.entity(entity);
|
|
546
|
+
if (found)
|
|
547
|
+
return found.entityClass;
|
|
548
|
+
if (remoteRouter) {
|
|
549
|
+
const route = await remoteRouter.route(entity);
|
|
550
|
+
// A remote door that stores nothing publishes ops and no shape. Saying so beats
|
|
551
|
+
// handing back an empty schema, which would validate every input it was given.
|
|
552
|
+
if (!route.schema) {
|
|
553
|
+
throw new Error(`'${entity}' is served by frond '${route.frond}' but stores no rows, so it has no schema. `
|
|
554
|
+
+ `Call its operations through the façade instead.`);
|
|
555
|
+
}
|
|
556
|
+
return route.schema;
|
|
557
|
+
}
|
|
558
|
+
throw new Error(notLoaded(entity));
|
|
559
|
+
};
|
|
560
|
+
const facadeAt = (key, topology) => {
|
|
561
|
+
try {
|
|
562
|
+
return topology
|
|
563
|
+
? resolve(key)
|
|
564
|
+
: container.resolve(key);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* THE membership rule, stated once — every projection reads this and nothing
|
|
572
|
+
* else. It used to live twice, verbatim, in the REST and GraphQL adapters,
|
|
573
|
+
* and two other readers invented their own answer.
|
|
574
|
+
*
|
|
575
|
+
* Naming an audience closes it. Three ways to name an entity into a surface,
|
|
576
|
+
* in precedence order:
|
|
577
|
+
* - `surfaces:` in frond.config.ts — when the list exists, it IS the list;
|
|
578
|
+
* - a handler under `handlers/<surface>/` — which also restricts the façade;
|
|
579
|
+
* - the `@expose` sugar, resolved into the same two by the scan.
|
|
580
|
+
* What none of them names is not served. It used to be the reverse: no
|
|
581
|
+
* `public:categoryHandler` meant the FULL CategoryHandler rode the public
|
|
582
|
+
* door, create/update/delete included (measured on demos/nuxt-blog).
|
|
583
|
+
*
|
|
584
|
+
* A named entity with no façade of its own falls back to the default one —
|
|
585
|
+
* that is not the old widening, it is the author saying "this one, as it is".
|
|
586
|
+
* The leak was exposure with no statement behind it.
|
|
587
|
+
*
|
|
588
|
+
* A surface key stays local: a doublure serves whatever the remote's own door
|
|
589
|
+
* serves, which is the remote's business and not ours to re-audience.
|
|
590
|
+
*/
|
|
591
|
+
const facadeFor = (entity, surface) => {
|
|
592
|
+
if (!surface)
|
|
593
|
+
return facadeAt(facadeKeyOf(entity), true);
|
|
594
|
+
const own = facadeAt(facadeKeyOf(entity, surface), false);
|
|
595
|
+
const declared = fronds.owner(entity)?.surfaces?.[surface];
|
|
596
|
+
if (!declared)
|
|
597
|
+
return own;
|
|
598
|
+
if (!declared.some((n) => n.toLowerCase() === entity.toLowerCase()))
|
|
599
|
+
return undefined;
|
|
600
|
+
if (own)
|
|
601
|
+
return own;
|
|
602
|
+
const fallback = facadeAt(facadeKeyOf(entity), false);
|
|
603
|
+
return fallback
|
|
604
|
+
? new FacadeEntry(localDispatcher, entity, routeRegistry.operationNames(entity, surface), surface).operations
|
|
605
|
+
: undefined;
|
|
606
|
+
};
|
|
607
|
+
if (remoteRouter) {
|
|
608
|
+
const remoteFacades = new Map();
|
|
609
|
+
routeRegistry.addResolver(new RemoteRouteResolver((entity) => {
|
|
610
|
+
const known = remoteFacades.get(entity);
|
|
611
|
+
if (known)
|
|
612
|
+
return known;
|
|
613
|
+
const facade = createRemoteFacade(entity, remoteRouter, getMiddlewares);
|
|
614
|
+
remoteFacades.set(entity, facade);
|
|
615
|
+
return facade;
|
|
616
|
+
}));
|
|
617
|
+
}
|
|
618
|
+
/** The terms beside a door, with the exact same named-surface fallback rule. */
|
|
619
|
+
const operationsFor = (entity, surface) => {
|
|
620
|
+
if (!surface)
|
|
621
|
+
return effectiveByKey.get(facadeKeyOf(entity));
|
|
622
|
+
const own = effectiveByKey.get(facadeKeyOf(entity, surface));
|
|
623
|
+
const declared = fronds.owner(entity)?.surfaces?.[surface];
|
|
624
|
+
if (!declared)
|
|
625
|
+
return own;
|
|
626
|
+
return declared.some((name) => name.toLowerCase() === entity.toLowerCase())
|
|
627
|
+
? (own ?? effectiveByKey.get(facadeKeyOf(entity)))
|
|
628
|
+
: undefined;
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* The storage an entity is backed by — the dual of `facadeFor`, which serves its
|
|
632
|
+
* client-facing door. Both are ways in; an entity that opens none of them still has rows.
|
|
633
|
+
*
|
|
634
|
+
* It resolves through the owning frond's scope, because that is where entity ORMs live:
|
|
635
|
+
* `resolve('UserOrm')` reads the ROOT container and never finds one, so callers outside
|
|
636
|
+
* the frond concluded there was no storage. The seed loop did exactly that, and skipped
|
|
637
|
+
* every entity with no façade — the one case its own fallback existed for.
|
|
638
|
+
*/
|
|
639
|
+
const ormFor = (entity) => {
|
|
640
|
+
const owner = fronds.owner(entity);
|
|
641
|
+
if (!owner)
|
|
642
|
+
return undefined;
|
|
643
|
+
const key = ormKeyOf(entity);
|
|
644
|
+
try {
|
|
645
|
+
return container.resolve(`frond:${owner.name}`).resolve(key);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* The presenter of an entity, resolved through its owning frond's scope.
|
|
653
|
+
*
|
|
654
|
+
* Same shape as `ormFor`, and it exists for the same reason: without it an adapter
|
|
655
|
+
* has to spell the container key itself. `schema-graphql` did — a hand-written
|
|
656
|
+
* `${Name}Presenter` — and a key respelled elsewhere finds nothing and says nothing
|
|
657
|
+
* the day the convention moves, which is exactly the failure `verify.ts` refuses.
|
|
658
|
+
*/
|
|
659
|
+
const presenterFor = (entity) => {
|
|
660
|
+
const owner = fronds.owner(entity);
|
|
661
|
+
if (!owner)
|
|
662
|
+
return undefined;
|
|
663
|
+
try {
|
|
664
|
+
return container.resolve(`frond:${owner.name}`).resolve(presenterKeyOf(entity));
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
const app = {
|
|
671
|
+
container,
|
|
672
|
+
fronds,
|
|
673
|
+
// What this app publishes, straight from fougere.config.ts — the doors read it,
|
|
674
|
+
// so an undeclared adapter serves nothing whatever a host mounted.
|
|
675
|
+
adapters: options.adapters ?? {},
|
|
676
|
+
dispatch: (call) => dispatcher.dispatch(call),
|
|
677
|
+
local: localDispatcher,
|
|
678
|
+
resolve,
|
|
679
|
+
schemaFor,
|
|
680
|
+
facadeFor,
|
|
681
|
+
operationsFor,
|
|
682
|
+
listensTo: () => emissions.listensTo(),
|
|
683
|
+
deliver: (fact, payload) => emissions.deliver(fact, payload),
|
|
684
|
+
ormFor,
|
|
685
|
+
presenterFor,
|
|
686
|
+
dispose: release,
|
|
687
|
+
drain,
|
|
688
|
+
inFlight: () => inflight.count,
|
|
689
|
+
[Symbol.asyncDispose]: release,
|
|
690
|
+
serveRpc(op, answer) {
|
|
691
|
+
// Refused rather than replaced: two declarations of one name would make the answer
|
|
692
|
+
// depend on wiring order, and `discover` is in here precisely so it cannot be taken.
|
|
693
|
+
const address = new RouteAddress({ entity: 'rpc', operation: op });
|
|
694
|
+
if (routeRegistry.find(address)) {
|
|
695
|
+
throw new Error(`rpc operation '${op}' is already served; a second declaration would depend on wiring order`);
|
|
696
|
+
}
|
|
697
|
+
routeRegistry.register(new OperationRoute('system', address, (call) => answer(call.invocation, call.address.surface)));
|
|
698
|
+
},
|
|
699
|
+
extensions: () => appLifecycle.names(),
|
|
700
|
+
use(...args) {
|
|
701
|
+
if (typeof args[0] === 'string') {
|
|
702
|
+
const [entity, mw] = args;
|
|
703
|
+
const list = scopedMiddlewares.get(entity) ?? [];
|
|
704
|
+
list.push(mw);
|
|
705
|
+
scopedMiddlewares.set(entity, list);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
globalMiddlewares.push(args[0]);
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
auth: authRuntime,
|
|
712
|
+
};
|
|
713
|
+
// The card is an rpc op like any other, so one registry answers and one refusal names
|
|
714
|
+
// what is served. A package's op is declared the same way, from outside.
|
|
715
|
+
app.serveRpc('discover', (_invocation, surface) => identityCardOf(app, surface));
|
|
716
|
+
// The last thing the boot does, and the first thing a release undoes. An extension may
|
|
717
|
+
// await here — which is what a provider needing to OPEN something could never do.
|
|
718
|
+
await appLifecycle.up(app);
|
|
719
|
+
return app;
|
|
720
|
+
}
|
|
721
|
+
//# sourceMappingURL=bootstrap.js.map
|