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,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DI,
|
|
3
|
+
HOSTED_SERVICE,
|
|
4
|
+
Module,
|
|
5
|
+
createToken,
|
|
6
|
+
createModuleOwnedMetadataChannel,
|
|
7
|
+
registerModuleOwnedProviderContributor,
|
|
8
|
+
registerModuleOwnedContributionValidator,
|
|
9
|
+
type ModuleConfig,
|
|
10
|
+
type ModuleExport,
|
|
11
|
+
type BazisModuleRef,
|
|
12
|
+
type ProviderDefinition,
|
|
13
|
+
} from "../di";
|
|
14
|
+
import { ConfigRegistry, HEALTH_CHECK } from "../kernel";
|
|
15
|
+
import { InfraError, type InfraConnector } from "./InfraConnector";
|
|
16
|
+
import { InfraLifecycle } from "./InfraLifecycle";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Infrastructure manifest: a readable "logical name → connector" map.
|
|
20
|
+
* The key (`db`, `cache`, `search`) is the instance name: it appears in the
|
|
21
|
+
* health check (`infra:db`) and in diagnostics, so the manifest file reads as
|
|
22
|
+
* the application's connection spec.
|
|
23
|
+
*/
|
|
24
|
+
export type InfraManifest = Readonly<Record<string, InfraConnector>>;
|
|
25
|
+
|
|
26
|
+
const instances = new WeakMap<ProviderDefinition, { name: string; token: InfraConnector["token"] }>();
|
|
27
|
+
const instanceChannel = createModuleOwnedMetadataChannel<{ name: string; token: InfraConnector["token"] }>("infra.instances");
|
|
28
|
+
registerModuleOwnedProviderContributor((metadata, context) => {
|
|
29
|
+
for (const provider of metadata.providers ?? []) {
|
|
30
|
+
const instance = instances.get(provider);
|
|
31
|
+
if (instance) context.addMetadata(instanceChannel, instance);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
registerModuleOwnedContributionValidator((snapshot) => {
|
|
35
|
+
const owners = new Map<InfraConnector["token"], string>();
|
|
36
|
+
for (const { ownerName, payload } of snapshot.getMetadataContributions(instanceChannel)) {
|
|
37
|
+
const name = `${ownerName}:${payload.name}`;
|
|
38
|
+
const previous = owners.get(payload.token);
|
|
39
|
+
if (previous !== undefined) {
|
|
40
|
+
throw new InfraError(`Infra instances "${previous}" and "${name}" share the same connector token; each instance needs a distinct token.`);
|
|
41
|
+
}
|
|
42
|
+
owners.set(payload.token, name);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function buildInfraDefinitions(manifest: InfraManifest): {
|
|
47
|
+
providers: ProviderDefinition[];
|
|
48
|
+
exports: ModuleExport[];
|
|
49
|
+
config: ModuleConfig[];
|
|
50
|
+
} {
|
|
51
|
+
const providers: ProviderDefinition[] = [];
|
|
52
|
+
const exports: ModuleExport[] = [];
|
|
53
|
+
const config: ModuleConfig[] = [];
|
|
54
|
+
const seenConfig = new Set<ModuleConfig>();
|
|
55
|
+
// One token = one singleton client. Two instances under one token would give
|
|
56
|
+
// a shared client with two lifecycles (double connect/dispose), so fail loudly.
|
|
57
|
+
const seenTokens = new Map<InfraConnector["token"], string>();
|
|
58
|
+
|
|
59
|
+
for (const [name, connector] of Object.entries(manifest)) {
|
|
60
|
+
if (!name.trim() || name !== name.trim()) throw new InfraError("Infra instance names must be non-empty and trimmed.");
|
|
61
|
+
if (connector.phase !== undefined && !Number.isSafeInteger(connector.phase)) {
|
|
62
|
+
throw new InfraError(`Infra instance "${name}": phase must be a finite safe integer.`);
|
|
63
|
+
}
|
|
64
|
+
const previous = seenTokens.get(connector.token);
|
|
65
|
+
if (previous !== undefined) {
|
|
66
|
+
throw new InfraError(
|
|
67
|
+
`Infra manifest: instances "${previous}" and "${name}" share the same connector token; each instance needs a distinct token.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
seenTokens.set(connector.token, name);
|
|
71
|
+
|
|
72
|
+
const connectorConfigs =
|
|
73
|
+
connector.config === undefined ? [] : Array.isArray(connector.config) ? connector.config : [connector.config];
|
|
74
|
+
for (let index = 0; index < connectorConfigs.length; index += 1) {
|
|
75
|
+
const item = connectorConfigs[index] as ModuleConfig;
|
|
76
|
+
if (!seenConfig.has(item)) {
|
|
77
|
+
seenConfig.add(item);
|
|
78
|
+
config.push(item);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// DI registers the owner before the client: even an early resolution or
|
|
83
|
+
// a failing factory elsewhere cannot leave a created resource without cleanup.
|
|
84
|
+
const ownerToken = createToken<InfraLifecycle<unknown>>(`infra:${name}:lifetime`);
|
|
85
|
+
const owner = DI.singleton(DI.factoryProviderWithResolver(ownerToken, [], (resolver) =>
|
|
86
|
+
new InfraLifecycle(name, connector, undefined, resolver.has(ConfigRegistry) ? resolver.resolve(ConfigRegistry) : undefined)));
|
|
87
|
+
instances.set(owner, { name, token: connector.token });
|
|
88
|
+
providers.push(owner);
|
|
89
|
+
providers.push(
|
|
90
|
+
DI.singleton(DI.externallyOwned(DI.factoryProvider(connector.token, [ownerToken], (lifetime) => lifetime.getClient()))),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 2. Lifecycle: opens/closes the connection in the kernel phases.
|
|
94
|
+
providers.push(
|
|
95
|
+
DI.singleton(
|
|
96
|
+
DI.factoryProvider(HOSTED_SERVICE, [ownerToken], (lifetime) => lifetime),
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// 3. Health check: only if the connector supports it.
|
|
101
|
+
if (connector.healthCheck) {
|
|
102
|
+
const healthCheck = connector.healthCheck.bind(connector);
|
|
103
|
+
providers.push(
|
|
104
|
+
DI.singleton(
|
|
105
|
+
DI.factoryProvider(HEALTH_CHECK, [connector.token], (client) => ({
|
|
106
|
+
name: `infra:${name}`,
|
|
107
|
+
check: async (signal?: AbortSignal) => ({ healthy: await healthCheck(client, signal) }),
|
|
108
|
+
})),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
exports.push(connector.token);
|
|
114
|
+
|
|
115
|
+
// 4. Extra connector providers (for example a distributed cache on top of Redis).
|
|
116
|
+
if (connector.providers !== undefined) {
|
|
117
|
+
providers.push(...connector.providers);
|
|
118
|
+
}
|
|
119
|
+
if (connector.exports !== undefined) {
|
|
120
|
+
exports.push(...connector.exports);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { providers, exports, config };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Dynamic infrastructure module (a plain metadata object): the same as
|
|
129
|
+
* `@Infra`, but without a class. Handy for `imports` or `runApp({ infra })`.
|
|
130
|
+
*/
|
|
131
|
+
export function infraModule(manifest: InfraManifest): BazisModuleRef {
|
|
132
|
+
const { providers, exports, config } = buildInfraDefinitions(manifest);
|
|
133
|
+
return { global: true, providers, exports, config };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Infrastructure decorator. Puts the metadata of a regular **global module**
|
|
138
|
+
* on the class, so the class becomes a valid `BazisModuleRef`: it can be passed
|
|
139
|
+
* to `runApp({ infra: AppInfra })` or to `imports`.
|
|
140
|
+
*
|
|
141
|
+
* The class stays pure metadata (like `@Module`) and is **never instantiated**:
|
|
142
|
+
* only the clients it lists live and hold state.
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* @Infra({
|
|
146
|
+
* db: postgres(dbConfig),
|
|
147
|
+
* cache: redisConnect(redisConfig),
|
|
148
|
+
* search: openSearchConnect(searchConfig),
|
|
149
|
+
* })
|
|
150
|
+
* export class AppInfra {}
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function Infra(manifest: InfraManifest) {
|
|
154
|
+
const { providers, exports, config } = buildInfraDefinitions(manifest);
|
|
155
|
+
return (target: abstract new (...args: never) => unknown, _context: ClassDecoratorContext): void => {
|
|
156
|
+
Module({ global: true, providers, exports, config })(target, _context);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { InjectionToken, ModuleConfig, ModuleExport, ProviderDefinition } from "../di";
|
|
2
|
+
import { redactSensitiveText } from "../../library/redaction";
|
|
3
|
+
import type { ConfigRegistry } from "../kernel/config/ConfigRegistry";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Infrastructure connector contract: one shape for "a connection to something
|
|
7
|
+
* external" (a database, cache, search engine, bus). A connector encapsulates
|
|
8
|
+
* four things that would otherwise be hand-written in every integration module:
|
|
9
|
+
*
|
|
10
|
+
* 1. **creating the client** from the subsystem config ({@link create}): the
|
|
11
|
+
* connector gets a typed `defineConfig` object and reads the declared keys
|
|
12
|
+
* itself (secrets are revealed at the boundary, not read from `process.env`);
|
|
13
|
+
* 2. **opening the connection** at application start ({@link connect});
|
|
14
|
+
* 3. **closing the connection** at shutdown ({@link dispose}): graceful shutdown;
|
|
15
|
+
* 4. **health check** ({@link healthCheck}) for the built-in `/health`.
|
|
16
|
+
*
|
|
17
|
+
* The {@link Infra} decorator expands a set of connectors into a regular global
|
|
18
|
+
* module: the client is registered as a singleton under its {@link token}, the
|
|
19
|
+
* lifecycle as a `HOSTED_SERVICE` (the default phase is negative so that
|
|
20
|
+
* infrastructure starts before the servers), the health check as `HEALTH_CHECK`.
|
|
21
|
+
* The connector implementation defines the connection type; there is no common
|
|
22
|
+
* list of types. The manifest entry name is used for diagnostics; the client is
|
|
23
|
+
* identified by its DI token.
|
|
24
|
+
*
|
|
25
|
+
* @typeParam TClient Type of the client injected in the application by {@link token}.
|
|
26
|
+
*/
|
|
27
|
+
export interface InfraConnector<TClient = unknown> {
|
|
28
|
+
/** Token under which the client is available for injection into application services. */
|
|
29
|
+
readonly token: InjectionToken<TClient>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configs owned by the connector. `@Infra` lifts them into the module
|
|
33
|
+
* metadata, and `runApp` validates them before the application starts.
|
|
34
|
+
*/
|
|
35
|
+
readonly config?: ModuleConfig | readonly ModuleConfig[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Start phase of the hosted lifecycle. Lower starts earlier and stops later.
|
|
39
|
+
* Defaults to `-100`, so infrastructure is ready before the servers.
|
|
40
|
+
*/
|
|
41
|
+
readonly phase?: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates the client (**not** connected yet: the connection is opened in
|
|
45
|
+
* {@link connect}). The connector keeps the subsystem config itself (it was
|
|
46
|
+
* passed to its factory, e.g. `postgres(dbConfig)`); this is where
|
|
47
|
+
* credentials are read and required keys are validated.
|
|
48
|
+
*/
|
|
49
|
+
create(configs?: ConfigRegistry): TClient;
|
|
50
|
+
|
|
51
|
+
/** Opens the connection / warms up the pool. Called at application start. */
|
|
52
|
+
connect(client: TClient, signal?: AbortSignal): Promise<void> | void;
|
|
53
|
+
|
|
54
|
+
/** Closes the connection / releases resources. Called at shutdown. */
|
|
55
|
+
dispose(client: TClient): Promise<void> | void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Connection liveness check for `/health`. If not set, no health check is
|
|
59
|
+
* registered for this connector.
|
|
60
|
+
*/
|
|
61
|
+
healthCheck?(client: TClient, signal?: AbortSignal): Promise<boolean> | boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extra providers the connector adds to the infrastructure module besides
|
|
65
|
+
* the client itself (for example `redisConnect(cfg, { cache: "distributed" })`
|
|
66
|
+
* publishes a distributed cache backend on top of the client). Usually a
|
|
67
|
+
* `factoryProvider` that refers to {@link token}. Exposed through {@link exports}.
|
|
68
|
+
*/
|
|
69
|
+
readonly providers?: readonly ProviderDefinition[];
|
|
70
|
+
|
|
71
|
+
/** Tokens from {@link providers} to make visible to the application. */
|
|
72
|
+
readonly exports?: readonly ModuleExport[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Infrastructure configuration/connection error. */
|
|
76
|
+
export class InfraError extends Error {
|
|
77
|
+
public constructor(message: string) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "InfraError";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Safely extracts the error text (in case a non-`Error` was thrown). */
|
|
84
|
+
export function errorMessage(error: unknown): string {
|
|
85
|
+
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
86
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { HostedService } from "../di";
|
|
2
|
+
import { InfraError, type InfraConnector } from "./InfraConnector";
|
|
3
|
+
import { bindConnectorLifecycle } from "./connectorIdentity";
|
|
4
|
+
import { awaitAbortable } from "../kernel/internal/awaitAbortable";
|
|
5
|
+
import type { ConfigRegistry } from "../kernel/config/ConfigRegistry";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_INFRA_PHASE = -100;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hosted service that wraps an {@link InfraConnector} into the kernel
|
|
11
|
+
* lifecycle: opens the connection at start and closes it at shutdown. One
|
|
12
|
+
* lifecycle per connector. DI owns the lifecycle even when the client is
|
|
13
|
+
* resolved before start.
|
|
14
|
+
*
|
|
15
|
+
* The phase comes from the connector (negative by default), so connections
|
|
16
|
+
* come up before the HTTP server and background tasks and go down after them.
|
|
17
|
+
*/
|
|
18
|
+
export class InfraLifecycle<TClient> implements HostedService {
|
|
19
|
+
public readonly phase: number;
|
|
20
|
+
private client?: TClient;
|
|
21
|
+
private created = false;
|
|
22
|
+
private disposed = false;
|
|
23
|
+
private startPromise?: Promise<void>;
|
|
24
|
+
private disposePromise?: Promise<void>;
|
|
25
|
+
|
|
26
|
+
public constructor(
|
|
27
|
+
private readonly name: string,
|
|
28
|
+
private readonly connector: InfraConnector<TClient>,
|
|
29
|
+
client?: TClient,
|
|
30
|
+
private readonly configs?: ConfigRegistry,
|
|
31
|
+
) {
|
|
32
|
+
this.client = client;
|
|
33
|
+
this.created = client !== undefined;
|
|
34
|
+
this.phase = connector.phase ?? DEFAULT_INFRA_PHASE;
|
|
35
|
+
bindConnectorLifecycle(connector, this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public getClient(): TClient {
|
|
39
|
+
if (this.disposed) throw new InfraError(`Infra connector "${this.name}" is already disposed.`);
|
|
40
|
+
if (!this.created) {
|
|
41
|
+
this.client = this.connector.create(this.configs);
|
|
42
|
+
this.created = true;
|
|
43
|
+
}
|
|
44
|
+
return this.client as TClient;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public start(signal?: AbortSignal): Promise<void> {
|
|
48
|
+
this.startPromise ??= Promise.resolve().then(() => this.connect(signal));
|
|
49
|
+
return this.startPromise;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async connect(signal?: AbortSignal): Promise<void> {
|
|
53
|
+
try {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
const client = this.getClient();
|
|
56
|
+
await awaitAbortable(Promise.resolve(this.connector.connect(client, signal)), signal);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
try {
|
|
59
|
+
await this.dispose();
|
|
60
|
+
} catch (cleanupError) {
|
|
61
|
+
throw new AggregateError(
|
|
62
|
+
[error, cleanupError],
|
|
63
|
+
`Infra connector "${this.name}" failed to start and rollback also failed.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public stop(): Promise<void> {
|
|
71
|
+
return this.dispose();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public dispose(): Promise<void> {
|
|
75
|
+
this.disposed = true;
|
|
76
|
+
this.disposePromise ??= Promise.resolve().then(async () => {
|
|
77
|
+
if (this.created) await this.connector.dispose(this.client as TClient);
|
|
78
|
+
});
|
|
79
|
+
return this.disposePromise;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Infrastructure instance name (the key in the `@Infra` manifest), for diagnostics. */
|
|
83
|
+
public get instanceName(): string {
|
|
84
|
+
return this.name;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DistributedCache,
|
|
3
|
+
NamedCacheRegistry,
|
|
4
|
+
httpPayloadCacheCodec,
|
|
5
|
+
jsonCacheCodec,
|
|
6
|
+
type CachedHttpPayload,
|
|
7
|
+
type DistributedCacheStores,
|
|
8
|
+
type DistributedCachePing,
|
|
9
|
+
type IDistributedCache,
|
|
10
|
+
} from "../../cache";
|
|
11
|
+
import type { RedisCommandClient } from "./RedisDistributedCacheDriver";
|
|
12
|
+
import { RedisDistributedCacheDriver } from "./RedisDistributedCacheDriver";
|
|
13
|
+
|
|
14
|
+
/** Tuning of the Redis-backed distributed cache (all optional). */
|
|
15
|
+
export interface RedisDistributedCacheTuning {
|
|
16
|
+
/** Connection name for the decorators (`connection`). Defaults to `"default"`. */
|
|
17
|
+
readonly connection?: string;
|
|
18
|
+
/** Prefix of all keys; isolates the application cache in a shared Redis. */
|
|
19
|
+
readonly keyPrefix?: string;
|
|
20
|
+
/** TTL of the anti-stampede lock on a miss (seconds). */
|
|
21
|
+
readonly defaultLockSeconds?: number;
|
|
22
|
+
/** Maximum key length. */
|
|
23
|
+
readonly maxKeyLength?: number;
|
|
24
|
+
/** Size limit of a serialized value (bytes); `set` degrades gracefully. */
|
|
25
|
+
readonly maxValueBytes?: number;
|
|
26
|
+
/** Polling interval (ms) while waiting for another worker's result. */
|
|
27
|
+
readonly pollIntervalMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_KEY_PREFIX = "bazis:cache:";
|
|
31
|
+
const DEFAULT_LOCK_SECONDS = 10;
|
|
32
|
+
const DEFAULT_CONNECTION = "default";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Distributed cache backend on top of one Redis connection. Builds two stores
|
|
36
|
+
* (HTTP responses and method values) with separate namespaces and codecs and
|
|
37
|
+
* exposes them as a {@link NamedCacheRegistry}. The `redisConnect(...)`
|
|
38
|
+
* connector opens and closes the connection.
|
|
39
|
+
*/
|
|
40
|
+
export class RedisDistributedCacheBackend implements DistributedCacheStores {
|
|
41
|
+
public readonly connectionNames: readonly string[];
|
|
42
|
+
public readonly outputCache: NamedCacheRegistry<IDistributedCache<CachedHttpPayload>>;
|
|
43
|
+
public readonly serviceCache: NamedCacheRegistry<IDistributedCache>;
|
|
44
|
+
|
|
45
|
+
private readonly connection: string;
|
|
46
|
+
|
|
47
|
+
public constructor(
|
|
48
|
+
private readonly client: RedisCommandClient,
|
|
49
|
+
tuning: RedisDistributedCacheTuning = {},
|
|
50
|
+
) {
|
|
51
|
+
this.connection = tuning.connection ?? DEFAULT_CONNECTION;
|
|
52
|
+
this.connectionNames = [this.connection];
|
|
53
|
+
|
|
54
|
+
const driver = new RedisDistributedCacheDriver(client);
|
|
55
|
+
const base = {
|
|
56
|
+
connectionName: this.connection,
|
|
57
|
+
keyPrefix: tuning.keyPrefix ?? DEFAULT_KEY_PREFIX,
|
|
58
|
+
defaultLockSeconds: tuning.defaultLockSeconds ?? DEFAULT_LOCK_SECONDS,
|
|
59
|
+
maxKeyLength: tuning.maxKeyLength,
|
|
60
|
+
maxValueBytes: tuning.maxValueBytes,
|
|
61
|
+
pollIntervalMs: tuning.pollIntervalMs,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const output = new DistributedCache<CachedHttpPayload>(driver, httpPayloadCacheCodec, {
|
|
65
|
+
...base,
|
|
66
|
+
namespace: "out:",
|
|
67
|
+
});
|
|
68
|
+
const service = new DistributedCache(driver, jsonCacheCodec, { ...base, namespace: "svc:" });
|
|
69
|
+
|
|
70
|
+
this.outputCache = new NamedCacheRegistry(new Map([[this.connection, output]]));
|
|
71
|
+
this.serviceCache = new NamedCacheRegistry(new Map([[this.connection, service]]));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async ping(): Promise<readonly DistributedCachePing[]> {
|
|
75
|
+
try {
|
|
76
|
+
const reply = await this.client.send("PING", []);
|
|
77
|
+
return [{ connection: this.connection, healthy: reply === "PONG" }];
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
connection: this.connection,
|
|
82
|
+
healthy: false,
|
|
83
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { DistributedCacheDriver, DistributedCacheFencedWrite } from "../../cache";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The narrow Redis client contract the driver needs. Bun `RedisClient`
|
|
5
|
+
* satisfies it structurally, and the interface lets the driver be tested
|
|
6
|
+
* without a live server (any fake implementing these five methods).
|
|
7
|
+
*/
|
|
8
|
+
export interface RedisCommandClient {
|
|
9
|
+
get(key: string): Promise<string | null>;
|
|
10
|
+
set(key: string, value: string, ...options: string[]): Promise<string | null>;
|
|
11
|
+
del(...keys: string[]): Promise<number>;
|
|
12
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
13
|
+
send(command: string, args: string[]): Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Releases the lock only if it still holds our fencing token (atomically).
|
|
18
|
+
* Otherwise a slow worker could delete a lock already re-acquired by another.
|
|
19
|
+
*/
|
|
20
|
+
const RELEASE_LOCK_SCRIPT =
|
|
21
|
+
"if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]); if KEYS[2] then redis.call('del', KEYS[2]) end; return 1 else return 0 end";
|
|
22
|
+
|
|
23
|
+
const DELETE_IF_VALUE_SCRIPT =
|
|
24
|
+
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
|
25
|
+
|
|
26
|
+
const INVALIDATE_SCRIPT =
|
|
27
|
+
"redis.call('incr', KEYS[1]); return redis.call('del', KEYS[2])";
|
|
28
|
+
|
|
29
|
+
const INVALIDATE_WHILE_LOCKED_SCRIPT =
|
|
30
|
+
"if redis.call('exists', KEYS[3]) == 1 then local lockTtl = redis.call('pttl', KEYS[3]); redis.call('incr', KEYS[1]); if lockTtl > 0 then redis.call('pexpire', KEYS[1], lockTtl + 60000) end else redis.call('del', KEYS[1]) end; return redis.call('del', KEYS[2])";
|
|
31
|
+
|
|
32
|
+
const ADVANCE_TAG_GENERATION_SCRIPT =
|
|
33
|
+
"local next = redis.call('incr', KEYS[1]); redis.call('sadd', KEYS[2], tostring(next - 1)); return next";
|
|
34
|
+
|
|
35
|
+
const ADD_TAG_MEMBERS_SCRIPT = `
|
|
36
|
+
local previousTtl = redis.call('ttl', KEYS[1])
|
|
37
|
+
for i = 2, #ARGV do redis.call('sadd', KEYS[1], ARGV[i]) end
|
|
38
|
+
if ARGV[1] == '' then
|
|
39
|
+
redis.call('persist', KEYS[1])
|
|
40
|
+
elseif previousTtl == -2 or (previousTtl >= 0 and previousTtl < tonumber(ARGV[1])) then
|
|
41
|
+
redis.call('expire', KEYS[1], ARGV[1])
|
|
42
|
+
end
|
|
43
|
+
return 1`;
|
|
44
|
+
|
|
45
|
+
const WRITE_IF_LOCK_OWNER_SCRIPT = `
|
|
46
|
+
if redis.call('get', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
47
|
+
local versionCount = tonumber(ARGV[4])
|
|
48
|
+
local tagCount = tonumber(ARGV[5])
|
|
49
|
+
for i = 1, versionCount do
|
|
50
|
+
local actual = redis.call('get', KEYS[2 + i])
|
|
51
|
+
local expected = ARGV[5 + i]
|
|
52
|
+
if expected == '__BAZIS_MISSING_VERSION__' then
|
|
53
|
+
if actual then return 0 end
|
|
54
|
+
elseif actual ~= expected then
|
|
55
|
+
return 0
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
if ARGV[3] == '' then
|
|
59
|
+
redis.call('set', KEYS[2], ARGV[2])
|
|
60
|
+
else
|
|
61
|
+
redis.call('set', KEYS[2], ARGV[2], 'EX', ARGV[3])
|
|
62
|
+
end
|
|
63
|
+
for i = 1, tagCount do
|
|
64
|
+
local tagKey = KEYS[2 + versionCount + i]
|
|
65
|
+
local previousTtl = redis.call('ttl', tagKey)
|
|
66
|
+
redis.call('sadd', tagKey, ARGV[5 + versionCount + i])
|
|
67
|
+
if ARGV[3] == '' then
|
|
68
|
+
redis.call('persist', tagKey)
|
|
69
|
+
elseif previousTtl == -2 or (previousTtl >= 0 and previousTtl < tonumber(ARGV[3])) then
|
|
70
|
+
redis.call('expire', tagKey, ARGV[3])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
return 1`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* {@link DistributedCacheDriver} implementation on top of Redis/Valkey (the
|
|
77
|
+
* native Bun `RedisClient`). Low-level primitives only: the whole cache policy
|
|
78
|
+
* (anti-stampede, tags, limits) lives in the kernel `DistributedCache`.
|
|
79
|
+
*/
|
|
80
|
+
export class RedisDistributedCacheDriver implements DistributedCacheDriver {
|
|
81
|
+
public constructor(private readonly client: RedisCommandClient) {}
|
|
82
|
+
|
|
83
|
+
public read(key: string): Promise<string | null> {
|
|
84
|
+
return this.client.get(key);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async write(key: string, value: string, ttlSeconds?: number): Promise<void> {
|
|
88
|
+
if (ttlSeconds !== undefined) {
|
|
89
|
+
await this.client.set(key, value, "EX", String(ttlSeconds));
|
|
90
|
+
} else {
|
|
91
|
+
await this.client.set(key, value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public delete(keys: readonly string[]): Promise<number> {
|
|
96
|
+
if (keys.length === 0) {
|
|
97
|
+
return Promise.resolve(0);
|
|
98
|
+
}
|
|
99
|
+
return this.client.del(...keys);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async acquireLock(lockKey: string, token: string, ttlSeconds: number): Promise<boolean> {
|
|
103
|
+
const reply = await this.client.set(lockKey, token, "NX", "EX", String(ttlSeconds));
|
|
104
|
+
return reply === "OK";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public async releaseLock(lockKey: string, token: string, entryVersionKey?: string): Promise<void> {
|
|
108
|
+
const keys = entryVersionKey === undefined ? [lockKey] : [lockKey, entryVersionKey];
|
|
109
|
+
await this.client.send("EVAL", [RELEASE_LOCK_SCRIPT, String(keys.length), ...keys, token]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async writeIfLockOwner(request: DistributedCacheFencedWrite): Promise<boolean> {
|
|
113
|
+
const versionKeys = request.versionChecks.map((check) => check.key);
|
|
114
|
+
const tagKeys = request.tags.map((tag) => tag.key);
|
|
115
|
+
const expectedVersions = request.versionChecks.map((check) => check.expected ?? "__BAZIS_MISSING_VERSION__");
|
|
116
|
+
const tagMembers = request.tags.map((tag) => tag.member);
|
|
117
|
+
const keys = [request.lockKey, request.key, ...versionKeys, ...tagKeys];
|
|
118
|
+
const args = [
|
|
119
|
+
request.lockToken,
|
|
120
|
+
request.value,
|
|
121
|
+
request.ttlSeconds === undefined ? "" : String(request.ttlSeconds),
|
|
122
|
+
String(versionKeys.length),
|
|
123
|
+
String(tagKeys.length),
|
|
124
|
+
...expectedVersions,
|
|
125
|
+
...tagMembers,
|
|
126
|
+
];
|
|
127
|
+
const reply = await this.client.send("EVAL", [WRITE_IF_LOCK_OWNER_SCRIPT, String(keys.length), ...keys, ...args]);
|
|
128
|
+
return Number(reply) === 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public async deleteIfValue(key: string, expectedValue: string): Promise<boolean> {
|
|
132
|
+
const reply = await this.client.send("EVAL", [DELETE_IF_VALUE_SCRIPT, "1", key, expectedValue]);
|
|
133
|
+
return Number(reply) === 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async increment(key: string): Promise<number> {
|
|
137
|
+
const reply = await this.client.send("INCR", [key]);
|
|
138
|
+
const value = Number(reply);
|
|
139
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
140
|
+
throw new Error("Redis returned an invalid cache invalidation generation");
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async invalidate(versionKey: string, valueKey: string, lockKey?: string): Promise<boolean> {
|
|
146
|
+
const reply = lockKey === undefined
|
|
147
|
+
? await this.client.send("EVAL", [INVALIDATE_SCRIPT, "2", versionKey, valueKey])
|
|
148
|
+
: await this.client.send(
|
|
149
|
+
"EVAL",
|
|
150
|
+
[INVALIDATE_WHILE_LOCKED_SCRIPT, "3", versionKey, valueKey, lockKey],
|
|
151
|
+
);
|
|
152
|
+
return Number(reply) > 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public async advanceTagGeneration(versionKey: string, pendingKey: string): Promise<number> {
|
|
156
|
+
const reply = await this.client.send("EVAL", [ADVANCE_TAG_GENERATION_SCRIPT, "2", versionKey, pendingKey]);
|
|
157
|
+
const value = Number(reply);
|
|
158
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
159
|
+
throw new Error("Redis returned an invalid tag invalidation generation");
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async addTagMembers(tagKey: string, members: readonly string[], ttlSeconds?: number): Promise<void> {
|
|
165
|
+
if (members.length === 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await this.client.send("EVAL", [
|
|
169
|
+
ADD_TAG_MEMBERS_SCRIPT,
|
|
170
|
+
"1",
|
|
171
|
+
tagKey,
|
|
172
|
+
ttlSeconds === undefined ? "" : String(ttlSeconds),
|
|
173
|
+
...members,
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public async removeTagMembers(tagKey: string, members: readonly string[]): Promise<void> {
|
|
178
|
+
if (members.length === 0) return;
|
|
179
|
+
await this.client.send("SREM", [tagKey, ...members]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async tagMembers(tagKey: string): Promise<readonly string[]> {
|
|
183
|
+
const reply = await this.client.send("SMEMBERS", [tagKey]);
|
|
184
|
+
return Array.isArray(reply) ? (reply as string[]) : [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Distributed cache backend on top of Redis. It implements the `@/core/cache`
|
|
2
|
+
// abstractions (DistributedCacheDriver/Backend), so the cache core stays backend-agnostic.
|
|
3
|
+
export {
|
|
4
|
+
RedisDistributedCacheBackend,
|
|
5
|
+
type RedisDistributedCacheTuning,
|
|
6
|
+
} from "./RedisDistributedCacheBackend";
|
|
7
|
+
export { RedisDistributedCacheDriver, type RedisCommandClient } from "./RedisDistributedCacheDriver";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { InfraError } from "./InfraConnector";
|
|
2
|
+
import type { AppConfig } from "../kernel/config/defineConfig";
|
|
3
|
+
import type { ConfigRegistry } from "../kernel/config/ConfigRegistry";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal dynamic access to a declarative subsystem config.
|
|
7
|
+
*
|
|
8
|
+
* Connectors accept a typed config (`AppConfig<...Shape>`): at the call site
|
|
9
|
+
* this guarantees that the required keys are declared with the right types.
|
|
10
|
+
* Inside, the connector reads them by name; the runtime shape is known from
|
|
11
|
+
* `Shape`, so a narrow cast to this reader is acceptable here.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConfigReader {
|
|
14
|
+
get(key: string): unknown;
|
|
15
|
+
has(key: string): boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Narrow cast of a config to the dynamic reader (the shape is type-checked at the input). */
|
|
19
|
+
export function reader(config: object, configs?: ConfigRegistry): ConfigReader {
|
|
20
|
+
return (configs?.get(config as AppConfig<object>) ?? config) as ConfigReader;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Reads a required non-empty string value or fails with a clear error. */
|
|
24
|
+
export function requireValue(value: unknown, field: string, kind: string): string {
|
|
25
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
26
|
+
throw new InfraError(`Infra connector "${kind}": "${field}" is required and must be a non-empty string.`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HostedService } from "../di";
|
|
2
|
+
import type { InfraConnector } from "./InfraConnector";
|
|
3
|
+
|
|
4
|
+
type ConnectorIdentity = Readonly<{ role: string; fingerprint: readonly unknown[] }>;
|
|
5
|
+
type LifecycleBinding = Readonly<{ connector: InfraConnector; identity?: ConnectorIdentity }>;
|
|
6
|
+
|
|
7
|
+
const identities = new WeakMap<object, ConnectorIdentity>();
|
|
8
|
+
const lifecycles = new WeakMap<object, LifecycleBinding>();
|
|
9
|
+
const values = (c: InfraConnector) => [c.token, c.config, c.phase, c.create, c.connect, c.dispose, c.healthCheck, c.providers, c.exports] as const;
|
|
10
|
+
const unchanged = (c: InfraConnector, identity: ConnectorIdentity) => values(c).every((value, index) => Object.is(value, identity.fingerprint[index]));
|
|
11
|
+
|
|
12
|
+
/** @internal A factory identifies its original connector; copies inherit no identity. */
|
|
13
|
+
export function identifyConnector<T>(connector: InfraConnector<T>, role: string): InfraConnector<T> {
|
|
14
|
+
identities.set(connector, Object.freeze({ role, fingerprint: values(connector) }));
|
|
15
|
+
return connector;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @internal Records resource ownership without interpreting role, token or phase. */
|
|
19
|
+
export function bindConnectorLifecycle(connector: InfraConnector, lifecycle: HostedService): void {
|
|
20
|
+
const identity = identities.get(connector);
|
|
21
|
+
lifecycles.set(lifecycle, Object.freeze({
|
|
22
|
+
connector,
|
|
23
|
+
identity: identity !== undefined && unchanged(connector, identity) ? identity : undefined,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @internal Read-only evidence for the owning integration's plan validator. */
|
|
28
|
+
export function getLifecycleConnector(lifecycle: HostedService): InfraConnector | undefined {
|
|
29
|
+
return lifecycles.get(lifecycle)?.connector;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @internal Both binding-time and current fingerprints must match the original factory result. */
|
|
33
|
+
export function getLifecycleConnectorRole(lifecycle: HostedService): string | undefined {
|
|
34
|
+
const binding = lifecycles.get(lifecycle);
|
|
35
|
+
if (binding?.identity === undefined) return undefined;
|
|
36
|
+
return identities.get(binding.connector) === binding.identity && unchanged(binding.connector, binding.identity)
|
|
37
|
+
? binding.identity.role
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|