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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal in-process fixed-window limiter for WebSocket admission and traffic.
|
|
3
|
+
* Single-node; a clustered deployment would track windows in the adapter.
|
|
4
|
+
*/
|
|
5
|
+
export interface WsRateLimiterOptions {
|
|
6
|
+
readonly maxBuckets?: number;
|
|
7
|
+
readonly purgeIntervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface WsRateLimiterStats {
|
|
11
|
+
readonly buckets: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class WsRateLimiter {
|
|
15
|
+
private readonly buckets = new Map<string, { count: number; resetAt: number }>();
|
|
16
|
+
private readonly maxBuckets: number;
|
|
17
|
+
private readonly purgeIntervalMs: number;
|
|
18
|
+
private nextPurgeAt = 0;
|
|
19
|
+
|
|
20
|
+
public constructor(options: WsRateLimiterOptions = {}) {
|
|
21
|
+
this.maxBuckets = positiveInteger(options.maxBuckets, 10_000);
|
|
22
|
+
this.purgeIntervalMs = nonNegativeInteger(options.purgeIntervalMs, 60_000);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Returns true if the call is allowed; false when the window is exhausted. */
|
|
26
|
+
public check(key: string, limit: number, windowMs: number): boolean {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
if (now >= this.nextPurgeAt || this.buckets.size >= this.maxBuckets) {
|
|
29
|
+
this.purgeExpired(now);
|
|
30
|
+
}
|
|
31
|
+
const bucket = this.buckets.get(key);
|
|
32
|
+
if (!bucket || now >= bucket.resetAt) {
|
|
33
|
+
this.ensureCapacity();
|
|
34
|
+
this.buckets.set(key, { count: 1, resetAt: now + windowMs });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (bucket.count >= limit) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
bucket.count += 1;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Drops a key's window (e.g. on disconnect). */
|
|
45
|
+
public forget(key: string): void {
|
|
46
|
+
this.buckets.delete(key);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public forgetMany(keys: Iterable<string>): void {
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
this.buckets.delete(key);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public clear(): void {
|
|
56
|
+
this.buckets.clear();
|
|
57
|
+
this.nextPurgeAt = 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public purgeExpired(now = Date.now()): number {
|
|
61
|
+
let purged = 0;
|
|
62
|
+
for (const [key, bucket] of this.buckets) {
|
|
63
|
+
if (now >= bucket.resetAt) {
|
|
64
|
+
this.buckets.delete(key);
|
|
65
|
+
purged += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.nextPurgeAt = now + this.purgeIntervalMs;
|
|
69
|
+
return purged;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public getStats(): WsRateLimiterStats {
|
|
73
|
+
this.purgeExpired();
|
|
74
|
+
return { buckets: this.buckets.size };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private ensureCapacity(): void {
|
|
78
|
+
if (this.buckets.size < this.maxBuckets) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let oldestKey: string | undefined;
|
|
82
|
+
let oldestReset = Number.POSITIVE_INFINITY;
|
|
83
|
+
for (const [key, bucket] of this.buckets) {
|
|
84
|
+
if (bucket.resetAt < oldestReset) {
|
|
85
|
+
oldestKey = key;
|
|
86
|
+
oldestReset = bucket.resetAt;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (oldestKey !== undefined) {
|
|
90
|
+
this.buckets.delete(oldestKey);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function positiveInteger(value: number | undefined, fallback: number): number {
|
|
96
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function nonNegativeInteger(value: number | undefined, fallback: number): number {
|
|
100
|
+
return value !== undefined && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
|
|
101
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SessionState } from "../session-manager";
|
|
2
|
+
import type { ReliableRoomDelivery } from "../reliable-delivery";
|
|
3
|
+
|
|
4
|
+
export interface WebSocketAdapterHooks {
|
|
5
|
+
/** Deliver payload to all local subscribers of topic (Bun server.publish). */
|
|
6
|
+
localPublish: (topic: string, payload: Uint8Array) => void;
|
|
7
|
+
/** Advisory wake-up only; persisted mailboxes are also polled. */
|
|
8
|
+
deliveriesReady?: (sids: readonly string[]) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WebSocketAdapter {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly instanceId: string;
|
|
14
|
+
readonly reliableRooms?: ReliableRoomDelivery;
|
|
15
|
+
/** Readiness of the backing store; rejection means unavailable. */
|
|
16
|
+
healthCheck?(): Promise<void>;
|
|
17
|
+
|
|
18
|
+
initialize(hooks: WebSocketAdapterHooks): Promise<void>;
|
|
19
|
+
|
|
20
|
+
/** Fan-out to all nodes; each node calls localPublish for local subscribers. */
|
|
21
|
+
publish(topic: string, payload: Uint8Array): Promise<void>;
|
|
22
|
+
|
|
23
|
+
/** Persist only when `state.revision` is newer than the stored revision. */
|
|
24
|
+
saveSession(state: SessionState): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Optional optimistic claim used by distributed adapters when a physical
|
|
27
|
+
* connection takes ownership of a session. Implementations must only store
|
|
28
|
+
* `state` when the currently persisted revision equals `expectedRevision`.
|
|
29
|
+
* Returning `false` keeps the existing owner. Older adapters remain valid;
|
|
30
|
+
* the runtime falls back to ordered `saveSession` calls on one node.
|
|
31
|
+
*/
|
|
32
|
+
compareAndSwapSession?(state: SessionState, expectedRevision: number): Promise<boolean>;
|
|
33
|
+
loadSession(sid: string): Promise<SessionState | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Deletes a session. Version-aware distributed adapters should retain a
|
|
36
|
+
* tombstone at `revision` so a delayed older save cannot resurrect it.
|
|
37
|
+
*/
|
|
38
|
+
deleteSession(sid: string, revision?: number): Promise<void>;
|
|
39
|
+
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createInstanceId(prefix = "bazis"): string {
|
|
44
|
+
return `${prefix}-${process.pid}-${crypto.randomUUID()}`;
|
|
45
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { SessionCapacityError, type SessionState } from "../session-manager";
|
|
2
|
+
import { localSessionCreationOrder } from "../session-creation";
|
|
3
|
+
import {
|
|
4
|
+
createInstanceId,
|
|
5
|
+
type WebSocketAdapter,
|
|
6
|
+
type WebSocketAdapterHooks,
|
|
7
|
+
} from "./adapter.interface";
|
|
8
|
+
|
|
9
|
+
export interface InMemoryWebSocketAdapterOptions {
|
|
10
|
+
/** Bound for live sessions. Default 10,000. Deletion history has its own cap. */
|
|
11
|
+
readonly maxEntries?: number;
|
|
12
|
+
/** Maximum deletion markers before conservative compaction. Default maxEntries. */
|
|
13
|
+
readonly maxTombstones?: number;
|
|
14
|
+
/** Initial-write/deletion protection window. Default 60 seconds. */
|
|
15
|
+
readonly tombstoneTtlMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class InMemoryWebSocketAdapter implements WebSocketAdapter {
|
|
19
|
+
readonly name = "in-memory";
|
|
20
|
+
readonly instanceId = createInstanceId();
|
|
21
|
+
|
|
22
|
+
private hooks: WebSocketAdapterHooks | null = null;
|
|
23
|
+
private readonly sessions = new Map<string, SessionState>();
|
|
24
|
+
private readonly tombstones = new Map<string, { revision: number; expiresAt: number; createdAt: number; creationToken?: string }>();
|
|
25
|
+
private readonly maxEntries: number;
|
|
26
|
+
private readonly maxTombstones: number;
|
|
27
|
+
private readonly tombstoneTtlMs: number;
|
|
28
|
+
private creationFloor = 0;
|
|
29
|
+
private legacyCreationFloor = Number.NEGATIVE_INFINITY;
|
|
30
|
+
private closed = false;
|
|
31
|
+
|
|
32
|
+
constructor(options: InMemoryWebSocketAdapterOptions = {}) {
|
|
33
|
+
this.maxEntries = positiveInteger(options.maxEntries, 10_000, "maxEntries");
|
|
34
|
+
this.maxTombstones = positiveInteger(options.maxTombstones, this.maxEntries, "maxTombstones");
|
|
35
|
+
this.tombstoneTtlMs = positiveInteger(options.tombstoneTtlMs, 60_000, "tombstoneTtlMs");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async initialize(hooks: WebSocketAdapterHooks): Promise<void> {
|
|
39
|
+
this.closed = false;
|
|
40
|
+
this.hooks = hooks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Single-node: room fan-out is already delivered locally by the originating
|
|
45
|
+
* socket's `ws.publish`, so cross-node publish is a no-op. (`localPublish`
|
|
46
|
+
* exists for real multi-node adapters that receive off a bus.)
|
|
47
|
+
*/
|
|
48
|
+
async publish(_topic: string, _payload: Uint8Array): Promise<void> {
|
|
49
|
+
void this.hooks;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async saveSession(state: SessionState): Promise<void> {
|
|
53
|
+
if (this.closed) return;
|
|
54
|
+
this.purgeTombstones();
|
|
55
|
+
const current = this.sessions.get(state.sid);
|
|
56
|
+
const revision = state.revision ?? 0;
|
|
57
|
+
if (
|
|
58
|
+
(!current && !this.canCreate(state))
|
|
59
|
+
|| (current && (current.createdAt !== state.createdAt || current.creationToken !== state.creationToken))
|
|
60
|
+
|| (current?.revision ?? 0) >= revision
|
|
61
|
+
|| (this.tombstones.get(state.sid)?.revision ?? -1) >= revision
|
|
62
|
+
) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.ensureCapacity(state.sid);
|
|
66
|
+
this.sessions.set(state.sid, structuredClone(state));
|
|
67
|
+
this.tombstones.delete(state.sid);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async compareAndSwapSession(state: SessionState, expectedRevision: number): Promise<boolean> {
|
|
71
|
+
if (this.closed) return false;
|
|
72
|
+
this.purgeTombstones();
|
|
73
|
+
const current = this.sessions.get(state.sid);
|
|
74
|
+
const currentRevision = current?.revision ?? this.tombstones.get(state.sid)?.revision ?? 0;
|
|
75
|
+
if (
|
|
76
|
+
currentRevision !== expectedRevision
|
|
77
|
+
|| (state.revision ?? 0) <= expectedRevision
|
|
78
|
+
|| (!current && !this.canCreate(state))
|
|
79
|
+
|| (current && (current.createdAt !== state.createdAt || current.creationToken !== state.creationToken))
|
|
80
|
+
) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
this.ensureCapacity(state.sid);
|
|
84
|
+
this.sessions.set(state.sid, structuredClone(state));
|
|
85
|
+
this.tombstones.delete(state.sid);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async loadSession(sid: string): Promise<SessionState | null> {
|
|
90
|
+
this.purgeTombstones();
|
|
91
|
+
const state = this.sessions.get(sid);
|
|
92
|
+
return state ? structuredClone(state) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async deleteSession(sid: string, revision?: number): Promise<void> {
|
|
96
|
+
if (this.closed) return;
|
|
97
|
+
this.purgeTombstones();
|
|
98
|
+
const current = this.sessions.get(sid);
|
|
99
|
+
const currentRevision = current?.revision ?? 0;
|
|
100
|
+
if (revision !== undefined && revision < currentRevision) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const previous = this.tombstones.get(sid);
|
|
104
|
+
const tombstoneRevision = Math.max(
|
|
105
|
+
previous?.revision ?? 0,
|
|
106
|
+
revision ?? currentRevision + 1,
|
|
107
|
+
);
|
|
108
|
+
// Once a session's creation window is over, an absent SID cannot be
|
|
109
|
+
// restored by its old snapshots. No per-SID marker is needed beyond it.
|
|
110
|
+
const expiresAt = current
|
|
111
|
+
? current.createdAt + this.tombstoneTtlMs
|
|
112
|
+
: previous?.expiresAt ?? Date.now() + this.tombstoneTtlMs;
|
|
113
|
+
const marker = { revision: tombstoneRevision, expiresAt,
|
|
114
|
+
createdAt: current?.createdAt ?? previous?.createdAt ?? Date.now(),
|
|
115
|
+
creationToken: current?.creationToken ?? previous?.creationToken };
|
|
116
|
+
if (expiresAt <= Date.now()) {
|
|
117
|
+
this.sessions.delete(sid);
|
|
118
|
+
this.tombstones.delete(sid);
|
|
119
|
+
this.compact(marker);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.sessions.delete(sid);
|
|
123
|
+
this.tombstones.set(sid, marker);
|
|
124
|
+
while (this.tombstones.size > this.maxTombstones) {
|
|
125
|
+
const [oldestSid, oldest] = this.tombstones.entries().next().value!;
|
|
126
|
+
this.compact(oldest);
|
|
127
|
+
this.tombstones.delete(oldestSid);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async close(): Promise<void> {
|
|
132
|
+
this.closed = true;
|
|
133
|
+
this.hooks = null;
|
|
134
|
+
for (const state of this.sessions.values()) this.compact(state);
|
|
135
|
+
for (const marker of this.tombstones.values()) this.compact(marker);
|
|
136
|
+
this.sessions.clear();
|
|
137
|
+
this.tombstones.clear();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** DevTools: count of sessions stored in adapter. */
|
|
141
|
+
sessionCount(): number {
|
|
142
|
+
return this.sessions.size;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private canCreate(state: SessionState): boolean {
|
|
146
|
+
// A deleted SID is never reused, including by a snapshot with a larger
|
|
147
|
+
// revision. Once the marker is compacted, the creation floor takes over.
|
|
148
|
+
if (this.tombstones.has(state.sid)) return false;
|
|
149
|
+
const order = localSessionCreationOrder(state.creationToken);
|
|
150
|
+
if (order !== undefined) return order > this.creationFloor;
|
|
151
|
+
return Number.isFinite(state.createdAt)
|
|
152
|
+
&& state.createdAt > this.legacyCreationFloor
|
|
153
|
+
&& state.createdAt > Date.now() - this.tombstoneTtlMs;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private compact(state: { createdAt: number; creationToken?: string }): void {
|
|
157
|
+
const order = localSessionCreationOrder(state.creationToken);
|
|
158
|
+
if (order !== undefined) this.creationFloor = Math.max(this.creationFloor, order);
|
|
159
|
+
this.legacyCreationFloor = Math.max(this.legacyCreationFloor, state.createdAt);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private purgeTombstones(): void {
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
for (const [sid, tombstone] of this.tombstones) {
|
|
165
|
+
if (tombstone.expiresAt <= now) {
|
|
166
|
+
this.compact(tombstone);
|
|
167
|
+
this.tombstones.delete(sid);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private ensureCapacity(sid: string): void {
|
|
173
|
+
if (
|
|
174
|
+
!this.sessions.has(sid)
|
|
175
|
+
&& this.sessions.size >= this.maxEntries
|
|
176
|
+
) {
|
|
177
|
+
throw new SessionCapacityError(this.maxEntries);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function positiveInteger(value: number | undefined, fallback: number, field: string): number {
|
|
183
|
+
if (value === undefined) return fallback;
|
|
184
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
185
|
+
throw new Error(`WebSocket in-memory ${field} must be a positive integer.`);
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const inMemoryWebSocketAdapter = new InMemoryWebSocketAdapter();
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Shared key encoding matches encodeURIComponent used by the session adapter.
|
|
2
|
+
export const KEYS = `
|
|
3
|
+
local function escape(value)
|
|
4
|
+
return (value:gsub("[^%w%-_%.!~*'()]", function(c) return string.format('%%%02X', string.byte(c)) end))
|
|
5
|
+
end
|
|
6
|
+
local function sessionKey(prefix, sid) return prefix .. ':session:' .. escape(sid) end
|
|
7
|
+
local function queueKey(prefix, sid) return prefix .. ':delivery:' .. escape(sid) end
|
|
8
|
+
local function roomKey(prefix, ns, room) return prefix .. ':room:' .. ns .. string.char(0) .. room end
|
|
9
|
+
local clock = redis.call('TIME')
|
|
10
|
+
local now = tonumber(clock[1]) * 1000 + math.floor(tonumber(clock[2]) / 1000)
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
// Called only after a successful session revision/identity check.
|
|
14
|
+
export const UPDATE_ROOMS = `
|
|
15
|
+
local previous = raw and cjson.decode(raw) or nil
|
|
16
|
+
local old = previous and previous.state and cjson.decode(previous.state) or nil
|
|
17
|
+
if old then
|
|
18
|
+
for _, room in ipairs(old.rooms or {}) do redis.call('ZREM', roomKey(ARGV[4], old.namespace, room), old.sid) end
|
|
19
|
+
end
|
|
20
|
+
for _, room in ipairs(next.rooms or {}) do
|
|
21
|
+
local key = roomKey(ARGV[4], next.namespace, room)
|
|
22
|
+
local remaining = redis.call('PTTL', key)
|
|
23
|
+
redis.call('ZADD', key, next.expiresAt, next.sid)
|
|
24
|
+
redis.call('PEXPIREAT', key, math.max(next.expiresAt, now + math.max(0, remaining)))
|
|
25
|
+
end
|
|
26
|
+
redis.call('PEXPIREAT', queueKey(ARGV[4], next.sid), next.expiresAt)
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export const DELETE_ROOMS = `
|
|
30
|
+
if current and current.state then
|
|
31
|
+
local old = cjson.decode(current.state)
|
|
32
|
+
for _, room in ipairs(old.rooms or {}) do redis.call('ZREM', roomKey(ARGV[3], old.namespace, room), old.sid) end
|
|
33
|
+
end
|
|
34
|
+
redis.call('DEL', queueKey(ARGV[3], ARGV[4]))
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export const PUBLISH = KEYS + `
|
|
38
|
+
local prefix = ARGV[1]
|
|
39
|
+
local request = cjson.decode(ARGV[2])
|
|
40
|
+
local limits = cjson.decode(ARGV[3])
|
|
41
|
+
local op = prefix .. ':operation:' .. request.messageId
|
|
42
|
+
local previous = redis.call('GET', op)
|
|
43
|
+
if previous then
|
|
44
|
+
local record = cjson.decode(previous)
|
|
45
|
+
if record.fingerprint ~= ARGV[4] then return {'MESSAGE_ID_CONFLICT'} end
|
|
46
|
+
return {'OK', tostring(record.recipients), '1'}
|
|
47
|
+
end
|
|
48
|
+
if request.expiresAt <= now or request.expiresAt > now + 60000 then return {'EXPIRED_PUBLICATION'} end
|
|
49
|
+
local ops = prefix .. ':operations'
|
|
50
|
+
redis.call('ZREMRANGEBYSCORE', ops, '-inf', now)
|
|
51
|
+
if redis.call('ZCARD', ops) >= limits.maxOperations then return {'OPERATION_CAPACITY'} end
|
|
52
|
+
local room = roomKey(prefix, request.namespace, request.room)
|
|
53
|
+
redis.call('ZREMRANGEBYSCORE', room, '-inf', now)
|
|
54
|
+
local members = redis.call('ZRANGE', room, 0, limits.maxRecipients + 1)
|
|
55
|
+
local recipients = {}
|
|
56
|
+
local packet = ARGV[5]
|
|
57
|
+
local queues = {}
|
|
58
|
+
for _, sid in ipairs(members) do
|
|
59
|
+
if sid ~= request.excludeSid then
|
|
60
|
+
local raw = redis.call('GET', sessionKey(prefix, sid))
|
|
61
|
+
local record = raw and cjson.decode(raw) or nil
|
|
62
|
+
local state = record and record.state and cjson.decode(record.state) or nil
|
|
63
|
+
if state and state.expiresAt > now then
|
|
64
|
+
if state.replayDelivery ~= 'client-ack' then return {'RECIPIENT_REQUIRES_ACK'} end
|
|
65
|
+
table.insert(recipients, sid)
|
|
66
|
+
if #recipients > limits.maxRecipients or #recipients * #packet > limits.maxFanoutBytes then return {'FANOUT_CAPACITY'} end
|
|
67
|
+
local key = queueKey(prefix, sid)
|
|
68
|
+
local items = redis.call('LRANGE', key, 0, limits.maxQueueMessages)
|
|
69
|
+
local bytes = #packet
|
|
70
|
+
for _, item in ipairs(items) do bytes = bytes + #item end
|
|
71
|
+
if #items >= limits.maxQueueMessages or bytes > limits.maxQueueBytes then return {'QUEUE_CAPACITY'} end
|
|
72
|
+
table.insert(queues, { key=key, expiresAt=state.expiresAt })
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
-- All admission checks precede writes. Once submitted, an I/O failure has an
|
|
77
|
+
-- indeterminate outcome; retry the identical operation, never a fresh ID.
|
|
78
|
+
for _, queue in ipairs(queues) do
|
|
79
|
+
redis.call('RPUSH', queue.key, packet)
|
|
80
|
+
redis.call('PEXPIREAT', queue.key, queue.expiresAt)
|
|
81
|
+
end
|
|
82
|
+
redis.call('SET', op, cjson.encode({fingerprint=ARGV[4], recipients=#recipients}), 'PX', request.expiresAt - now)
|
|
83
|
+
redis.call('ZADD', ops, request.expiresAt, request.messageId)
|
|
84
|
+
redis.call('PEXPIRE', ops, 60000)
|
|
85
|
+
if #recipients > 0 then
|
|
86
|
+
redis.call('PUBLISH', prefix .. ':broadcast', cjson.encode({kind='deliveries', sids=recipients}))
|
|
87
|
+
end
|
|
88
|
+
return {'OK', tostring(#recipients), '0'}
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
const OWNER = `
|
|
92
|
+
local function owned(prefix, owner)
|
|
93
|
+
local raw = redis.call('GET', sessionKey(prefix, owner.sid))
|
|
94
|
+
local record = raw and cjson.decode(raw) or nil
|
|
95
|
+
local state = record and record.state and cjson.decode(record.state) or nil
|
|
96
|
+
return state and state.expiresAt > now and state.activeLeaseExpiresAt and state.activeLeaseExpiresAt > now
|
|
97
|
+
and state.ownerInstanceId == owner.ownerInstanceId and state.activeConnId == owner.connId
|
|
98
|
+
end
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
export const READ = KEYS + OWNER + `
|
|
102
|
+
local result = {}
|
|
103
|
+
local bytes = 0
|
|
104
|
+
local limit = tonumber(ARGV[3])
|
|
105
|
+
for _, owner in ipairs(cjson.decode(ARGV[2])) do
|
|
106
|
+
if owned(ARGV[1], owner) then
|
|
107
|
+
local items = redis.call('LRANGE', queueKey(ARGV[1], owner.sid), 0, tonumber(ARGV[4]) - 1)
|
|
108
|
+
local row = {owner.sid}
|
|
109
|
+
local issued = {}
|
|
110
|
+
for _, id in ipairs(owner.issuedIds or {}) do issued[id] = true end
|
|
111
|
+
for _, item in ipairs(items) do
|
|
112
|
+
if not issued[cjson.decode(item).deliveryId] then
|
|
113
|
+
if bytes + #item > limit then break end
|
|
114
|
+
bytes = bytes + #item
|
|
115
|
+
table.insert(row, item)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
table.insert(result, row)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
return result
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
export const ACK = KEYS + OWNER + `
|
|
125
|
+
local owner = cjson.decode(ARGV[2])
|
|
126
|
+
if not owned(ARGV[1], owner) then return -1 end
|
|
127
|
+
local ids = {}
|
|
128
|
+
for _, id in ipairs(cjson.decode(ARGV[3])) do ids[id] = true end
|
|
129
|
+
local key = queueKey(ARGV[1], owner.sid)
|
|
130
|
+
local removed = 0
|
|
131
|
+
for _, item in ipairs(redis.call('LRANGE', key, 0, tonumber(ARGV[4]) - 1)) do
|
|
132
|
+
if ids[cjson.decode(item).deliveryId] then removed = removed + redis.call('LREM', key, 1, item) end
|
|
133
|
+
end
|
|
134
|
+
return removed
|
|
135
|
+
`;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { canonicalJsonHashV1 } from "../../../library/boundary";
|
|
2
|
+
import type { ServerPacket } from "../types";
|
|
3
|
+
import { deliveryUuid, WebSocketDeliveryError, type ReliableRoomDelivery, type ReliableRoomPublication, type ReliableSessionOwner, type ReliableDeliveryBatch, type ReliableBroadcastReceipt } from "../reliable-delivery";
|
|
4
|
+
import { RedisWebSocketOperations } from "./redis-operations";
|
|
5
|
+
import { PUBLISH, READ, ACK } from "./redis-delivery-scripts";
|
|
6
|
+
|
|
7
|
+
export interface RedisReliableDeliveryOptions {
|
|
8
|
+
readonly requireAof?: boolean;
|
|
9
|
+
readonly maxQueueMessages?: number;
|
|
10
|
+
readonly maxQueueBytes?: number;
|
|
11
|
+
readonly maxRecipients?: number;
|
|
12
|
+
readonly maxFanoutBytes?: number;
|
|
13
|
+
readonly maxOperations?: number;
|
|
14
|
+
readonly maxReadBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RedisRoomDeliveryStore implements ReliableRoomDelivery {
|
|
18
|
+
private readonly limits: Required<Omit<RedisReliableDeliveryOptions, "requireAof">>;
|
|
19
|
+
private readonly requireAof: boolean;
|
|
20
|
+
constructor(private readonly operations: Pick<RedisWebSocketOperations, "send">, private readonly prefix: string, options: RedisReliableDeliveryOptions = {}) {
|
|
21
|
+
if (options.requireAof !== undefined && typeof options.requireAof !== "boolean") throw new Error("Invalid Redis reliable requireAof.");
|
|
22
|
+
this.requireAof = options.requireAof ?? true;
|
|
23
|
+
this.limits = { maxQueueMessages: 100, maxQueueBytes: 1024 * 1024, maxRecipients: 1000,
|
|
24
|
+
maxFanoutBytes: 8 * 1024 * 1024, maxOperations: 10_000, maxReadBytes: 1024 * 1024 };
|
|
25
|
+
for (const key of Object.keys(this.limits) as (keyof typeof this.limits)[]) {
|
|
26
|
+
const value = options[key];
|
|
27
|
+
if (value !== undefined) {
|
|
28
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`Invalid Redis reliable ${key}.`);
|
|
29
|
+
this.limits = { ...this.limits, [key]: value };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (this.limits.maxReadBytes < this.limits.maxQueueBytes) throw new Error("Redis reliable maxReadBytes must cover maxQueueBytes.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async initialize(): Promise<void> {
|
|
36
|
+
await this.healthCheck();
|
|
37
|
+
const policy = JSON.stringify({ ...this.limits, requireAof: this.requireAof });
|
|
38
|
+
const key = `${this.prefix}:delivery-policy`;
|
|
39
|
+
await this.operations.send("SET", [key, policy, "NX"]);
|
|
40
|
+
if (await this.operations.send("GET", [key]) !== policy) throw new WebSocketDeliveryError("DELIVERY_POLICY_MISMATCH");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async healthCheck(): Promise<void> {
|
|
44
|
+
if (!this.requireAof) return;
|
|
45
|
+
const settings = await this.operations.send("CONFIG", ["GET", "appendonly", "appendfsync", "no-appendfsync-on-rewrite", "maxmemory-policy"]) as string[] | Record<string, string>;
|
|
46
|
+
const values = new Map<string, string>();
|
|
47
|
+
if (Array.isArray(settings)) for (let i = 0; i < settings.length; i += 2) values.set(settings[i]!, settings[i + 1]!);
|
|
48
|
+
else for (const [key, value] of Object.entries(settings)) values.set(key, value);
|
|
49
|
+
const info = await this.operations.send("INFO", ["persistence"]) as string;
|
|
50
|
+
if (values.get("appendonly") !== "yes" || values.get("appendfsync") !== "always" || values.get("maxmemory-policy") !== "noeviction"
|
|
51
|
+
|| values.get("no-appendfsync-on-rewrite") !== "no"
|
|
52
|
+
|| !info.includes("aof_last_write_status:ok") || !info.includes("loading:0")) throw new WebSocketDeliveryError("DURABILITY_CONFIGURATION");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async publish(request: ReliableRoomPublication): Promise<ReliableBroadcastReceipt> {
|
|
56
|
+
if (!deliveryUuid.test(request.messageId) || !Number.isSafeInteger(request.expiresAt)
|
|
57
|
+
|| typeof request.namespace !== "string" || request.namespace.length > 256 || request.namespace.includes("\0")
|
|
58
|
+
|| typeof request.room !== "string" || !request.room.length || request.room.length > 256 || request.room.includes("\0")
|
|
59
|
+
|| typeof request.excludeSid !== "string" || request.excludeSid.length > 128
|
|
60
|
+
|| request.packet.type !== "event" || typeof request.packet.event !== "string" || !request.packet.event.length
|
|
61
|
+
|| request.packet.event.length > 256) throw new WebSocketDeliveryError("INVALID_PUBLICATION");
|
|
62
|
+
const packet = JSON.stringify({ ...request.packet, v: 1, namespace: request.namespace, deliveryId: request.messageId });
|
|
63
|
+
if (Buffer.byteLength(packet) > this.limits.maxQueueBytes) throw new WebSocketDeliveryError("QUEUE_CAPACITY");
|
|
64
|
+
const metadata = JSON.stringify({ messageId: request.messageId, expiresAt: request.expiresAt,
|
|
65
|
+
namespace: request.namespace, room: request.room, excludeSid: request.excludeSid });
|
|
66
|
+
const fingerprint = canonicalJsonHashV1("bazis.websocket.publication/v1", { metadata: JSON.parse(metadata), packet: JSON.parse(packet) });
|
|
67
|
+
const result = await this.operations.send("EVAL", [PUBLISH, "0", this.prefix, metadata, JSON.stringify(this.limits), fingerprint, packet]) as string[];
|
|
68
|
+
if (result[0] !== "OK") throw new WebSocketDeliveryError(result[0] ?? "STORE_PROTOCOL");
|
|
69
|
+
return { messageId: request.messageId, recipients: Number(result[1]), duplicate: result[2] === "1" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async readPending(owners: readonly ReliableSessionOwner[]): Promise<readonly ReliableDeliveryBatch[]> {
|
|
73
|
+
if (owners.length > 128 || owners.some((owner) => (owner.issuedIds?.length ?? 0) > this.limits.maxQueueMessages)) throw new WebSocketDeliveryError("READ_CAPACITY");
|
|
74
|
+
if (!owners.length) return [];
|
|
75
|
+
const rows = await this.operations.send("EVAL", [READ, "0", this.prefix, JSON.stringify(owners), String(this.limits.maxReadBytes), String(this.limits.maxQueueMessages)]) as string[][];
|
|
76
|
+
return rows.map(([sid, ...packets]) => ({ sid: sid!, packets: packets.map((packet) => JSON.parse(packet) as ServerPacket) }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async acknowledge(owner: ReliableSessionOwner, deliveryIds: readonly string[]): Promise<number> {
|
|
80
|
+
if (!deliveryIds.length) return 0;
|
|
81
|
+
if (deliveryIds.length > this.limits.maxQueueMessages || deliveryIds.some((id) => !deliveryUuid.test(id))) throw new WebSocketDeliveryError("INVALID_ACK");
|
|
82
|
+
const count = Number(await this.operations.send("EVAL", [ACK, "0", this.prefix, JSON.stringify(owner), JSON.stringify(deliveryIds), String(this.limits.maxQueueMessages)]));
|
|
83
|
+
if (count < 0) throw new WebSocketDeliveryError("OWNERSHIP_LOST");
|
|
84
|
+
return count;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RedisClient } from "bun";
|
|
2
|
+
import { WebSocketDeliveryError } from "../reliable-delivery";
|
|
3
|
+
|
|
4
|
+
/** Bounds waiting and native in-flight work without closing the host's client. */
|
|
5
|
+
export class RedisWebSocketOperations {
|
|
6
|
+
private pending = 0;
|
|
7
|
+
private timedOut = 0;
|
|
8
|
+
constructor(private readonly client: RedisClient, private readonly timeoutMs: number, private readonly maxPending: number) {}
|
|
9
|
+
|
|
10
|
+
async send(command: string, args: string[]): Promise<unknown> {
|
|
11
|
+
return this.run(() => this.client.send(command, args));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run(operation: () => Promise<unknown>): Promise<unknown> {
|
|
15
|
+
if (this.timedOut > 0 || this.pending >= this.maxPending) throw new WebSocketDeliveryError("STORE_BUSY");
|
|
16
|
+
this.pending++;
|
|
17
|
+
let expired = false;
|
|
18
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
19
|
+
const native = Promise.resolve().then(operation);
|
|
20
|
+
const settled = native.finally(() => {
|
|
21
|
+
this.pending--;
|
|
22
|
+
if (expired) this.timedOut--;
|
|
23
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
24
|
+
});
|
|
25
|
+
return Promise.race([settled, new Promise<never>((_, reject) => {
|
|
26
|
+
timer = setTimeout(() => {
|
|
27
|
+
expired = true;
|
|
28
|
+
this.timedOut++;
|
|
29
|
+
reject(new WebSocketDeliveryError("STORE_TIMEOUT"));
|
|
30
|
+
}, this.timeoutMs);
|
|
31
|
+
})]);
|
|
32
|
+
}
|
|
33
|
+
}
|