@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
package/src/verify.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { FrondDescriptor } from './scan/frond.js';
|
|
2
|
+
import { lowerFirst } from '@fougere/schema';
|
|
3
|
+
import { repositoryKeyOf } from './prefab/repository.js';
|
|
4
|
+
import { ormKeyOf } from './orm.js';
|
|
5
|
+
import { presenterKeyOf } from './prefab/presenter.js';
|
|
6
|
+
import { collectorKeyOf } from './prefab/collector.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a rule found in an app.
|
|
10
|
+
*
|
|
11
|
+
* `message` is the sentence the rule replaces — the prose a human used to have
|
|
12
|
+
* to remember. `Known issues` says "Keep collectors in the consuming frond";
|
|
13
|
+
* this is that sentence, told about a specific handler, by something that runs.
|
|
14
|
+
*/
|
|
15
|
+
export interface Violation {
|
|
16
|
+
/** Rule name, stable — 'cross-frond-dependency'. */
|
|
17
|
+
rule: string;
|
|
18
|
+
/**
|
|
19
|
+
* What it costs, decided by the rule that raises it — never by whoever renders it.
|
|
20
|
+
*
|
|
21
|
+
* The two rules here are not the same animal: a cross-frond dependency resolves
|
|
22
|
+
* today and stops resolving the day the other frond answers over the wire, while a
|
|
23
|
+
* collector declared elsewhere is already wrong in one process. A reader holding a
|
|
24
|
+
* table of rule names would be a second opinion on a fact the rule already has.
|
|
25
|
+
*/
|
|
26
|
+
severity: 'blocking' | 'warning';
|
|
27
|
+
/** The frond the subject lives in. */
|
|
28
|
+
frond: string;
|
|
29
|
+
/** What violates it — 'PostHandler'. */
|
|
30
|
+
subject: string;
|
|
31
|
+
/** Where to go and look. */
|
|
32
|
+
filePath: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the subject reaches for, and where it actually lives.
|
|
35
|
+
*
|
|
36
|
+
* `frond` above is the CONSUMER; the target used to exist only inside the
|
|
37
|
+
* sentence. A reader that has to parse prose to learn which frond a violation
|
|
38
|
+
* points at is a second opinion on a fact the checker already held — so the
|
|
39
|
+
* boot can ask "does this violation target a frond named in `remotes:`?" by
|
|
40
|
+
* reading a field.
|
|
41
|
+
*/
|
|
42
|
+
dependsOn: { key: string; frond: string; kind: string };
|
|
43
|
+
/** What breaks, and what the caller gets instead. */
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A dependency declared in a frond's scope, and what kind of thing it is. */
|
|
48
|
+
type Registration = { frond: string; kind: string };
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The container keys a frond registers in its own scope, keyed as a handler's
|
|
52
|
+
* `deps` spell them — DI resolves by type name, so both sides are PascalCase.
|
|
53
|
+
*
|
|
54
|
+
* Every key comes from the function that states it, never from a derivation
|
|
55
|
+
* respelled here: a second reader that spells one differently finds nothing and
|
|
56
|
+
* reports nothing wrong, which is the failure mode a checker must not have.
|
|
57
|
+
*/
|
|
58
|
+
function registrationsOf(frond: FrondDescriptor): Map<string, Registration> {
|
|
59
|
+
const out = new Map<string, Registration>();
|
|
60
|
+
const put = (key: string, kind: string) => out.set(key, { frond: frond.name, kind });
|
|
61
|
+
|
|
62
|
+
for (const p of frond.providers) put(p.ctor.name, 'provider');
|
|
63
|
+
for (const p of frond.presenters) put(presenterKeyOf(p.entityName), 'presenter');
|
|
64
|
+
for (const c of frond.collectors) put(collectorKeyOf(c.typeName), 'collector');
|
|
65
|
+
for (const e of frond.entities) {
|
|
66
|
+
put(ormKeyOf(e.name), 'ORM');
|
|
67
|
+
put(repositoryKeyOf(e.name), 'repository');
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Everything in a frond that is constructed by DI, so carries `deps`.
|
|
74
|
+
* Handlers, presenters and collectors are all built from a constructor whose
|
|
75
|
+
* argument types the scan read; each one can therefore reach across.
|
|
76
|
+
*/
|
|
77
|
+
function injectablesOf(frond: FrondDescriptor) {
|
|
78
|
+
return [
|
|
79
|
+
...frond.handlers.map((h) => ({ name: h.ctor.name, deps: h.deps, filePath: h.filePath })),
|
|
80
|
+
...frond.presenters.map((p) => ({ name: p.ctor.name, deps: p.deps, filePath: p.filePath })),
|
|
81
|
+
...frond.collectors.map((c) => ({ name: c.ctor.name, deps: c.deps, filePath: c.filePath })),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Does this app survive being split?
|
|
87
|
+
*
|
|
88
|
+
* A frond runs in-process or in its own process behind JSON-RPC with identical
|
|
89
|
+
* user code — that is the whole claim. What the claim does not say is that
|
|
90
|
+
* *every* app survives the move: a dependency that crosses a frond boundary
|
|
91
|
+
* resolves today because both scopes live in one container, and resolves to
|
|
92
|
+
* nothing the day the other frond answers over the wire.
|
|
93
|
+
*
|
|
94
|
+
* The question only exists where a module boundary is meant to become a process
|
|
95
|
+
* boundary later. Where every inter-module call is already remote there is
|
|
96
|
+
* nothing to check, and where no exit is planned there is nothing to check
|
|
97
|
+
* either. Here it means something, and until now it had no answer.
|
|
98
|
+
*
|
|
99
|
+
* Pure over `app.fronds`: no mount, no process, no file. Call it from a test.
|
|
100
|
+
*/
|
|
101
|
+
export function verify(app: { fronds: readonly FrondDescriptor[] }): Violation[] {
|
|
102
|
+
const index = new Map<string, Registration>();
|
|
103
|
+
for (const frond of app.fronds) {
|
|
104
|
+
for (const [key, reg] of registrationsOf(frond)) index.set(key, reg);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Which frond declares a collector for which entity. Collectors are the one
|
|
108
|
+
// thing a handler asks for by OPERATION PARAMETER, never by constructor, so
|
|
109
|
+
// they need their own index and their own rule.
|
|
110
|
+
const collectorFronds = new Map<string, string>();
|
|
111
|
+
for (const frond of app.fronds) {
|
|
112
|
+
for (const c of frond.collectors) collectorFronds.set(c.typeName, frond.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const violations: Violation[] = [];
|
|
116
|
+
for (const frond of app.fronds) {
|
|
117
|
+
// Rule 1 — a constructor dependency declared in another frond's scope.
|
|
118
|
+
// Façade keys are exempt by construction, not by omission: a `Facade<X>`
|
|
119
|
+
// dependency is spelled camelCase (`articleHandler`) while every type key
|
|
120
|
+
// here is PascalCase, so the two namespaces cannot collide. The façade IS
|
|
121
|
+
// the sanctioned crossing — see tests/cross-frond.test.ts.
|
|
122
|
+
for (const subject of injectablesOf(frond)) {
|
|
123
|
+
for (const dep of subject.deps) {
|
|
124
|
+
const declared = index.get(dep);
|
|
125
|
+
// Not in the index at all = a builtin (Logger, Config), a
|
|
126
|
+
// façade key, or an unresolved name. None is a boundary crossing, and
|
|
127
|
+
// an unresolved dependency is the container's complaint, not this rule's.
|
|
128
|
+
if (!declared || declared.frond === frond.name) continue;
|
|
129
|
+
violations.push({
|
|
130
|
+
rule: 'cross-frond-dependency',
|
|
131
|
+
severity: 'warning',
|
|
132
|
+
frond: frond.name,
|
|
133
|
+
subject: subject.name,
|
|
134
|
+
filePath: subject.filePath,
|
|
135
|
+
dependsOn: { key: dep, frond: declared.frond, kind: declared.kind },
|
|
136
|
+
message:
|
|
137
|
+
`${subject.name} depends on ${dep}, the ${declared.kind} of frond '${declared.frond}'. ` +
|
|
138
|
+
`Each frond registers into its own scope, so this does not resolve here. ` +
|
|
139
|
+
`Declare ${dep} in '${frond.name}', or reach '${declared.frond}' through its ` +
|
|
140
|
+
`façade — the one sanctioned crossing.`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Rule 2 — an operation parameter that wanted a collector this frond has not
|
|
146
|
+
// got. The preliminary binding convention can only see this frond's collector
|
|
147
|
+
// set, so it produces body evidence. EffectiveOperation refuses that evidence
|
|
148
|
+
// before boot: another frond's collector proves the provenance is not a body.
|
|
149
|
+
const own = new Set(frond.collectors.map((c) => c.typeName));
|
|
150
|
+
for (const handler of frond.handlers) {
|
|
151
|
+
for (const [opName, op] of handler.operations) {
|
|
152
|
+
for (const param of op.signature?.params ?? []) {
|
|
153
|
+
// Same key the scan writes and the binding plan looks up — `toLowerCase()`
|
|
154
|
+
// here missed a two-word type in BOTH directions: neither confirmed the
|
|
155
|
+
// collector was local nor found it elsewhere, so the rule reported nothing.
|
|
156
|
+
const wanted = lowerFirst(param.type.name);
|
|
157
|
+
if (own.has(wanted)) continue;
|
|
158
|
+
const elsewhere = collectorFronds.get(wanted);
|
|
159
|
+
// No collector anywhere for that type may be an ordinary body parameter.
|
|
160
|
+
// Only a collector that exists in the wrong place makes that interpretation
|
|
161
|
+
// provably false.
|
|
162
|
+
if (!elsewhere) continue;
|
|
163
|
+
violations.push({
|
|
164
|
+
rule: 'collector-in-another-frond',
|
|
165
|
+
severity: 'blocking',
|
|
166
|
+
frond: frond.name,
|
|
167
|
+
subject: `${handler.ctor.name}.${opName}(${param.name})`,
|
|
168
|
+
filePath: handler.filePath,
|
|
169
|
+
dependsOn: { key: collectorKeyOf(wanted), frond: elsewhere, kind: 'collector' },
|
|
170
|
+
message:
|
|
171
|
+
`'${param.name}' is typed ${param.type.name}, and the collector that produces one ` +
|
|
172
|
+
`is declared in frond '${elsewhere}'. A binding plan only sees its own frond's ` +
|
|
173
|
+
`collectors, so the preliminary body interpretation is invalid. Fougere refuses ` +
|
|
174
|
+
`the operation before it can receive what the caller sent. ` +
|
|
175
|
+
`Move the collector into '${frond.name}'.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return violations;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The same verdict, as an assertion. Throws naming every rule that failed, so a
|
|
186
|
+
* test is one line and its output is the sentence — not a diff of two objects.
|
|
187
|
+
*/
|
|
188
|
+
export function assertSplittable(app: { fronds: readonly FrondDescriptor[] }): void {
|
|
189
|
+
const violations = verify(app);
|
|
190
|
+
if (violations.length === 0) return;
|
|
191
|
+
const lines = violations.map((v) => ` [${v.rule}] ${v.frond}/${v.subject}\n ${v.message}\n ${v.filePath}`);
|
|
192
|
+
throw new Error(`${violations.length} violation(s) — this app does not survive a split:\n${lines.join('\n')}`);
|
|
193
|
+
}
|
package/src/wire/call.ts
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call contract — a frond call is a value.
|
|
3
|
+
*
|
|
4
|
+
* (frond, entity, op, invocation) → result, or a thrown FougereError.
|
|
5
|
+
* A transport is a function that executes this call elsewhere; the local
|
|
6
|
+
* runner below is the reference realization. Transports move the value,
|
|
7
|
+
* they never reshape it.
|
|
8
|
+
*/
|
|
9
|
+
import { Card, type SchemaDescriptor } from '@fougere/schema';
|
|
10
|
+
import { factsAnnouncedBy } from '../emit.js';
|
|
11
|
+
import type { InvocationContext } from './invocation.js';
|
|
12
|
+
import { FougereError, ErrorCode } from './errors.js';
|
|
13
|
+
import type { App } from '../boot/types.js';
|
|
14
|
+
import { createTransportEntry } from '../entry/TransportEntry.js';
|
|
15
|
+
|
|
16
|
+
/** Target of a call — which façade operation, wherever it lives. */
|
|
17
|
+
export interface FrondCall {
|
|
18
|
+
/** Frond name, when the caller knows it. Routing hint only. */
|
|
19
|
+
frond?: string;
|
|
20
|
+
/** Entity registration key (e.g. 'product'). */
|
|
21
|
+
entity: string;
|
|
22
|
+
/** Façade operation name (e.g. 'findById', 'search'). */
|
|
23
|
+
op: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A transport executes a call somewhere else. Failures surface as thrown FougereError. */
|
|
27
|
+
export type Transport = (call: FrondCall, invocation: InvocationContext) => Promise<unknown>;
|
|
28
|
+
|
|
29
|
+
/** Reserved namespace — calls the runner answers itself, never a façade. */
|
|
30
|
+
export const RPC_ENTITY = 'rpc';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* What an `rpc` op answers — the door for what the app says about ITSELF, never about a row.
|
|
34
|
+
*
|
|
35
|
+
* `discover` is core's own, registered like any other (`bootstrap.ts`): a package declares
|
|
36
|
+
* its reading beside it, and an app that installed none serves none.
|
|
37
|
+
*/
|
|
38
|
+
export type RpcAnswer = (invocation: InvocationContext, surface?: string) => unknown;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Everything a caller's envelope covers — the call as the sender meant it.
|
|
42
|
+
*
|
|
43
|
+
* Here and not beside the signing code because it is a CONTRACT: the sender binds it,
|
|
44
|
+
* the receiver re-presents what arrived, and a frond written in another language reads
|
|
45
|
+
* this shape without reading our crypto. `body` is bound by digest (arbitrary, possibly
|
|
46
|
+
* large), the rest by value — which is what keeps a canonical-JSON dependency out.
|
|
47
|
+
*/
|
|
48
|
+
export interface SignedCall {
|
|
49
|
+
entity: string;
|
|
50
|
+
op: string;
|
|
51
|
+
params?: Record<string, unknown>;
|
|
52
|
+
query?: Record<string, unknown>;
|
|
53
|
+
body?: unknown;
|
|
54
|
+
state?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One operation, as a stranger meets it — its name, what it is for, what it takes
|
|
59
|
+
* and whether it reads or writes. Everything here already existed on the contract;
|
|
60
|
+
* it just never left the process.
|
|
61
|
+
*/
|
|
62
|
+
export interface CardOp {
|
|
63
|
+
name: string;
|
|
64
|
+
/** The author's own doc sentence, when the method carries one. */
|
|
65
|
+
description?: string;
|
|
66
|
+
/** JSON Schema of what it accepts, when the contract names a view. */
|
|
67
|
+
input?: SchemaDescriptor;
|
|
68
|
+
/** JSON Schema of what it emits, when the contract names one. */
|
|
69
|
+
output?: SchemaDescriptor;
|
|
70
|
+
/** `query` reads, `command` writes — the same call REST turns into GET vs POST. */
|
|
71
|
+
kind: 'query' | 'command';
|
|
72
|
+
/**
|
|
73
|
+
* How much `output` describes: one row, maybe one, many, a page, or nothing shaped.
|
|
74
|
+
* `output` alone is the shape of a ROW, so a consumer generating a signature from
|
|
75
|
+
* this card would otherwise have to guess — and `list` returning a page rather than
|
|
76
|
+
* an array is exactly the guess it would get wrong.
|
|
77
|
+
*/
|
|
78
|
+
cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What an app hosts — the wire projection of its scanned fronds.
|
|
83
|
+
*
|
|
84
|
+
* Two lists per frond, and they are duals: **what you may call**, and **what you will
|
|
85
|
+
* receive**. A door is entered from outside; a fact leaves on its own.
|
|
86
|
+
*
|
|
87
|
+
* `doors` was called `entities` and the name lied: an entry is an ADDRESS, and the shape
|
|
88
|
+
* behind it is optional (`facadeKeyOf` builds its key from a handler's name, which need not
|
|
89
|
+
* be an entity's). The lie cost something real — `fougere sync` demanded a descriptor on
|
|
90
|
+
* every entry and refused whole cards over a health check.
|
|
91
|
+
*/
|
|
92
|
+
export interface IdentityCard {
|
|
93
|
+
fronds: {
|
|
94
|
+
name: string;
|
|
95
|
+
doors: {
|
|
96
|
+
name: string;
|
|
97
|
+
ops: CardOp[];
|
|
98
|
+
/**
|
|
99
|
+
* The shape stored under this name — **absent when nothing is**. A handler may
|
|
100
|
+
* carry no entity (`bootstrap`: *pointing at nothing is legal*), and a door with
|
|
101
|
+
* no rows behind it is ordinary: a health check, a computation, a search across
|
|
102
|
+
* several shapes. A reader that needs the shape must say what it does without one.
|
|
103
|
+
*/
|
|
104
|
+
schema?: SchemaDescriptor;
|
|
105
|
+
}[];
|
|
106
|
+
/**
|
|
107
|
+
* The facts this frond ANNOUNCES — one entry per `Emit<T>` its handlers inject.
|
|
108
|
+
*
|
|
109
|
+
* They carry no operation, which is why they cannot ride in `doors`: the rule there is
|
|
110
|
+
* *hosting means answering*, and an entity with no façade is excluded on purpose (it
|
|
111
|
+
* would publish the auth tables to anyone who asks). A fact is the opposite case —
|
|
112
|
+
* someone WROTE that it leaves, so publishing its shape is honouring a statement, not
|
|
113
|
+
* leaking one.
|
|
114
|
+
*
|
|
115
|
+
* Without this, a fact stopped at the repository boundary: `remotes:` gives the
|
|
116
|
+
* location, colocation gives the contract, and across two repositories there is no
|
|
117
|
+
* colocation. The subscriber had to hand-copy the emitter's declaration.
|
|
118
|
+
*
|
|
119
|
+
* `schema` is absent when the announced type is not a declared entity — legal (nothing
|
|
120
|
+
* requires a fact to be one) and worth saying rather than hiding: the name travels, the
|
|
121
|
+
* shape does not, and a consumer can see exactly that.
|
|
122
|
+
*/
|
|
123
|
+
facts: { name: string; schema?: SchemaDescriptor }[];
|
|
124
|
+
}[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A frond this process knows about, and whether it runs here. */
|
|
128
|
+
export interface FrondPlacement {
|
|
129
|
+
frond: string;
|
|
130
|
+
placement: 'local' | 'remote';
|
|
131
|
+
entities: number;
|
|
132
|
+
doors: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** One frond calling another — an edge of the graph, counted where the call was made. */
|
|
136
|
+
export interface Edge {
|
|
137
|
+
from: string;
|
|
138
|
+
to: string;
|
|
139
|
+
count: number;
|
|
140
|
+
errors: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The shape of the system as ONE process discovered it — the answer to `rpc.topology`.
|
|
145
|
+
*
|
|
146
|
+
* Here and not in the package that produces it, for the reason `SignedCall` is here: it
|
|
147
|
+
* crosses a process boundary, so a reader consumes it without depending on the producer.
|
|
148
|
+
* `@fougere/observability` fills it; a panel reads it; neither imports the other.
|
|
149
|
+
*
|
|
150
|
+
* Nothing in it is declared — a frond is `local` because this process scanned it, `remote`
|
|
151
|
+
* because it answered a call nobody here hosts. `remotes:` states an intent, and the two
|
|
152
|
+
* disagree exactly when something is misconfigured.
|
|
153
|
+
*/
|
|
154
|
+
export interface TopologyReport {
|
|
155
|
+
/** When this process started counting — an edge count is read against it. */
|
|
156
|
+
since: number;
|
|
157
|
+
/** Calls running right now: the one signal a static shape cannot carry. */
|
|
158
|
+
active: number;
|
|
159
|
+
fronds: FrondPlacement[];
|
|
160
|
+
edges: Edge[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The shape a card must have to be walked — `fronds`, and each frond's `doors`.
|
|
165
|
+
*
|
|
166
|
+
* A card crosses a process boundary, so it is judged like anything else that does.
|
|
167
|
+
* The check lives beside the type because two readers walk it: the boot indexes a
|
|
168
|
+
* remote's doors, and `fougere sync` writes classes from them. `TypeError:
|
|
169
|
+
* card.fronds is not iterable` was what a malformed card produced at the boot,
|
|
170
|
+
* naming neither the remote nor the address.
|
|
171
|
+
*
|
|
172
|
+
* The descriptor behind a door is NOT checked here — `Card.toSchema` refuses it, and
|
|
173
|
+
* only where one is consumed.
|
|
174
|
+
*/
|
|
175
|
+
export function assertIdentityCard(value: unknown, source: string): IdentityCard {
|
|
176
|
+
const card = value as IdentityCard | undefined;
|
|
177
|
+
const fronds = Array.isArray(card?.fronds) ? card.fronds : undefined;
|
|
178
|
+
if (!fronds) throw cardRefusal(source, 'no fronds array');
|
|
179
|
+
for (const frond of fronds) {
|
|
180
|
+
if (!frond || typeof frond.name !== 'string') throw cardRefusal(source, 'a frond with no name');
|
|
181
|
+
if (!Array.isArray(frond.doors)) throw cardRefusal(source, `frond '${frond.name}' has no valid doors array`);
|
|
182
|
+
}
|
|
183
|
+
return card as IdentityCard;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function cardRefusal(source: string, what: string): FougereError {
|
|
187
|
+
return new FougereError({
|
|
188
|
+
code: ErrorCode.INTERNAL_ERROR,
|
|
189
|
+
message:
|
|
190
|
+
`${source} answered an invalid identity card: ${what}.\n`
|
|
191
|
+
+ ` A card is what tells this process what the other one hosts, so nothing can be routed from it.\n`
|
|
192
|
+
+ ` - Check that the address serves a Fougere app, and that its version still speaks this card.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** A façade as the runtime holds it: op names to functions, nothing typed about them. */
|
|
197
|
+
type AnyFacade = Record<string, (invocation?: InvocationContext) => Promise<unknown>>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The door built in front of a handler — the framework's second port, after `EntityOrm`.
|
|
201
|
+
*
|
|
202
|
+
* `Facade<PostHandler>` is what a neighbouring frond injects. It names what ARRIVES
|
|
203
|
+
* rather than what is written: never the handler (nobody injects it, and its methods take
|
|
204
|
+
* positional arguments), but the door, whose every op takes the invocation and whose
|
|
205
|
+
* implementation is the local façade or a doublure. A signature therefore says nothing
|
|
206
|
+
* about where the other frond runs, which is the whole point.
|
|
207
|
+
*
|
|
208
|
+
* `keyof T` is the right set by construction, not by approximation: the scan skips
|
|
209
|
+
* `private` and `protected` (`handler-parser.ts`) because "TypeScript already has the
|
|
210
|
+
* word for it", so a handler's public methods ARE its operations — and `keyof` excludes
|
|
211
|
+
* the rest for the same reason.
|
|
212
|
+
*/
|
|
213
|
+
export type Facade<T> = {
|
|
214
|
+
[K in keyof T]: T[K] extends (...args: never[]) => infer R
|
|
215
|
+
? (invocation?: InvocationContext) => R
|
|
216
|
+
: never;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The container key of a façade — THE one place that spells the format.
|
|
221
|
+
*
|
|
222
|
+
* A surface is a named audience, and it is a key: `handlers/public/PostHandler.ts`
|
|
223
|
+
* registers `public:postHandler` next to the default `postHandler`. The default
|
|
224
|
+
* surface is the empty key, so callers that know nothing about surfaces keep
|
|
225
|
+
* designating exactly what they designated before.
|
|
226
|
+
*
|
|
227
|
+
* Nobody outside this package builds this string: adapters ask `app.facadeFor()`,
|
|
228
|
+
* which is why an adapter can stay structurally typed and core-free.
|
|
229
|
+
*/
|
|
230
|
+
export function facadeKeyOf(entityName: string, surface?: string): string {
|
|
231
|
+
return surface ? `${surface}:${entityName}Handler` : `${entityName}Handler`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Where the contracts behind a façade live — the dual of `facadeKeyOf`.
|
|
236
|
+
*
|
|
237
|
+
* The façade answers calls; it does not say what it answers. That sentence used
|
|
238
|
+
* to be true only inside `buildFacade`, so anything asking the app what it hosts
|
|
239
|
+
* got bare names. A door and its terms are registered together, under the same
|
|
240
|
+
* audience.
|
|
241
|
+
*/
|
|
242
|
+
export function contractsKeyOf(entityName: string, surface?: string): string {
|
|
243
|
+
return `${facadeKeyOf(entityName, surface)}:contracts`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Serialize what the app hosts, for one audience. Served on `rpc.discover`,
|
|
248
|
+
* cached by callers.
|
|
249
|
+
*
|
|
250
|
+
* Hosting means answering: an entity with no façade declares no operation, so
|
|
251
|
+
* it is not among the DOORS — publishing its shape would hand an anonymous caller
|
|
252
|
+
* the structure of tables it can never reach (the auth tables, typically), and
|
|
253
|
+
* `fougere sync` would rebuild a schema with nothing to call on it.
|
|
254
|
+
*
|
|
255
|
+
* `facts` is the one thing that shape-without-a-door rule does not cover, and the
|
|
256
|
+
* reason is the opposite of a leak: an entity nobody exposed is silent, whereas a
|
|
257
|
+
* fact was explicitly declared to leave.
|
|
258
|
+
*
|
|
259
|
+
* `surface` makes that sentence audience-aware rather than app-wide: a door
|
|
260
|
+
* answers with what IT serves. Nothing falls back to the full façade — under a
|
|
261
|
+
* named surface, an entity with no façade of its own is simply not there.
|
|
262
|
+
*/
|
|
263
|
+
export function identityCardOf(app: App, surface?: string): IdentityCard {
|
|
264
|
+
const declared = app.fronds.schemas();
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
fronds: app.fronds.map((frond) => {
|
|
268
|
+
// What the frond answers to, not what it stores. This walked `frond.entities`, so a
|
|
269
|
+
// handler carrying no entity — a health check, a search across shapes — was built,
|
|
270
|
+
// served, and absent from the card: `sync` could not generate its door and a remote
|
|
271
|
+
// consumer had no way to know it existed. The boot has said "pointing at nothing is
|
|
272
|
+
// legal" since handlers became the subject; the card had not caught up.
|
|
273
|
+
const byEntity = new Map(frond.entities.map((entity) => [entity.name, entity]));
|
|
274
|
+
const addresses = [...new Set([
|
|
275
|
+
...frond.entities.map((entity) => entity.name),
|
|
276
|
+
...frond.handlers.map((handler) => handler.address),
|
|
277
|
+
])];
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
name: frond.name,
|
|
281
|
+
doors: addresses.flatMap((address) => {
|
|
282
|
+
const ops = facadeOps(app, address, surface);
|
|
283
|
+
if (ops.length === 0) return [];
|
|
284
|
+
const entity = byEntity.get(address);
|
|
285
|
+
return [{
|
|
286
|
+
name: address,
|
|
287
|
+
ops,
|
|
288
|
+
// Absent when nothing of that name is stored. A door is still a door.
|
|
289
|
+
...(entity ? { schema: Card.fromSchema(entity.entityClass, address).descriptor } : {}),
|
|
290
|
+
}];
|
|
291
|
+
}),
|
|
292
|
+
/**
|
|
293
|
+
* What leaves on its own — the same list on every surface, deliberately.
|
|
294
|
+
*
|
|
295
|
+
* A door has an audience; a fact does not. `Emit<T>` names a subject and *the
|
|
296
|
+
* number of readers is not the emitter's business* (`emit.ts`), so narrowing this
|
|
297
|
+
* by `surface` would invent an axis the primitive refuses. The consequence is
|
|
298
|
+
* stated rather than hidden: a fact's SHAPE is readable by anyone who can read the
|
|
299
|
+
* card at all. What must not be published must not be announced.
|
|
300
|
+
*/
|
|
301
|
+
facts: factsAnnouncedBy(frond.handlers).map((name) => {
|
|
302
|
+
const entityClass = declared.get(name);
|
|
303
|
+
return { name, ...(entityClass ? { schema: Card.fromSchema(entityClass, name).descriptor } : {}) };
|
|
304
|
+
}),
|
|
305
|
+
};
|
|
306
|
+
}),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function facadeOps(app: App, entityName: string, surface?: string): CardOp[] {
|
|
311
|
+
let facade: AnyFacade;
|
|
312
|
+
try {
|
|
313
|
+
facade = app.container.resolve<AnyFacade>(facadeKeyOf(entityName, surface));
|
|
314
|
+
} catch {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// The façade is the list of names; the model is the resolved terms.
|
|
319
|
+
const effective = app.operationsFor(entityName, surface);
|
|
320
|
+
if (!effective) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Facade '${facadeKeyOf(entityName, surface)}' exists without an effective operation table.`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return Object.keys(facade).map((name) => {
|
|
327
|
+
const contract = effective.get(name);
|
|
328
|
+
if (!contract) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Facade '${facadeKeyOf(entityName, surface)}' serves '${name}' without an effective contract.`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
name,
|
|
336
|
+
...(contract?.description && { description: contract.description }),
|
|
337
|
+
...(contract?.input && { input: Card.fromSchema(contract.input, name).descriptor }),
|
|
338
|
+
...(contract?.output && { output: Card.fromSchema(contract.output, name).descriptor }),
|
|
339
|
+
...(contract?.cardinality && { cardinality: contract.cardinality }),
|
|
340
|
+
kind: contract.kind,
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Build the local runner — the reference realization of Transport.
|
|
347
|
+
*
|
|
348
|
+
* Resolves strictly from the app's own container: a call that lands here is
|
|
349
|
+
* judged here. A miss is a typed NOT_FOUND, never a forward to another remote.
|
|
350
|
+
*/
|
|
351
|
+
export function createLocalRunner(app: App, surface?: string): Transport {
|
|
352
|
+
return createTransportEntry(app.local, surface);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Build the app runner — same judgment, but resolution follows the app's
|
|
357
|
+
* topology: local façades and remote doublures alike. This is the runner
|
|
358
|
+
* an app's own entry points (browser endpoint, bridges) stand on.
|
|
359
|
+
*
|
|
360
|
+
* `surface` is the audience this runner serves, and it belongs to the DOOR that
|
|
361
|
+
* builds the runner, never to the call: a caller cannot name its own audience
|
|
362
|
+
* any more than it can name its own identity (`state` is stamped server-side
|
|
363
|
+
* for the same reason). Reaching the admin door IS the proof. So `FrondCall`
|
|
364
|
+
* gains nothing and the wire format gains nothing — a remote frond's audience
|
|
365
|
+
* is simply which URL `remotes:` points at.
|
|
366
|
+
*/
|
|
367
|
+
export function createAppRunner(app: App, surface?: string): Transport {
|
|
368
|
+
return createTransportEntry(app, surface);
|
|
369
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error vocabulary — what a refusal IS, independently of who hears it.
|
|
3
|
+
*
|
|
4
|
+
* It travels on the wire (`toJSON`/`fromJSON`) and through the browser-safe
|
|
5
|
+
* `@fougere/core/contract`, so nothing here may know about HTTP, a logger, or a
|
|
6
|
+
* middleware chain. Its HTTP reading is one file over, in `http-error.ts`; it used
|
|
7
|
+
* to live in `middleware.ts`, which named the one concept this is not.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ValidationError } from '@fougere/schema';
|
|
11
|
+
|
|
12
|
+
// ── ErrorCode ──────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** Semantic error codes — transport-agnostic. Each bridge maps them to its own format. */
|
|
15
|
+
export enum ErrorCode {
|
|
16
|
+
// Input
|
|
17
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED',
|
|
18
|
+
BAD_REQUEST = 'BAD_REQUEST',
|
|
19
|
+
|
|
20
|
+
// Auth
|
|
21
|
+
UNAUTHORIZED = 'UNAUTHORIZED',
|
|
22
|
+
FORBIDDEN = 'FORBIDDEN',
|
|
23
|
+
|
|
24
|
+
// Resources
|
|
25
|
+
NOT_FOUND = 'NOT_FOUND',
|
|
26
|
+
GONE = 'GONE',
|
|
27
|
+
CONFLICT = 'CONFLICT',
|
|
28
|
+
LOCKED = 'LOCKED',
|
|
29
|
+
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
|
|
30
|
+
|
|
31
|
+
// Limits
|
|
32
|
+
PRECONDITION_FAILED = 'PRECONDITION_FAILED',
|
|
33
|
+
PAYLOAD_TOO_LARGE = 'PAYLOAD_TOO_LARGE',
|
|
34
|
+
UNPROCESSABLE_ENTITY = 'UNPROCESSABLE_ENTITY',
|
|
35
|
+
TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS',
|
|
36
|
+
REQUEST_TIMEOUT = 'REQUEST_TIMEOUT',
|
|
37
|
+
|
|
38
|
+
// Server
|
|
39
|
+
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
|
40
|
+
NOT_IMPLEMENTED = 'NOT_IMPLEMENTED',
|
|
41
|
+
BAD_GATEWAY = 'BAD_GATEWAY',
|
|
42
|
+
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
|
|
43
|
+
GATEWAY_TIMEOUT = 'GATEWAY_TIMEOUT',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── FougereError ────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export interface FougereErrorOptions {
|
|
49
|
+
code: ErrorCode;
|
|
50
|
+
message: string;
|
|
51
|
+
entity?: string;
|
|
52
|
+
operation?: string;
|
|
53
|
+
details?: unknown;
|
|
54
|
+
cause?: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class FougereError extends Error {
|
|
58
|
+
readonly code: ErrorCode;
|
|
59
|
+
readonly entity?: string;
|
|
60
|
+
readonly operation?: string;
|
|
61
|
+
readonly details?: unknown;
|
|
62
|
+
|
|
63
|
+
constructor(options: FougereErrorOptions) {
|
|
64
|
+
super(options.message, { cause: options.cause });
|
|
65
|
+
this.name = 'FougereError';
|
|
66
|
+
this.code = options.code;
|
|
67
|
+
this.entity = options.entity;
|
|
68
|
+
this.operation = options.operation;
|
|
69
|
+
this.details = options.details;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
toJSON() {
|
|
73
|
+
return {
|
|
74
|
+
code: this.code,
|
|
75
|
+
message: this.message,
|
|
76
|
+
...(this.entity && { entity: this.entity }),
|
|
77
|
+
...(this.operation && { operation: this.operation }),
|
|
78
|
+
...(this.details !== undefined && { details: this.details }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Dual of toJSON — rebuild a typed error from its wire form.
|
|
84
|
+
*
|
|
85
|
+
* Wire input is untrusted: an unknown code degrades to INTERNAL_ERROR
|
|
86
|
+
* (original code kept in details) instead of forging a fake semantic code.
|
|
87
|
+
*/
|
|
88
|
+
static fromJSON(json: unknown): FougereError {
|
|
89
|
+
const raw = (typeof json === 'object' && json !== null ? json : {}) as Record<string, unknown>;
|
|
90
|
+
const known = Object.values(ErrorCode).includes(raw.code as ErrorCode);
|
|
91
|
+
return new FougereError({
|
|
92
|
+
code: known ? (raw.code as ErrorCode) : ErrorCode.INTERNAL_ERROR,
|
|
93
|
+
message: typeof raw.message === 'string' ? raw.message : 'Unknown error',
|
|
94
|
+
entity: typeof raw.entity === 'string' ? raw.entity : undefined,
|
|
95
|
+
operation: typeof raw.operation === 'string' ? raw.operation : undefined,
|
|
96
|
+
details: known ? raw.details : { originalCode: raw.code, details: raw.details },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The refusals behind a VALIDATION_FAILED, or nothing — the ONE place that reads
|
|
103
|
+
* `details` under that code.
|
|
104
|
+
*
|
|
105
|
+
* `details` is `unknown` because it is genuinely polymorphic: `StorageGuard.judge` puts a
|
|
106
|
+
* `string[]` there under INTERNAL_ERROR, `fromJSON` puts `{ originalCode, details }`
|
|
107
|
+
* there for a code it does not know. So the shape is not the field's, it is a reading
|
|
108
|
+
* of the PAIR (code, details) — which is why this is a reader and not a narrower type.
|
|
109
|
+
*
|
|
110
|
+
* It judges rather than casts: five call sites asserted `{ path, message }[]` by hand,
|
|
111
|
+
* and a malformed VALIDATION_FAILED would have been accepted as structured refusals.
|
|
112
|
+
*/
|
|
113
|
+
export function validationErrorsOf(error: unknown): ValidationError[] | undefined {
|
|
114
|
+
if (!(error instanceof FougereError) || error.code !== ErrorCode.VALIDATION_FAILED) return undefined;
|
|
115
|
+
const { details } = error;
|
|
116
|
+
if (!Array.isArray(details)) return undefined;
|
|
117
|
+
const refusals = details.filter(
|
|
118
|
+
(entry): entry is ValidationError =>
|
|
119
|
+
typeof entry === 'object' && entry !== null
|
|
120
|
+
&& typeof (entry as ValidationError).path === 'string'
|
|
121
|
+
&& typeof (entry as ValidationError).message === 'string',
|
|
122
|
+
);
|
|
123
|
+
return refusals.length === details.length ? refusals : undefined;
|
|
124
|
+
}
|