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,134 @@
|
|
|
1
|
+
/** Bounded SSE assembly. Tool arguments are executed only after the complete response is validated. */
|
|
2
|
+
export async function readOpenAiTextStream(
|
|
3
|
+
response: Response, signal: AbortSignal, maxBytes: number, onText: (text: string) => void, allowTools = false,
|
|
4
|
+
): Promise<unknown> {
|
|
5
|
+
const fail = (reason: string): never => { throw new Error(`OpenAI-compatible stream ${reason}.`); };
|
|
6
|
+
const advertised = Number(response.headers.get("content-length"));
|
|
7
|
+
if (advertised > maxBytes) {
|
|
8
|
+
void response.body?.cancel().catch(() => undefined);
|
|
9
|
+
fail("exceeds maxResponseBytes");
|
|
10
|
+
}
|
|
11
|
+
if (!response.body) return fail("body is empty");
|
|
12
|
+
const reader = response.body.getReader();
|
|
13
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
14
|
+
const text: string[] = [];
|
|
15
|
+
const calls = new Map<number, { id: string; type: "function"; function: { name: string; arguments: string } }>();
|
|
16
|
+
let buffer = "", fields: string[] = [], frameChars = 0, total = 0;
|
|
17
|
+
let finish: string | undefined, done = false, usage: unknown;
|
|
18
|
+
const maxFrameChars = Math.min(maxBytes, 1024 * 1024);
|
|
19
|
+
const cancel = () => { void reader.cancel().catch(() => undefined); };
|
|
20
|
+
const dispatch = () => {
|
|
21
|
+
if (!fields.length) return;
|
|
22
|
+
const data = fields.join("\n"); fields = []; frameChars = 0;
|
|
23
|
+
if (data === "[DONE]") {
|
|
24
|
+
if (!finish) fail("ended without a finish reason");
|
|
25
|
+
done = true;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let packet: Record<string, unknown>;
|
|
29
|
+
try {
|
|
30
|
+
const value = JSON.parse(data);
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail("contains an invalid event");
|
|
32
|
+
packet = value;
|
|
33
|
+
} catch { return fail("contains invalid JSON"); }
|
|
34
|
+
if (packet.error) fail("reported a provider error");
|
|
35
|
+
if (packet.usage) usage = packet.usage;
|
|
36
|
+
if (!Array.isArray(packet.choices)) return fail("contains invalid choices");
|
|
37
|
+
for (const choice of packet.choices) {
|
|
38
|
+
if (!choice || choice.index !== 0) fail("contains an unexpected choice");
|
|
39
|
+
const delta = choice.delta;
|
|
40
|
+
if (!delta || typeof delta !== "object" || Array.isArray(delta)
|
|
41
|
+
|| (!allowTools && delta.tool_calls != null) || delta.function_call != null) fail("contains an unsupported delta");
|
|
42
|
+
if (delta.tool_calls != null) {
|
|
43
|
+
if (finish || !Array.isArray(delta.tool_calls) || delta.tool_calls.length > 16) fail("contains invalid tool calls");
|
|
44
|
+
for (const part of delta.tool_calls) {
|
|
45
|
+
if (!part || !Number.isInteger(part.index) || part.index < 0 || part.index >= 16
|
|
46
|
+
|| (part.type !== undefined && part.type !== "function")) fail("contains an invalid tool fragment");
|
|
47
|
+
const call = calls.get(part.index) ?? { id: "", type: "function" as const, function: { name: "", arguments: "" } };
|
|
48
|
+
if (part.id !== undefined) {
|
|
49
|
+
if (typeof part.id !== "string" || !part.id || part.id.length > 256 || (call.id && call.id !== part.id)) fail("contains an invalid tool id");
|
|
50
|
+
call.id = part.id;
|
|
51
|
+
}
|
|
52
|
+
if (part.function !== undefined) {
|
|
53
|
+
if (!part.function || typeof part.function !== "object" || Array.isArray(part.function)) fail("contains an invalid function");
|
|
54
|
+
for (const key of ["name", "arguments"] as const) {
|
|
55
|
+
const value = part.function[key];
|
|
56
|
+
if (value !== undefined) {
|
|
57
|
+
if (typeof value !== "string") fail("contains an invalid function fragment");
|
|
58
|
+
call.function[key] += value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (call.function.name.length > 128 || call.function.arguments.length > maxBytes) fail("tool exceeds its size limit");
|
|
63
|
+
calls.set(part.index, call);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (delta.content != null) {
|
|
67
|
+
if (typeof delta.content !== "string" || finish) fail("contains invalid text");
|
|
68
|
+
if (delta.content) {
|
|
69
|
+
signal.throwIfAborted();
|
|
70
|
+
text.push(delta.content);
|
|
71
|
+
onText(delta.content);
|
|
72
|
+
signal.throwIfAborted();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (choice.finish_reason != null) {
|
|
76
|
+
if (finish || !["stop", "length", "content_filter", ...(allowTools ? ["tool_calls"] : [])].includes(choice.finish_reason)) fail("contains an invalid finish reason");
|
|
77
|
+
finish = choice.finish_reason;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const consume = () => {
|
|
82
|
+
let start = 0;
|
|
83
|
+
for (let index = 0; index < buffer.length; index++) {
|
|
84
|
+
const char = buffer[index];
|
|
85
|
+
if (char !== "\r" && char !== "\n") continue;
|
|
86
|
+
// Preserve a CR at a chunk boundary until its possible LF arrives.
|
|
87
|
+
if (char === "\r" && index + 1 === buffer.length) break;
|
|
88
|
+
const line = buffer.slice(start, index);
|
|
89
|
+
if (char === "\r" && buffer[index + 1] === "\n") index++;
|
|
90
|
+
start = index + 1;
|
|
91
|
+
if (line === "") dispatch();
|
|
92
|
+
else if (line.startsWith("data:")) {
|
|
93
|
+
const value = line.slice(5).replace(/^ /, "");
|
|
94
|
+
frameChars += value.length + 1;
|
|
95
|
+
if (frameChars > maxFrameChars) fail("event exceeds its size limit");
|
|
96
|
+
fields.push(value);
|
|
97
|
+
}
|
|
98
|
+
if (done) break;
|
|
99
|
+
}
|
|
100
|
+
buffer = buffer.slice(start);
|
|
101
|
+
if (!done && buffer.length + frameChars > maxFrameChars) fail("event exceeds its size limit");
|
|
102
|
+
};
|
|
103
|
+
if (signal.aborted) cancel();
|
|
104
|
+
else signal.addEventListener("abort", cancel, { once: true });
|
|
105
|
+
try {
|
|
106
|
+
while (!done) {
|
|
107
|
+
signal.throwIfAborted();
|
|
108
|
+
const item = await reader.read();
|
|
109
|
+
signal.throwIfAborted();
|
|
110
|
+
if (item.done) {
|
|
111
|
+
// CR alone is a valid SSE line ending, including the last byte at EOF.
|
|
112
|
+
if (buffer.endsWith("\r")) { buffer += "\n"; consume(); }
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
total += item.value.byteLength;
|
|
116
|
+
if (total > maxBytes) fail("exceeds maxResponseBytes");
|
|
117
|
+
try { buffer += decoder.decode(item.value, { stream: true }); }
|
|
118
|
+
catch { fail("contains invalid UTF-8"); }
|
|
119
|
+
consume();
|
|
120
|
+
}
|
|
121
|
+
// EOF alone is not success: both a terminal choice and [DONE] are required.
|
|
122
|
+
if (!done) fail("was interrupted before completion");
|
|
123
|
+
const toolCalls = [...calls.entries()].sort(([a], [b]) => a - b).map(([index, call], position) => {
|
|
124
|
+
if (index !== position || !call.id || !call.function.name || !call.function.arguments) fail("contains an incomplete tool call");
|
|
125
|
+
return call;
|
|
126
|
+
});
|
|
127
|
+
if ((toolCalls.length > 0) !== (finish === "tool_calls") || new Set(toolCalls.map(call => call.id)).size !== toolCalls.length) fail("contains inconsistent tool calls");
|
|
128
|
+
return { choices: [{ finish_reason: finish, message: { content: text.join(""), ...(toolCalls.length ? { tool_calls: toolCalls } : {}) } }], usage };
|
|
129
|
+
} finally {
|
|
130
|
+
signal.removeEventListener("abort", cancel);
|
|
131
|
+
cancel(); // Also release a peer that sends [DONE] but never closes its body.
|
|
132
|
+
reader.releaseLock();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createToken, type InjectionToken } from "../../di";
|
|
2
|
+
import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
|
|
3
|
+
import { reader, requireValue } from "../connectorConfig";
|
|
4
|
+
import { InfraError, type InfraConnector } from "../InfraConnector";
|
|
5
|
+
import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
|
|
6
|
+
|
|
7
|
+
/** Default cluster request timeout (ms): fail fast instead of hanging. */
|
|
8
|
+
const DEFAULT_OPENSEARCH_TIMEOUT_MS = 5000;
|
|
9
|
+
const DEFAULT_OPENSEARCH_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
10
|
+
const MAX_ERROR_PREVIEW_CHARS = 4096;
|
|
11
|
+
|
|
12
|
+
export interface OpenSearchClientOptions {
|
|
13
|
+
/** Cluster base URL, e.g. `https://localhost:9200`. */
|
|
14
|
+
readonly url: string;
|
|
15
|
+
/** User (basic auth), optional. */
|
|
16
|
+
readonly username?: string;
|
|
17
|
+
/** Password (basic auth), optional. A revealed secret; never logged. */
|
|
18
|
+
readonly password?: string;
|
|
19
|
+
/** Request timeout in ms (default 5000). Keeps startup from hanging. */
|
|
20
|
+
readonly timeoutMs?: number;
|
|
21
|
+
/** Maximum response body size. Defaults to 1 MiB. */
|
|
22
|
+
readonly maxResponseBytes?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Minimal OpenSearch client on top of `fetch`, with no external dependencies.
|
|
27
|
+
* Covers what an application usually needs: ping/health, search, index,
|
|
28
|
+
* through the OpenSearch REST API.
|
|
29
|
+
*/
|
|
30
|
+
export class OpenSearchClient {
|
|
31
|
+
private readonly lifetime = new AbortController();
|
|
32
|
+
private readonly base: string;
|
|
33
|
+
private readonly headers: Readonly<Record<string, string>>;
|
|
34
|
+
private readonly timeoutMs: number;
|
|
35
|
+
private readonly maxResponseBytes: number;
|
|
36
|
+
|
|
37
|
+
public constructor(options: OpenSearchClientOptions) {
|
|
38
|
+
this.base = normalizeBaseUrl(options.url);
|
|
39
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_OPENSEARCH_TIMEOUT_MS;
|
|
40
|
+
this.maxResponseBytes = options.maxResponseBytes ?? DEFAULT_OPENSEARCH_MAX_RESPONSE_BYTES;
|
|
41
|
+
if (!Number.isSafeInteger(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
42
|
+
throw new InfraError("OpenSearch timeoutMs must be a positive integer.");
|
|
43
|
+
}
|
|
44
|
+
if (!Number.isSafeInteger(this.maxResponseBytes) || this.maxResponseBytes <= 0) {
|
|
45
|
+
throw new InfraError("OpenSearch maxResponseBytes must be a positive integer.");
|
|
46
|
+
}
|
|
47
|
+
// Headers are constant for the client's lifetime, so build them once.
|
|
48
|
+
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
49
|
+
if (options.username !== undefined || options.password !== undefined) {
|
|
50
|
+
const raw = `${options.username ?? ""}:${options.password ?? ""}`;
|
|
51
|
+
headers.authorization = `Basic ${Buffer.from(raw).toString("base64")}`;
|
|
52
|
+
}
|
|
53
|
+
this.headers = headers;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Low-level cluster request. Throws {@link InfraError} on a non-2xx response. */
|
|
57
|
+
public async request<T = unknown>(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<T> {
|
|
58
|
+
const label = `${safeMethod(method)} ${safePath(path)}`;
|
|
59
|
+
let response: Response;
|
|
60
|
+
try {
|
|
61
|
+
this.lifetime.signal.throwIfAborted();
|
|
62
|
+
response = await fetch(`${this.base}${path}`, {
|
|
63
|
+
method,
|
|
64
|
+
headers: this.headers,
|
|
65
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
66
|
+
signal: AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.timeoutMs), ...(signal ? [signal] : [])]),
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const detail = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
70
|
+
throw new InfraError(`OpenSearch ${label} request failed: ${detail}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let text: string;
|
|
74
|
+
try {
|
|
75
|
+
text = await readBoundedText(response, this.maxResponseBytes, label);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof InfraError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
const detail = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
81
|
+
throw new InfraError(`OpenSearch ${label} response read failed: ${detail}`);
|
|
82
|
+
}
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const redacted = redactOpenSearchErrorText(text);
|
|
85
|
+
const preview = redacted.length > MAX_ERROR_PREVIEW_CHARS
|
|
86
|
+
? `${redacted.slice(0, MAX_ERROR_PREVIEW_CHARS)}…`
|
|
87
|
+
: redacted;
|
|
88
|
+
throw new InfraError(
|
|
89
|
+
`OpenSearch ${label} -> ${response.status}: ${preview}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(text) as T;
|
|
94
|
+
} catch {
|
|
95
|
+
throw new InfraError(`OpenSearch ${label} returned invalid JSON.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Cluster information (`GET /`). */
|
|
100
|
+
public info(): Promise<unknown> {
|
|
101
|
+
return this.request("GET", "/");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Abort current requests, including response bodies, and reject subsequent calls. */
|
|
105
|
+
public dispose(): void {
|
|
106
|
+
this.lifetime.abort(new InfraError("OpenSearch client is disposed."));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Cluster health (`GET /_cluster/health`). */
|
|
110
|
+
public clusterHealth(signal?: AbortSignal): Promise<{ status: "green" | "yellow" | "red" }> {
|
|
111
|
+
return this.request("GET", "/_cluster/health", undefined, signal);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Searches an index. */
|
|
115
|
+
public search<T = unknown>(index: string, body: unknown): Promise<T> {
|
|
116
|
+
return this.request("POST", `/${indexSegment(index)}/_search`, body);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Indexes a document (with an explicit id: PUT; without: POST with an auto id). */
|
|
120
|
+
public index<T = unknown>(index: string, document: unknown, id?: string): Promise<T> {
|
|
121
|
+
return id === undefined
|
|
122
|
+
? this.request("POST", `/${indexSegment(index)}/_doc`, document)
|
|
123
|
+
: this.request("PUT", `/${indexSegment(index)}/_doc/${documentIdSegment(id)}`, document);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Healthy only for explicitly accepted cluster states. */
|
|
127
|
+
public async ping(signal?: AbortSignal): Promise<boolean> {
|
|
128
|
+
try {
|
|
129
|
+
const health = await this.clusterHealth(signal);
|
|
130
|
+
return health?.status === "green" || health?.status === "yellow";
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** OpenSearch client token for injection into application services. */
|
|
138
|
+
export const OPENSEARCH: InjectionToken<OpenSearchClient> = createToken<OpenSearchClient>("OpenSearch");
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Config interface required by the OpenSearch connector. The search subsystem
|
|
142
|
+
* config (`defineConfig<SearchConfig>("search", ...)`) must provide these keys.
|
|
143
|
+
*/
|
|
144
|
+
export interface OpenSearchConfigShape {
|
|
145
|
+
/** Cluster base URL, e.g. `https://localhost:9200`. */
|
|
146
|
+
readonly url: string;
|
|
147
|
+
readonly username: string;
|
|
148
|
+
/** Basic-auth password: declared as `secret(...)`, read as a `Secret`. */
|
|
149
|
+
readonly password: Secret;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface OpenSearchConnectorOptions {
|
|
153
|
+
readonly token?: InjectionToken<OpenSearchClient>;
|
|
154
|
+
/** Request timeout in ms (default 5000). */
|
|
155
|
+
readonly timeoutMs?: number;
|
|
156
|
+
/** Maximum response body size. Defaults to 1 MiB. */
|
|
157
|
+
readonly maxResponseBytes?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildOpenSearchOptions<T extends OpenSearchConfigShape>(
|
|
161
|
+
config: AppConfig<T>,
|
|
162
|
+
options: OpenSearchConnectorOptions,
|
|
163
|
+
configs?: ConfigRegistry,
|
|
164
|
+
): OpenSearchClientOptions {
|
|
165
|
+
const c = reader(config, configs);
|
|
166
|
+
return {
|
|
167
|
+
url: requireValue(c.get("url"), "url", "opensearch"),
|
|
168
|
+
username: c.get("username") as string,
|
|
169
|
+
password: (c.get("password") as Secret).reveal(),
|
|
170
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
171
|
+
...(options.maxResponseBytes !== undefined ? { maxResponseBytes: options.maxResponseBytes } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeBaseUrl(raw: string): string {
|
|
176
|
+
let url: URL;
|
|
177
|
+
try {
|
|
178
|
+
url = new URL(raw);
|
|
179
|
+
} catch {
|
|
180
|
+
throw new InfraError("OpenSearch url must be a valid absolute URL.");
|
|
181
|
+
}
|
|
182
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
183
|
+
throw new InfraError("OpenSearch url must use http or https.");
|
|
184
|
+
}
|
|
185
|
+
if (url.username.length > 0 || url.password.length > 0) {
|
|
186
|
+
throw new InfraError("OpenSearch url must not contain embedded credentials; use username/password config fields.");
|
|
187
|
+
}
|
|
188
|
+
if (url.search.length > 0 || url.hash.length > 0) {
|
|
189
|
+
throw new InfraError("OpenSearch url must not contain a query string or fragment.");
|
|
190
|
+
}
|
|
191
|
+
return url.toString().replace(/\/+$/, "");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function indexSegment(index: string): string {
|
|
195
|
+
if (index.trim().length === 0) {
|
|
196
|
+
throw new InfraError("OpenSearch index must be a non-empty string.");
|
|
197
|
+
}
|
|
198
|
+
return encodeURIComponent(index);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function documentIdSegment(id: string): string {
|
|
202
|
+
if (id.trim().length === 0) {
|
|
203
|
+
throw new InfraError("OpenSearch document id must be a non-empty string.");
|
|
204
|
+
}
|
|
205
|
+
return encodeURIComponent(id);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function safeMethod(method: string): string {
|
|
209
|
+
const normalized = method.toUpperCase().replace(/[^A-Z]/g, "");
|
|
210
|
+
return normalized.length === 0 ? "REQUEST" : normalized;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function safePath(path: string): string {
|
|
214
|
+
return redactSensitiveText(path.replace(/[\r\n\t]/g, " ").slice(0, 512));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function redactOpenSearchErrorText(text: string): string {
|
|
218
|
+
try {
|
|
219
|
+
return JSON.stringify(redactSensitive(JSON.parse(text)));
|
|
220
|
+
} catch {
|
|
221
|
+
return redactSensitiveText(text);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function readBoundedText(response: Response, maxBytes: number, label: string): Promise<string> {
|
|
226
|
+
const declared = response.headers.get("content-length");
|
|
227
|
+
if (declared !== null) {
|
|
228
|
+
const length = Number(declared);
|
|
229
|
+
if (Number.isFinite(length) && length > maxBytes) {
|
|
230
|
+
await response.body?.cancel().catch(() => undefined);
|
|
231
|
+
throw new InfraError(`OpenSearch ${label} response exceeded ${maxBytes} bytes.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (response.body === null) {
|
|
235
|
+
return "";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const reader = response.body.getReader();
|
|
239
|
+
const chunks: Uint8Array[] = [];
|
|
240
|
+
let total = 0;
|
|
241
|
+
try {
|
|
242
|
+
for (;;) {
|
|
243
|
+
const { done, value } = await reader.read();
|
|
244
|
+
if (done) {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
total += value.byteLength;
|
|
248
|
+
if (total > maxBytes) {
|
|
249
|
+
await reader.cancel().catch(() => undefined);
|
|
250
|
+
throw new InfraError(`OpenSearch ${label} response exceeded ${maxBytes} bytes.`);
|
|
251
|
+
}
|
|
252
|
+
chunks.push(value);
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
reader.releaseLock();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const bytes = new Uint8Array(total);
|
|
259
|
+
let offset = 0;
|
|
260
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
261
|
+
const chunk = chunks[index]!;
|
|
262
|
+
bytes.set(chunk, offset);
|
|
263
|
+
offset += chunk.byteLength;
|
|
264
|
+
}
|
|
265
|
+
return new TextDecoder().decode(bytes);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* OpenSearch connector for the `@Infra` manifest. Configuration comes from the
|
|
270
|
+
* given `searchConfig` (`defineConfig("search", ...)`): the connector reads the
|
|
271
|
+
* declared `url`/`username`/`password` keys. The client is a fetch wrapper, so
|
|
272
|
+
* "opening the connection" is a cluster ping at start (fail fast if the cluster
|
|
273
|
+
* is unreachable). Closing aborts active requests and rejects new ones.
|
|
274
|
+
*
|
|
275
|
+
* ```ts
|
|
276
|
+
* export const searchConfig = defineConfig("search", {
|
|
277
|
+
* default: { url: "https://localhost:9200", username: "admin", password: secret("dev") },
|
|
278
|
+
* });
|
|
279
|
+
* @Infra({ search: openSearchConnect(searchConfig) })
|
|
280
|
+
* export class AppInfra {}
|
|
281
|
+
* // injection by token: scoped(ISearchService, SearchService, [OPENSEARCH] as const)
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
export function openSearchConnect<T extends OpenSearchConfigShape>(
|
|
285
|
+
config: AppConfig<T>,
|
|
286
|
+
options: OpenSearchConnectorOptions = {},
|
|
287
|
+
): InfraConnector<OpenSearchClient> {
|
|
288
|
+
return {
|
|
289
|
+
token: options.token ?? OPENSEARCH,
|
|
290
|
+
config,
|
|
291
|
+
create(configs) {
|
|
292
|
+
return new OpenSearchClient(buildOpenSearchOptions(config, options, configs));
|
|
293
|
+
},
|
|
294
|
+
async connect(client, signal) {
|
|
295
|
+
const alive = await client.ping(signal);
|
|
296
|
+
if (!alive) {
|
|
297
|
+
throw new InfraError(`Infra connector "opensearch": cluster is not reachable or unhealthy.`);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
dispose(client) {
|
|
301
|
+
client.dispose();
|
|
302
|
+
},
|
|
303
|
+
healthCheck(client, signal) {
|
|
304
|
+
return client.ping(signal);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { SQL } from "bun";
|
|
2
|
+
import { createToken, type InjectionToken } from "../../di";
|
|
3
|
+
import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
|
|
4
|
+
import { reader, requireValue } from "../connectorConfig";
|
|
5
|
+
import { errorMessage, InfraError, type InfraConnector } from "../InfraConnector";
|
|
6
|
+
|
|
7
|
+
/** PostgreSQL client (native Bun `SQL`, no external dependencies). */
|
|
8
|
+
export const POSTGRES: InjectionToken<SQL> = createToken<SQL>("Postgres");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Config interface required by the PostgreSQL connector. The database subsystem
|
|
12
|
+
* config (`defineConfig<DbConfig>("db", ...)`) must provide these keys.
|
|
13
|
+
*/
|
|
14
|
+
export interface PostgresConfigShape {
|
|
15
|
+
readonly host: string;
|
|
16
|
+
readonly port: number;
|
|
17
|
+
readonly database: string;
|
|
18
|
+
readonly username: string;
|
|
19
|
+
readonly password: Secret;
|
|
20
|
+
readonly tls?: PostgresSslMode;
|
|
21
|
+
readonly max?: number;
|
|
22
|
+
readonly connectionTimeout?: number;
|
|
23
|
+
readonly idleTimeout?: number;
|
|
24
|
+
readonly maxLifetime?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type PostgresSslMode = "disable" | "allow" | "prefer" | "require" | "verify-ca" | "verify-full";
|
|
28
|
+
export interface PostgresConnectorOptions {
|
|
29
|
+
readonly token?: InjectionToken<SQL>;
|
|
30
|
+
/** Maximum pool drain before closing connections. Default 1000ms; 0 closes immediately. */
|
|
31
|
+
readonly shutdownTimeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Shared raw SQL/ORM options. Driver timeout fields are in seconds. */
|
|
35
|
+
export function postgresConnectionOptions(config: AppConfig<PostgresConfigShape>, configs?: ConfigRegistry): Bun.SQL.Options {
|
|
36
|
+
const c = reader(config, configs);
|
|
37
|
+
const integer = (key: string, min: number, max: number): number | undefined => {
|
|
38
|
+
const value = c.has(key) ? c.get(key) : undefined;
|
|
39
|
+
if (value === undefined) return undefined;
|
|
40
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) throw new InfraError(`postgres ${key} must be an integer ${min}..${max}`);
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
const tls = c.has("tls") ? c.get("tls") : undefined;
|
|
44
|
+
if (tls !== undefined && !["disable", "allow", "prefer", "require", "verify-ca", "verify-full"].includes(String(tls))) throw new InfraError("postgres tls is invalid");
|
|
45
|
+
return {
|
|
46
|
+
hostname: requireValue(c.get("host"), "host", "postgres"),
|
|
47
|
+
port: integer("port", 1, 65535),
|
|
48
|
+
database: requireValue(c.get("database"), "database", "postgres"),
|
|
49
|
+
username: requireValue(c.get("username"), "username", "postgres"),
|
|
50
|
+
password: (c.get("password") as Secret).reveal(),
|
|
51
|
+
...(tls === undefined ? {} : { tls: tls as PostgresSslMode }),
|
|
52
|
+
max: integer("max", 1, 2147483647),
|
|
53
|
+
connectionTimeout: integer("connectionTimeout", 1, 2147483647),
|
|
54
|
+
idleTimeout: integer("idleTimeout", 0, 2147483647),
|
|
55
|
+
maxLifetime: integer("maxLifetime", 0, 2147483647),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PostgreSQL connector for the `@Infra` manifest. The whole configuration comes
|
|
61
|
+
* from the given `dbConfig` object (`defineConfig("db", ...)`): the connector
|
|
62
|
+
* reads the declared keys (`host`/`port`/`database`/`username`/`password`)
|
|
63
|
+
* itself, with no string keys in the manifest. Opens the connection at start
|
|
64
|
+
* (`SELECT 1`) and closes it at shutdown.
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* export const dbConfig = defineConfig("db", {
|
|
68
|
+
* default: { host: "localhost", port: 5432, database: "app", username: "postgres", password: secret("dev") },
|
|
69
|
+
* });
|
|
70
|
+
* @Infra({ db: postgres(dbConfig) })
|
|
71
|
+
* export class AppInfra {}
|
|
72
|
+
* // injection by token: scoped(IReportsService, ReportsService, [POSTGRES] as const)
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function postgres<T extends PostgresConfigShape>(config: AppConfig<T>, options: PostgresConnectorOptions = {}): InfraConnector<SQL> {
|
|
76
|
+
const shutdownTimeoutMs = options.shutdownTimeoutMs === undefined ? 1000 : options.shutdownTimeoutMs;
|
|
77
|
+
if (!Number.isSafeInteger(shutdownTimeoutMs) || shutdownTimeoutMs < 0 || shutdownTimeoutMs > 2147483647) {
|
|
78
|
+
throw new InfraError("postgres shutdownTimeoutMs must be an integer 0..2147483647");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
token: options.token ?? POSTGRES,
|
|
82
|
+
config,
|
|
83
|
+
create(configs) {
|
|
84
|
+
return new SQL(postgresConnectionOptions(config, configs));
|
|
85
|
+
},
|
|
86
|
+
async connect(client) {
|
|
87
|
+
try {
|
|
88
|
+
await client.unsafe("SELECT 1");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new InfraError(`Infra connector "postgres": failed to connect — ${errorMessage(error)}`);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async dispose(client) {
|
|
94
|
+
await client.close({ timeout: shutdownTimeoutMs / 1000 });
|
|
95
|
+
},
|
|
96
|
+
async healthCheck(client) {
|
|
97
|
+
try {
|
|
98
|
+
await client.unsafe("SELECT 1");
|
|
99
|
+
return true;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { RedisClient } from "bun";
|
|
2
|
+
import {
|
|
3
|
+
DISTRIBUTED_CACHE_BACKEND,
|
|
4
|
+
DISTRIBUTED_OUTPUT_CACHE,
|
|
5
|
+
DISTRIBUTED_SERVICE_CACHE,
|
|
6
|
+
type DistributedCacheStores,
|
|
7
|
+
} from "../../cache";
|
|
8
|
+
import { createToken, DI, type InjectionToken, type ProviderDefinition } from "../../di";
|
|
9
|
+
import type { AppConfig, Secret } from "../../kernel";
|
|
10
|
+
import { RedisDistributedCacheBackend, type RedisDistributedCacheTuning } from "../cache/RedisDistributedCacheBackend";
|
|
11
|
+
import { reader, requireValue } from "../connectorConfig";
|
|
12
|
+
import { errorMessage, InfraError, type InfraConnector } from "../InfraConnector";
|
|
13
|
+
|
|
14
|
+
/** Redis/Valkey client (native Bun `RedisClient`, no external dependencies). */
|
|
15
|
+
export const REDIS: InjectionToken<RedisClient> = createToken<RedisClient>("Redis");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Config interface required by the Redis connector. The subsystem config
|
|
19
|
+
* (`defineConfig<RedisConfig>("redis", ...)`) must provide these keys.
|
|
20
|
+
*/
|
|
21
|
+
export interface RedisConfigShape {
|
|
22
|
+
/** Full connection string, e.g. `redis://localhost:6379`. */
|
|
23
|
+
readonly url: string | Secret;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type RedisConnectorCacheMode =
|
|
27
|
+
| "distributed"
|
|
28
|
+
| ({ readonly mode: "distributed" } & RedisDistributedCacheTuning);
|
|
29
|
+
|
|
30
|
+
export interface RedisConnectorOptions {
|
|
31
|
+
readonly token?: InjectionToken<RedisClient>;
|
|
32
|
+
/**
|
|
33
|
+
* Publishes a distributed cache backend on top of the client: `{ cache: "distributed" }`.
|
|
34
|
+
* For fine tuning use `{ cache: { mode: "distributed", keyPrefix, ... } }`.
|
|
35
|
+
* An application may have one backend; a second one is rejected when the container is built.
|
|
36
|
+
*/
|
|
37
|
+
readonly cache?: RedisConnectorCacheMode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Distributed cache providers on top of the Redis client: the backend plus two
|
|
42
|
+
* registries (HTTP responses and method values). The cache module picks them up from DI.
|
|
43
|
+
*/
|
|
44
|
+
function distributedCacheProviders(tuning: RedisDistributedCacheTuning, token: InjectionToken<RedisClient>): ProviderDefinition[] {
|
|
45
|
+
return [
|
|
46
|
+
DI.singleton(
|
|
47
|
+
DI.factoryProvider(
|
|
48
|
+
DISTRIBUTED_CACHE_BACKEND,
|
|
49
|
+
[token],
|
|
50
|
+
(client: RedisClient) => new RedisDistributedCacheBackend(client, tuning),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
DI.singleton(
|
|
54
|
+
DI.factoryProvider(DISTRIBUTED_OUTPUT_CACHE, [DISTRIBUTED_CACHE_BACKEND], (backend: DistributedCacheStores) =>
|
|
55
|
+
backend.outputCache,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
DI.singleton(
|
|
59
|
+
DI.factoryProvider(DISTRIBUTED_SERVICE_CACHE, [DISTRIBUTED_CACHE_BACKEND], (backend: DistributedCacheStores) =>
|
|
60
|
+
backend.serviceCache,
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Redis connector for the `@Infra` manifest. Configuration comes from the given
|
|
68
|
+
* `redisConfig` (`defineConfig("redis", ...)`): the connector reads the declared
|
|
69
|
+
* `url` key. Opens the connection at start and closes it at shutdown. The second
|
|
70
|
+
* argument takes connector options (distributed cache and so on).
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* export const redisConfig = defineConfig("redis", { default: { url: "redis://localhost:6379" } });
|
|
74
|
+
* @Infra({ cache: redisConnect(redisConfig, { cache: "distributed" }) })
|
|
75
|
+
* export class AppInfra {}
|
|
76
|
+
* // injection by token: scoped(ISessionsService, SessionsService, [REDIS] as const)
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function redisConnect<T extends RedisConfigShape>(
|
|
80
|
+
config: AppConfig<T>,
|
|
81
|
+
options: RedisConnectorOptions = {},
|
|
82
|
+
): InfraConnector<RedisClient> {
|
|
83
|
+
const cache = options.cache;
|
|
84
|
+
const token = options.token ?? REDIS;
|
|
85
|
+
const cacheProviders =
|
|
86
|
+
cache === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: distributedCacheProviders(cache === "distributed" ? {} : withoutMode(cache), token);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
token,
|
|
92
|
+
config,
|
|
93
|
+
providers: cacheProviders,
|
|
94
|
+
exports: cacheProviders ? [DISTRIBUTED_CACHE_BACKEND, DISTRIBUTED_OUTPUT_CACHE, DISTRIBUTED_SERVICE_CACHE] : undefined,
|
|
95
|
+
create(configs) {
|
|
96
|
+
return new RedisClient(redisUrl(reader(config, configs).get("url")));
|
|
97
|
+
},
|
|
98
|
+
async connect(client) {
|
|
99
|
+
try {
|
|
100
|
+
await client.connect();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new InfraError(`Infra connector "redis": failed to connect — ${errorMessage(error)}`);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
dispose(client) {
|
|
106
|
+
client.close();
|
|
107
|
+
},
|
|
108
|
+
async healthCheck(client) {
|
|
109
|
+
try {
|
|
110
|
+
return (await client.ping()) === "PONG";
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function redisUrl(value: unknown): string {
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
return requireValue(value, "url", "redis");
|
|
121
|
+
}
|
|
122
|
+
const reveal = (value as { readonly reveal?: unknown } | null)?.reveal;
|
|
123
|
+
if (typeof reveal === "function") {
|
|
124
|
+
return requireValue(reveal.call(value), "url", "redis");
|
|
125
|
+
}
|
|
126
|
+
throw new InfraError('Infra connector "redis": "url" must be a string or Secret.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function withoutMode(cache: { readonly mode: "distributed" } & RedisDistributedCacheTuning): RedisDistributedCacheTuning {
|
|
130
|
+
const { mode: _mode, ...tuning } = cache;
|
|
131
|
+
return tuning;
|
|
132
|
+
}
|