bazis 0.96.1
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/LICENSE +21 -0
- package/README.md +146 -0
- package/cli/build.ts +147 -0
- package/cli/codegen.ts +40 -0
- package/cli/generateModule.ts +187 -0
- package/cli/generateProject.ts +183 -0
- package/cli/main.ts +125 -0
- package/cli/moduleRegistration.ts +127 -0
- package/cli/naming.ts +124 -0
- package/cli/parseCli.ts +151 -0
- package/cli/templates/module.ts +497 -0
- package/cli/templates/pack.ts +76 -0
- package/cli/templates/passport.ts +178 -0
- package/core/agent/AgentContextBuilder.ts +567 -0
- package/core/agent/AgentDataDefinition.ts +8 -0
- package/core/agent/AgentRegistry.ts +539 -0
- package/core/agent/AgentRuntime.ts +47 -0
- package/core/agent/AgentToolExecutor.ts +2130 -0
- package/core/agent/AgentToolHooks.ts +39 -0
- package/core/agent/ModelProvider.ts +21 -0
- package/core/agent/README.md +229 -0
- package/core/agent/Tool.contract.ts +20 -0
- package/core/agent/decorators/Agent.ts +11 -0
- package/core/agent/decorators/Prompt.ts +11 -0
- package/core/agent/decorators/Task.ts +29 -0
- package/core/agent/decorators/Tool.ts +12 -0
- package/core/agent/errors.ts +43 -0
- package/core/agent/generated/catalog.ts +26 -0
- package/core/agent/index.ts +170 -0
- package/core/agent/internal/AgentExecutionDriver.ts +1497 -0
- package/core/agent/internal/AgentExecutionState.ts +775 -0
- package/core/agent/internal/AgentJsonSchema.ts +150 -0
- package/core/agent/internal/AgentModelBinding.ts +57 -0
- package/core/agent/internal/Schema.contract.ts +36 -0
- package/core/agent/internal/ToolAudit.projector.ts +163 -0
- package/core/agent/internal/ToolContract.validator.ts +102 -0
- package/core/agent/metadata.ts +398 -0
- package/core/agent/moduleContributions-v1.ts +113 -0
- package/core/agent/semantic.ts +903 -0
- package/core/agent/session/codec.ts +33 -0
- package/core/agent/session/contracts.ts +96 -0
- package/core/agent/session/entities.ts +84 -0
- package/core/agent/session/protector.ts +92 -0
- package/core/app/index.ts +5 -0
- package/core/app/runApp.ts +293 -0
- package/core/app/ui/index.ts +29 -0
- package/core/app/ui/uiProfileResolver.ts +966 -0
- package/core/app/ui/uiSurfaceHosting.ts +38 -0
- package/core/app/ui/uiSurfaceHttp.ts +476 -0
- package/core/background/BackgroundService.ts +223 -0
- package/core/background/backgroundModule.ts +42 -0
- package/core/background/decorator.ts +43 -0
- package/core/background/delay.ts +27 -0
- package/core/background/index.ts +15 -0
- package/core/cache/COMPATIBILITY.md +13 -0
- package/core/cache/ICache.ts +71 -0
- package/core/cache/MemoryCache.ts +394 -0
- package/core/cache/README.md +83 -0
- package/core/cache/SPEC.md +1355 -0
- package/core/cache/cacheModule.ts +21 -0
- package/core/cache/decorators/Cacheable.ts +38 -0
- package/core/cache/decorators/CacheableRedis.ts +39 -0
- package/core/cache/decorators/OutputCache.ts +33 -0
- package/core/cache/decorators/OutputRedisCache.ts +33 -0
- package/core/cache/decorators/cacheableMetadata.ts +78 -0
- package/core/cache/decorators/cacheableRedisMetadata.ts +81 -0
- package/core/cache/decorators/outputCacheMetadata.ts +71 -0
- package/core/cache/decorators/outputRedisCacheMetadata.ts +77 -0
- package/core/cache/di/classProviderHook.ts +26 -0
- package/core/cache/distributed/CacheCodec.ts +5 -0
- package/core/cache/distributed/DistributedCache.ts +403 -0
- package/core/cache/distributed/DistributedCacheDriver.ts +83 -0
- package/core/cache/distributed/DistributedCacheStores.ts +28 -0
- package/core/cache/distributed/IDistributedCache.ts +40 -0
- package/core/cache/distributed/NamedCacheRegistry.ts +23 -0
- package/core/cache/distributed/codecs.ts +32 -0
- package/core/cache/distributed/index.ts +19 -0
- package/core/cache/errors/CacheError.ts +16 -0
- package/core/cache/http/CachedHttpPayload.ts +213 -0
- package/core/cache/http/applyClientCacheHeaders.ts +61 -0
- package/core/cache/http/buildOutputCacheKey.ts +154 -0
- package/core/cache/http/buildRedisOutputCacheKey.ts +16 -0
- package/core/cache/http/composeOutputCache.ts +130 -0
- package/core/cache/http/outputCacheMiddleware.ts +111 -0
- package/core/cache/http/outputCacheSecurityWarning.ts +58 -0
- package/core/cache/http/outputRedisCacheMiddleware.ts +118 -0
- package/core/cache/index.ts +97 -0
- package/core/cache/internal/AutomaticCacheKey.ts +106 -0
- package/core/cache/internal/CacheKeyGuard.ts +56 -0
- package/core/cache/internal/buildCacheProviders.ts +146 -0
- package/core/cache/internal/normalizeCacheKey.ts +9 -0
- package/core/cache/internal/resolveCachePolicy.ts +32 -0
- package/core/cache/internal/resolveCacheableRedisPolicy.ts +17 -0
- package/core/cache/internal/resolveOutputRedisCachePolicy.ts +17 -0
- package/core/cache/providers/cachedProviders.ts +68 -0
- package/core/cache/services/cacheProxy.ts +243 -0
- package/core/cache/tokens/CACHE_POLICIES.ts +5 -0
- package/core/cache/tokens/DISTRIBUTED_CACHE.ts +30 -0
- package/core/cache/types/CacheEntry.ts +7 -0
- package/core/cache/types/CacheOptions.ts +60 -0
- package/core/cache/types/CachePolicy.ts +74 -0
- package/core/cache/types/CacheableRedisOptions.ts +23 -0
- package/core/cache/types/OutputRedisCacheOptions.ts +25 -0
- package/core/di/README.md +104 -0
- package/core/di/ServiceCollection.ts +185 -0
- package/core/di/ServiceProvider.ts +689 -0
- package/core/di/ServiceScope.ts +61 -0
- package/core/di/container.ts +38 -0
- package/core/di/errors/AmbiguousNamedDependencyError.ts +10 -0
- package/core/di/errors/AsyncResolutionRequiredError.ts +12 -0
- package/core/di/errors/CircularDependencyError.ts +10 -0
- package/core/di/errors/ClassDependenciesMismatchError.ts +15 -0
- package/core/di/errors/DiError.ts +6 -0
- package/core/di/errors/InvalidProviderError.ts +9 -0
- package/core/di/errors/ModuleEncapsulationError.ts +7 -0
- package/core/di/errors/ModuleOwnedProviderConflictError.ts +8 -0
- package/core/di/errors/NamedDependencyNotFoundError.ts +7 -0
- package/core/di/errors/OptionsValidationError.ts +7 -0
- package/core/di/errors/ProviderNotFoundError.ts +10 -0
- package/core/di/errors/ScopeDisposedError.ts +7 -0
- package/core/di/errors/ScopedServiceFromRootError.ts +9 -0
- package/core/di/errors/ServiceValidationError.ts +7 -0
- package/core/di/errors/index.ts +14 -0
- package/core/di/extensions/activator.ts +39 -0
- package/core/di/extensions/application.ts +117 -0
- package/core/di/extensions/hosted-service.ts +124 -0
- package/core/di/extensions/index.ts +23 -0
- package/core/di/extensions/options-reloadable.ts +137 -0
- package/core/di/extensions/options.ts +91 -0
- package/core/di/extensions/service-provider-token.ts +21 -0
- package/core/di/index.ts +97 -0
- package/core/di/internal/GraphValidator.ts +119 -0
- package/core/di/internal/HostedServicePlan.ts +158 -0
- package/core/di/internal/NamedTokenIndex.ts +55 -0
- package/core/di/internal/OpenGenericRegistration.ts +14 -0
- package/core/di/internal/ResolutionPlan.ts +18 -0
- package/core/di/internal/ResolutionScopeState.ts +19 -0
- package/core/di/internal/ResolutionTracker.ts +153 -0
- package/core/di/internal/ScopeLifecycle.ts +159 -0
- package/core/di/internal/ServiceRegistration.ts +14 -0
- package/core/di/internal/ServiceRegistry.ts +252 -0
- package/core/di/internal/classDeps.ts +38 -0
- package/core/di/internal/disposal.ts +58 -0
- package/core/di/internal/providerDeps.ts +26 -0
- package/core/di/module/DI.ts +146 -0
- package/core/di/module/Global.ts +25 -0
- package/core/di/module/Module.ts +44 -0
- package/core/di/module/ModuleRegistrar.ts +69 -0
- package/core/di/module/autoDeps.ts +39 -0
- package/core/di/module/classProviderHooks.ts +66 -0
- package/core/di/module/collectModuleConfigs.ts +57 -0
- package/core/di/module/collectModuleControllers.ts +53 -0
- package/core/di/module/collectModuleUiProfiles.ts +53 -0
- package/core/di/module/createContainer.ts +261 -0
- package/core/di/module/encapsulation.ts +416 -0
- package/core/di/module/encapsulationHooks.ts +36 -0
- package/core/di/module/generatedProviderAttachments.ts +102 -0
- package/core/di/module/index.ts +41 -0
- package/core/di/module/moduleExtensions.ts +35 -0
- package/core/di/module/moduleNameBinding.ts +136 -0
- package/core/di/module/moduleOwnedProviderContributors.ts +409 -0
- package/core/di/module/providerSelection.ts +90 -0
- package/core/di/module/shortcuts.ts +111 -0
- package/core/di/module/types/BazisModule.ts +71 -0
- package/core/di/module/types/DiRegistrar.ts +24 -0
- package/core/di/module/types/index.ts +2 -0
- package/core/di/provider/index.ts +26 -0
- package/core/di/provider/providerGuards.ts +17 -0
- package/core/di/provider/types/AsyncFactoryProvider.ts +15 -0
- package/core/di/provider/types/ClassProvider.ts +18 -0
- package/core/di/provider/types/FactoryProvider.ts +20 -0
- package/core/di/provider/types/KeyedDependency.ts +20 -0
- package/core/di/provider/types/LazyDependency.ts +31 -0
- package/core/di/provider/types/NamedDependency.ts +16 -0
- package/core/di/provider/types/Provider.ts +10 -0
- package/core/di/provider/types/ProviderDefinition.ts +11 -0
- package/core/di/provider/types/ProviderDependencyList.ts +11 -0
- package/core/di/provider/types/ProviderLifetime.ts +1 -0
- package/core/di/provider/types/ProviderOwnership.ts +7 -0
- package/core/di/provider/types/ResolvedDeps.ts +18 -0
- package/core/di/provider/types/ValueProvider.ts +6 -0
- package/core/di/provider/types/index.ts +13 -0
- package/core/di/token.ts +84 -0
- package/core/di/types/BuildServiceProviderOptions.ts +4 -0
- package/core/di/types/ServiceKey.ts +1 -0
- package/core/di/types/ServiceResolver.ts +16 -0
- package/core/di/types/index.ts +3 -0
- package/core/generatedFingerprint.ts +60 -0
- package/core/generatedRuntime.ts +187 -0
- package/core/grpc/Grpc.module.ts +23 -0
- package/core/grpc/GrpcBinding.contract.ts +35 -0
- package/core/grpc/GrpcCall.ts +73 -0
- package/core/grpc/GrpcClient.ts +172 -0
- package/core/grpc/GrpcClientCall.ts +267 -0
- package/core/grpc/GrpcError.ts +19 -0
- package/core/grpc/GrpcRequest.binding.ts +45 -0
- package/core/grpc/GrpcServer.ts +204 -0
- package/core/grpc/GrpcStatus.ts +8 -0
- package/core/grpc/GrpcTransport.ts +174 -0
- package/core/grpc/Metadata.ts +73 -0
- package/core/grpc/ProtoCodec.ts +225 -0
- package/core/grpc/ProtoParser.ts +192 -0
- package/core/grpc/ProtoReader.ts +34 -0
- package/core/grpc/ProtoSchema.ts +52 -0
- package/core/grpc/ServerCredentials.ts +30 -0
- package/core/grpc/clientContracts.ts +39 -0
- package/core/grpc/contracts.ts +45 -0
- package/core/grpc/decorators.ts +82 -0
- package/core/grpc/grpcClientProvider.ts +9 -0
- package/core/grpc/index.ts +17 -0
- package/core/grpc/moduleControllers.ts +38 -0
- package/core/grpc/protobuf.ts +80 -0
- package/core/grpc/serviceDefinition.ts +20 -0
- package/core/http/Authorization/Authorize.ts +52 -0
- package/core/http/Authorization/authorizeComposer.ts +31 -0
- package/core/http/Authorization/metadata.ts +158 -0
- package/core/http/Binding/ParameterBinder.ts +109 -0
- package/core/http/Binding/RequestModel.ts +27 -0
- package/core/http/Binding/autoBindings.ts +125 -0
- package/core/http/Binding/bindings.ts +19 -0
- package/core/http/Binding/convert.ts +48 -0
- package/core/http/Binding/generatedSpec.ts +18 -0
- package/core/http/Binding/listModelRegistry.ts +47 -0
- package/core/http/Binding/modelBinder.ts +345 -0
- package/core/http/Binding/modelValidator.ts +30 -0
- package/core/http/Binding/requestModelRegistry.ts +117 -0
- package/core/http/Decorators/attributes.ts +92 -0
- package/core/http/Decorators/controller.ts +24 -0
- package/core/http/Decorators/metadata.ts +107 -0
- package/core/http/Decorators/routes.ts +131 -0
- package/core/http/Errors/HttpError.ts +92 -0
- package/core/http/HttpContext/HttpContext.ts +148 -0
- package/core/http/HttpContext/ResponseBuilder.ts +37 -0
- package/core/http/HttpContext/inspectableRedirects.ts +47 -0
- package/core/http/HttpContext/principal.ts +24 -0
- package/core/http/HttpContext/responseLifetime.ts +45 -0
- package/core/http/HttpServer.ts +627 -0
- package/core/http/Middleware/accessLog.ts +90 -0
- package/core/http/Middleware/cors.ts +160 -0
- package/core/http/Middleware/errorHandler.ts +100 -0
- package/core/http/Middleware/pipeline.ts +21 -0
- package/core/http/Middleware/rateLimit.ts +104 -0
- package/core/http/Middleware/securityHeaders.ts +118 -0
- package/core/http/Middleware/types.ts +19 -0
- package/core/http/OpenApi/generatedOpenApiRegistry.ts +102 -0
- package/core/http/OpenApi/openApiDocs.ts +75 -0
- package/core/http/OpenApi/openApiDocument.ts +356 -0
- package/core/http/README.md +139 -0
- package/core/http/Results/HttpResult.ts +19 -0
- package/core/http/Results/normalize.ts +93 -0
- package/core/http/Results/results.ts +70 -0
- package/core/http/Routing/Router.ts +201 -0
- package/core/http/Routing/RouterBuilder.ts +184 -0
- package/core/http/Routing/template.ts +112 -0
- package/core/http/SPEC.md +981 -0
- package/core/http/Versioning/VersionReader.ts +20 -0
- package/core/http/WebSocketUpgrade.ts +29 -0
- package/core/http/composeRouteMiddlewareComposers.ts +21 -0
- package/core/http/correlation/createCorrelationIdMiddleware.ts +79 -0
- package/core/http/httpModule.ts +48 -0
- package/core/http/index.ts +140 -0
- package/core/http/middlewareTokens.ts +48 -0
- package/core/http/options.ts +134 -0
- package/core/http-client/index.ts +105 -0
- package/core/infra/Infra.ts +158 -0
- package/core/infra/InfraConnector.ts +86 -0
- package/core/infra/InfraLifecycle.ts +86 -0
- package/core/infra/cache/RedisDistributedCacheBackend.ts +88 -0
- package/core/infra/cache/RedisDistributedCacheDriver.ts +186 -0
- package/core/infra/cache/index.ts +7 -0
- package/core/infra/connectorConfig.ts +29 -0
- package/core/infra/connectorIdentity.ts +39 -0
- package/core/infra/connectors/codex/CodexAppServer.ts +146 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +258 -0
- package/core/infra/connectors/codex/CodexPolicy.ts +114 -0
- package/core/infra/connectors/codex/CodexTurn.ts +133 -0
- package/core/infra/connectors/codex/contracts.ts +82 -0
- package/core/infra/connectors/codex.ts +26 -0
- package/core/infra/connectors/llm.ts +78 -0
- package/core/infra/connectors/llmRouter.ts +443 -0
- package/core/infra/connectors/openaiCompatible.ts +590 -0
- package/core/infra/connectors/openaiTextStream.ts +134 -0
- package/core/infra/connectors/opensearch.ts +307 -0
- package/core/infra/connectors/postgres.ts +105 -0
- package/core/infra/connectors/redis.ts +132 -0
- package/core/infra/index.ts +58 -0
- package/core/kernel/ApplicationLifetime.ts +91 -0
- package/core/kernel/Bazis.ts +73 -0
- package/core/kernel/Environment.ts +53 -0
- package/core/kernel/Kernel.ts +391 -0
- package/core/kernel/KernelBuilder.ts +172 -0
- package/core/kernel/LifecycleCoordinator.ts +204 -0
- package/core/kernel/README.md +66 -0
- package/core/kernel/SupervisedHostedService.ts +147 -0
- package/core/kernel/config/ConfigRegistry.ts +48 -0
- package/core/kernel/config/Configuration.ts +110 -0
- package/core/kernel/config/README.md +62 -0
- package/core/kernel/config/Secret.ts +29 -0
- package/core/kernel/config/addConfigOptions.ts +63 -0
- package/core/kernel/config/defineConfig.ts +211 -0
- package/core/kernel/config/sources.ts +123 -0
- package/core/kernel/correlation/requestContext.ts +57 -0
- package/core/kernel/errors/ConfigKeyMissingError.ts +7 -0
- package/core/kernel/errors/KernelError.ts +7 -0
- package/core/kernel/errors/ShutdownTimeoutError.ts +7 -0
- package/core/kernel/errors/StartupAbortedError.ts +7 -0
- package/core/kernel/errors/StartupTimeoutError.ts +7 -0
- package/core/kernel/errors/index.ts +5 -0
- package/core/kernel/events/EventBus.ts +140 -0
- package/core/kernel/events/EventToken.ts +17 -0
- package/core/kernel/events/eventSubscribers.ts +126 -0
- package/core/kernel/events/kernelEvents.ts +16 -0
- package/core/kernel/events/onEvent.ts +32 -0
- package/core/kernel/health/HealthCheckContracts.ts +42 -0
- package/core/kernel/health/HealthService.ts +93 -0
- package/core/kernel/index.ts +96 -0
- package/core/kernel/internal/awaitAbortable.ts +18 -0
- package/core/kernel/internal/awaitShutdown.ts +21 -0
- package/core/kernel/internal/reportDiagnosticFailure.ts +8 -0
- package/core/kernel/internal/validateTimeout.ts +8 -0
- package/core/kernel/lifecycleHooks.ts +18 -0
- package/core/kernel/logging/ConsoleLogger.ts +68 -0
- package/core/kernel/logging/Logger.ts +22 -0
- package/core/kernel/types/ConfigSource.ts +9 -0
- package/core/kernel/types/EnvironmentName.ts +1 -0
- package/core/kernel/types/KernelOptions.ts +23 -0
- package/core/kernel/types/LifecycleHook.ts +18 -0
- package/core/kernel/types/index.ts +4 -0
- package/core/orm/DATABASE_PROVIDER.ts +5 -0
- package/core/orm/OrmHostedPlan.validator.ts +55 -0
- package/core/orm/OrmLifecycle.ts +146 -0
- package/core/orm/OrmOwnedStoreLifecycle.ts +96 -0
- package/core/orm/buildOrmProviders.ts +152 -0
- package/core/orm/databaseConnector.ts +108 -0
- package/core/orm/encapsulationHook.ts +19 -0
- package/core/orm/index.ts +95 -0
- package/core/orm/listQuery.ts +198 -0
- package/core/orm/ormModule.ts +173 -0
- package/core/orm/ownedStoreContributions.ts +195 -0
- package/core/orm/registerRepositories.ts +27 -0
- package/core/orm/repository.ts +45 -0
- package/core/orm/strictSchemaHostedIdentity.ts +17 -0
- package/core/scripts/agent-codegen.ts +1166 -0
- package/core/scripts/di-generate-target.ts +90 -0
- package/core/scripts/di-generate.ts +1837 -0
- package/core/scripts/orm-predicate-codegen.ts +33 -0
- package/core/scripts/request-model-codegen.ts +326 -0
- package/core/websocket/DESIGN.md +297 -0
- package/core/websocket/WsRateLimiter.ts +101 -0
- package/core/websocket/adapter/adapter.interface.ts +45 -0
- package/core/websocket/adapter/in-memory.adapter.ts +190 -0
- package/core/websocket/adapter/redis-delivery-scripts.ts +135 -0
- package/core/websocket/adapter/redis-delivery-store.ts +86 -0
- package/core/websocket/adapter/redis-operations.ts +33 -0
- package/core/websocket/adapter/redis.adapter.ts +218 -0
- package/core/websocket/codec/binary.codec.ts +87 -0
- package/core/websocket/codec/json.codec.ts +34 -0
- package/core/websocket/codec/packet-codec.interface.ts +17 -0
- package/core/websocket/decorators.ts +165 -0
- package/core/websocket/explorer.ts +126 -0
- package/core/websocket/index.ts +78 -0
- package/core/websocket/middleware.ts +146 -0
- package/core/websocket/packet-codec.ts +75 -0
- package/core/websocket/reliable-delivery.ts +56 -0
- package/core/websocket/session-creation.ts +16 -0
- package/core/websocket/session-manager.ts +856 -0
- package/core/websocket/socket-wrapper.ts +314 -0
- package/core/websocket/topic-cache.ts +59 -0
- package/core/websocket/types.ts +122 -0
- package/core/websocket/websocketModule.ts +96 -0
- package/core/websocket/ws-dispatch.ts +123 -0
- package/core/websocket/ws-server.ts +1707 -0
- package/index.ts +281 -0
- package/library/boundary/diagnostics-v1.ts +136 -0
- package/library/boundary/index.ts +73 -0
- package/library/boundary/json/decodeBoundedJsonV1.ts +518 -0
- package/library/boundary/json/limits-v1.ts +46 -0
- package/library/boundary/json/normalizeBoundedJsonV1.ts +256 -0
- package/library/boundary/json/types-v1.ts +11 -0
- package/library/boundary/schema/boundarySchemaHashV1.ts +6 -0
- package/library/boundary/schema/collectBoundaryValueDiagnosticsV1.ts +325 -0
- package/library/boundary/schema/decodeBoundarySchemaV1.ts +646 -0
- package/library/boundary/schema/index.ts +8 -0
- package/library/boundary/schema/types-v1.ts +99 -0
- package/library/boundary/schema/validateBoundaryValueV1.ts +58 -0
- package/library/boundary/serialization/canonicalJsonV1.ts +66 -0
- package/library/http-client/HttpClient.ts +969 -0
- package/library/http-client/HttpClientFactory.ts +45 -0
- package/library/http-client/errors.ts +68 -0
- package/library/http-client/index.ts +43 -0
- package/library/http-client/interceptors.ts +40 -0
- package/library/http-client/progress.ts +62 -0
- package/library/http-client/redirectProtocol.ts +6 -0
- package/library/http-client/serialize.ts +133 -0
- package/library/http-client/types.ts +174 -0
- package/library/jsonapi/ListQueryError.ts +25 -0
- package/library/jsonapi/ListRequest.ts +37 -0
- package/library/jsonapi/decorators.ts +161 -0
- package/library/jsonapi/document.ts +124 -0
- package/library/jsonapi/index.ts +40 -0
- package/library/jsonapi/parseListQuery.ts +291 -0
- package/library/jsonapi/types.ts +125 -0
- package/library/jwt/JwtEncoder.ts +40 -0
- package/library/jwt/JwtKeyRing.ts +164 -0
- package/library/jwt/JwtValidator.ts +210 -0
- package/library/jwt/TokenIssuer.ts +149 -0
- package/library/jwt/TokenService.ts +51 -0
- package/library/jwt/base64url.ts +61 -0
- package/library/jwt/claims.ts +36 -0
- package/library/jwt/errors.ts +44 -0
- package/library/jwt/index.ts +59 -0
- package/library/jwt/limits.ts +8 -0
- package/library/jwt/signing/Hs256Algorithm.ts +62 -0
- package/library/jwt/signing/Rs256Algorithm.ts +133 -0
- package/library/jwt/signing/SigningAlgorithm.ts +18 -0
- package/library/jwt/signing/pem.ts +30 -0
- package/library/openapi/codegen.ts +875 -0
- package/library/openapi/document.ts +326 -0
- package/library/openapi/index.ts +13 -0
- package/library/openapi/types.ts +69 -0
- package/library/openapi/ui.ts +182 -0
- package/library/orm/DatabaseFacade.ts +209 -0
- package/library/orm/DbContext.ts +107 -0
- package/library/orm/DbContextFactory.ts +25 -0
- package/library/orm/DbContextOptions.ts +33 -0
- package/library/orm/Metadata/DynamicModelBuilder.ts +497 -0
- package/library/orm/Metadata/KeyTuple.ts +36 -0
- package/library/orm/Metadata/ModelBuilder.ts +299 -0
- package/library/orm/Metadata/OrmModel.ts +80 -0
- package/library/orm/Metadata/ValueConverter.ts +57 -0
- package/library/orm/Metadata/conventions.ts +54 -0
- package/library/orm/Metadata/decorators.ts +396 -0
- package/library/orm/Metadata/types.ts +140 -0
- package/library/orm/Providers/OwnedStoreCatalog.reader.ts +1802 -0
- package/library/orm/Providers/PostgresDialect.ts +234 -0
- package/library/orm/Providers/PostgresProvider.ts +1494 -0
- package/library/orm/Providers/PostgresSchema.decoder.ts +107 -0
- package/library/orm/Providers/limits.ts +29 -0
- package/library/orm/Providers/operationDeadline.ts +43 -0
- package/library/orm/Providers/ormOwnedStoreRuntime.ts +19 -0
- package/library/orm/Providers/ormTransactionRuntime.ts +117 -0
- package/library/orm/Providers/propertyConversion.ts +14 -0
- package/library/orm/Providers/resolvedForeignKey.ts +16 -0
- package/library/orm/Providers/transactionCallbacks.ts +45 -0
- package/library/orm/Providers/transactionOutcome.ts +82 -0
- package/library/orm/Providers/transactionScopes.ts +78 -0
- package/library/orm/Providers/types.ts +207 -0
- package/library/orm/Query/DbSet.ts +78 -0
- package/library/orm/Query/EntityQuery.ts +247 -0
- package/library/orm/Query/ImmediateMutations.ts +226 -0
- package/library/orm/Query/IncludeLoader.ts +200 -0
- package/library/orm/Query/QueryPlan.ts +55 -0
- package/library/orm/Query/SqlTranslator.ts +195 -0
- package/library/orm/Query/conditions.ts +127 -0
- package/library/orm/Query/materialize.ts +39 -0
- package/library/orm/README.md +256 -0
- package/library/orm/Repository/README.md +684 -0
- package/library/orm/Repository/Repository.ts +68 -0
- package/library/orm/Saving/CommandBuilder.ts +139 -0
- package/library/orm/Saving/ExecutionStrategy.ts +128 -0
- package/library/orm/Saving/SaveExecutor.ts +413 -0
- package/library/orm/Saving/applyConventions.ts +64 -0
- package/library/orm/Schema/CheckExpression.ts +327 -0
- package/library/orm/Schema/ExactSchemaVerifier.ts +168 -0
- package/library/orm/Schema/ExpectedSchema.ts +43 -0
- package/library/orm/Schema/MigrationRunner.ts +146 -0
- package/library/orm/Schema/Migrator.ts +73 -0
- package/library/orm/Schema/OrmOwnedStore.ts +123 -0
- package/library/orm/Schema/OwnedStoreAdmission.ts +752 -0
- package/library/orm/Schema/OwnedStoreCanonical.ts +273 -0
- package/library/orm/Schema/OwnedStoreCatalog.ts +1325 -0
- package/library/orm/Schema/SafeAdditiveSchema.ts +100 -0
- package/library/orm/Schema/SchemaAdmission.ts +62 -0
- package/library/orm/Schema/SchemaDiffer.ts +78 -0
- package/library/orm/Schema/formatSchemaDifferences.ts +62 -0
- package/library/orm/Schema/introspection.ts +52 -0
- package/library/orm/Schema/physicalColumnTypes.ts +37 -0
- package/library/orm/Schema/tableKey.ts +26 -0
- package/library/orm/Tracking/ChangeTracker.ts +448 -0
- package/library/orm/Tracking/EntityState.ts +13 -0
- package/library/orm/Transactions/OrmTransaction.ts +30 -0
- package/library/orm/Transactions/TransactionScopeCoordinator.ts +377 -0
- package/library/orm/errors.ts +190 -0
- package/library/orm/index.ts +162 -0
- package/library/orm/runtime.ts +14 -0
- package/library/redaction/index.ts +166 -0
- package/library/ui/UiProfile.ts +77 -0
- package/library/ui/compiled-v1.ts +131 -0
- package/library/ui/compiler/compileUiSurfaceV1.ts +717 -0
- package/library/ui/compiler/contracts-v1.ts +25 -0
- package/library/ui/compiler/index.ts +12 -0
- package/library/ui/compiler/openApiIndex.ts +246 -0
- package/library/ui/defineUiProfile.ts +340 -0
- package/library/ui/diagnostics-v1.ts +33 -0
- package/library/ui/index.ts +129 -0
- package/library/ui/profile-v1.ts +191 -0
- package/library/ui/serialization.ts +130 -0
- package/library/ui/uiProfileAuthoring.ts +289 -0
- package/library/ui/uiProfileAuthoringMetadata.ts +78 -0
- package/library/validation/MessageRegistry.ts +144 -0
- package/library/validation/README.md +62 -0
- package/library/validation/RuleCompiler.ts +183 -0
- package/library/validation/RuleEngine.ts +401 -0
- package/library/validation/SPEC.md +579 -0
- package/library/validation/ValidationError.ts +37 -0
- package/library/validation/ValidationResult.ts +63 -0
- package/library/validation/Validator.ts +101 -0
- package/library/validation/index.ts +16 -0
- package/library/validation/metadata.ts +69 -0
- package/library/validation/modelValidatorAdapter.ts +119 -0
- package/library/validation/types/ValidationCode.ts +69 -0
- package/library/validation/types/ValidatorOptions.ts +153 -0
- package/package.json +80 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ConfigKeyMissingError, KernelError } from "../errors";
|
|
2
|
+
import type { ConfigSource } from "../types";
|
|
3
|
+
import { Secret } from "./Secret";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Immutable merged configuration: a flat map of dot-separated keys
|
|
7
|
+
* ("db.host") to string values. Built once from layered sources
|
|
8
|
+
* (defaults -> file -> env -> CLI), later sources override earlier ones.
|
|
9
|
+
*/
|
|
10
|
+
export class Configuration {
|
|
11
|
+
private readonly values: ReadonlyMap<string, string>;
|
|
12
|
+
private readonly origins: ReadonlyMap<string, { readonly source: string; readonly priority: number }>;
|
|
13
|
+
|
|
14
|
+
public constructor(values: ReadonlyMap<string, string>, origins: ReadonlyMap<string, { readonly source: string; readonly priority: number }> = new Map()) {
|
|
15
|
+
this.values = new Map([...values].map(([key, value]) => [key.toLowerCase(), value]));
|
|
16
|
+
this.origins = new Map([...origins].map(([key, value]) => [key.toLowerCase(), Object.freeze({ ...value })]));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static empty(): Configuration {
|
|
20
|
+
return new Configuration(new Map());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public has(key: string): boolean {
|
|
24
|
+
return this.values.has(key.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public get(key: string): string | undefined {
|
|
28
|
+
return this.values.get(key.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public origin(key: string): { readonly source: string; readonly priority: number } {
|
|
32
|
+
return this.origins.get(key.toLowerCase()) ?? { source: "configuration", priority: 0 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public getOrDefault(key: string, defaultValue: string): string {
|
|
36
|
+
return this.get(key) ?? defaultValue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public require(key: string): string {
|
|
40
|
+
const value = this.get(key);
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
throw new ConfigKeyMissingError(key);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public getNumber(key: string): number | undefined {
|
|
48
|
+
const raw = this.get(key);
|
|
49
|
+
if (raw === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const parsed = Number(raw);
|
|
53
|
+
if (!raw.trim() || !Number.isFinite(parsed)) {
|
|
54
|
+
throw new KernelError(`Configuration key "${key}" must be a finite number.`);
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public requireNumber(key: string): number {
|
|
60
|
+
const value = this.getNumber(key);
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
throw new ConfigKeyMissingError(key);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getBoolean(key: string): boolean | undefined {
|
|
68
|
+
const raw = this.get(key);
|
|
69
|
+
if (raw === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const normalized = raw.toLowerCase();
|
|
73
|
+
if (normalized === "true" || normalized === "1") {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (normalized === "false" || normalized === "0") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
throw new KernelError(`Configuration key "${key}" is not a boolean.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Wraps the value into a Secret so it cannot leak through logs. */
|
|
83
|
+
public getSecret(key: string): Secret | undefined {
|
|
84
|
+
const raw = this.get(key);
|
|
85
|
+
return raw === undefined ? undefined : new Secret(raw);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public requireSecret(key: string): Secret {
|
|
89
|
+
return new Secret(this.require(key));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public keys(): readonly string[] {
|
|
93
|
+
return [...this.values.keys()];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Merges sources in order: later sources win. */
|
|
98
|
+
export async function loadConfiguration(sources: readonly ConfigSource[]): Promise<Configuration> {
|
|
99
|
+
const merged = new Map<string, string>();
|
|
100
|
+
const origins = new Map<string, { source: string; priority: number }>();
|
|
101
|
+
for (let index = 0; index < sources.length; index += 1) {
|
|
102
|
+
const source = sources[index] as ConfigSource;
|
|
103
|
+
const values = await source.load();
|
|
104
|
+
for (const [key, value] of Object.entries(values)) {
|
|
105
|
+
merged.set(key.toLowerCase(), value);
|
|
106
|
+
origins.set(key.toLowerCase(), { source: source.description ?? `source[${index}]`, priority: index });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return new Configuration(merged, origins);
|
|
110
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Configuration contracts
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-14. Contracts per MODULE_ARCHITECTURE §5.4.
|
|
4
|
+
A component of the existing kernel; no new architectural module is created.
|
|
5
|
+
|
|
6
|
+
Isolation of the views of each kernel, its services and connectors is implemented and
|
|
7
|
+
checked: [report](https://github.com/asus97178/osnova/blob/33a4513a56abb43a1694e7a6e56373187b928a70/docs/audits/kernel-config-isolation-2026-09-14.md).
|
|
8
|
+
|
|
9
|
+
`defineConfig` returns an immutable `ConfigDefinition<T>` declaration.
|
|
10
|
+
The `default`, `development`, `test`, `production` fields are kept. Additions:
|
|
11
|
+
|
|
12
|
+
| Input | Type / default | Rule |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `env` | Optional map of local key → name or array of names | Explicit extra BAZIS_* names; the canonical name is accepted too; different values in one source are an error |
|
|
15
|
+
| `validate` | Optional map of key → `(value) => string \| undefined` | Synchronous domain check; a string is a safe error description without the value |
|
|
16
|
+
| `configEnum(values, defaultValue)` | A non-empty readonly array of strings or numbers, the default taken from it | Restricts both the TS type and the runtime env; needed for fields with a literal union |
|
|
17
|
+
| `resolve(environment?, configuration?)` | Environment; an immutable Configuration | Creates an independent ConfigView; without sources it snapshots BAZIS_* at call time |
|
|
18
|
+
| `token` | InjectionToken<ConfigView<T>> | The kernel registers the view in the existing DI for every declaration from Module.config |
|
|
19
|
+
| `ConfigRegistry.get(definition)` | A declaration from the current kernel's graph | Returns the one view of this kernel; an unknown declaration is an error |
|
|
20
|
+
| `AppConfig.resolve(environment?, configuration?)` | Optional factory of a user declaration | A composite config builds its own independent view from the kernel snapshot; all its consumers use the same result |
|
|
21
|
+
| `ConfigView.inspect()` | No arguments | Key, type, allowed env names, source and a safe value; a Secret is always `***` |
|
|
22
|
+
|
|
23
|
+
Default → environment section → sources snapshot. Kernel sources are merged in
|
|
24
|
+
registration order; if no list is given, env and then CLI args are used.
|
|
25
|
+
Flat Configuration key names are case-insensitive. A later source also wins
|
|
26
|
+
between the canonical name and an explicit env alias.
|
|
27
|
+
Numbers are finite; an empty string never becomes zero; a Secret does not allow an
|
|
28
|
+
empty or whitespace value. TypeScript forbids unsupported object types.
|
|
29
|
+
|
|
30
|
+
A declaration stores no chosen environment, values or cache. `get/has/ensureValid`
|
|
31
|
+
stay for standalone reading of the process env, but `ensureValid` no longer
|
|
32
|
+
switches later reads. Host code gets `resolve`, a service injects
|
|
33
|
+
`definition.token`, a connector gets `ConfigRegistry` as the create argument.
|
|
34
|
+
All built-in connectors use this argument; a custom connector reads
|
|
35
|
+
`configs?.get(config) ?? config`. A direct global get inside a service does not
|
|
36
|
+
read the settings of its kernel.
|
|
37
|
+
|
|
38
|
+
A custom configuration with nested values, for example a set of session protection
|
|
39
|
+
keys, implements `resolve(environment, configuration)` if the values depend on the
|
|
40
|
+
run. The factory returns an immutable `AppConfig` and does not change the
|
|
41
|
+
declaration. Older objects with only `ensureValid` stay compatible as
|
|
42
|
+
validators/ready values; their author is responsible for having no mutable shared
|
|
43
|
+
run state. The session protection adapter also reads the registry view.
|
|
44
|
+
|
|
45
|
+
The kernel collects the errors of all declarations before creating DI clients. An
|
|
46
|
+
error in one kernel does not change the other views. The historical activeStand
|
|
47
|
+
guard is removed only together with the regressions for two kernels, sources,
|
|
48
|
+
secrets and connectors.
|
|
49
|
+
|
|
50
|
+
Health: `HealthService.check(options?)` / `Kernel.health(options?)` accept
|
|
51
|
+
`timeoutMs` (the whole report, default 5000), `checkTimeoutMs` (one check, default
|
|
52
|
+
1000), `concurrency` (default 4) and an optional `signal`. Numbers are positive
|
|
53
|
+
integers, timers up to 2147483647 ms, concurrency up to 1024. Results come in
|
|
54
|
+
registration order. `HealthCheck.check(signal?)` gets the cancellation; a timeout
|
|
55
|
+
or an exception gives unhealthy. Expired checks that have not started are not run.
|
|
56
|
+
This bounds the wait; a custom check must honor the signal to stop its own work.
|
|
57
|
+
Diagnostics go through the existing redaction.
|
|
58
|
+
Concurrent reports of one HealthService share one operation per singleton
|
|
59
|
+
check instance. Cancelling one report does not abort the others.
|
|
60
|
+
When all waiters finish, the operation's signal is aborted. If the operation keeps
|
|
61
|
+
hanging, repeated reports give unhealthy until it actually finishes, without starting
|
|
62
|
+
duplicates. The next call after it finishes runs a fresh check.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const REDACTED = "***";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A secret value that never leaks through logs: toString/toJSON/inspect all
|
|
5
|
+
* print "***". The raw value is available only via an explicit `reveal()`.
|
|
6
|
+
*/
|
|
7
|
+
export class Secret {
|
|
8
|
+
readonly #value: string;
|
|
9
|
+
|
|
10
|
+
public constructor(value: string) {
|
|
11
|
+
this.#value = value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public reveal(): string {
|
|
15
|
+
return this.#value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public toString(): string {
|
|
19
|
+
return REDACTED;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public toJSON(): string {
|
|
23
|
+
return REDACTED;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public [Symbol.for("nodejs.util.inspect.custom")](): string {
|
|
27
|
+
return `Secret(${REDACTED})`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { InjectionToken, Options, ProviderDefinition, ServiceCollection, ServiceResolver } from "../../di";
|
|
2
|
+
import { DI, OPTIONS_STARTUP_VALIDATOR, OptionsValidationError } from "../../di";
|
|
3
|
+
import { ConfigKeyMissingError } from "../errors";
|
|
4
|
+
import { Configuration } from "./Configuration";
|
|
5
|
+
|
|
6
|
+
export interface ConfigOptionsBinding<T> {
|
|
7
|
+
/** Maps the merged configuration to a typed options value. */
|
|
8
|
+
readonly bind: (config: Configuration) => T;
|
|
9
|
+
/** Returns a list of problems; empty list or `undefined` means valid. */
|
|
10
|
+
readonly validate?: (value: T) => readonly string[] | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Config-bound validated options (Symfony bundle configuration style):
|
|
15
|
+
* a module declares its own options next to itself, binding reads from the
|
|
16
|
+
* kernel `Configuration`, and the kernel fails fast on start with every
|
|
17
|
+
* config problem aggregated.
|
|
18
|
+
*
|
|
19
|
+
* Module usage: `providers: [...configOptions(SMTP_OPTIONS, { bind, validate })]`.
|
|
20
|
+
*/
|
|
21
|
+
export function configOptions<T>(
|
|
22
|
+
token: InjectionToken<Options<T>>,
|
|
23
|
+
binding: ConfigOptionsBinding<T>,
|
|
24
|
+
): readonly ProviderDefinition[] {
|
|
25
|
+
const optionsDefinition = DI.singleton(
|
|
26
|
+
DI.factoryProvider(token, [Configuration] as const, (config): Options<T> => {
|
|
27
|
+
const name = token.description;
|
|
28
|
+
let value: T;
|
|
29
|
+
try {
|
|
30
|
+
value = binding.bind(config);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Missing keys become validation issues, so the startup check can
|
|
33
|
+
// aggregate them with every other config problem.
|
|
34
|
+
if (error instanceof ConfigKeyMissingError) {
|
|
35
|
+
throw new OptionsValidationError([`${name}: ${error.message}`]);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const issues = binding.validate?.(value) ?? [];
|
|
40
|
+
if (issues.length > 0) {
|
|
41
|
+
throw new OptionsValidationError(issues.map((issue) => `${name}: ${issue}`));
|
|
42
|
+
}
|
|
43
|
+
return { value };
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const validatorDefinition = DI.transient(
|
|
48
|
+
DI.factoryProviderWithResolver(OPTIONS_STARTUP_VALIDATOR, [], (resolver: ServiceResolver) => (): void => {
|
|
49
|
+
resolver.resolve(token);
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return [optionsDefinition, validatorDefinition];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Collection-style registration of the same config-bound options. */
|
|
57
|
+
export function addConfigOptions<T>(
|
|
58
|
+
services: ServiceCollection,
|
|
59
|
+
token: InjectionToken<Options<T>>,
|
|
60
|
+
binding: ConfigOptionsBinding<T>,
|
|
61
|
+
): void {
|
|
62
|
+
services.addMany(configOptions(token, binding));
|
|
63
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createToken, type InjectionToken } from "../../di";
|
|
2
|
+
import { Environment } from "../Environment";
|
|
3
|
+
import { KernelError } from "../errors";
|
|
4
|
+
import { Configuration } from "./Configuration";
|
|
5
|
+
import { Secret } from "./Secret";
|
|
6
|
+
import { ENV_PREFIX } from "./sources";
|
|
7
|
+
import { isSensitiveKey, redactSensitiveText } from "../../../library/redaction";
|
|
8
|
+
|
|
9
|
+
type Stand = "development" | "test" | "production";
|
|
10
|
+
type Primitive = string | number | boolean;
|
|
11
|
+
export interface SecretSpec { readonly __secret: true; readonly default?: string }
|
|
12
|
+
export function secret(value?: string): SecretSpec {
|
|
13
|
+
return Object.freeze(value === undefined ? { __secret: true } : { __secret: true, default: value });
|
|
14
|
+
}
|
|
15
|
+
export interface ConfigEnum<T extends string | number = string | number> {
|
|
16
|
+
readonly __enum: true;
|
|
17
|
+
readonly values: readonly T[];
|
|
18
|
+
readonly default: T;
|
|
19
|
+
}
|
|
20
|
+
export function configEnum<const V extends readonly [string | number, ...(string | number)[]]>(values: V, defaultValue: V[number]): ConfigEnum<V[number]> {
|
|
21
|
+
if (!values.length || !values.includes(defaultValue) || values.some(value => typeof value !== typeof defaultValue || (typeof value === "number" && !Number.isFinite(value)))) {
|
|
22
|
+
throw new KernelError("configEnum requires non-empty homogeneous finite values and a declared default.");
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze({ __enum: true, values: Object.freeze([...values]), default: defaultValue });
|
|
25
|
+
}
|
|
26
|
+
type Cell = Primitive | SecretSpec | ConfigEnum;
|
|
27
|
+
type Defaults = Record<string, Cell>;
|
|
28
|
+
export type ConfigValueType = Primitive | Secret;
|
|
29
|
+
export type ConfigShape = Record<string, ConfigValueType>;
|
|
30
|
+
type Resolved<V> = V extends SecretSpec ? Secret : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
31
|
+
type ResolveAll<D extends Defaults> = { [K in keyof D]: Resolved<D[K]> };
|
|
32
|
+
type WidenCell<V> = V extends SecretSpec ? SecretSpec : V extends ConfigEnum<infer E> ? E : V extends number ? number : V extends boolean ? boolean : string;
|
|
33
|
+
type CellFor<T> = [T] extends [Secret] ? SecretSpec : [T] extends [string] ? (string extends T ? string : ConfigEnum<T & string>) : [T] extends [number] ? (number extends T ? number : ConfigEnum<T & number>) : [T] extends [boolean] ? (boolean extends T ? boolean : never) : never;
|
|
34
|
+
type Declaration<T extends object> = { [K in keyof T]: CellFor<T[K]> };
|
|
35
|
+
type OverrideFor<T> = T extends Secret ? SecretSpec : T;
|
|
36
|
+
interface Rules<T extends object> {
|
|
37
|
+
readonly env?: Partial<{ [K in keyof T]: string | readonly string[] }>;
|
|
38
|
+
readonly validate?: Partial<{ [K in keyof T]: (value: T[K]) => string | undefined }>;
|
|
39
|
+
}
|
|
40
|
+
export interface ConfigSchema<D extends Defaults> extends Rules<ResolveAll<D>> {
|
|
41
|
+
readonly default: D;
|
|
42
|
+
readonly development?: Partial<{ [K in keyof D]: WidenCell<D[K]> }>;
|
|
43
|
+
readonly test?: Partial<{ [K in keyof D]: WidenCell<D[K]> }>;
|
|
44
|
+
readonly production?: Partial<{ [K in keyof D]: WidenCell<D[K]> }>;
|
|
45
|
+
}
|
|
46
|
+
export interface SchemaFor<T extends object> extends Rules<T> {
|
|
47
|
+
readonly default: Declaration<T>;
|
|
48
|
+
readonly development?: Partial<{ [K in keyof T]: OverrideFor<T[K]> }>;
|
|
49
|
+
readonly test?: Partial<{ [K in keyof T]: OverrideFor<T[K]> }>;
|
|
50
|
+
readonly production?: Partial<{ [K in keyof T]: OverrideFor<T[K]> }>;
|
|
51
|
+
}
|
|
52
|
+
export interface ValidatableConfig {
|
|
53
|
+
ensureValid(environment?: Stand): void;
|
|
54
|
+
/** Derived declarations create a fresh view from this kernel's source snapshot. */
|
|
55
|
+
resolve?(environment?: Stand, configuration?: Configuration): ValidatableConfig;
|
|
56
|
+
}
|
|
57
|
+
export interface AppConfig<T extends object> extends ValidatableConfig {
|
|
58
|
+
get<K extends keyof T>(key: K): T[K];
|
|
59
|
+
has<K extends keyof T>(key: K): boolean;
|
|
60
|
+
resolve?(environment?: Stand, configuration?: Configuration): AppConfig<T>;
|
|
61
|
+
}
|
|
62
|
+
export interface ConfigInspection {
|
|
63
|
+
readonly key: string;
|
|
64
|
+
readonly type: "string" | "number" | "boolean" | "secret";
|
|
65
|
+
readonly env: readonly string[];
|
|
66
|
+
readonly source: string;
|
|
67
|
+
readonly value: Primitive;
|
|
68
|
+
}
|
|
69
|
+
export interface ConfigView<T extends object> extends AppConfig<T> {
|
|
70
|
+
readonly environment: Stand;
|
|
71
|
+
inspect(): readonly ConfigInspection[];
|
|
72
|
+
}
|
|
73
|
+
export interface ConfigDefinition<T extends object> extends AppConfig<T> {
|
|
74
|
+
readonly token: InjectionToken<ConfigView<T>>;
|
|
75
|
+
resolve(environment?: Stand, configuration?: Configuration): ConfigView<T>;
|
|
76
|
+
}
|
|
77
|
+
const definitions = new WeakSet<object>();
|
|
78
|
+
export function isConfigDefinition(config: ValidatableConfig): config is ConfigDefinition<object> {
|
|
79
|
+
return definitions.has(config);
|
|
80
|
+
}
|
|
81
|
+
export function processConfiguration(): Configuration {
|
|
82
|
+
const values = new Map<string, string>();
|
|
83
|
+
const origins = new Map<string, { source: string; priority: number }>();
|
|
84
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
85
|
+
if (name.startsWith(ENV_PREFIX) && value !== undefined) {
|
|
86
|
+
const key = name.slice(ENV_PREFIX.length).toLowerCase().replaceAll("__", ".");
|
|
87
|
+
values.set(key, value);
|
|
88
|
+
origins.set(key, { source: "env(BAZIS_*)", priority: 0 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return new Configuration(values, origins);
|
|
92
|
+
}
|
|
93
|
+
function isSecret(cell: Cell): cell is SecretSpec { return typeof cell === "object" && cell !== null && "__secret" in cell; }
|
|
94
|
+
function isEnum(cell: Cell): cell is ConfigEnum { return typeof cell === "object" && cell !== null && "__enum" in cell; }
|
|
95
|
+
function copyCell(cell: Cell): Cell {
|
|
96
|
+
if (isSecret(cell)) return secret(cell.default);
|
|
97
|
+
if (isEnum(cell)) return Object.freeze({ ...cell, values: Object.freeze([...cell.values]) });
|
|
98
|
+
if (["string", "number", "boolean"].includes(typeof cell)) return cell;
|
|
99
|
+
throw new KernelError("Configuration supports only primitive values, secret() and configEnum().");
|
|
100
|
+
}
|
|
101
|
+
function safeValue(key: string, value: ConfigValueType): Primitive {
|
|
102
|
+
if (value instanceof Secret || (typeof value === "string" && isSensitiveKey(key))) return "***";
|
|
103
|
+
if (typeof value !== "string") return value;
|
|
104
|
+
try {
|
|
105
|
+
const url = new URL(value);
|
|
106
|
+
if (url.username || url.password) { url.username = "***"; url.password = "***"; return redactSensitiveText(url.toString()); }
|
|
107
|
+
} catch { /* Most strings are not URLs. */ }
|
|
108
|
+
return redactSensitiveText(value);
|
|
109
|
+
}
|
|
110
|
+
export function defineConfig<const D extends Defaults>(schema: ConfigSchema<D>): ConfigDefinition<ResolveAll<D>>;
|
|
111
|
+
export function defineConfig<const D extends Defaults>(prefix: string, schema: ConfigSchema<D>): ConfigDefinition<ResolveAll<D>>;
|
|
112
|
+
export function defineConfig<T extends object>(schema: SchemaFor<T>): ConfigDefinition<T>;
|
|
113
|
+
export function defineConfig<T extends object>(prefix: string, schema: SchemaFor<T>): ConfigDefinition<T>;
|
|
114
|
+
export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, maybeSchema?: ConfigSchema<Defaults>): ConfigDefinition<ConfigShape> {
|
|
115
|
+
const prefix = typeof prefixOrSchema === "string" ? prefixOrSchema : "";
|
|
116
|
+
const input = typeof prefixOrSchema === "string" ? maybeSchema! : prefixOrSchema;
|
|
117
|
+
const schema = Object.freeze({
|
|
118
|
+
default: Object.freeze(Object.fromEntries(Object.entries(input.default).map(([key, value]) => [key, copyCell(value)]))),
|
|
119
|
+
...Object.fromEntries((["development", "test", "production"] as const).map(stand => [stand, Object.freeze(Object.fromEntries(Object.entries(input[stand] ?? {}).map(([key, value]) => [key, copyCell(value as Cell)])))])),
|
|
120
|
+
}) as ConfigSchema<Defaults>;
|
|
121
|
+
const validators = { ...input.validate };
|
|
122
|
+
const keys = Object.keys(schema.default);
|
|
123
|
+
const fullKey = (key: string) => prefix ? `${prefix}.${key}` : key;
|
|
124
|
+
const names = new Map<string, readonly string[]>();
|
|
125
|
+
const claimed = new Set<string>();
|
|
126
|
+
for (const key of keys) {
|
|
127
|
+
const alias = input.env?.[key];
|
|
128
|
+
const env = [...new Set([`${ENV_PREFIX}${fullKey(key).replaceAll(".", "__").toUpperCase()}`, ...(typeof alias === "string" ? [alias] : alias ?? [])])];
|
|
129
|
+
for (const name of env) {
|
|
130
|
+
if (!name.startsWith(ENV_PREFIX) || name.length === ENV_PREFIX.length || !/^[A-Z0-9_-]+$/.test(name) || claimed.has(name)) throw new KernelError(`Invalid or duplicate configuration environment name: ${name}.`);
|
|
131
|
+
claimed.add(name);
|
|
132
|
+
}
|
|
133
|
+
names.set(key, Object.freeze(env));
|
|
134
|
+
}
|
|
135
|
+
for (const stand of ["development", "test", "production"] as const) {
|
|
136
|
+
for (const key of Object.keys(schema[stand] ?? {})) if (!keys.includes(key)) throw new KernelError(`Unknown configuration key: ${fullKey(key)}.`);
|
|
137
|
+
}
|
|
138
|
+
const resolve = (environment: Stand = Environment.fromProcess().name, configuration = processConfiguration()): ConfigView<ConfigShape> => {
|
|
139
|
+
if (!["development", "test", "production"].includes(environment)) throw new KernelError("Unknown configuration environment.");
|
|
140
|
+
const values = new Map<string, ConfigValueType>();
|
|
141
|
+
const inspection: ConfigInspection[] = [];
|
|
142
|
+
const issues: string[] = [];
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
const base = schema.default[key]!;
|
|
145
|
+
const override = schema[environment]?.[key];
|
|
146
|
+
const cell = override ?? base;
|
|
147
|
+
const type = isSecret(base) ? "secret" : isEnum(base) ? typeof base.default : typeof base;
|
|
148
|
+
const candidates: { raw: string; source: string; priority: number }[] = [];
|
|
149
|
+
for (const name of names.get(key)!) {
|
|
150
|
+
const sourceKey = name.slice(ENV_PREFIX.length).toLowerCase().replaceAll("__", ".");
|
|
151
|
+
const raw = configuration.get(sourceKey);
|
|
152
|
+
if (raw === undefined) continue;
|
|
153
|
+
const origin = configuration.origin(sourceKey);
|
|
154
|
+
candidates.push({ raw, ...origin });
|
|
155
|
+
}
|
|
156
|
+
const priority = Math.max(...candidates.map(candidate => candidate.priority));
|
|
157
|
+
const winners = candidates.filter(candidate => candidate.priority === priority);
|
|
158
|
+
const selected = winners[0];
|
|
159
|
+
if (winners.some(candidate => candidate.raw !== selected?.raw)) issues.push(`${fullKey(key)}: conflicting environment aliases`);
|
|
160
|
+
const raw = selected?.raw ?? (isSecret(cell) || isEnum(cell) ? cell.default : cell);
|
|
161
|
+
let value: ConfigValueType | undefined;
|
|
162
|
+
if (type === "secret") {
|
|
163
|
+
if (typeof raw !== "string" || !raw.trim()) issues.push(`${fullKey(key)}: required non-empty secret is not set (${names.get(key)!.join(", ")})`);
|
|
164
|
+
else value = new Secret(raw);
|
|
165
|
+
} else if (type === "number") {
|
|
166
|
+
const parsed = Number(raw);
|
|
167
|
+
if (raw === undefined || (typeof raw === "string" && !raw.trim()) || !Number.isFinite(parsed)) issues.push(`${fullKey(key)}: expected a finite number`);
|
|
168
|
+
else value = parsed;
|
|
169
|
+
} else if (type === "boolean") {
|
|
170
|
+
const normalized = String(raw).toLowerCase();
|
|
171
|
+
if (normalized === "true" || normalized === "1") value = true;
|
|
172
|
+
else if (normalized === "false" || normalized === "0") value = false;
|
|
173
|
+
else issues.push(`${fullKey(key)}: expected a boolean`);
|
|
174
|
+
} else if (typeof raw === "string") value = raw;
|
|
175
|
+
else issues.push(`${fullKey(key)}: expected a string`);
|
|
176
|
+
if (value === undefined) continue;
|
|
177
|
+
if (isEnum(base) && !base.values.includes(value as string | number)) issues.push(`${fullKey(key)}: value is not one of the declared enum values`);
|
|
178
|
+
try {
|
|
179
|
+
const issue = validators[key]?.(value);
|
|
180
|
+
if (issue) issues.push(`${fullKey(key)}: ${issue}`);
|
|
181
|
+
} catch { issues.push(`${fullKey(key)}: validator failed`); }
|
|
182
|
+
values.set(key, value);
|
|
183
|
+
inspection.push(Object.freeze({ key: fullKey(key), type: type as ConfigInspection["type"], env: names.get(key)!, source: selected?.source ?? (override === undefined ? "default" : environment), value: safeValue(fullKey(key), value) }));
|
|
184
|
+
}
|
|
185
|
+
if (issues.length) {
|
|
186
|
+
let message = issues.join("; ");
|
|
187
|
+
for (const [key, value] of values) {
|
|
188
|
+
const sensitive = value instanceof Secret ? value.reveal() : typeof value === "string" && isSensitiveKey(key) ? value : undefined;
|
|
189
|
+
if (sensitive) message = message.replaceAll(sensitive, "***");
|
|
190
|
+
}
|
|
191
|
+
throw new KernelError(`Invalid configuration (environment "${environment}"): ${message}.`);
|
|
192
|
+
}
|
|
193
|
+
const rows = Object.freeze(inspection);
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
environment,
|
|
196
|
+
get: <K extends keyof ConfigShape>(key: K): ConfigShape[K] => values.get(String(key)) as ConfigShape[K],
|
|
197
|
+
has: <K extends keyof ConfigShape>(key: K) => values.has(String(key)),
|
|
198
|
+
ensureValid(stand?: Stand) { if (stand !== undefined && stand !== environment) throw new KernelError("A resolved configuration view cannot change environment."); },
|
|
199
|
+
inspect: () => rows,
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
const definition: ConfigDefinition<ConfigShape> = Object.freeze({
|
|
203
|
+
token: Object.freeze(createToken<ConfigView<ConfigShape>>(`Config:${prefix || "default"}`)),
|
|
204
|
+
resolve,
|
|
205
|
+
get: <K extends keyof ConfigShape>(key: K): ConfigShape[K] => resolve().get(key),
|
|
206
|
+
has: <K extends keyof ConfigShape>(key: K) => resolve().has(key),
|
|
207
|
+
ensureValid(environment?: Stand) { resolve(environment); },
|
|
208
|
+
});
|
|
209
|
+
definitions.add(definition);
|
|
210
|
+
return definition;
|
|
211
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { KernelError } from "../errors";
|
|
2
|
+
import type { ConfigSource } from "../types";
|
|
3
|
+
|
|
4
|
+
type ConfigTree = { [key: string]: ConfigTreeValue };
|
|
5
|
+
type ConfigTreeValue = string | number | boolean | null | ConfigTreeValue[] | ConfigTree;
|
|
6
|
+
|
|
7
|
+
/** In-memory defaults. Nested objects are flattened into dot keys. */
|
|
8
|
+
/** Prefix of framework environment variables: `BAZIS_DB__HOST` -> `db.host`. */
|
|
9
|
+
export const ENV_PREFIX = "BAZIS_";
|
|
10
|
+
|
|
11
|
+
export function memorySource(values: ConfigTree, description = "memory"): ConfigSource {
|
|
12
|
+
return {
|
|
13
|
+
description,
|
|
14
|
+
load: () => flatten(values),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Environment variables. `BAZIS_DB__HOST=x` -> `db.host = "x"`:
|
|
20
|
+
* the prefix is stripped, `__` becomes `.`, keys are lowercased.
|
|
21
|
+
*/
|
|
22
|
+
export function envSource(options?: {
|
|
23
|
+
readonly prefix?: string;
|
|
24
|
+
/** Override for tests; defaults to process.env. */
|
|
25
|
+
readonly variables?: Record<string, string | undefined>;
|
|
26
|
+
}): ConfigSource {
|
|
27
|
+
const prefix = options?.prefix ?? ENV_PREFIX;
|
|
28
|
+
return {
|
|
29
|
+
description: `env(${prefix}*)`,
|
|
30
|
+
load: () => {
|
|
31
|
+
const variables = options?.variables ?? process.env;
|
|
32
|
+
const result: Record<string, string> = {};
|
|
33
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
34
|
+
if (value === undefined || !name.startsWith(prefix)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const key = name.slice(prefix.length).toLowerCase().replaceAll("__", ".");
|
|
38
|
+
if (key.length > 0) {
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** CLI arguments in the form `--db.host=localhost`. */
|
|
48
|
+
export function argsSource(argv: readonly string[] = Bun.argv.slice(2)): ConfigSource {
|
|
49
|
+
return {
|
|
50
|
+
description: "args",
|
|
51
|
+
load: () => {
|
|
52
|
+
const result: Record<string, string> = {};
|
|
53
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
54
|
+
const arg = argv[index] as string;
|
|
55
|
+
if (!arg.startsWith("--")) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const separator = arg.indexOf("=");
|
|
59
|
+
if (separator <= 2) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
result[arg.slice(2, separator)] = arg.slice(separator + 1);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* JSON config file read at runtime via Bun.file — the file lives next to the
|
|
71
|
+
* compiled binary, not inside it. Nested objects are flattened into dot keys.
|
|
72
|
+
*/
|
|
73
|
+
export function jsonFileSource(path: string, options?: { readonly optional?: boolean }): ConfigSource {
|
|
74
|
+
return {
|
|
75
|
+
description: `json(${path})`,
|
|
76
|
+
load: async () => {
|
|
77
|
+
const file = Bun.file(path);
|
|
78
|
+
if (!(await file.exists())) {
|
|
79
|
+
if (options?.optional === true) {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
throw new KernelError(`Configuration file not found: "${path}".`);
|
|
83
|
+
}
|
|
84
|
+
let parsed: unknown;
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(await file.text());
|
|
87
|
+
} catch {
|
|
88
|
+
// Parser messages may quote the input, including credentials.
|
|
89
|
+
throw new KernelError(`Configuration file "${path}" is not valid JSON.`);
|
|
90
|
+
}
|
|
91
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
92
|
+
throw new KernelError(`Configuration file "${path}" must contain a JSON object at the top level.`);
|
|
93
|
+
}
|
|
94
|
+
return flatten(parsed as ConfigTree);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function flatten(tree: ConfigTree, prefix = "", into: Record<string, string> = {}): Record<string, string> {
|
|
100
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
101
|
+
const fullKey = prefix.length > 0 ? `${prefix}.${key}` : key;
|
|
102
|
+
if (value === null) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
107
|
+
const item = value[index];
|
|
108
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
109
|
+
flatten(item, `${fullKey}.${index}`, into);
|
|
110
|
+
} else if (item !== null && item !== undefined) {
|
|
111
|
+
into[`${fullKey}.${index}`] = String(item);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (typeof value === "object") {
|
|
117
|
+
flatten(value, fullKey, into);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
into[fullKey] = String(value);
|
|
121
|
+
}
|
|
122
|
+
return into;
|
|
123
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
/** HTTP header used for inbound/outbound correlation IDs. */
|
|
4
|
+
export const REQUEST_ID_HEADER = "x-request-id";
|
|
5
|
+
|
|
6
|
+
/** W3C Trace Context header propagated on outbound HTTP calls when present. */
|
|
7
|
+
export const TRACEPARENT_HEADER = "traceparent";
|
|
8
|
+
|
|
9
|
+
/** Key in {@link HttpContext.state} for the active request ID. */
|
|
10
|
+
export const REQUEST_ID_STATE_KEY = "bazis.requestId";
|
|
11
|
+
|
|
12
|
+
/** Key in {@link HttpContext.state} for inbound W3C traceparent. */
|
|
13
|
+
export const TRACEPARENT_STATE_KEY = "bazis.traceparent";
|
|
14
|
+
|
|
15
|
+
export interface RequestLogContext {
|
|
16
|
+
readonly requestId: string;
|
|
17
|
+
readonly traceparent?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const storage = new AsyncLocalStorage<RequestLogContext>();
|
|
21
|
+
|
|
22
|
+
/** Returns the correlation ID for the current async context, if any. */
|
|
23
|
+
export function getRequestId(): string | undefined {
|
|
24
|
+
return storage.getStore()?.requestId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Returns the active W3C traceparent value, if any. */
|
|
28
|
+
export function getTraceparent(): string | undefined {
|
|
29
|
+
return storage.getStore()?.traceparent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Headers to attach to outbound HTTP requests from the current async context. */
|
|
33
|
+
export function getOutboundCorrelationHeaders(): Readonly<Record<string, string>> {
|
|
34
|
+
const headers: Record<string, string> = {};
|
|
35
|
+
const requestId = getRequestId();
|
|
36
|
+
if (requestId !== undefined) {
|
|
37
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
38
|
+
}
|
|
39
|
+
const traceparent = getTraceparent();
|
|
40
|
+
if (traceparent !== undefined) {
|
|
41
|
+
headers[TRACEPARENT_HEADER] = traceparent;
|
|
42
|
+
}
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Runs `fn` with the given request context bound to the async local store. */
|
|
47
|
+
export function runWithRequestContext<T>(context: RequestLogContext, fn: () => T): T {
|
|
48
|
+
return storage.run(context, fn);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Runs async `fn` with the given request context bound to the async local store. */
|
|
52
|
+
export async function runWithRequestContextAsync<T>(
|
|
53
|
+
context: RequestLogContext,
|
|
54
|
+
fn: () => Promise<T>,
|
|
55
|
+
): Promise<T> {
|
|
56
|
+
return storage.run(context, fn);
|
|
57
|
+
}
|