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,627 @@
|
|
|
1
|
+
import type { Class, HostedService, ServiceProvider, ServiceResolver, ServiceScope } from "../di";
|
|
2
|
+
import { loadBazisGeneratedRuntime } from "../generatedRuntime";
|
|
3
|
+
import { Environment, HealthService, LOGGER, type HealthReport } from "../kernel";
|
|
4
|
+
import { redactSensitive } from "../../library/redaction";
|
|
5
|
+
import { HttpContext } from "./HttpContext/HttpContext";
|
|
6
|
+
import { holdResponseScope } from "./HttpContext/responseLifetime";
|
|
7
|
+
import { inspectableRedirectResponse } from "./HttpContext/inspectableRedirects";
|
|
8
|
+
import { accessLog } from "./Middleware/accessLog";
|
|
9
|
+
import { cors, isPreflight, preflightResponse } from "./Middleware/cors";
|
|
10
|
+
import { errorHandler } from "./Middleware/errorHandler";
|
|
11
|
+
import { runPipeline } from "./Middleware/pipeline";
|
|
12
|
+
import { applySecurityHeaders, securityHeaders } from "./Middleware/securityHeaders";
|
|
13
|
+
import type { HttpMiddleware } from "./Middleware/types";
|
|
14
|
+
import { prepareOpenApiDocs, serveOpenApiDocs } from "./OpenApi/openApiDocs";
|
|
15
|
+
import type { PreparedOpenApiDocs } from "../../library/openapi";
|
|
16
|
+
import { RouterBuilder } from "./Routing/RouterBuilder";
|
|
17
|
+
import type { Router } from "./Routing/Router";
|
|
18
|
+
import { parseRequestPath } from "./Routing/template";
|
|
19
|
+
import { createVersionReader, type VersionReader } from "./Versioning/VersionReader";
|
|
20
|
+
import { HTTP_ERROR_HOOK, ROUTE_MIDDLEWARE_COMPOSER, SERVER_MIDDLEWARE, sortByOrder } from "./middlewareTokens";
|
|
21
|
+
import type { HealthEndpointOptions, RouteMiddlewareComposer, HttpModuleOptions } from "./options";
|
|
22
|
+
import { WEBSOCKET_UPGRADE, type WebSocketUpgrade } from "./WebSocketUpgrade";
|
|
23
|
+
import { HttpSetupError } from "./Errors/HttpError";
|
|
24
|
+
import { getModelValidator, type ModelValidator } from "./Binding/modelValidator";
|
|
25
|
+
|
|
26
|
+
const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
27
|
+
const DEFAULT_HEALTH_PATH = "/health";
|
|
28
|
+
const DEFAULT_REQUEST_SCOPE_DISPOSE_TIMEOUT_MS = 5_000;
|
|
29
|
+
|
|
30
|
+
interface ListenOptions {
|
|
31
|
+
readonly port: number;
|
|
32
|
+
readonly hostname?: string;
|
|
33
|
+
readonly idleTimeout?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function json(status: number, body: unknown, headers?: Record<string, string>): Response {
|
|
37
|
+
return new Response(JSON.stringify(body), {
|
|
38
|
+
status,
|
|
39
|
+
headers: { "content-type": "application/json; charset=utf-8", ...headers },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** HEAD responses must carry no body, only headers/status. */
|
|
44
|
+
function stripBody(response: Response): Response {
|
|
45
|
+
if (!response.body) return response;
|
|
46
|
+
const headers = new Headers(response.headers);
|
|
47
|
+
// A GET fallback may have returned a file/upstream stream. Explicitly cancel
|
|
48
|
+
// it so HEAD does not leave the producer or an outbound connection hanging.
|
|
49
|
+
// An asynchronous producer may never acknowledge cancellation. HEAD must
|
|
50
|
+
// still return its status/headers; observe cleanup without awaiting it.
|
|
51
|
+
void response.body.cancel().catch(() => undefined);
|
|
52
|
+
return new Response(null, {
|
|
53
|
+
status: response.status,
|
|
54
|
+
statusText: response.statusText,
|
|
55
|
+
headers,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The HTTP host: a regular `HostedService` (phase 10 by default, so it
|
|
61
|
+
* starts after infrastructure and stops first).
|
|
62
|
+
*
|
|
63
|
+
* Startup builds the router and all per-route pipelines once; request
|
|
64
|
+
* dispatch is: parse path -> radix match -> create DI scope -> run the
|
|
65
|
+
* precompiled chain -> finish response producer -> dispose scope.
|
|
66
|
+
* All responses retain the native Bun body path. JS producers retain their
|
|
67
|
+
* scope until EOF, error, cancellation or disconnect; materialized/native
|
|
68
|
+
* bodies release it after Bun takes ownership.
|
|
69
|
+
* Built-in security/CORS/error/access-log
|
|
70
|
+
* middleware also wraps framework-generated short-circuit responses.
|
|
71
|
+
*/
|
|
72
|
+
export class HttpServer implements HostedService {
|
|
73
|
+
readonly phase: number;
|
|
74
|
+
|
|
75
|
+
private server?: ReturnType<typeof Bun.serve>;
|
|
76
|
+
private router?: Router;
|
|
77
|
+
private readVersion: VersionReader = () => undefined;
|
|
78
|
+
/** Body cap in bytes; `undefined` when the limit is disabled (`maxBodyBytes: 0`). */
|
|
79
|
+
private readonly maxBodyBytes?: number;
|
|
80
|
+
private readonly requestScopeDisposeTimeoutMs?: number;
|
|
81
|
+
private readonly responseCompletions = new Set<Promise<void>>();
|
|
82
|
+
/** Absolute health-probe path, or `undefined` when disabled. */
|
|
83
|
+
private readonly healthPath?: string;
|
|
84
|
+
private readonly healthOptions?: HealthEndpointOptions;
|
|
85
|
+
private healthInFlight?: Promise<HealthReport>;
|
|
86
|
+
private healthCache?: { readonly report: HealthReport; readonly expiresAt: number };
|
|
87
|
+
/** Built-ins reused for 404/405/health/docs/preflight responses. */
|
|
88
|
+
private shortCircuitChain: readonly HttpMiddleware[] = [];
|
|
89
|
+
private readonly modelValidator?: ModelValidator;
|
|
90
|
+
/** Optional WebSocket runtime sharing this listener (resolved at start). */
|
|
91
|
+
private webSocket?: WebSocketUpgrade;
|
|
92
|
+
/** Generated API docs served outside the controller router. */
|
|
93
|
+
private docs?: PreparedOpenApiDocs;
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
private readonly options: HttpModuleOptions,
|
|
97
|
+
/** Root container: the per-request scope factory. */
|
|
98
|
+
private readonly provider: ServiceProvider,
|
|
99
|
+
/** Root resolver for optional kernel services (Environment). */
|
|
100
|
+
private readonly resolver: ServiceResolver,
|
|
101
|
+
) {
|
|
102
|
+
this.phase = options.phase ?? 10;
|
|
103
|
+
this.modelValidator = options.validator ?? getModelValidator();
|
|
104
|
+
assertHttpLimits(options);
|
|
105
|
+
if (options.inspectableRedirects !== undefined && typeof options.inspectableRedirects !== "boolean") {
|
|
106
|
+
throw new HttpSetupError("inspectableRedirects must be a boolean.");
|
|
107
|
+
}
|
|
108
|
+
this.maxBodyBytes = options.maxBodyBytes === 0 ? undefined : (options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
|
|
109
|
+
this.requestScopeDisposeTimeoutMs = options.requestScopeDisposeTimeoutMs === 0
|
|
110
|
+
? undefined
|
|
111
|
+
: options.requestScopeDisposeTimeoutMs ?? DEFAULT_REQUEST_SCOPE_DISPOSE_TIMEOUT_MS;
|
|
112
|
+
if (options.health) {
|
|
113
|
+
this.healthOptions = options.health === true ? {} : options.health;
|
|
114
|
+
this.healthPath = this.healthOptions.path ?? DEFAULT_HEALTH_PATH;
|
|
115
|
+
if (!this.healthPath.startsWith("/")) {
|
|
116
|
+
throw new HttpSetupError("HTTP health path must be absolute and start with '/'.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Actual listening port (useful with `port: 0` in tests). */
|
|
122
|
+
get port(): number {
|
|
123
|
+
return this.server?.port ?? -1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async start(): Promise<void> {
|
|
127
|
+
// Auto-register convention body/list models and OpenAPI metadata generated
|
|
128
|
+
// by `di:generate`. The generated code is project-owned and loaded through
|
|
129
|
+
// registries, so framework core does not import application modules directly.
|
|
130
|
+
await loadBazisGeneratedRuntime();
|
|
131
|
+
|
|
132
|
+
const exposeDetails =
|
|
133
|
+
this.options.exposeErrorDetails ?? this.resolver.tryResolve(Environment)?.debug ?? false;
|
|
134
|
+
|
|
135
|
+
// Outermost first: access log measures the whole pipeline, CORS decorates
|
|
136
|
+
// responses, the error boundary guards everything below (global -> route
|
|
137
|
+
// -> action).
|
|
138
|
+
const serverChain: HttpMiddleware[] = [];
|
|
139
|
+
if (this.options.accessLog) {
|
|
140
|
+
const base = this.options.accessLog === true ? {} : this.options.accessLog;
|
|
141
|
+
const logger = this.resolver.tryResolve(LOGGER);
|
|
142
|
+
// Explicit `log`/`logger` in options win; otherwise use the kernel logger.
|
|
143
|
+
serverChain.push(accessLog(logger ? { logger, ...base } : base));
|
|
144
|
+
}
|
|
145
|
+
if (this.options.cors) {
|
|
146
|
+
serverChain.push(cors(this.options.cors));
|
|
147
|
+
}
|
|
148
|
+
// Secure-by-default: on unless explicitly disabled. Wraps the error
|
|
149
|
+
// boundary so headers land on error responses too.
|
|
150
|
+
if (this.options.securityHeaders !== false) {
|
|
151
|
+
serverChain.push(securityHeaders(this.options.securityHeaders ?? {}));
|
|
152
|
+
}
|
|
153
|
+
serverChain.push(
|
|
154
|
+
errorHandler({ exposeDetails, ...this.options.errorHandler, onUnexpectedError: this.collectErrorHook() }),
|
|
155
|
+
);
|
|
156
|
+
this.shortCircuitChain = [...serverChain];
|
|
157
|
+
serverChain.push(...this.collectServerMiddleware());
|
|
158
|
+
|
|
159
|
+
const builder = new RouterBuilder(
|
|
160
|
+
serverChain,
|
|
161
|
+
this.options.prefix,
|
|
162
|
+
this.options.versioning,
|
|
163
|
+
this.collectRouteComposer(),
|
|
164
|
+
);
|
|
165
|
+
this.router = builder.build(this.options.controllers as readonly Class<object>[]);
|
|
166
|
+
this.readVersion = createVersionReader(this.options.versioning);
|
|
167
|
+
this.docs = prepareOpenApiDocs({
|
|
168
|
+
option: this.options.docs,
|
|
169
|
+
controllers: this.options.controllers as readonly Class<object>[],
|
|
170
|
+
globalPrefix: this.options.prefix,
|
|
171
|
+
versioning: this.options.versioning,
|
|
172
|
+
environment: this.resolver.tryResolve(Environment),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
this.webSocket = this.resolver.tryResolve(WEBSOCKET_UPGRADE);
|
|
176
|
+
|
|
177
|
+
if (this.webSocket) {
|
|
178
|
+
const ws = this.webSocket;
|
|
179
|
+
try {
|
|
180
|
+
// Initialize adapters and gateways before exposing a listener. This
|
|
181
|
+
// prevents successful upgrades into a partially initialized runtime.
|
|
182
|
+
await ws.initialize();
|
|
183
|
+
this.server = await this.startBunServer((listen) => Bun.serve({
|
|
184
|
+
...listen,
|
|
185
|
+
error: (error) => this.transportError(error),
|
|
186
|
+
websocket: ws.createBunHandler(),
|
|
187
|
+
fetch: async (request, server) => {
|
|
188
|
+
// null -> not for the WS runtime, fall through to HTTP.
|
|
189
|
+
// undefined -> upgraded (return nothing). Response -> rejected handshake.
|
|
190
|
+
const upgraded = await ws.tryUpgrade(request, server);
|
|
191
|
+
return upgraded === null ? this.dispatch(request, server.requestIP(request)?.address) : (upgraded ?? undefined);
|
|
192
|
+
},
|
|
193
|
+
}));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const listener = this.server;
|
|
196
|
+
this.server = undefined;
|
|
197
|
+
this.webSocket = undefined;
|
|
198
|
+
// A Bun listener normally cannot exist when its constructor throws,
|
|
199
|
+
// but keep rollback complete if that contract changes.
|
|
200
|
+
if (listener) {
|
|
201
|
+
void Promise.resolve(listener.stop(true)).catch(() => undefined);
|
|
202
|
+
}
|
|
203
|
+
await Promise.resolve(ws.close()).catch(() => undefined);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
this.server = await this.startBunServer((listen) =>
|
|
208
|
+
Bun.serve({
|
|
209
|
+
...listen,
|
|
210
|
+
fetch: (request, server) => this.dispatch(request, server.requestIP(request)?.address),
|
|
211
|
+
error: (error) => this.transportError(error),
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private async startBunServer(create: (listen: ListenOptions) => ReturnType<typeof Bun.serve>): Promise<ReturnType<typeof Bun.serve>> {
|
|
218
|
+
const attempts = this.options.port === 0 ? 10 : 1;
|
|
219
|
+
let lastError: unknown;
|
|
220
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
221
|
+
const listen = await this.resolveListenOptions();
|
|
222
|
+
try {
|
|
223
|
+
return create(listen);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (this.options.port !== 0 || !isAddressInUse(error)) {
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
lastError = error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
throw lastError;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async resolveListenOptions(): Promise<ListenOptions> {
|
|
235
|
+
const hostname = this.options.hostname ?? (this.options.port === 0 ? "127.0.0.1" : undefined);
|
|
236
|
+
// Let the operating system reserve an ephemeral port atomically. Picking a
|
|
237
|
+
// random port in user space creates a check/use race between concurrent
|
|
238
|
+
// application or test processes and can still exhaust every retry.
|
|
239
|
+
const port = this.options.port ?? 3000;
|
|
240
|
+
return {
|
|
241
|
+
port,
|
|
242
|
+
...(hostname !== undefined ? { hostname } : {}),
|
|
243
|
+
...(this.options.idleTimeoutSeconds !== undefined ? { idleTimeout: this.options.idleTimeoutSeconds } : {}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Explicit `options.middleware` (order 0) merged with DI {@link SERVER_MIDDLEWARE}. */
|
|
248
|
+
private collectServerMiddleware(): HttpMiddleware[] {
|
|
249
|
+
const explicit = (this.options.middleware ?? []).map((middleware) => ({ order: 0, middleware }));
|
|
250
|
+
const collected = this.resolver.resolveAll(SERVER_MIDDLEWARE);
|
|
251
|
+
return sortByOrder([...explicit, ...collected]).map((registration) => registration.middleware);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Explicit `options.errorHandler.onUnexpectedError` merged with DI {@link HTTP_ERROR_HOOK} hooks. */
|
|
255
|
+
private collectErrorHook(): ((ctx: HttpContext, error: unknown) => void) | undefined {
|
|
256
|
+
const explicit = this.options.errorHandler?.onUnexpectedError;
|
|
257
|
+
const hooks = this.resolver.resolveAll(HTTP_ERROR_HOOK);
|
|
258
|
+
if (explicit === undefined && hooks.length === 0) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return (ctx, error) => {
|
|
262
|
+
explicit?.(ctx, error);
|
|
263
|
+
for (const hook of hooks) {
|
|
264
|
+
hook(ctx, error);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Explicit `options.routeMiddlewareComposer` (order 0) merged with DI {@link ROUTE_MIDDLEWARE_COMPOSER}. */
|
|
270
|
+
private collectRouteComposer(): RouteMiddlewareComposer | undefined {
|
|
271
|
+
const explicit = this.options.routeMiddlewareComposer
|
|
272
|
+
? [{ order: 0, compose: this.options.routeMiddlewareComposer }]
|
|
273
|
+
: [];
|
|
274
|
+
const collected = this.resolver.resolveAll(ROUTE_MIDDLEWARE_COMPOSER);
|
|
275
|
+
const userComposers = sortByOrder([...explicit, ...collected]).map((registration) => registration.compose);
|
|
276
|
+
|
|
277
|
+
if (userComposers.length === 0) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
return (controllerClass, methodName, httpMeta, action) =>
|
|
281
|
+
userComposers.flatMap((compose) => compose(controllerClass, methodName, httpMeta, action));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async stop(): Promise<void> {
|
|
285
|
+
// Graceful: stop accepting new connections, let in-flight requests finish.
|
|
286
|
+
// Long-lived WebSocket connections never drain on their own, so when a WS
|
|
287
|
+
// runtime is attached we force active connections closed.
|
|
288
|
+
const webSocket = this.webSocket;
|
|
289
|
+
const listener = this.server;
|
|
290
|
+
this.webSocket = undefined;
|
|
291
|
+
this.server = undefined;
|
|
292
|
+
this.docs = undefined;
|
|
293
|
+
|
|
294
|
+
let firstError: unknown;
|
|
295
|
+
try {
|
|
296
|
+
await webSocket?.close();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
firstError = error;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (listener) {
|
|
302
|
+
try {
|
|
303
|
+
const stopped = Promise.resolve(listener.stop(webSocket !== undefined));
|
|
304
|
+
if (webSocket) {
|
|
305
|
+
// Bun 1.3 keeps this promise pending after a listener has ever upgraded
|
|
306
|
+
// a WebSocket, even after all sockets are terminated. `stop(true)` is
|
|
307
|
+
// still synchronous in initiating the forced listener shutdown.
|
|
308
|
+
void stopped.catch(() => undefined);
|
|
309
|
+
} else {
|
|
310
|
+
await stopped;
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
firstError ??= error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
await Promise.allSettled([...this.responseCompletions]);
|
|
317
|
+
if (firstError !== undefined) {
|
|
318
|
+
throw firstError;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async dispatch(request: Request, clientIp?: string): Promise<Response> {
|
|
323
|
+
let response: Response;
|
|
324
|
+
try {
|
|
325
|
+
response = await this.handle(request, clientIp);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
// Covers failures before a request context can be created. Normal route
|
|
328
|
+
// and short-circuit failures are handled by the configured error boundary.
|
|
329
|
+
response = this.prepareClientResponse(this.transportError(error), request);
|
|
330
|
+
}
|
|
331
|
+
// HEAD must echo the GET response's status/headers without a body.
|
|
332
|
+
return request.method === "HEAD" ? stripBody(response) : response;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private transportError(error: unknown): Response {
|
|
336
|
+
try {
|
|
337
|
+
console.error("[http] transport failed:", redactSensitive(error));
|
|
338
|
+
} catch {
|
|
339
|
+
// Best-effort diagnostics only.
|
|
340
|
+
}
|
|
341
|
+
const response = json(500, { error: "Internal Server Error" });
|
|
342
|
+
if (this.options.securityHeaders !== false) {
|
|
343
|
+
applySecurityHeaders(response, this.options.securityHeaders ?? {});
|
|
344
|
+
}
|
|
345
|
+
return response;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private async handle(request: Request, clientIp?: string): Promise<Response> {
|
|
349
|
+
const url = new URL(request.url);
|
|
350
|
+
|
|
351
|
+
if (this.healthPath !== undefined && url.pathname === this.healthPath && isReadMethod(request.method)) {
|
|
352
|
+
return this.runShortCircuit(request, url, clientIp, () => this.healthResponse());
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (this.docs !== undefined) {
|
|
356
|
+
const docsResponse = serveOpenApiDocs(this.docs, url.pathname, request.method);
|
|
357
|
+
if (docsResponse !== undefined) {
|
|
358
|
+
return this.runShortCircuit(request, url, clientIp, () => docsResponse);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (this.options.cors && isPreflight(request)) {
|
|
363
|
+
const corsOptions = this.options.cors;
|
|
364
|
+
return this.runShortCircuit(request, url, clientIp, () => preflightResponse(corsOptions, request));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const segments = parseRequestPath(url.pathname);
|
|
368
|
+
if (!segments) {
|
|
369
|
+
if (this.maxBodyBytes !== undefined && exceedsBodyLimit(request, this.maxBodyBytes)) {
|
|
370
|
+
return this.runShortCircuit(request, url, clientIp, () =>
|
|
371
|
+
json(413, { error: "Payload Too Large", details: { maxBytes: this.maxBodyBytes } }));
|
|
372
|
+
}
|
|
373
|
+
return this.runShortCircuit(request, url, clientIp, () => json(400, { error: "Malformed request path" }));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const match = this.router!.match(request.method, segments, this.readVersion(request, url));
|
|
377
|
+
const effectiveMaxBodyBytes = match.kind === "matched"
|
|
378
|
+
? (match.action.maxBodyBytes ?? this.maxBodyBytes)
|
|
379
|
+
: this.maxBodyBytes;
|
|
380
|
+
if (effectiveMaxBodyBytes !== undefined && exceedsBodyLimit(request, effectiveMaxBodyBytes)) {
|
|
381
|
+
return this.runShortCircuit(request, url, clientIp, () =>
|
|
382
|
+
json(413, { error: "Payload Too Large", details: { maxBytes: effectiveMaxBodyBytes } }));
|
|
383
|
+
}
|
|
384
|
+
if (match.kind === "not-found") {
|
|
385
|
+
return this.runShortCircuit(request, url, clientIp, () => json(404, { error: "Not Found" }));
|
|
386
|
+
}
|
|
387
|
+
if (match.kind === "method-not-allowed") {
|
|
388
|
+
return this.runShortCircuit(request, url, clientIp, () =>
|
|
389
|
+
json(405, { error: "Method Not Allowed" }, { allow: match.allow.join(", ") }));
|
|
390
|
+
}
|
|
391
|
+
if (match.kind === "unsupported-version") {
|
|
392
|
+
return this.runShortCircuit(request, url, clientIp, () =>
|
|
393
|
+
json(400, { error: "Unsupported API version", details: { supported: match.supported } }));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const scope = this.provider.createScope();
|
|
397
|
+
const ctx = new HttpContext(
|
|
398
|
+
request,
|
|
399
|
+
url,
|
|
400
|
+
match.params,
|
|
401
|
+
scope,
|
|
402
|
+
match.action.version,
|
|
403
|
+
effectiveMaxBodyBytes,
|
|
404
|
+
clientIp,
|
|
405
|
+
this.modelValidator,
|
|
406
|
+
);
|
|
407
|
+
let scopeTransferred = false;
|
|
408
|
+
try {
|
|
409
|
+
await runPipeline(match.action.chain, ctx);
|
|
410
|
+
// The error boundary always sets a response on failure; an empty
|
|
411
|
+
// response here means no middleware produced one.
|
|
412
|
+
const response = this.prepareClientResponse(ctx.response ?? json(404, { error: "Not Found" }), request);
|
|
413
|
+
if (request.method === "HEAD") return stripBody(response);
|
|
414
|
+
if (!response.body) return response;
|
|
415
|
+
this.trackResponse(holdResponseScope(response, request.signal, () =>
|
|
416
|
+
disposeRequestScope(scope, this.requestScopeDisposeTimeoutMs)));
|
|
417
|
+
scopeTransferred = true;
|
|
418
|
+
return response;
|
|
419
|
+
} finally {
|
|
420
|
+
// Disposal must never replace the response with Bun's bare 500.
|
|
421
|
+
if (!scopeTransferred) await disposeRequestScope(scope, this.requestScopeDisposeTimeoutMs);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private async runShortCircuit(
|
|
426
|
+
request: Request,
|
|
427
|
+
url: URL,
|
|
428
|
+
clientIp: string | undefined,
|
|
429
|
+
produce: () => Response | Promise<Response>,
|
|
430
|
+
): Promise<Response> {
|
|
431
|
+
const scope = this.provider.createScope();
|
|
432
|
+
const ctx = new HttpContext(
|
|
433
|
+
request,
|
|
434
|
+
url,
|
|
435
|
+
{},
|
|
436
|
+
scope,
|
|
437
|
+
undefined,
|
|
438
|
+
this.maxBodyBytes,
|
|
439
|
+
clientIp,
|
|
440
|
+
this.modelValidator,
|
|
441
|
+
);
|
|
442
|
+
const terminal: HttpMiddleware = async (context) => {
|
|
443
|
+
context.response = await produce();
|
|
444
|
+
};
|
|
445
|
+
let scopeTransferred = false;
|
|
446
|
+
try {
|
|
447
|
+
await runPipeline([...this.shortCircuitChain, terminal], ctx);
|
|
448
|
+
const response = this.prepareClientResponse(ctx.response ?? json(500, { error: "Internal Server Error" }), request);
|
|
449
|
+
if (request.method === "HEAD") return stripBody(response);
|
|
450
|
+
if (!response.body) return response;
|
|
451
|
+
this.trackResponse(holdResponseScope(response, request.signal, () =>
|
|
452
|
+
disposeRequestScope(scope, this.requestScopeDisposeTimeoutMs)));
|
|
453
|
+
scopeTransferred = true;
|
|
454
|
+
return response;
|
|
455
|
+
} finally {
|
|
456
|
+
if (!scopeTransferred) await disposeRequestScope(scope, this.requestScopeDisposeTimeoutMs);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private trackResponse(completion: Promise<void>): void {
|
|
461
|
+
this.responseCompletions.add(completion);
|
|
462
|
+
void completion.finally(() => this.responseCompletions.delete(completion)).catch(() => undefined);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private prepareClientResponse(response: Response, request: Request): Response {
|
|
466
|
+
return this.options.inspectableRedirects ? inspectableRedirectResponse(response, request) : response;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private async healthResponse(): Promise<Response> {
|
|
470
|
+
const report = await this.getHealthReport();
|
|
471
|
+
const exposed = this.healthOptions?.exposeDetails
|
|
472
|
+
? report
|
|
473
|
+
: {
|
|
474
|
+
healthy: report.healthy,
|
|
475
|
+
checks: report.checks.map(({ details: _details, ...entry }) => entry),
|
|
476
|
+
};
|
|
477
|
+
return json(report.healthy ? 200 : 503, exposed);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private async getHealthReport(): Promise<HealthReport> {
|
|
481
|
+
const now = Date.now();
|
|
482
|
+
if (this.healthCache && this.healthCache.expiresAt > now) {
|
|
483
|
+
return this.healthCache.report;
|
|
484
|
+
}
|
|
485
|
+
if (this.healthInFlight) {
|
|
486
|
+
return this.healthInFlight;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const service = this.resolver.tryResolve(HealthService);
|
|
490
|
+
const timeoutMs = this.healthOptions?.timeoutMs ?? 5_000;
|
|
491
|
+
const startedAt = performance.now();
|
|
492
|
+
const work = Promise.resolve().then(() =>
|
|
493
|
+
service?.check() ?? ({ healthy: true, checks: [] } satisfies HealthReport));
|
|
494
|
+
const inFlight = timeoutMs === 0
|
|
495
|
+
? work
|
|
496
|
+
: healthWithTimeout(work, timeoutMs, startedAt);
|
|
497
|
+
this.healthInFlight = inFlight;
|
|
498
|
+
// A timed-out check may ignore cancellation and never settle. Keep its
|
|
499
|
+
// already-resolved timeout report as the single-flight quarantine so every
|
|
500
|
+
// later probe does not start another immortal check. Release only after the
|
|
501
|
+
// source work itself eventually settles.
|
|
502
|
+
void Promise.allSettled([work, inFlight]).then(() => {
|
|
503
|
+
if (this.healthInFlight === inFlight) {
|
|
504
|
+
this.healthInFlight = undefined;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
const report = await inFlight;
|
|
508
|
+
const cacheMs = this.healthOptions?.cacheMs ?? 0;
|
|
509
|
+
if (cacheMs > 0) {
|
|
510
|
+
this.healthCache = { report, expiresAt: Date.now() + cacheMs };
|
|
511
|
+
}
|
|
512
|
+
return report;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isAddressInUse(error: unknown): boolean {
|
|
517
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function isReadMethod(method: string): boolean {
|
|
521
|
+
return method === "GET" || method === "HEAD";
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Fast pre-read guard for requests that advertise a `Content-Length`. */
|
|
525
|
+
function exceedsBodyLimit(request: Request, maxBytes: number): boolean {
|
|
526
|
+
const contentLength = request.headers.get("content-length");
|
|
527
|
+
if (contentLength === null) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
const length = Number(contentLength);
|
|
531
|
+
return Number.isFinite(length) && length > maxBytes;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function assertHttpLimits(options: HttpModuleOptions): void {
|
|
535
|
+
if (
|
|
536
|
+
options.maxBodyBytes !== undefined &&
|
|
537
|
+
(!Number.isSafeInteger(options.maxBodyBytes) || options.maxBodyBytes < 0)
|
|
538
|
+
) {
|
|
539
|
+
throw new HttpSetupError("HTTP maxBodyBytes must be a non-negative integer.");
|
|
540
|
+
}
|
|
541
|
+
if (
|
|
542
|
+
options.idleTimeoutSeconds !== undefined &&
|
|
543
|
+
(!Number.isFinite(options.idleTimeoutSeconds) || options.idleTimeoutSeconds < 0 || options.idleTimeoutSeconds > 255)
|
|
544
|
+
) {
|
|
545
|
+
throw new HttpSetupError("HTTP idleTimeoutSeconds must be between 0 and 255.");
|
|
546
|
+
}
|
|
547
|
+
if (
|
|
548
|
+
options.requestScopeDisposeTimeoutMs !== undefined
|
|
549
|
+
&& (!Number.isSafeInteger(options.requestScopeDisposeTimeoutMs) || options.requestScopeDisposeTimeoutMs < 0)
|
|
550
|
+
) {
|
|
551
|
+
throw new HttpSetupError("HTTP requestScopeDisposeTimeoutMs must be a non-negative safe integer.");
|
|
552
|
+
}
|
|
553
|
+
if (typeof options.health === "object") {
|
|
554
|
+
const { timeoutMs, cacheMs } = options.health;
|
|
555
|
+
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
556
|
+
throw new HttpSetupError("HTTP health timeoutMs must be a non-negative finite number.");
|
|
557
|
+
}
|
|
558
|
+
if (cacheMs !== undefined && (!Number.isFinite(cacheMs) || cacheMs < 0)) {
|
|
559
|
+
throw new HttpSetupError("HTTP health cacheMs must be a non-negative finite number.");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function disposeRequestScope(scope: ServiceScope, timeoutMs: number | undefined): Promise<void> {
|
|
565
|
+
const disposal = Promise.resolve().then(() => scope.dispose());
|
|
566
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
567
|
+
try {
|
|
568
|
+
if (timeoutMs === undefined) {
|
|
569
|
+
await disposal;
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const timedOut = Symbol("request-scope-disposal-timeout");
|
|
573
|
+
const outcome = await Promise.race([
|
|
574
|
+
disposal.then(() => undefined),
|
|
575
|
+
new Promise<typeof timedOut>((resolve) => {
|
|
576
|
+
timer = setTimeout(() => resolve(timedOut), timeoutMs);
|
|
577
|
+
}),
|
|
578
|
+
]);
|
|
579
|
+
if (outcome === timedOut) {
|
|
580
|
+
// Keep the late rejection observed even though the response is released.
|
|
581
|
+
void disposal.catch((error) => logScopeDisposalError(error));
|
|
582
|
+
try {
|
|
583
|
+
console.error(`[http] request scope disposal timed out after ${timeoutMs}ms`);
|
|
584
|
+
} catch {
|
|
585
|
+
// Best-effort diagnostics only.
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch (error) {
|
|
589
|
+
logScopeDisposalError(error);
|
|
590
|
+
} finally {
|
|
591
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function logScopeDisposalError(error: unknown): void {
|
|
596
|
+
// Disposal must never replace the response, and cleanup errors may contain
|
|
597
|
+
// connection/configuration secrets.
|
|
598
|
+
try {
|
|
599
|
+
console.error("[http] request scope disposal failed:", redactSensitive(error));
|
|
600
|
+
} catch {
|
|
601
|
+
// Best-effort diagnostics only.
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function healthWithTimeout(
|
|
606
|
+
work: Promise<HealthReport>,
|
|
607
|
+
timeoutMs: number,
|
|
608
|
+
startedAt: number,
|
|
609
|
+
): Promise<HealthReport> {
|
|
610
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
611
|
+
const timeout = new Promise<HealthReport>((resolve) => {
|
|
612
|
+
timer = setTimeout(() => resolve({
|
|
613
|
+
healthy: false,
|
|
614
|
+
checks: [{
|
|
615
|
+
name: "health",
|
|
616
|
+
healthy: false,
|
|
617
|
+
details: `Health check timed out after ${timeoutMs}ms`,
|
|
618
|
+
durationMs: performance.now() - startedAt,
|
|
619
|
+
}],
|
|
620
|
+
}), timeoutMs);
|
|
621
|
+
});
|
|
622
|
+
try {
|
|
623
|
+
return await Promise.race([work, timeout]);
|
|
624
|
+
} finally {
|
|
625
|
+
clearTimeout(timer);
|
|
626
|
+
}
|
|
627
|
+
}
|