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,218 @@
|
|
|
1
|
+
import type { RedisClient } from "bun";
|
|
2
|
+
import type { SessionState } from "../session-manager";
|
|
3
|
+
import { createInstanceId, type WebSocketAdapter, type WebSocketAdapterHooks } from "./adapter.interface";
|
|
4
|
+
import { RedisWebSocketOperations } from "./redis-operations";
|
|
5
|
+
import { RedisRoomDeliveryStore, type RedisReliableDeliveryOptions } from "./redis-delivery-store";
|
|
6
|
+
import { KEYS, UPDATE_ROOMS, DELETE_ROOMS } from "./redis-delivery-scripts";
|
|
7
|
+
|
|
8
|
+
export interface RedisWebSocketAdapterOptions {
|
|
9
|
+
/** Optional host-owned command client isolating delivery from session I/O; same logical store required. */
|
|
10
|
+
readonly deliveryClient?: RedisClient;
|
|
11
|
+
/** Shared by the nodes of one application; isolate unrelated applications. */
|
|
12
|
+
readonly keyPrefix?: string;
|
|
13
|
+
/** Initial-write/deletion protection window, milliseconds. Default 60,000. */
|
|
14
|
+
readonly writeProtectionMs?: number;
|
|
15
|
+
/** Maximum encoded session record. Default 8 MiB. */
|
|
16
|
+
readonly maxSessionBytes?: number;
|
|
17
|
+
/** Maximum pub/sub packet bytes. Default 1 MiB. */
|
|
18
|
+
readonly maxPublishBytes?: number;
|
|
19
|
+
readonly operationTimeoutMs?: number;
|
|
20
|
+
readonly maxPendingOperations?: number;
|
|
21
|
+
readonly reliable?: RedisReliableDeliveryOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// One Redis key per SID. The record survives until both reconnect TTL and the
|
|
25
|
+
// initial-write window have elapsed. Expiring a key can therefore never make an
|
|
26
|
+
// old snapshot eligible for an initial write again. All revision checks and
|
|
27
|
+
// writes execute atomically in the same Lua invocation.
|
|
28
|
+
const SAVE = KEYS + `
|
|
29
|
+
local raw = redis.call('GET', KEYS[1])
|
|
30
|
+
local next = cjson.decode(ARGV[1])
|
|
31
|
+
local expected = tonumber(ARGV[2])
|
|
32
|
+
local window = tonumber(ARGV[3])
|
|
33
|
+
local clock = redis.call('TIME')
|
|
34
|
+
local now = tonumber(clock[1]) * 1000 + math.floor(tonumber(clock[2]) / 1000)
|
|
35
|
+
if raw then
|
|
36
|
+
local current = cjson.decode(raw)
|
|
37
|
+
if current.deleted or next.revision <= current.revision then return 0 end
|
|
38
|
+
if expected >= 0 and expected ~= current.revision then return 0 end
|
|
39
|
+
if next.createdAt ~= current.createdAt or next.creationToken ~= current.creationToken then return 0 end
|
|
40
|
+
else
|
|
41
|
+
if expected > 0 or next.revision ~= 1 then return 0 end
|
|
42
|
+
if next.createdAt <= now - window or next.createdAt > now + 1000 then return 0 end
|
|
43
|
+
end
|
|
44
|
+
local ttl = math.ceil(math.max(next.expiresAt, next.createdAt + window) - now)
|
|
45
|
+
if ttl <= 0 then return 0 end
|
|
46
|
+
${UPDATE_ROOMS}
|
|
47
|
+
redis.call('SET', KEYS[1], cjson.encode({revision=next.revision, createdAt=next.createdAt, creationToken=next.creationToken, state=ARGV[1]}), 'PX', ttl)
|
|
48
|
+
return 1
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const DELETE = KEYS + `
|
|
52
|
+
local raw = redis.call('GET', KEYS[1])
|
|
53
|
+
local current = raw and cjson.decode(raw) or nil
|
|
54
|
+
local revision = tonumber(ARGV[1])
|
|
55
|
+
local clock = redis.call('TIME')
|
|
56
|
+
local now = tonumber(clock[1]) * 1000 + math.floor(tonumber(clock[2]) / 1000)
|
|
57
|
+
if current and revision >= 0 and revision < current.revision then return 0 end
|
|
58
|
+
if revision < 0 then revision = current and current.revision + 1 or 1 end
|
|
59
|
+
${DELETE_ROOMS}
|
|
60
|
+
local created = current and current.createdAt or now
|
|
61
|
+
local ttl = math.ceil(created + tonumber(ARGV[2]) - now)
|
|
62
|
+
if ttl <= 0 then redis.call('DEL', KEYS[1]); return 1 end
|
|
63
|
+
redis.call('SET', KEYS[1], cjson.encode({revision=revision, createdAt=created, deleted=true}), 'PX', ttl)
|
|
64
|
+
return 1
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Redis/Valkey transport and atomic session storage using the host's native Bun
|
|
69
|
+
* RedisClient. The adapter owns only a duplicate required by Redis pub/sub mode;
|
|
70
|
+
* it never closes the supplied command client or clears application keys.
|
|
71
|
+
*/
|
|
72
|
+
export class RedisWebSocketAdapter implements WebSocketAdapter {
|
|
73
|
+
readonly name = "redis";
|
|
74
|
+
readonly instanceId = createInstanceId("ws-redis");
|
|
75
|
+
readonly reliableRooms: RedisRoomDeliveryStore;
|
|
76
|
+
private readonly operations: RedisWebSocketOperations;
|
|
77
|
+
private readonly prefix: string;
|
|
78
|
+
private readonly channel: string;
|
|
79
|
+
private readonly writeProtectionMs: number;
|
|
80
|
+
private readonly maxSessionBytes: number;
|
|
81
|
+
private readonly maxPublishBytes: number;
|
|
82
|
+
private readonly deliveryClient: RedisClient;
|
|
83
|
+
private subscriber?: RedisClient;
|
|
84
|
+
private hooks?: WebSocketAdapterHooks;
|
|
85
|
+
|
|
86
|
+
constructor(private readonly client: RedisClient, options: RedisWebSocketAdapterOptions = {}) {
|
|
87
|
+
if (options.deliveryClient !== undefined && (!options.deliveryClient || typeof options.deliveryClient.send !== "function")) throw new Error("Invalid WebSocket deliveryClient.");
|
|
88
|
+
this.deliveryClient = options.deliveryClient ?? client;
|
|
89
|
+
this.prefix = options.keyPrefix ?? "bazis:ws";
|
|
90
|
+
if (!/^[a-zA-Z0-9:_-]{1,64}$/.test(this.prefix)) throw new Error("Invalid WebSocket Redis keyPrefix.");
|
|
91
|
+
this.channel = `${this.prefix}:broadcast`;
|
|
92
|
+
this.writeProtectionMs = positive(options.writeProtectionMs, 60_000, "writeProtectionMs");
|
|
93
|
+
this.maxSessionBytes = positive(options.maxSessionBytes, 8 * 1024 * 1024, "maxSessionBytes");
|
|
94
|
+
this.maxPublishBytes = positive(options.maxPublishBytes, 1024 * 1024, "maxPublishBytes");
|
|
95
|
+
this.operations = new RedisWebSocketOperations(client,
|
|
96
|
+
positive(options.operationTimeoutMs, 2000, "operationTimeoutMs"),
|
|
97
|
+
positive(options.maxPendingOperations, 64, "maxPendingOperations"));
|
|
98
|
+
this.reliableRooms = new RedisRoomDeliveryStore({
|
|
99
|
+
send: (command, args) => this.operations.run(() => this.deliveryClient.send(command, args)),
|
|
100
|
+
}, this.prefix, options.reliable);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async initialize(hooks: WebSocketAdapterHooks): Promise<void> {
|
|
104
|
+
if (this.subscriber) throw new Error("WebSocket Redis adapter is already initialized.");
|
|
105
|
+
if (this.deliveryClient !== this.client) {
|
|
106
|
+
const key = `${this.prefix}:connection-check:${this.instanceId}`;
|
|
107
|
+
const nonce = crypto.randomUUID();
|
|
108
|
+
await this.operations.send("SET", [key, nonce, "PX", "60000"]);
|
|
109
|
+
try {
|
|
110
|
+
if (await this.operations.run(() => this.deliveryClient.send("GET", [key])) !== nonce) throw new Error("WebSocket DELIVERY_STORE_MISMATCH.");
|
|
111
|
+
} finally { await this.operations.send("DEL", [key]); }
|
|
112
|
+
}
|
|
113
|
+
await this.reliableRooms.initialize();
|
|
114
|
+
let accepting = true;
|
|
115
|
+
let subscriber: RedisClient;
|
|
116
|
+
try {
|
|
117
|
+
subscriber = await this.operations.run(async () => {
|
|
118
|
+
const duplicate = await this.client.duplicate();
|
|
119
|
+
if (!accepting) { duplicate.close(); throw new Error("WebSocket subscription initialization expired."); }
|
|
120
|
+
return duplicate;
|
|
121
|
+
}) as RedisClient;
|
|
122
|
+
} finally { accepting = false; }
|
|
123
|
+
this.subscriber = subscriber;
|
|
124
|
+
this.hooks = hooks;
|
|
125
|
+
try {
|
|
126
|
+
await this.operations.run(() => subscriber.subscribe(this.channel, this.receive));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.hooks = undefined;
|
|
129
|
+
this.subscriber = undefined;
|
|
130
|
+
subscriber.close();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async publish(topic: string, payload: Uint8Array): Promise<void> {
|
|
136
|
+
if (payload.byteLength > this.maxPublishBytes || topic.length > 1024) throw new Error("WebSocket Redis publish exceeds limits.");
|
|
137
|
+
await this.operations.send("PUBLISH", [this.channel, JSON.stringify({
|
|
138
|
+
source: this.instanceId, topic, payload: Buffer.from(payload).toString("base64"),
|
|
139
|
+
})]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async saveSession(state: SessionState): Promise<void> {
|
|
143
|
+
await this.save(state, -1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async compareAndSwapSession(state: SessionState, expectedRevision: number): Promise<boolean> {
|
|
147
|
+
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new Error("Invalid WebSocket expected revision.");
|
|
148
|
+
return this.save(state, expectedRevision);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async loadSession(sid: string): Promise<SessionState | null> {
|
|
152
|
+
const raw = await this.operations.send("GET", [this.key(sid)]) as string | null;
|
|
153
|
+
if (raw === null) return null;
|
|
154
|
+
if (Buffer.byteLength(raw) > this.maxSessionBytes * 2 + 2048) throw new Error("Stored WebSocket session exceeds limits.");
|
|
155
|
+
const record = JSON.parse(raw) as { deleted?: boolean; state?: string };
|
|
156
|
+
// Keep application JSON opaque to Lua/cjson: empty arrays must stay arrays.
|
|
157
|
+
return record.deleted || record.state === undefined ? null : JSON.parse(record.state) as SessionState;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async deleteSession(sid: string, revision?: number): Promise<void> {
|
|
161
|
+
if (revision !== undefined && (!Number.isSafeInteger(revision) || revision < 0)) throw new Error("Invalid WebSocket deletion revision.");
|
|
162
|
+
await this.operations.send("EVAL", [DELETE, "1", this.key(sid), String(revision ?? -1), String(this.writeProtectionMs), this.prefix, sid]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async healthCheck(): Promise<void> {
|
|
166
|
+
if (await this.operations.send("PING", []) !== "PONG") throw new Error("WebSocket store unavailable.");
|
|
167
|
+
await this.reliableRooms.healthCheck();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async close(): Promise<void> {
|
|
171
|
+
const subscriber = this.subscriber;
|
|
172
|
+
this.subscriber = undefined;
|
|
173
|
+
this.hooks = undefined;
|
|
174
|
+
if (subscriber) {
|
|
175
|
+
try { await this.operations.run(() => subscriber.unsubscribe(this.channel, this.receive)); }
|
|
176
|
+
finally { subscriber.close(); }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async save(state: SessionState, expected: number): Promise<boolean> {
|
|
181
|
+
if (!Number.isSafeInteger(state.revision) || state.revision! < 1
|
|
182
|
+
|| !Number.isFinite(state.createdAt) || !Number.isFinite(state.expiresAt)) throw new Error("Invalid WebSocket session version or timestamps.");
|
|
183
|
+
const encoded = JSON.stringify(state);
|
|
184
|
+
if (Buffer.byteLength(encoded) > this.maxSessionBytes) throw new Error("WebSocket session exceeds Redis storage limit.");
|
|
185
|
+
return Number(await this.operations.send("EVAL", [SAVE, "1", this.key(state.sid), encoded, String(expected), String(this.writeProtectionMs), this.prefix])) === 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private key(sid: string): string {
|
|
189
|
+
if (typeof sid !== "string" || sid.length === 0 || sid.length > 128) throw new Error("Invalid WebSocket session id.");
|
|
190
|
+
return `${this.prefix}:session:${encodeURIComponent(sid)}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private readonly receive = (message: string): void => {
|
|
194
|
+
if (!this.hooks || message.length > Math.ceil(this.maxPublishBytes * 4 / 3) + 4096) return;
|
|
195
|
+
try {
|
|
196
|
+
const data = JSON.parse(message) as { source?: unknown; topic?: unknown; payload?: unknown; kind?: unknown; sids?: unknown };
|
|
197
|
+
if (data.kind === "deliveries") {
|
|
198
|
+
if (Array.isArray(data.sids) && data.sids.length <= 1000 && data.sids.every((sid) => typeof sid === "string" && sid.length <= 128)) {
|
|
199
|
+
this.hooks.deliveriesReady?.(data.sids);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (typeof data.source !== "string" || data.source === this.instanceId
|
|
204
|
+
|| typeof data.topic !== "string" || data.topic.length > 1024 || typeof data.payload !== "string") return;
|
|
205
|
+
const payload = Buffer.from(data.payload, "base64");
|
|
206
|
+
if (payload.byteLength > this.maxPublishBytes) return;
|
|
207
|
+
this.hooks.localPublish(data.topic, payload);
|
|
208
|
+
} catch {
|
|
209
|
+
// Invalid bus envelopes cannot escape a Redis callback into the process.
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function positive(value: number | undefined, fallback: number, field: string): number {
|
|
215
|
+
if (value === undefined) return fallback;
|
|
216
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`WebSocket Redis ${field} must be a positive integer.`);
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ClientPacket, ServerPacket } from "../types";
|
|
2
|
+
import { PacketCodecError } from "../packet-codec";
|
|
3
|
+
import type { PacketCodec } from "./packet-codec.interface";
|
|
4
|
+
|
|
5
|
+
/** Binary wire format: 0x01 magic + uint32be length + UTF-8 JSON body */
|
|
6
|
+
const MAGIC = 0x01;
|
|
7
|
+
const HEADER_SIZE = 5;
|
|
8
|
+
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
|
|
12
|
+
function wrapJson(json: string): Uint8Array {
|
|
13
|
+
const body = encoder.encode(json);
|
|
14
|
+
const frame = new Uint8Array(HEADER_SIZE + body.length);
|
|
15
|
+
frame[0] = MAGIC;
|
|
16
|
+
new DataView(frame.buffer).setUint32(1, body.length, false);
|
|
17
|
+
frame.set(body, HEADER_SIZE);
|
|
18
|
+
return frame;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function unwrapJson(raw: string | Buffer | Uint8Array): string {
|
|
22
|
+
const bytes =
|
|
23
|
+
typeof raw === "string"
|
|
24
|
+
? encoder.encode(raw)
|
|
25
|
+
: raw instanceof Buffer
|
|
26
|
+
? new Uint8Array(raw)
|
|
27
|
+
: raw;
|
|
28
|
+
|
|
29
|
+
if (bytes.length < HEADER_SIZE || bytes[0] !== MAGIC) {
|
|
30
|
+
throw new PacketCodecError("Invalid binary packet frame");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const length = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(1, false);
|
|
34
|
+
const end = HEADER_SIZE + length;
|
|
35
|
+
if (end > bytes.length) {
|
|
36
|
+
throw new PacketCodecError("Truncated binary packet frame");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return decoder.decode(bytes.subarray(HEADER_SIZE, end));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class BinaryPacketCodec implements PacketCodec {
|
|
43
|
+
readonly name = "binary";
|
|
44
|
+
|
|
45
|
+
encodeServer(packet: Omit<ServerPacket, "v"> & { v?: 1 }): Uint8Array {
|
|
46
|
+
return wrapJson(JSON.stringify({ v: 1, ...packet }));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
encodeClient(packet: Omit<ClientPacket, "v"> & { v?: 1 }): Uint8Array {
|
|
50
|
+
return wrapJson(JSON.stringify({ v: 1, ...packet }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
decodeClient(raw: string | Buffer | Uint8Array): ClientPacket {
|
|
54
|
+
const text = unwrapJson(raw);
|
|
55
|
+
let parsed: unknown;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(text);
|
|
58
|
+
} catch {
|
|
59
|
+
throw new PacketCodecError("Invalid JSON in binary frame");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
63
|
+
throw new PacketCodecError("Packet must be a JSON object");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const packet = parsed as Partial<ClientPacket>;
|
|
67
|
+
if (packet.v !== 1) {
|
|
68
|
+
throw new PacketCodecError("Unsupported packet version");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (packet.type !== "event" && packet.type !== "reconnect" && packet.type !== "ping" && packet.type !== "replay-ack") {
|
|
72
|
+
throw new PacketCodecError(`Unknown packet type: ${String(packet.type)}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return packet as ClientPacket;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isBinary(raw: string | Buffer | Uint8Array): boolean {
|
|
79
|
+
if (typeof raw === "string") {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const bytes = raw instanceof Buffer ? new Uint8Array(raw) : raw;
|
|
83
|
+
return bytes.length > 0 && bytes[0] === MAGIC;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const binaryPacketCodec = new BinaryPacketCodec();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ClientPacket, ServerPacket } from "../types";
|
|
2
|
+
import {
|
|
3
|
+
decodeClientPacket as decodeJsonClient,
|
|
4
|
+
encodeClientPacket,
|
|
5
|
+
encodeServerPacket,
|
|
6
|
+
PacketCodecError,
|
|
7
|
+
} from "../packet-codec";
|
|
8
|
+
import type { PacketCodec } from "./packet-codec.interface";
|
|
9
|
+
|
|
10
|
+
export class JsonPacketCodec implements PacketCodec {
|
|
11
|
+
readonly name = "json";
|
|
12
|
+
|
|
13
|
+
encodeServer(packet: Omit<ServerPacket, "v"> & { v?: 1 }): string {
|
|
14
|
+
return encodeServerPacket(packet);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
encodeClient(packet: Omit<ClientPacket, "v"> & { v?: 1 }): string {
|
|
18
|
+
return encodeClientPacket(packet);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
decodeClient(raw: string | Buffer | Uint8Array): ClientPacket {
|
|
22
|
+
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
23
|
+
return decodeJsonClient(text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
isBinary(raw: string | Buffer | Uint8Array): boolean {
|
|
27
|
+
void raw;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const jsonPacketCodec = new JsonPacketCodec();
|
|
33
|
+
|
|
34
|
+
export { PacketCodecError };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ClientPacket, ServerPacket } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface PacketCodec {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
encodeServer(packet: Omit<ServerPacket, "v"> & { v?: 1 }): string | Uint8Array;
|
|
6
|
+
encodeClient(packet: Omit<ClientPacket, "v"> & { v?: 1 }): string | Uint8Array;
|
|
7
|
+
decodeClient(raw: string | Buffer | Uint8Array): ClientPacket;
|
|
8
|
+
isBinary(raw: string | Buffer | Uint8Array): boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function payloadToBytes(payload: string | Uint8Array): Uint8Array {
|
|
12
|
+
return typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function payloadToText(payload: string | Uint8Array): string {
|
|
16
|
+
return typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
17
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { WsMiddleware } from "./middleware";
|
|
2
|
+
import type { BazisSocket } from "./types";
|
|
3
|
+
|
|
4
|
+
// Bun runs TC39 decorators natively, but Symbol.metadata may be absent.
|
|
5
|
+
(Symbol as { metadata?: symbol }).metadata ??= Symbol.for("Symbol.metadata");
|
|
6
|
+
|
|
7
|
+
const GATEWAY_META = Symbol.for("bazis:ws:gateway");
|
|
8
|
+
const HANDLERS_META = Symbol.for("bazis:ws:handlers");
|
|
9
|
+
|
|
10
|
+
/** Per-message rate limit (fixed window). */
|
|
11
|
+
export interface WsRateLimit {
|
|
12
|
+
readonly limit: number;
|
|
13
|
+
readonly windowMs: number;
|
|
14
|
+
/** Custom bucket key; defaults to `${socket.id}:${event}`. */
|
|
15
|
+
readonly key?: (socket: BazisSocket) => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WebSocketGatewayOptions {
|
|
19
|
+
/** Logical namespace. Default: "/". */
|
|
20
|
+
readonly namespace?: string;
|
|
21
|
+
/** HTTP upgrade path. Default: "/ws" + namespace ("/ws", "/ws/chat"). */
|
|
22
|
+
readonly path?: string;
|
|
23
|
+
/** Allowed origins for the upgrade. Default: app/server origins. */
|
|
24
|
+
readonly cors?: { origins?: string[]; credentials?: boolean };
|
|
25
|
+
/** Gateway-level upgrade middleware. */
|
|
26
|
+
readonly middleware?: WsMiddleware[];
|
|
27
|
+
/** Max inbound payload bytes. Default: 64 KiB. */
|
|
28
|
+
readonly maxPayloadBytes?: number;
|
|
29
|
+
/** Max outbound frame bytes; defaults to maxPayloadBytes. */
|
|
30
|
+
readonly maxOutboundPayloadBytes?: number;
|
|
31
|
+
/** Session TTL for reconnect. Default: 2h. */
|
|
32
|
+
readonly sessionTtlMs?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validates/transforms a message body before the handler runs. Return the
|
|
37
|
+
* (possibly coerced) value, or throw to reject. The client receives a generic
|
|
38
|
+
* validation error by default; applications may explicitly opt into exposing
|
|
39
|
+
* details for trusted development environments. Deliberately framework-
|
|
40
|
+
* agnostic: wrap `@/library/validation`, a DTO class, or any schema you like.
|
|
41
|
+
*/
|
|
42
|
+
export type WsBodyValidator = (
|
|
43
|
+
body: unknown,
|
|
44
|
+
/** Aborted when message processing is cancelled or times out. */
|
|
45
|
+
signal?: AbortSignal,
|
|
46
|
+
) => unknown | Promise<unknown>;
|
|
47
|
+
|
|
48
|
+
export interface MessageHandlerDecl {
|
|
49
|
+
readonly propertyKey: string | symbol;
|
|
50
|
+
readonly events: string[];
|
|
51
|
+
readonly rateLimit?: WsRateLimit;
|
|
52
|
+
readonly validate?: WsBodyValidator;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface GatewayCarrier {
|
|
56
|
+
[GATEWAY_META]?: WebSocketGatewayOptions;
|
|
57
|
+
[HANDLERS_META]?: MessageHandlerDecl[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SubscribeMessageOptions {
|
|
61
|
+
readonly rateLimit?: WsRateLimit;
|
|
62
|
+
readonly validate?: WsBodyValidator;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Marks a class as a WebSocket gateway (analogous to `@Controller`). */
|
|
66
|
+
export function WebSocketGateway(options: WebSocketGatewayOptions = {}) {
|
|
67
|
+
return (_value: abstract new (...args: never[]) => unknown, context: ClassDecoratorContext): void => {
|
|
68
|
+
(context.metadata as GatewayCarrier)[GATEWAY_META] = options;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ownHandlers(carrier: GatewayCarrier): MessageHandlerDecl[] {
|
|
73
|
+
if (!Object.prototype.hasOwnProperty.call(carrier, HANDLERS_META)) {
|
|
74
|
+
carrier[HANDLERS_META] = carrier[HANDLERS_META] ? [...carrier[HANDLERS_META]] : [];
|
|
75
|
+
}
|
|
76
|
+
return carrier[HANDLERS_META]!;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Subscribes a method to one or more events. The handler is called by
|
|
81
|
+
* positional convention: `(socket, body, ack)` — declare only what you need.
|
|
82
|
+
* Returning a value auto-acks when the client packet carried an `id`.
|
|
83
|
+
*/
|
|
84
|
+
export function SubscribeMessage(event: string | readonly string[], options?: SubscribeMessageOptions) {
|
|
85
|
+
return (_value: (this: never, ...args: never[]) => unknown, context: ClassMethodDecoratorContext): void => {
|
|
86
|
+
if (context.static) {
|
|
87
|
+
throw new Error("@SubscribeMessage cannot be applied to static methods.");
|
|
88
|
+
}
|
|
89
|
+
ownHandlers(context.metadata as GatewayCarrier).push({
|
|
90
|
+
propertyKey: context.name,
|
|
91
|
+
events: Array.isArray(event) ? [...event] : [event as string],
|
|
92
|
+
...(options?.rateLimit ? { rateLimit: options.rateLimit } : {}),
|
|
93
|
+
...(options?.validate ? { validate: options.validate } : {}),
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getWebSocketGatewayMetadata(ctor: object): WebSocketGatewayOptions | undefined {
|
|
99
|
+
const metadata = (ctor as { [key: symbol]: unknown })[Symbol.metadata as unknown as symbol] as
|
|
100
|
+
| GatewayCarrier
|
|
101
|
+
| undefined;
|
|
102
|
+
return metadata?.[GATEWAY_META];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getGatewayMessageHandlers(ctor: object): readonly MessageHandlerDecl[] {
|
|
106
|
+
const metadata = (ctor as { [key: symbol]: unknown })[Symbol.metadata as unknown as symbol] as
|
|
107
|
+
| GatewayCarrier
|
|
108
|
+
| undefined;
|
|
109
|
+
return metadata?.[HANDLERS_META] ?? [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Normalizes a namespace to a leading-slash form ("chat" -> "/chat", "" -> "/"). */
|
|
113
|
+
export function normalizeNamespace(namespace: string | undefined): string {
|
|
114
|
+
const value = namespace?.trim();
|
|
115
|
+
if (!value || value === "/") {
|
|
116
|
+
return "/";
|
|
117
|
+
}
|
|
118
|
+
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
119
|
+
if (normalized.includes("\0") || normalized.includes("?") || normalized.includes("#")) {
|
|
120
|
+
throw new Error("WebSocket namespace contains an invalid separator.");
|
|
121
|
+
}
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ResolvedGatewayOptions {
|
|
126
|
+
readonly namespace: string;
|
|
127
|
+
readonly path: string;
|
|
128
|
+
readonly cors?: { origins?: string[]; credentials?: boolean };
|
|
129
|
+
readonly middleware: WsMiddleware[];
|
|
130
|
+
readonly maxPayloadBytes: number;
|
|
131
|
+
readonly maxOutboundPayloadBytes?: number;
|
|
132
|
+
readonly sessionTtlMs: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const DEFAULT_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
136
|
+
const DEFAULT_SESSION_TTL_MS = 2 * 60 * 60 * 1000;
|
|
137
|
+
|
|
138
|
+
export function resolveWebSocketGatewayOptions(options: WebSocketGatewayOptions): ResolvedGatewayOptions {
|
|
139
|
+
const namespace = normalizeNamespace(options.namespace);
|
|
140
|
+
const path = options.path ?? (namespace === "/" ? "/ws" : `/ws${namespace}`);
|
|
141
|
+
if (!path.startsWith("/") || path.includes("\0") || path.includes("?") || path.includes("#")) {
|
|
142
|
+
throw new Error(`WebSocket path "${path}" must be an absolute path without query/hash separators.`);
|
|
143
|
+
}
|
|
144
|
+
const maxPayloadBytes = positiveOption(options.maxPayloadBytes, DEFAULT_MAX_PAYLOAD_BYTES, "maxPayloadBytes");
|
|
145
|
+
const sessionTtlMs = positiveOption(options.sessionTtlMs, DEFAULT_SESSION_TTL_MS, "sessionTtlMs");
|
|
146
|
+
return {
|
|
147
|
+
namespace,
|
|
148
|
+
path,
|
|
149
|
+
...(options.cors ? { cors: options.cors } : {}),
|
|
150
|
+
middleware: options.middleware ?? [],
|
|
151
|
+
maxPayloadBytes,
|
|
152
|
+
maxOutboundPayloadBytes: positiveOption(options.maxOutboundPayloadBytes, maxPayloadBytes, "maxOutboundPayloadBytes"),
|
|
153
|
+
sessionTtlMs,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function positiveOption(value: number | undefined, fallback: number, field: string): number {
|
|
158
|
+
if (value === undefined) {
|
|
159
|
+
return fallback;
|
|
160
|
+
}
|
|
161
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
162
|
+
throw new Error(`WebSocket ${field} must be a positive number.`);
|
|
163
|
+
}
|
|
164
|
+
return Math.floor(value);
|
|
165
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getGatewayMessageHandlers,
|
|
3
|
+
getWebSocketGatewayMetadata,
|
|
4
|
+
resolveWebSocketGatewayOptions,
|
|
5
|
+
type WsBodyValidator,
|
|
6
|
+
type WsRateLimit,
|
|
7
|
+
} from "./decorators";
|
|
8
|
+
import type { WsMiddleware } from "./middleware";
|
|
9
|
+
|
|
10
|
+
export interface CompiledWsHandler {
|
|
11
|
+
events: string[];
|
|
12
|
+
handler: (...args: unknown[]) => unknown;
|
|
13
|
+
handlerName: string | symbol;
|
|
14
|
+
/** Declared parameter count; drives positional `(socket, body, ack)` binding. */
|
|
15
|
+
arity: number;
|
|
16
|
+
rateLimit?: WsRateLimit;
|
|
17
|
+
validate?: WsBodyValidator;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RegisteredNamespace {
|
|
21
|
+
namespace: string;
|
|
22
|
+
path: string;
|
|
23
|
+
gatewayInstance: unknown;
|
|
24
|
+
handlers: Map<string, CompiledWsHandler>;
|
|
25
|
+
middleware: WsMiddleware[];
|
|
26
|
+
corsOrigins: string[];
|
|
27
|
+
maxPayloadBytes: number;
|
|
28
|
+
maxOutboundPayloadBytes?: number;
|
|
29
|
+
sessionTtlMs: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compiles gateway instances into per-namespace handler maps (event -> handler).
|
|
34
|
+
* Handler binding is positional `(socket, body, ack)` by arity — no parameter
|
|
35
|
+
* decorators (TC39 decorators do not support them).
|
|
36
|
+
*/
|
|
37
|
+
export class WebSocketExplorer {
|
|
38
|
+
public explore(gatewayInstances: readonly unknown[]): RegisteredNamespace[] {
|
|
39
|
+
const namespaces: RegisteredNamespace[] = [];
|
|
40
|
+
const namespacesByName = new Map<string, string>();
|
|
41
|
+
const namespacesByPath = new Map<string, string>();
|
|
42
|
+
|
|
43
|
+
for (const instance of gatewayInstances) {
|
|
44
|
+
const gatewayClass = (instance as { constructor: object } | null)?.constructor;
|
|
45
|
+
if (!gatewayClass) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const metadata = getWebSocketGatewayMetadata(gatewayClass);
|
|
49
|
+
if (!metadata) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resolved = resolveWebSocketGatewayOptions(metadata);
|
|
54
|
+
const gatewayName = (gatewayClass as { name?: string }).name ?? "?";
|
|
55
|
+
const duplicateNamespace = namespacesByName.get(resolved.namespace);
|
|
56
|
+
if (duplicateNamespace !== undefined) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Duplicate WebSocket namespace "${resolved.namespace}" on gateways "${duplicateNamespace}" and "${gatewayName}".`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const duplicatePath = namespacesByPath.get(resolved.path);
|
|
62
|
+
if (duplicatePath !== undefined) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Duplicate WebSocket path "${resolved.path}" on gateways "${duplicatePath}" and "${gatewayName}".`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
namespacesByName.set(resolved.namespace, gatewayName);
|
|
68
|
+
namespacesByPath.set(resolved.path, gatewayName);
|
|
69
|
+
const handlers = new Map<string, CompiledWsHandler>();
|
|
70
|
+
|
|
71
|
+
for (const decl of getGatewayMessageHandlers(gatewayClass)) {
|
|
72
|
+
const method = (instance as Record<string | symbol, unknown>)[decl.propertyKey];
|
|
73
|
+
if (typeof method !== "function") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const compiled: CompiledWsHandler = {
|
|
77
|
+
events: decl.events,
|
|
78
|
+
handler: (method as (...args: unknown[]) => unknown).bind(instance),
|
|
79
|
+
handlerName: decl.propertyKey,
|
|
80
|
+
arity: method.length,
|
|
81
|
+
...(decl.rateLimit ? { rateLimit: decl.rateLimit } : {}),
|
|
82
|
+
...(decl.validate ? { validate: decl.validate } : {}),
|
|
83
|
+
};
|
|
84
|
+
if (decl.rateLimit !== undefined) {
|
|
85
|
+
assertRateLimit(decl.rateLimit, gatewayName, decl.propertyKey);
|
|
86
|
+
}
|
|
87
|
+
for (const event of decl.events) {
|
|
88
|
+
if (typeof event !== "string" || event.trim().length === 0) {
|
|
89
|
+
throw new Error(`@SubscribeMessage event on ${gatewayName}.${String(decl.propertyKey)} must not be empty.`);
|
|
90
|
+
}
|
|
91
|
+
if (handlers.has(event)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Duplicate @SubscribeMessage("${event}") in gateway "${(gatewayClass as { name?: string }).name ?? "?"}"`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
handlers.set(event, compiled);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
namespaces.push({
|
|
101
|
+
namespace: resolved.namespace,
|
|
102
|
+
path: resolved.path,
|
|
103
|
+
gatewayInstance: instance,
|
|
104
|
+
handlers,
|
|
105
|
+
middleware: resolved.middleware,
|
|
106
|
+
// Empty means "inherit the server/module default". An explicit
|
|
107
|
+
// gateway wildcard remains ["*"] and deliberately overrides it.
|
|
108
|
+
corsOrigins: resolved.cors?.origins ? [...resolved.cors.origins] : [],
|
|
109
|
+
maxPayloadBytes: resolved.maxPayloadBytes,
|
|
110
|
+
maxOutboundPayloadBytes: resolved.maxOutboundPayloadBytes,
|
|
111
|
+
sessionTtlMs: resolved.sessionTtlMs,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return namespaces;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function assertRateLimit(rateLimit: WsRateLimit, gateway: string, method: string | symbol): void {
|
|
120
|
+
if (!Number.isInteger(rateLimit.limit) || rateLimit.limit <= 0) {
|
|
121
|
+
throw new Error(`WebSocket rateLimit.limit on ${gateway}.${String(method)} must be a positive integer.`);
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isFinite(rateLimit.windowMs) || rateLimit.windowMs <= 0) {
|
|
124
|
+
throw new Error(`WebSocket rateLimit.windowMs on ${gateway}.${String(method)} must be positive.`);
|
|
125
|
+
}
|
|
126
|
+
}
|