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,33 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** Reject JS truthiness where the caller is constructing an ORM condition. */
|
|
4
|
+
export function analyzeOrmPredicates(checker: ts.TypeChecker, source: ts.SourceFile): readonly string[] {
|
|
5
|
+
const diagnostics: string[] = [];
|
|
6
|
+
const isPredicate = (type: ts.Type): boolean => {
|
|
7
|
+
if (type.isUnionOrIntersection()) return type.types.some(isPredicate);
|
|
8
|
+
const symbol = type.getSymbol();
|
|
9
|
+
return symbol?.name === "Predicate" && (symbol.declarations ?? []).some(declaration =>
|
|
10
|
+
declaration.getSourceFile().fileName.replaceAll("\\", "/").endsWith("/library/orm/Query/conditions.ts"));
|
|
11
|
+
};
|
|
12
|
+
const reject = (node: ts.Node): void => {
|
|
13
|
+
const location = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
14
|
+
diagnostics.push(`BAZIS_ORM_PREDICATE_LOGIC: ${source.fileName}:${location.line + 1}:${location.character + 1}: `
|
|
15
|
+
+ "ORM conditions cannot use JavaScript truthiness. Use .and(), .or(), .not() or an explicit boolean condition.");
|
|
16
|
+
};
|
|
17
|
+
const visit = (node: ts.Node): void => {
|
|
18
|
+
if (ts.isBinaryExpression(node)
|
|
19
|
+
&& (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken)
|
|
20
|
+
&& (isPredicate(checker.getTypeAtLocation(node.left)) || isPredicate(checker.getTypeAtLocation(node.right)))) {
|
|
21
|
+
reject(node);
|
|
22
|
+
} else if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken
|
|
23
|
+
&& isPredicate(checker.getTypeAtLocation(node.operand))) {
|
|
24
|
+
reject(node);
|
|
25
|
+
} else if ((ts.isIfStatement(node) || ts.isConditionalExpression(node) || ts.isWhileStatement(node) || ts.isDoStatement(node))
|
|
26
|
+
&& isPredicate(checker.getTypeAtLocation(ts.isConditionalExpression(node) ? node.condition : node.expression))) {
|
|
27
|
+
reject(node);
|
|
28
|
+
}
|
|
29
|
+
ts.forEachChild(node, visit);
|
|
30
|
+
};
|
|
31
|
+
visit(source);
|
|
32
|
+
return diagnostics;
|
|
33
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export type RequestModelPrimitive = "string" | "number" | "boolean";
|
|
4
|
+
|
|
5
|
+
/** Exactly one of `model` (nested DTO) or `primitive` is set. */
|
|
6
|
+
export interface RequestModelHydrationField {
|
|
7
|
+
readonly property: string;
|
|
8
|
+
readonly model?: ts.ClassDeclaration;
|
|
9
|
+
readonly primitive?: RequestModelPrimitive;
|
|
10
|
+
readonly array: boolean;
|
|
11
|
+
readonly nullable: boolean;
|
|
12
|
+
readonly elementNullable: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RequestModelHydration {
|
|
16
|
+
readonly declarations: readonly ts.ClassDeclaration[];
|
|
17
|
+
readonly fields: ReadonlyMap<ts.ClassDeclaration, readonly RequestModelHydrationField[]>;
|
|
18
|
+
readonly errors: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RequestModelHydrationAnalyzerOptions {
|
|
22
|
+
readonly checker: ts.TypeChecker;
|
|
23
|
+
/** Every class whose transitive request shape should be inspected. */
|
|
24
|
+
readonly roots: readonly ts.ClassDeclaration[];
|
|
25
|
+
/** Roots that must be imported even when they have no nested fields. */
|
|
26
|
+
readonly requiredRoots?: readonly ts.ClassDeclaration[];
|
|
27
|
+
readonly isProjectDeclaration: (declaration: ts.ClassDeclaration) => boolean;
|
|
28
|
+
readonly isExcludedDeclaration: (declaration: ts.ClassDeclaration) => boolean;
|
|
29
|
+
readonly isNamedExportedTopLevelClass: (declaration: ts.ClassDeclaration) => boolean;
|
|
30
|
+
readonly sourcePathForDeclaration: (declaration: ts.ClassDeclaration) => string;
|
|
31
|
+
readonly sourceLocation: (node: ts.Node) => string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface UnwrappedModelType {
|
|
35
|
+
readonly model?: ts.ClassDeclaration;
|
|
36
|
+
readonly array: boolean;
|
|
37
|
+
readonly nullable: boolean;
|
|
38
|
+
readonly elementNullable: boolean;
|
|
39
|
+
readonly ambiguous: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Pure TypeScript-source analyzer for generated nested request DTO hydration.
|
|
44
|
+
* It has no filesystem/global-generator state and is therefore directly
|
|
45
|
+
* regression-testable without introducing app fixtures.
|
|
46
|
+
*/
|
|
47
|
+
export function analyzeRequestModelHydration(
|
|
48
|
+
options: RequestModelHydrationAnalyzerOptions,
|
|
49
|
+
): RequestModelHydration {
|
|
50
|
+
const { checker } = options;
|
|
51
|
+
const queue = [...new Set(options.roots)];
|
|
52
|
+
const visited = new Set<ts.ClassDeclaration>();
|
|
53
|
+
const requiredDeclarations = new Set<ts.ClassDeclaration>(options.requiredRoots ?? []);
|
|
54
|
+
const fields = new Map<ts.ClassDeclaration, RequestModelHydrationField[]>();
|
|
55
|
+
const errors: string[] = [];
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
58
|
+
const owner = queue[index] as ts.ClassDeclaration;
|
|
59
|
+
if (visited.has(owner)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
visited.add(owner);
|
|
63
|
+
|
|
64
|
+
const ownerFields: RequestModelHydrationField[] = [];
|
|
65
|
+
for (const member of owner.members) {
|
|
66
|
+
if (
|
|
67
|
+
!ts.isPropertyDeclaration(member) ||
|
|
68
|
+
!ts.isIdentifier(member.name) ||
|
|
69
|
+
hasModifier(member, ts.SyntaxKind.StaticKeyword) ||
|
|
70
|
+
hasModifier(member, ts.SyntaxKind.PrivateKeyword)
|
|
71
|
+
) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const nested = modelTypeForProperty(checker, member);
|
|
75
|
+
const explicitNested = hasExplicitNestedValidator(member);
|
|
76
|
+
const primitive = nested.model === undefined && !explicitNested ? primitiveTypeForProperty(checker, member) : undefined;
|
|
77
|
+
if (primitive !== undefined) {
|
|
78
|
+
// Primitive checks are an addition: a DTO that cannot be imported by
|
|
79
|
+
// generated code keeps its previous (unchecked) binding instead of failing.
|
|
80
|
+
if (options.isNamedExportedTopLevelClass(owner)) {
|
|
81
|
+
ownerFields.push({ property: member.name.text, ...primitive });
|
|
82
|
+
requiredDeclarations.add(owner);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (nested.model === undefined) {
|
|
87
|
+
if (explicitNested && nested.ambiguous) {
|
|
88
|
+
errors.push(
|
|
89
|
+
`${options.sourceLocation(member)}: @Validator({ nested: true }) field "${member.name.text}" ` +
|
|
90
|
+
`must have one concrete class type or class-array element type so HTTP binding can hydrate it safely.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!options.isProjectDeclaration(nested.model)) {
|
|
96
|
+
if (explicitNested) {
|
|
97
|
+
errors.push(
|
|
98
|
+
`${options.sourceLocation(member)}: nested request model "${nested.model.name?.text ?? "<anonymous>"}" ` +
|
|
99
|
+
`is not declared in project source and cannot be hydrated by Bazis codegen.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (options.isExcludedDeclaration(nested.model)) {
|
|
105
|
+
errors.push(
|
|
106
|
+
`${options.sourceLocation(member)}: nested request model "${nested.model.name?.text ?? "<anonymous>"}" ` +
|
|
107
|
+
`belongs to an excluded source layer and cannot be imported into generated runtime metadata.`,
|
|
108
|
+
);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
ownerFields.push({
|
|
112
|
+
property: member.name.text,
|
|
113
|
+
model: nested.model,
|
|
114
|
+
array: nested.array,
|
|
115
|
+
nullable: nested.nullable,
|
|
116
|
+
elementNullable: nested.elementNullable,
|
|
117
|
+
});
|
|
118
|
+
requiredDeclarations.add(owner);
|
|
119
|
+
requiredDeclarations.add(nested.model);
|
|
120
|
+
queue.push(nested.model);
|
|
121
|
+
}
|
|
122
|
+
if (ownerFields.length > 0) {
|
|
123
|
+
ownerFields.sort((left, right) => left.property.localeCompare(right.property));
|
|
124
|
+
fields.set(owner, ownerFields);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const clause of owner.heritageClauses ?? []) {
|
|
128
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
for (const baseType of clause.types) {
|
|
132
|
+
const base = classDeclarationForExpression(checker, baseType.expression);
|
|
133
|
+
if (base !== undefined && !options.isExcludedDeclaration(base)) {
|
|
134
|
+
queue.push(base);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const declaration of requiredDeclarations) {
|
|
141
|
+
if (!options.isNamedExportedTopLevelClass(declaration)) {
|
|
142
|
+
errors.push(
|
|
143
|
+
`${options.sourceLocation(declaration)}: request model "${declaration.name?.text ?? "<anonymous>"}" must be a named ` +
|
|
144
|
+
`top-level export so generated nested DTO hydration metadata can import it.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const declarations = [...requiredDeclarations].sort((left, right) => {
|
|
150
|
+
const byFile = options.sourcePathForDeclaration(left).localeCompare(options.sourcePathForDeclaration(right));
|
|
151
|
+
return byFile !== 0 ? byFile : (left.name?.text ?? "").localeCompare(right.name?.text ?? "");
|
|
152
|
+
});
|
|
153
|
+
return { declarations, fields, errors };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Renders only the generated `registerRequestModelShape(...)` calls. */
|
|
157
|
+
export function renderRequestModelShapeRegistrations(
|
|
158
|
+
hydration: Pick<RequestModelHydration, "declarations" | "fields">,
|
|
159
|
+
aliasFor: (declaration: ts.ClassDeclaration) => string | undefined,
|
|
160
|
+
): string[] {
|
|
161
|
+
const lines: string[] = [];
|
|
162
|
+
for (const owner of hydration.declarations) {
|
|
163
|
+
const ownerFields = hydration.fields.get(owner);
|
|
164
|
+
const ownerAlias = aliasFor(owner);
|
|
165
|
+
if (ownerFields === undefined || ownerFields.length === 0 || ownerAlias === undefined) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
lines.push(`registerRequestModelShape(${ownerAlias}, {`);
|
|
169
|
+
for (const field of ownerFields) {
|
|
170
|
+
const modelAlias = field.model === undefined ? undefined : aliasFor(field.model);
|
|
171
|
+
if (field.primitive === undefined && modelAlias === undefined) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const values: string[] = [field.primitive === undefined ? `model: ${modelAlias}` : `primitive: ${JSON.stringify(field.primitive)}`];
|
|
175
|
+
if (field.array) {
|
|
176
|
+
values.push("array: true");
|
|
177
|
+
}
|
|
178
|
+
if (field.nullable) {
|
|
179
|
+
values.push("nullable: true");
|
|
180
|
+
}
|
|
181
|
+
if (field.elementNullable) {
|
|
182
|
+
values.push("elementNullable: true");
|
|
183
|
+
}
|
|
184
|
+
lines.push(` ${JSON.stringify(field.property)}: { ${values.join(", ")} },`);
|
|
185
|
+
}
|
|
186
|
+
lines.push("});");
|
|
187
|
+
}
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function modelTypeForProperty(checker: ts.TypeChecker, member: ts.PropertyDeclaration): UnwrappedModelType {
|
|
192
|
+
const outer = unwrapNullableType(checker.getTypeAtLocation(member));
|
|
193
|
+
if (outer.type === undefined) {
|
|
194
|
+
return { array: false, nullable: outer.nullable, elementNullable: false, ambiguous: true };
|
|
195
|
+
}
|
|
196
|
+
if (isArrayModelType(checker, outer.type)) {
|
|
197
|
+
const elementType = checker.getIndexTypeOfType(outer.type, ts.IndexKind.Number);
|
|
198
|
+
if (elementType === undefined) {
|
|
199
|
+
return { array: true, nullable: outer.nullable, elementNullable: false, ambiguous: true };
|
|
200
|
+
}
|
|
201
|
+
const element = unwrapNullableType(elementType);
|
|
202
|
+
const model = element.type === undefined ? undefined : classDeclarationForType(element.type);
|
|
203
|
+
return {
|
|
204
|
+
model,
|
|
205
|
+
array: true,
|
|
206
|
+
nullable: outer.nullable,
|
|
207
|
+
elementNullable: element.nullable,
|
|
208
|
+
ambiguous: element.type === undefined || model === undefined,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const model = classDeclarationForType(outer.type);
|
|
212
|
+
return {
|
|
213
|
+
model,
|
|
214
|
+
array: false,
|
|
215
|
+
nullable: outer.nullable,
|
|
216
|
+
elementNullable: false,
|
|
217
|
+
ambiguous: model === undefined,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* `string`, `number` or `boolean` exactly (literal unions, enums and other
|
|
223
|
+
* types are left unchecked), optionally `| null`, optional, or as array elements.
|
|
224
|
+
*/
|
|
225
|
+
function primitiveTypeForProperty(
|
|
226
|
+
checker: ts.TypeChecker,
|
|
227
|
+
member: ts.PropertyDeclaration,
|
|
228
|
+
): Pick<RequestModelHydrationField, "primitive" | "array" | "nullable" | "elementNullable"> | undefined {
|
|
229
|
+
const outer = splitNullable(checker.getTypeAtLocation(member));
|
|
230
|
+
const single = outer.types.length === 1 ? outer.types[0] : undefined;
|
|
231
|
+
if (single !== undefined && isArrayModelType(checker, single)) {
|
|
232
|
+
const elementType = checker.getIndexTypeOfType(single, ts.IndexKind.Number);
|
|
233
|
+
if (elementType === undefined || checker.isTupleType(single)) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const element = splitNullable(elementType);
|
|
237
|
+
const primitive = primitiveOf(element.types);
|
|
238
|
+
return primitive === undefined ? undefined : { primitive, array: true, nullable: outer.nullable, elementNullable: element.nullable };
|
|
239
|
+
}
|
|
240
|
+
const primitive = primitiveOf(outer.types);
|
|
241
|
+
return primitive === undefined ? undefined : { primitive, array: false, nullable: outer.nullable, elementNullable: false };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function splitNullable(input: ts.Type): { readonly types: readonly ts.Type[]; readonly nullable: boolean } {
|
|
245
|
+
const candidates = input.isUnion() ? input.types : [input];
|
|
246
|
+
const types = candidates.filter((candidate) => (candidate.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) === 0);
|
|
247
|
+
return { types, nullable: candidates.some((candidate) => (candidate.flags & ts.TypeFlags.Null) !== 0) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** `boolean` arrives as the union `true | false`. */
|
|
251
|
+
function primitiveOf(types: readonly ts.Type[]): RequestModelPrimitive | undefined {
|
|
252
|
+
if (types.length === 1 && types[0]!.flags === ts.TypeFlags.String) return "string";
|
|
253
|
+
if (types.length === 1 && types[0]!.flags === ts.TypeFlags.Number) return "number";
|
|
254
|
+
if (types.length === 1 && types[0]!.flags === ts.TypeFlags.Boolean) return "boolean";
|
|
255
|
+
if (types.length === 2 && types.every((type) => (type.flags & ts.TypeFlags.BooleanLiteral) !== 0)) return "boolean";
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function unwrapNullableType(input: ts.Type): { readonly type?: ts.Type; readonly nullable: boolean } {
|
|
260
|
+
const candidates = input.isUnion() ? input.types : [input];
|
|
261
|
+
let nullable = false;
|
|
262
|
+
const concrete: ts.Type[] = [];
|
|
263
|
+
for (const candidate of candidates) {
|
|
264
|
+
if ((candidate.flags & ts.TypeFlags.Null) !== 0) {
|
|
265
|
+
nullable = true;
|
|
266
|
+
} else if ((candidate.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) === 0) {
|
|
267
|
+
concrete.push(candidate);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return concrete.length === 1 ? { type: concrete[0], nullable } : { nullable };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isArrayModelType(checker: ts.TypeChecker, type: ts.Type): boolean {
|
|
274
|
+
if (checker.isArrayType(type) || checker.isTupleType(type)) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
const symbolName = type.getSymbol()?.getName();
|
|
278
|
+
return symbolName === "ReadonlyArray" || symbolName === "Array";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function classDeclarationForType(type: ts.Type): ts.ClassDeclaration | undefined {
|
|
282
|
+
return classDeclarationForSymbol(type.getSymbol() ?? type.aliasSymbol);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function classDeclarationForExpression(
|
|
286
|
+
checker: ts.TypeChecker,
|
|
287
|
+
expression: ts.Expression,
|
|
288
|
+
): ts.ClassDeclaration | undefined {
|
|
289
|
+
let symbol = checker.getSymbolAtLocation(expression);
|
|
290
|
+
if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
291
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
292
|
+
}
|
|
293
|
+
return classDeclarationForSymbol(symbol);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function classDeclarationForSymbol(symbol: ts.Symbol | undefined): ts.ClassDeclaration | undefined {
|
|
297
|
+
return symbol?.declarations?.find(ts.isClassDeclaration);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function hasExplicitNestedValidator(member: ts.PropertyDeclaration): boolean {
|
|
301
|
+
for (const decorator of ts.getDecorators(member) ?? []) {
|
|
302
|
+
const expression = decorator.expression;
|
|
303
|
+
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.expression.text !== "Validator") {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const options = expression.arguments[0];
|
|
307
|
+
if (!options || !ts.isObjectLiteralExpression(options)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
for (const property of options.properties) {
|
|
311
|
+
if (
|
|
312
|
+
ts.isPropertyAssignment(property) &&
|
|
313
|
+
((ts.isIdentifier(property.name) && property.name.text === "nested") ||
|
|
314
|
+
(ts.isStringLiteralLike(property.name) && property.name.text === "nested")) &&
|
|
315
|
+
property.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
316
|
+
) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function hasModifier(node: ts.HasModifiers, kind: ts.SyntaxKind): boolean {
|
|
325
|
+
return ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false;
|
|
326
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# WebSocket Module
|
|
2
|
+
|
|
3
|
+
Status: implemented, single-node by default; optional client-confirmed replay
|
|
4
|
+
and Redis/Valkey adapter. See [MODULE.md](MODULE.md) for the verified scope.
|
|
5
|
+
|
|
6
|
+
WebSocket in bazis shares the HTTP listener and uses native Bun WebSocket. The
|
|
7
|
+
public API is intentionally close to controllers: gateways are classes, message
|
|
8
|
+
handlers are methods, DI creates gateway instances, and the module registers an
|
|
9
|
+
upgrade port for `HttpServer`.
|
|
10
|
+
|
|
11
|
+
## Minimal Gateway
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { SubscribeMessage, WebSocketGateway, type BazisSocket } from "@/core/websocket";
|
|
15
|
+
|
|
16
|
+
@WebSocketGateway({ namespace: "chat" })
|
|
17
|
+
export class ChatGateway {
|
|
18
|
+
@SubscribeMessage("echo")
|
|
19
|
+
echo(_socket: BazisSocket, body: unknown) {
|
|
20
|
+
return { echoed: body };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Register it next to HTTP:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Module } from "@/core/di";
|
|
29
|
+
import { httpModule } from "@/core/http";
|
|
30
|
+
import { websocketModule } from "@/core/websocket";
|
|
31
|
+
|
|
32
|
+
@Module({
|
|
33
|
+
imports: [
|
|
34
|
+
httpModule({ port: 3000 }),
|
|
35
|
+
websocketModule({ gateways: [ChatGateway] }),
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
export class AppModule {}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The gateway above upgrades on `/ws/chat`.
|
|
42
|
+
|
|
43
|
+
## Handler Convention
|
|
44
|
+
|
|
45
|
+
`@SubscribeMessage(event)` uses positional convention:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
handler(socket, body, ack)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Declare only the parameters you need. If the client packet has an `id`, returning
|
|
52
|
+
a value sends an ack. The third argument is an explicit ack callback for deferred
|
|
53
|
+
or error replies.
|
|
54
|
+
|
|
55
|
+
A handler declaring the third argument and returning `undefined` waits for the
|
|
56
|
+
callback within `messageHandlingTimeoutMs`. Other handlers acknowledge their
|
|
57
|
+
return value, including `undefined`. Return and callback share a single reply;
|
|
58
|
+
duplicate or cancelled callbacks are ignored. Without a packet `id`, no reply
|
|
59
|
+
is sent and the dispatcher does not wait for the callback.
|
|
60
|
+
|
|
61
|
+
Parameter decorators such as `@MessageBody` are not part of the current API.
|
|
62
|
+
They can be added later, but the Bun/TC39-compatible path today is positional.
|
|
63
|
+
|
|
64
|
+
## Current Features
|
|
65
|
+
|
|
66
|
+
- Namespaces and default paths (`/ws`, `/ws/chat`)
|
|
67
|
+
- Rooms via `socket.join(room)` and `socket.to(room).emit(...)`
|
|
68
|
+
- Ack frames via return value or explicit `AckCallback`
|
|
69
|
+
- Reconnect by `sid` with room restoration
|
|
70
|
+
- Missed message replay with transport or client acknowledgement
|
|
71
|
+
- Atomic Redis/Valkey session storage and cross-process live pub/sub
|
|
72
|
+
- Per-message rate limit
|
|
73
|
+
- Optional body validation/transform function
|
|
74
|
+
- Upgrade middleware, origin check and token auth hook
|
|
75
|
+
- Pluggable codec and adapter interfaces
|
|
76
|
+
- Bounded in-process maps for sessions, topics and rate-limit buckets
|
|
77
|
+
- Bounded upgrade, per-message and shutdown-drain operations
|
|
78
|
+
|
|
79
|
+
## Wire Protocol
|
|
80
|
+
|
|
81
|
+
Client packets are JSON by default:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
{ "v": 1, "type": "event", "event": "echo", "data": "hi", "id": "1" }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Server packets include `connected`, `reconnected`, `event`, `ack`, `error` and
|
|
88
|
+
`pong`.
|
|
89
|
+
|
|
90
|
+
The selected codec applies to all frames, including acknowledgements and room
|
|
91
|
+
broadcasts. With the default JSON codec, browsers receive text frames; the
|
|
92
|
+
binary codec preserves binary framing for automatic, callback and error replies.
|
|
93
|
+
|
|
94
|
+
Replay uses the actual encoded frame size. A fitting prefix is included in
|
|
95
|
+
`reconnected.missed`; the remaining events follow in FIFO order as ordinary
|
|
96
|
+
packets before queued client messages run. Only a prefix accepted by the
|
|
97
|
+
transport is removed from the session queue. A failed send or an individually
|
|
98
|
+
oversized packet closes the connection and retains the unaccepted remainder.
|
|
99
|
+
Transport acceptance does not prove client receipt. This is the compatible
|
|
100
|
+
default, `replayDelivery: "transport"`.
|
|
101
|
+
|
|
102
|
+
### Client-confirmed replay
|
|
103
|
+
|
|
104
|
+
Opt in with `?replay=client-ack` on each connection, or require it for all clients
|
|
105
|
+
with `websocketModule({ gateways, replayDelivery: "client-ack" })`. The adapter
|
|
106
|
+
must support CAS. A saved client-ack session cannot reconnect in transport mode;
|
|
107
|
+
such an upgrade returns 409. Invalid/duplicate replay query values return 400.
|
|
108
|
+
|
|
109
|
+
In this mode, `connected`/`reconnected` advertise `replayDelivery: "client-ack"`.
|
|
110
|
+
Every queued packet has a stable `deliveryId`; `reconnected.replayCount` counts
|
|
111
|
+
both its `missed` prefix and subsequent standalone replay packets in the current
|
|
112
|
+
bounded batch. The delivery pump continues reading the global mailbox afterward. Process each
|
|
113
|
+
received packet, deduplicate its effect by deliveryId, then confirm received IDs:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
ws.send(JSON.stringify({
|
|
117
|
+
v: 1,
|
|
118
|
+
type: "replay-ack",
|
|
119
|
+
id: "receipt-1",
|
|
120
|
+
data: { deliveryIds: [receivedPacket.deliveryId] },
|
|
121
|
+
}));
|
|
122
|
+
// After persistence: { v: 1, type: "ack", id: "receipt-1",
|
|
123
|
+
// data: { acknowledged: 1 } }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This example uses the JSON codec; binary clients encode the same packet through
|
|
127
|
+
their selected codec. Batch IDs where possible, for example after receiving the
|
|
128
|
+
advertised replayCount. Split batches to respect the namespace payload bound and
|
|
129
|
+
control-frame rate limit. ACK processing waits for replay/open to finish, so do
|
|
130
|
+
not block reading subsequent replay frames while waiting for the ACK response.
|
|
131
|
+
|
|
132
|
+
The server accepts only UUIDs issued to that physical connection. Partial,
|
|
133
|
+
out-of-order acknowledgement is supported; repeating an accepted ID on the same
|
|
134
|
+
connection acknowledges zero additional packets while it remains in the bounded
|
|
135
|
+
recent-ACK window (maxOutboundQueuePerSession for global deliveries). On a new connection, confirm
|
|
136
|
+
only the replay actually received there. Disconnecting without acknowledgement
|
|
137
|
+
replays the same IDs. The correlation `id` is independent of deliveryId.
|
|
138
|
+
|
|
139
|
+
The queue retains unacknowledged packets and rejects new entries when full,
|
|
140
|
+
instead of evicting old ones. Transport prefix-drain cannot consume it. This
|
|
141
|
+
guarantee applies to admitted queued replay within session TTL. Surviving a
|
|
142
|
+
process crash also requires completed persistence (`flushSession`) and surviving
|
|
143
|
+
storage. Live emits/broadcasts are not automatically durable, and exactly-once
|
|
144
|
+
external effects require application-level deduplication.
|
|
145
|
+
|
|
146
|
+
### Redis / Valkey
|
|
147
|
+
|
|
148
|
+
Pass an existing native Bun RedisClient from the host infrastructure:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const adapter = new RedisWebSocketAdapter(hostRedisClient, {
|
|
152
|
+
keyPrefix: "my-app:ws", // same on participating nodes; distinct per application
|
|
153
|
+
});
|
|
154
|
+
const module = websocketModule({ gateways, adapter, replayDelivery: "client-ack" });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The adapter owns only a duplicate connection for pub/sub. Closing the adapter
|
|
158
|
+
does not close hostRedisClient or erase persisted sessions. An optional host-owned
|
|
159
|
+
deliveryClient separates delivery commands onto another connection; initialization
|
|
160
|
+
verifies both clients see the same logical DB using a temporary nonce. Both share
|
|
161
|
+
the same native-operation admission limit and remain open after adapter.close().
|
|
162
|
+
This option does not establish a throughput guarantee. The adapter uses one Redis
|
|
163
|
+
key per SID, Lua CAS/revision checks, a bounded initial-write protection window,
|
|
164
|
+
and TTL. Session JSON remains opaque to Lua so empty arrays/null are preserved.
|
|
165
|
+
Ordinary SessionManager mutations use CAS too; conflicts fail persistence and
|
|
166
|
+
discard the stale local index instead of overwriting a newer owner.
|
|
167
|
+
Activity-only writes are coalesced within one third of the smaller TTL/lease
|
|
168
|
+
interval. A local touch updates lastSeenAt without extending expiry or lease
|
|
169
|
+
until a persisted renewal; expired leases still reject traffic. Room changes,
|
|
170
|
+
explicit SessionManager.updateContext calls and delivery acknowledgements retain
|
|
171
|
+
their persistence boundaries; arbitrary socket.data mutations are not given a
|
|
172
|
+
new automatic persistence contract.
|
|
173
|
+
|
|
174
|
+
Ordinary emit uses live pub/sub. Reliable room publication uses a separate
|
|
175
|
+
persistent mailbox and global room membership for both connected and offline
|
|
176
|
+
sessions. Session CAS atomically updates membership; mailbox publish/ACK do not
|
|
177
|
+
change the session revision. Polling recovers missed pub/sub notifications.
|
|
178
|
+
Empty READ rows are skipped before local session lookup, avoiding one complete
|
|
179
|
+
expiry sweep per idle mailbox. Nonempty delivery still checks the current owner,
|
|
180
|
+
lease and backpressure after the store read.
|
|
181
|
+
|
|
182
|
+
### Reliable room publication
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// Generate these once per logical operation and retain them for retries.
|
|
186
|
+
const options = { messageId: crypto.randomUUID(), expiresAt: Date.now() + 30_000 };
|
|
187
|
+
const receipt = await socket.to("orders").emitReliable("updated", { orderId }, options);
|
|
188
|
+
// receipt = { messageId, recipients, duplicate }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The gateway authorizes room access. Namespace and sender SID come from the
|
|
192
|
+
server. Every recipient must use client-ack. A successful receipt means the
|
|
193
|
+
operation was stored for every recipient selected atomically at publication,
|
|
194
|
+
including offline sessions on other nodes. It does not mean clients processed
|
|
195
|
+
it. Receipt IDs are independent of request correlation IDs.
|
|
196
|
+
|
|
197
|
+
A transport failure or timeout can occur after commit. Retry the same operation
|
|
198
|
+
with exactly the same messageId, expiresAt, room, sender SID and data. The store
|
|
199
|
+
suppresses duplicate publication; a reused ID with different content fails with
|
|
200
|
+
MESSAGE_ID_CONFLICT. JSON property order is immaterial. The acceptance deadline
|
|
201
|
+
must be at most 60 seconds ahead of store time; after expiry, do not retry with
|
|
202
|
+
an extended deadline. Queue retention follows session TTL, not that deadline.
|
|
203
|
+
Clients deduplicate effects by deliveryId and ACK only after successful processing.
|
|
204
|
+
|
|
205
|
+
Queue count/byte, fan-out and operation-history limits reject admission before
|
|
206
|
+
mailbox writes; no unacknowledged packet is evicted. READ/ACK check the physical
|
|
207
|
+
connection owner and live lease. Issued IDs are skipped on subsequent reads;
|
|
208
|
+
reconnect issues any remaining packets again. Per-connection issued/recent ACK
|
|
209
|
+
sets are bounded. Legacy runtime offline overflow now reports
|
|
210
|
+
OFFLINE_QUEUE_CAPACITY instead of hiding truncated delivery behind a successful
|
|
211
|
+
handler ACK. Direct legacy SessionManager enqueue compatibility remains available.
|
|
212
|
+
|
|
213
|
+
Redis strict initialization/readiness requires appendonly=yes,
|
|
214
|
+
appendfsync=always, no-appendfsync-on-rewrite=no, maxmemory-policy=noeviction
|
|
215
|
+
and healthy AOF write status.
|
|
216
|
+
All nodes sharing a prefix must use the same delivery policy. AOF configuration
|
|
217
|
+
is an explicit migration requirement; reliable.requireAof=false disables the
|
|
218
|
+
durability gate and cannot guarantee storage-crash survival. Host credentials
|
|
219
|
+
need CONFIG GET and INFO persistence.
|
|
220
|
+
The application should wire runtime.getHealth() into its readiness endpoint and
|
|
221
|
+
export getStats().delivery or onDiagnostic counters. No application endpoint is
|
|
222
|
+
created by this library change.
|
|
223
|
+
|
|
224
|
+
The adapter bounds native operations and waiting. A timeout does not cancel
|
|
225
|
+
native I/O or free its admission slot; new operations fail STORE_BUSY until the
|
|
226
|
+
old native operation settles. Close owns only the subscriber. The host must use
|
|
227
|
+
bounded connection/retry/offline-queue settings and supervise process restart.
|
|
228
|
+
AOF LOADING is not readiness: keep the application out of traffic until the
|
|
229
|
+
store serves commands, then initialize with bounded retries.
|
|
230
|
+
|
|
231
|
+
Physical evidence and the supported deployment boundary are in the
|
|
232
|
+
[enterprise qualification](https://github.com/asus97178/osnova/blob/33a4513a56abb43a1694e7a6e56373187b928a70/docs/audits/2026-09-14-websocket-enterprise/REPORT.md).
|
|
233
|
+
The atomic boundary is one Redis primary/replication group. Cross-slot Redis
|
|
234
|
+
Cluster, arbitrary automatic replica promotion, WAN and production WSS require
|
|
235
|
+
separate deployment evidence. External exactly-once effects remain application-owned.
|
|
236
|
+
|
|
237
|
+
## Limits
|
|
238
|
+
|
|
239
|
+
- The default adapter is in-memory and single-node.
|
|
240
|
+
- Cross-process pub/sub can use `RedisWebSocketAdapter` or a custom adapter.
|
|
241
|
+
- Handler argument binding is positional, not decorator-based.
|
|
242
|
+
- `websocketModule({ limits })` tunes in-process memory bounds:
|
|
243
|
+
`maxSessions`, `maxRoomsPerSession`, `maxRoomNameLength`, `maxTopics`,
|
|
244
|
+
`maxRateLimitBuckets` and `maxOutboundQueuePerSession`.
|
|
245
|
+
- The complete upgrade, including middleware, authentication and session I/O,
|
|
246
|
+
defaults to a 10 second deadline. Connection initialization after upgrade has
|
|
247
|
+
a separate deadline of the same duration. Message validation/handling defaults
|
|
248
|
+
to 30 seconds, and graceful shutdown drain to 5 seconds.
|
|
249
|
+
Configure `handshakeTimeoutMs`, `messageHandlingTimeoutMs` and
|
|
250
|
+
`shutdownDrainTimeoutMs`; an explicit `0` disables the corresponding deadline.
|
|
251
|
+
- `maxBackpressureBytes` defaults to 1 MiB of native outgoing buffer. Large replay
|
|
252
|
+
waits for native drain within the open/message deadline. A runtime-initiated
|
|
253
|
+
close forcibly terminates an unresponsive peer after `socketCloseTimeoutMs`
|
|
254
|
+
(default 1 second). Both limits must be positive.
|
|
255
|
+
- `maxConcurrentHandshakes` counts unresolved upgrade operations, and
|
|
256
|
+
`maxConcurrentMessageHandlers` counts unresolved validators/handlers. A timeout
|
|
257
|
+
ends the client's wait but retains the slot until actual success or failure.
|
|
258
|
+
New work fails busy while the limit is occupied. Connection initialization
|
|
259
|
+
after upgrade has its own lifetime and is not an upgrade admission slot.
|
|
260
|
+
- `close()` is shared and idempotent, including its failure result. It aborts
|
|
261
|
+
waits and terminates sockets, but disconnect hooks wait for their connection's
|
|
262
|
+
callback, and gateway/adapter cleanup waits for actual work settlement.
|
|
263
|
+
Expiring `shutdownDrainTimeoutMs` rejects close; ordered cleanup continues
|
|
264
|
+
afterwards. A permanently unresolved callback retains its resources until
|
|
265
|
+
process termination. Timeout is not evidence that application effects ended.
|
|
266
|
+
- Middleware reads cancellation from `ctx.signal`; validators may accept a
|
|
267
|
+
second `AbortSignal`, authenticators an optional context, and handlers can use
|
|
268
|
+
`socket.signal`. Existing positional handler/authenticator signatures remain
|
|
269
|
+
valid. A timed-out message fails and closes its physical socket; late acks and
|
|
270
|
+
socket mutations are suppressed.
|
|
271
|
+
- Messages received immediately after browser `open` wait for session
|
|
272
|
+
persistence and `handleConnection`. The pending-message bound still applies
|
|
273
|
+
during initialization. An initialization timeout closes the connection and
|
|
274
|
+
suppresses queued handlers. Cancellation stops waiting and late activation;
|
|
275
|
+
it cannot roll back I/O already started by an arbitrary adapter.
|
|
276
|
+
- Reconnect always enforces the principal stored with the target session,
|
|
277
|
+
including principals established by custom gateway middleware. The optional
|
|
278
|
+
token on a reconnect control packet is additional reauthentication, not the
|
|
279
|
+
switch that enables ownership checks.
|
|
280
|
+
- Room names are validated; empty names, names containing the internal separator
|
|
281
|
+
and names longer than the configured limit are rejected.
|
|
282
|
+
- Expired sessions and rate-limit buckets are purged during normal operations;
|
|
283
|
+
default rate-limit keys are also forgotten on socket close.
|
|
284
|
+
- `InMemoryWebSocketAdapter.maxEntries` caps only live sessions (default 10,000;
|
|
285
|
+
the default runtime adapter uses `limits.maxSessions`). Deletion markers have
|
|
286
|
+
a separate `maxTombstones` cap, defaulting to maxEntries. Older markers compact
|
|
287
|
+
into a creation floor. Immutable runtime creation tokens allow fresh sessions
|
|
288
|
+
in the same millisecond to use freed live capacity while old deleted snapshots
|
|
289
|
+
remain rejected, including higher-revision writes. Existing sessions can still
|
|
290
|
+
be updated. A full live cap raises SessionCapacityError and closes with 1013.
|
|
291
|
+
- Compaction conservatively rejects absent snapshots predating its floor,
|
|
292
|
+
including delayed first writes. Legacy/imported states without a local token
|
|
293
|
+
additionally use immutable createdAt and `tombstoneTtlMs` (default 60 seconds)
|
|
294
|
+
as an initial-write window. Such states do not have the runtime's precise
|
|
295
|
+
same-millisecond ordering. SIDs are not reused for different sessions.
|
|
296
|
+
|
|
297
|
+
The module passport and verification links are in [MODULE.md](MODULE.md).
|