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.
Files changed (513) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/cli/build.ts +147 -0
  4. package/cli/codegen.ts +40 -0
  5. package/cli/generateModule.ts +187 -0
  6. package/cli/generateProject.ts +183 -0
  7. package/cli/main.ts +125 -0
  8. package/cli/moduleRegistration.ts +127 -0
  9. package/cli/naming.ts +124 -0
  10. package/cli/parseCli.ts +151 -0
  11. package/cli/templates/module.ts +497 -0
  12. package/cli/templates/pack.ts +76 -0
  13. package/cli/templates/passport.ts +178 -0
  14. package/core/agent/AgentContextBuilder.ts +567 -0
  15. package/core/agent/AgentDataDefinition.ts +8 -0
  16. package/core/agent/AgentRegistry.ts +539 -0
  17. package/core/agent/AgentRuntime.ts +47 -0
  18. package/core/agent/AgentToolExecutor.ts +2130 -0
  19. package/core/agent/AgentToolHooks.ts +39 -0
  20. package/core/agent/ModelProvider.ts +21 -0
  21. package/core/agent/README.md +229 -0
  22. package/core/agent/Tool.contract.ts +20 -0
  23. package/core/agent/decorators/Agent.ts +11 -0
  24. package/core/agent/decorators/Prompt.ts +11 -0
  25. package/core/agent/decorators/Task.ts +29 -0
  26. package/core/agent/decorators/Tool.ts +12 -0
  27. package/core/agent/errors.ts +43 -0
  28. package/core/agent/generated/catalog.ts +26 -0
  29. package/core/agent/index.ts +170 -0
  30. package/core/agent/internal/AgentExecutionDriver.ts +1497 -0
  31. package/core/agent/internal/AgentExecutionState.ts +775 -0
  32. package/core/agent/internal/AgentJsonSchema.ts +150 -0
  33. package/core/agent/internal/AgentModelBinding.ts +57 -0
  34. package/core/agent/internal/Schema.contract.ts +36 -0
  35. package/core/agent/internal/ToolAudit.projector.ts +163 -0
  36. package/core/agent/internal/ToolContract.validator.ts +102 -0
  37. package/core/agent/metadata.ts +398 -0
  38. package/core/agent/moduleContributions-v1.ts +113 -0
  39. package/core/agent/semantic.ts +903 -0
  40. package/core/agent/session/codec.ts +33 -0
  41. package/core/agent/session/contracts.ts +96 -0
  42. package/core/agent/session/entities.ts +84 -0
  43. package/core/agent/session/protector.ts +92 -0
  44. package/core/app/index.ts +5 -0
  45. package/core/app/runApp.ts +293 -0
  46. package/core/app/ui/index.ts +29 -0
  47. package/core/app/ui/uiProfileResolver.ts +966 -0
  48. package/core/app/ui/uiSurfaceHosting.ts +38 -0
  49. package/core/app/ui/uiSurfaceHttp.ts +476 -0
  50. package/core/background/BackgroundService.ts +223 -0
  51. package/core/background/backgroundModule.ts +42 -0
  52. package/core/background/decorator.ts +43 -0
  53. package/core/background/delay.ts +27 -0
  54. package/core/background/index.ts +15 -0
  55. package/core/cache/COMPATIBILITY.md +13 -0
  56. package/core/cache/ICache.ts +71 -0
  57. package/core/cache/MemoryCache.ts +394 -0
  58. package/core/cache/README.md +83 -0
  59. package/core/cache/SPEC.md +1355 -0
  60. package/core/cache/cacheModule.ts +21 -0
  61. package/core/cache/decorators/Cacheable.ts +38 -0
  62. package/core/cache/decorators/CacheableRedis.ts +39 -0
  63. package/core/cache/decorators/OutputCache.ts +33 -0
  64. package/core/cache/decorators/OutputRedisCache.ts +33 -0
  65. package/core/cache/decorators/cacheableMetadata.ts +78 -0
  66. package/core/cache/decorators/cacheableRedisMetadata.ts +81 -0
  67. package/core/cache/decorators/outputCacheMetadata.ts +71 -0
  68. package/core/cache/decorators/outputRedisCacheMetadata.ts +77 -0
  69. package/core/cache/di/classProviderHook.ts +26 -0
  70. package/core/cache/distributed/CacheCodec.ts +5 -0
  71. package/core/cache/distributed/DistributedCache.ts +403 -0
  72. package/core/cache/distributed/DistributedCacheDriver.ts +83 -0
  73. package/core/cache/distributed/DistributedCacheStores.ts +28 -0
  74. package/core/cache/distributed/IDistributedCache.ts +40 -0
  75. package/core/cache/distributed/NamedCacheRegistry.ts +23 -0
  76. package/core/cache/distributed/codecs.ts +32 -0
  77. package/core/cache/distributed/index.ts +19 -0
  78. package/core/cache/errors/CacheError.ts +16 -0
  79. package/core/cache/http/CachedHttpPayload.ts +213 -0
  80. package/core/cache/http/applyClientCacheHeaders.ts +61 -0
  81. package/core/cache/http/buildOutputCacheKey.ts +154 -0
  82. package/core/cache/http/buildRedisOutputCacheKey.ts +16 -0
  83. package/core/cache/http/composeOutputCache.ts +130 -0
  84. package/core/cache/http/outputCacheMiddleware.ts +111 -0
  85. package/core/cache/http/outputCacheSecurityWarning.ts +58 -0
  86. package/core/cache/http/outputRedisCacheMiddleware.ts +118 -0
  87. package/core/cache/index.ts +97 -0
  88. package/core/cache/internal/AutomaticCacheKey.ts +106 -0
  89. package/core/cache/internal/CacheKeyGuard.ts +56 -0
  90. package/core/cache/internal/buildCacheProviders.ts +146 -0
  91. package/core/cache/internal/normalizeCacheKey.ts +9 -0
  92. package/core/cache/internal/resolveCachePolicy.ts +32 -0
  93. package/core/cache/internal/resolveCacheableRedisPolicy.ts +17 -0
  94. package/core/cache/internal/resolveOutputRedisCachePolicy.ts +17 -0
  95. package/core/cache/providers/cachedProviders.ts +68 -0
  96. package/core/cache/services/cacheProxy.ts +243 -0
  97. package/core/cache/tokens/CACHE_POLICIES.ts +5 -0
  98. package/core/cache/tokens/DISTRIBUTED_CACHE.ts +30 -0
  99. package/core/cache/types/CacheEntry.ts +7 -0
  100. package/core/cache/types/CacheOptions.ts +60 -0
  101. package/core/cache/types/CachePolicy.ts +74 -0
  102. package/core/cache/types/CacheableRedisOptions.ts +23 -0
  103. package/core/cache/types/OutputRedisCacheOptions.ts +25 -0
  104. package/core/di/README.md +104 -0
  105. package/core/di/ServiceCollection.ts +185 -0
  106. package/core/di/ServiceProvider.ts +689 -0
  107. package/core/di/ServiceScope.ts +61 -0
  108. package/core/di/container.ts +38 -0
  109. package/core/di/errors/AmbiguousNamedDependencyError.ts +10 -0
  110. package/core/di/errors/AsyncResolutionRequiredError.ts +12 -0
  111. package/core/di/errors/CircularDependencyError.ts +10 -0
  112. package/core/di/errors/ClassDependenciesMismatchError.ts +15 -0
  113. package/core/di/errors/DiError.ts +6 -0
  114. package/core/di/errors/InvalidProviderError.ts +9 -0
  115. package/core/di/errors/ModuleEncapsulationError.ts +7 -0
  116. package/core/di/errors/ModuleOwnedProviderConflictError.ts +8 -0
  117. package/core/di/errors/NamedDependencyNotFoundError.ts +7 -0
  118. package/core/di/errors/OptionsValidationError.ts +7 -0
  119. package/core/di/errors/ProviderNotFoundError.ts +10 -0
  120. package/core/di/errors/ScopeDisposedError.ts +7 -0
  121. package/core/di/errors/ScopedServiceFromRootError.ts +9 -0
  122. package/core/di/errors/ServiceValidationError.ts +7 -0
  123. package/core/di/errors/index.ts +14 -0
  124. package/core/di/extensions/activator.ts +39 -0
  125. package/core/di/extensions/application.ts +117 -0
  126. package/core/di/extensions/hosted-service.ts +124 -0
  127. package/core/di/extensions/index.ts +23 -0
  128. package/core/di/extensions/options-reloadable.ts +137 -0
  129. package/core/di/extensions/options.ts +91 -0
  130. package/core/di/extensions/service-provider-token.ts +21 -0
  131. package/core/di/index.ts +97 -0
  132. package/core/di/internal/GraphValidator.ts +119 -0
  133. package/core/di/internal/HostedServicePlan.ts +158 -0
  134. package/core/di/internal/NamedTokenIndex.ts +55 -0
  135. package/core/di/internal/OpenGenericRegistration.ts +14 -0
  136. package/core/di/internal/ResolutionPlan.ts +18 -0
  137. package/core/di/internal/ResolutionScopeState.ts +19 -0
  138. package/core/di/internal/ResolutionTracker.ts +153 -0
  139. package/core/di/internal/ScopeLifecycle.ts +159 -0
  140. package/core/di/internal/ServiceRegistration.ts +14 -0
  141. package/core/di/internal/ServiceRegistry.ts +252 -0
  142. package/core/di/internal/classDeps.ts +38 -0
  143. package/core/di/internal/disposal.ts +58 -0
  144. package/core/di/internal/providerDeps.ts +26 -0
  145. package/core/di/module/DI.ts +146 -0
  146. package/core/di/module/Global.ts +25 -0
  147. package/core/di/module/Module.ts +44 -0
  148. package/core/di/module/ModuleRegistrar.ts +69 -0
  149. package/core/di/module/autoDeps.ts +39 -0
  150. package/core/di/module/classProviderHooks.ts +66 -0
  151. package/core/di/module/collectModuleConfigs.ts +57 -0
  152. package/core/di/module/collectModuleControllers.ts +53 -0
  153. package/core/di/module/collectModuleUiProfiles.ts +53 -0
  154. package/core/di/module/createContainer.ts +261 -0
  155. package/core/di/module/encapsulation.ts +416 -0
  156. package/core/di/module/encapsulationHooks.ts +36 -0
  157. package/core/di/module/generatedProviderAttachments.ts +102 -0
  158. package/core/di/module/index.ts +41 -0
  159. package/core/di/module/moduleExtensions.ts +35 -0
  160. package/core/di/module/moduleNameBinding.ts +136 -0
  161. package/core/di/module/moduleOwnedProviderContributors.ts +409 -0
  162. package/core/di/module/providerSelection.ts +90 -0
  163. package/core/di/module/shortcuts.ts +111 -0
  164. package/core/di/module/types/BazisModule.ts +71 -0
  165. package/core/di/module/types/DiRegistrar.ts +24 -0
  166. package/core/di/module/types/index.ts +2 -0
  167. package/core/di/provider/index.ts +26 -0
  168. package/core/di/provider/providerGuards.ts +17 -0
  169. package/core/di/provider/types/AsyncFactoryProvider.ts +15 -0
  170. package/core/di/provider/types/ClassProvider.ts +18 -0
  171. package/core/di/provider/types/FactoryProvider.ts +20 -0
  172. package/core/di/provider/types/KeyedDependency.ts +20 -0
  173. package/core/di/provider/types/LazyDependency.ts +31 -0
  174. package/core/di/provider/types/NamedDependency.ts +16 -0
  175. package/core/di/provider/types/Provider.ts +10 -0
  176. package/core/di/provider/types/ProviderDefinition.ts +11 -0
  177. package/core/di/provider/types/ProviderDependencyList.ts +11 -0
  178. package/core/di/provider/types/ProviderLifetime.ts +1 -0
  179. package/core/di/provider/types/ProviderOwnership.ts +7 -0
  180. package/core/di/provider/types/ResolvedDeps.ts +18 -0
  181. package/core/di/provider/types/ValueProvider.ts +6 -0
  182. package/core/di/provider/types/index.ts +13 -0
  183. package/core/di/token.ts +84 -0
  184. package/core/di/types/BuildServiceProviderOptions.ts +4 -0
  185. package/core/di/types/ServiceKey.ts +1 -0
  186. package/core/di/types/ServiceResolver.ts +16 -0
  187. package/core/di/types/index.ts +3 -0
  188. package/core/generatedFingerprint.ts +60 -0
  189. package/core/generatedRuntime.ts +187 -0
  190. package/core/grpc/Grpc.module.ts +23 -0
  191. package/core/grpc/GrpcBinding.contract.ts +35 -0
  192. package/core/grpc/GrpcCall.ts +73 -0
  193. package/core/grpc/GrpcClient.ts +172 -0
  194. package/core/grpc/GrpcClientCall.ts +267 -0
  195. package/core/grpc/GrpcError.ts +19 -0
  196. package/core/grpc/GrpcRequest.binding.ts +45 -0
  197. package/core/grpc/GrpcServer.ts +204 -0
  198. package/core/grpc/GrpcStatus.ts +8 -0
  199. package/core/grpc/GrpcTransport.ts +174 -0
  200. package/core/grpc/Metadata.ts +73 -0
  201. package/core/grpc/ProtoCodec.ts +225 -0
  202. package/core/grpc/ProtoParser.ts +192 -0
  203. package/core/grpc/ProtoReader.ts +34 -0
  204. package/core/grpc/ProtoSchema.ts +52 -0
  205. package/core/grpc/ServerCredentials.ts +30 -0
  206. package/core/grpc/clientContracts.ts +39 -0
  207. package/core/grpc/contracts.ts +45 -0
  208. package/core/grpc/decorators.ts +82 -0
  209. package/core/grpc/grpcClientProvider.ts +9 -0
  210. package/core/grpc/index.ts +17 -0
  211. package/core/grpc/moduleControllers.ts +38 -0
  212. package/core/grpc/protobuf.ts +80 -0
  213. package/core/grpc/serviceDefinition.ts +20 -0
  214. package/core/http/Authorization/Authorize.ts +52 -0
  215. package/core/http/Authorization/authorizeComposer.ts +31 -0
  216. package/core/http/Authorization/metadata.ts +158 -0
  217. package/core/http/Binding/ParameterBinder.ts +109 -0
  218. package/core/http/Binding/RequestModel.ts +27 -0
  219. package/core/http/Binding/autoBindings.ts +125 -0
  220. package/core/http/Binding/bindings.ts +19 -0
  221. package/core/http/Binding/convert.ts +48 -0
  222. package/core/http/Binding/generatedSpec.ts +18 -0
  223. package/core/http/Binding/listModelRegistry.ts +47 -0
  224. package/core/http/Binding/modelBinder.ts +345 -0
  225. package/core/http/Binding/modelValidator.ts +30 -0
  226. package/core/http/Binding/requestModelRegistry.ts +117 -0
  227. package/core/http/Decorators/attributes.ts +92 -0
  228. package/core/http/Decorators/controller.ts +24 -0
  229. package/core/http/Decorators/metadata.ts +107 -0
  230. package/core/http/Decorators/routes.ts +131 -0
  231. package/core/http/Errors/HttpError.ts +92 -0
  232. package/core/http/HttpContext/HttpContext.ts +148 -0
  233. package/core/http/HttpContext/ResponseBuilder.ts +37 -0
  234. package/core/http/HttpContext/inspectableRedirects.ts +47 -0
  235. package/core/http/HttpContext/principal.ts +24 -0
  236. package/core/http/HttpContext/responseLifetime.ts +45 -0
  237. package/core/http/HttpServer.ts +627 -0
  238. package/core/http/Middleware/accessLog.ts +90 -0
  239. package/core/http/Middleware/cors.ts +160 -0
  240. package/core/http/Middleware/errorHandler.ts +100 -0
  241. package/core/http/Middleware/pipeline.ts +21 -0
  242. package/core/http/Middleware/rateLimit.ts +104 -0
  243. package/core/http/Middleware/securityHeaders.ts +118 -0
  244. package/core/http/Middleware/types.ts +19 -0
  245. package/core/http/OpenApi/generatedOpenApiRegistry.ts +102 -0
  246. package/core/http/OpenApi/openApiDocs.ts +75 -0
  247. package/core/http/OpenApi/openApiDocument.ts +356 -0
  248. package/core/http/README.md +139 -0
  249. package/core/http/Results/HttpResult.ts +19 -0
  250. package/core/http/Results/normalize.ts +93 -0
  251. package/core/http/Results/results.ts +70 -0
  252. package/core/http/Routing/Router.ts +201 -0
  253. package/core/http/Routing/RouterBuilder.ts +184 -0
  254. package/core/http/Routing/template.ts +112 -0
  255. package/core/http/SPEC.md +981 -0
  256. package/core/http/Versioning/VersionReader.ts +20 -0
  257. package/core/http/WebSocketUpgrade.ts +29 -0
  258. package/core/http/composeRouteMiddlewareComposers.ts +21 -0
  259. package/core/http/correlation/createCorrelationIdMiddleware.ts +79 -0
  260. package/core/http/httpModule.ts +48 -0
  261. package/core/http/index.ts +140 -0
  262. package/core/http/middlewareTokens.ts +48 -0
  263. package/core/http/options.ts +134 -0
  264. package/core/http-client/index.ts +105 -0
  265. package/core/infra/Infra.ts +158 -0
  266. package/core/infra/InfraConnector.ts +86 -0
  267. package/core/infra/InfraLifecycle.ts +86 -0
  268. package/core/infra/cache/RedisDistributedCacheBackend.ts +88 -0
  269. package/core/infra/cache/RedisDistributedCacheDriver.ts +186 -0
  270. package/core/infra/cache/index.ts +7 -0
  271. package/core/infra/connectorConfig.ts +29 -0
  272. package/core/infra/connectorIdentity.ts +39 -0
  273. package/core/infra/connectors/codex/CodexAppServer.ts +146 -0
  274. package/core/infra/connectors/codex/CodexAppServerClient.ts +258 -0
  275. package/core/infra/connectors/codex/CodexPolicy.ts +114 -0
  276. package/core/infra/connectors/codex/CodexTurn.ts +133 -0
  277. package/core/infra/connectors/codex/contracts.ts +82 -0
  278. package/core/infra/connectors/codex.ts +26 -0
  279. package/core/infra/connectors/llm.ts +78 -0
  280. package/core/infra/connectors/llmRouter.ts +443 -0
  281. package/core/infra/connectors/openaiCompatible.ts +590 -0
  282. package/core/infra/connectors/openaiTextStream.ts +134 -0
  283. package/core/infra/connectors/opensearch.ts +307 -0
  284. package/core/infra/connectors/postgres.ts +105 -0
  285. package/core/infra/connectors/redis.ts +132 -0
  286. package/core/infra/index.ts +58 -0
  287. package/core/kernel/ApplicationLifetime.ts +91 -0
  288. package/core/kernel/Bazis.ts +73 -0
  289. package/core/kernel/Environment.ts +53 -0
  290. package/core/kernel/Kernel.ts +391 -0
  291. package/core/kernel/KernelBuilder.ts +172 -0
  292. package/core/kernel/LifecycleCoordinator.ts +204 -0
  293. package/core/kernel/README.md +66 -0
  294. package/core/kernel/SupervisedHostedService.ts +147 -0
  295. package/core/kernel/config/ConfigRegistry.ts +48 -0
  296. package/core/kernel/config/Configuration.ts +110 -0
  297. package/core/kernel/config/README.md +62 -0
  298. package/core/kernel/config/Secret.ts +29 -0
  299. package/core/kernel/config/addConfigOptions.ts +63 -0
  300. package/core/kernel/config/defineConfig.ts +211 -0
  301. package/core/kernel/config/sources.ts +123 -0
  302. package/core/kernel/correlation/requestContext.ts +57 -0
  303. package/core/kernel/errors/ConfigKeyMissingError.ts +7 -0
  304. package/core/kernel/errors/KernelError.ts +7 -0
  305. package/core/kernel/errors/ShutdownTimeoutError.ts +7 -0
  306. package/core/kernel/errors/StartupAbortedError.ts +7 -0
  307. package/core/kernel/errors/StartupTimeoutError.ts +7 -0
  308. package/core/kernel/errors/index.ts +5 -0
  309. package/core/kernel/events/EventBus.ts +140 -0
  310. package/core/kernel/events/EventToken.ts +17 -0
  311. package/core/kernel/events/eventSubscribers.ts +126 -0
  312. package/core/kernel/events/kernelEvents.ts +16 -0
  313. package/core/kernel/events/onEvent.ts +32 -0
  314. package/core/kernel/health/HealthCheckContracts.ts +42 -0
  315. package/core/kernel/health/HealthService.ts +93 -0
  316. package/core/kernel/index.ts +96 -0
  317. package/core/kernel/internal/awaitAbortable.ts +18 -0
  318. package/core/kernel/internal/awaitShutdown.ts +21 -0
  319. package/core/kernel/internal/reportDiagnosticFailure.ts +8 -0
  320. package/core/kernel/internal/validateTimeout.ts +8 -0
  321. package/core/kernel/lifecycleHooks.ts +18 -0
  322. package/core/kernel/logging/ConsoleLogger.ts +68 -0
  323. package/core/kernel/logging/Logger.ts +22 -0
  324. package/core/kernel/types/ConfigSource.ts +9 -0
  325. package/core/kernel/types/EnvironmentName.ts +1 -0
  326. package/core/kernel/types/KernelOptions.ts +23 -0
  327. package/core/kernel/types/LifecycleHook.ts +18 -0
  328. package/core/kernel/types/index.ts +4 -0
  329. package/core/orm/DATABASE_PROVIDER.ts +5 -0
  330. package/core/orm/OrmHostedPlan.validator.ts +55 -0
  331. package/core/orm/OrmLifecycle.ts +146 -0
  332. package/core/orm/OrmOwnedStoreLifecycle.ts +96 -0
  333. package/core/orm/buildOrmProviders.ts +152 -0
  334. package/core/orm/databaseConnector.ts +108 -0
  335. package/core/orm/encapsulationHook.ts +19 -0
  336. package/core/orm/index.ts +95 -0
  337. package/core/orm/listQuery.ts +198 -0
  338. package/core/orm/ormModule.ts +173 -0
  339. package/core/orm/ownedStoreContributions.ts +195 -0
  340. package/core/orm/registerRepositories.ts +27 -0
  341. package/core/orm/repository.ts +45 -0
  342. package/core/orm/strictSchemaHostedIdentity.ts +17 -0
  343. package/core/scripts/agent-codegen.ts +1166 -0
  344. package/core/scripts/di-generate-target.ts +90 -0
  345. package/core/scripts/di-generate.ts +1837 -0
  346. package/core/scripts/orm-predicate-codegen.ts +33 -0
  347. package/core/scripts/request-model-codegen.ts +326 -0
  348. package/core/websocket/DESIGN.md +297 -0
  349. package/core/websocket/WsRateLimiter.ts +101 -0
  350. package/core/websocket/adapter/adapter.interface.ts +45 -0
  351. package/core/websocket/adapter/in-memory.adapter.ts +190 -0
  352. package/core/websocket/adapter/redis-delivery-scripts.ts +135 -0
  353. package/core/websocket/adapter/redis-delivery-store.ts +86 -0
  354. package/core/websocket/adapter/redis-operations.ts +33 -0
  355. package/core/websocket/adapter/redis.adapter.ts +218 -0
  356. package/core/websocket/codec/binary.codec.ts +87 -0
  357. package/core/websocket/codec/json.codec.ts +34 -0
  358. package/core/websocket/codec/packet-codec.interface.ts +17 -0
  359. package/core/websocket/decorators.ts +165 -0
  360. package/core/websocket/explorer.ts +126 -0
  361. package/core/websocket/index.ts +78 -0
  362. package/core/websocket/middleware.ts +146 -0
  363. package/core/websocket/packet-codec.ts +75 -0
  364. package/core/websocket/reliable-delivery.ts +56 -0
  365. package/core/websocket/session-creation.ts +16 -0
  366. package/core/websocket/session-manager.ts +856 -0
  367. package/core/websocket/socket-wrapper.ts +314 -0
  368. package/core/websocket/topic-cache.ts +59 -0
  369. package/core/websocket/types.ts +122 -0
  370. package/core/websocket/websocketModule.ts +96 -0
  371. package/core/websocket/ws-dispatch.ts +123 -0
  372. package/core/websocket/ws-server.ts +1707 -0
  373. package/index.ts +281 -0
  374. package/library/boundary/diagnostics-v1.ts +136 -0
  375. package/library/boundary/index.ts +73 -0
  376. package/library/boundary/json/decodeBoundedJsonV1.ts +518 -0
  377. package/library/boundary/json/limits-v1.ts +46 -0
  378. package/library/boundary/json/normalizeBoundedJsonV1.ts +256 -0
  379. package/library/boundary/json/types-v1.ts +11 -0
  380. package/library/boundary/schema/boundarySchemaHashV1.ts +6 -0
  381. package/library/boundary/schema/collectBoundaryValueDiagnosticsV1.ts +325 -0
  382. package/library/boundary/schema/decodeBoundarySchemaV1.ts +646 -0
  383. package/library/boundary/schema/index.ts +8 -0
  384. package/library/boundary/schema/types-v1.ts +99 -0
  385. package/library/boundary/schema/validateBoundaryValueV1.ts +58 -0
  386. package/library/boundary/serialization/canonicalJsonV1.ts +66 -0
  387. package/library/http-client/HttpClient.ts +969 -0
  388. package/library/http-client/HttpClientFactory.ts +45 -0
  389. package/library/http-client/errors.ts +68 -0
  390. package/library/http-client/index.ts +43 -0
  391. package/library/http-client/interceptors.ts +40 -0
  392. package/library/http-client/progress.ts +62 -0
  393. package/library/http-client/redirectProtocol.ts +6 -0
  394. package/library/http-client/serialize.ts +133 -0
  395. package/library/http-client/types.ts +174 -0
  396. package/library/jsonapi/ListQueryError.ts +25 -0
  397. package/library/jsonapi/ListRequest.ts +37 -0
  398. package/library/jsonapi/decorators.ts +161 -0
  399. package/library/jsonapi/document.ts +124 -0
  400. package/library/jsonapi/index.ts +40 -0
  401. package/library/jsonapi/parseListQuery.ts +291 -0
  402. package/library/jsonapi/types.ts +125 -0
  403. package/library/jwt/JwtEncoder.ts +40 -0
  404. package/library/jwt/JwtKeyRing.ts +164 -0
  405. package/library/jwt/JwtValidator.ts +210 -0
  406. package/library/jwt/TokenIssuer.ts +149 -0
  407. package/library/jwt/TokenService.ts +51 -0
  408. package/library/jwt/base64url.ts +61 -0
  409. package/library/jwt/claims.ts +36 -0
  410. package/library/jwt/errors.ts +44 -0
  411. package/library/jwt/index.ts +59 -0
  412. package/library/jwt/limits.ts +8 -0
  413. package/library/jwt/signing/Hs256Algorithm.ts +62 -0
  414. package/library/jwt/signing/Rs256Algorithm.ts +133 -0
  415. package/library/jwt/signing/SigningAlgorithm.ts +18 -0
  416. package/library/jwt/signing/pem.ts +30 -0
  417. package/library/openapi/codegen.ts +875 -0
  418. package/library/openapi/document.ts +326 -0
  419. package/library/openapi/index.ts +13 -0
  420. package/library/openapi/types.ts +69 -0
  421. package/library/openapi/ui.ts +182 -0
  422. package/library/orm/DatabaseFacade.ts +209 -0
  423. package/library/orm/DbContext.ts +107 -0
  424. package/library/orm/DbContextFactory.ts +25 -0
  425. package/library/orm/DbContextOptions.ts +33 -0
  426. package/library/orm/Metadata/DynamicModelBuilder.ts +497 -0
  427. package/library/orm/Metadata/KeyTuple.ts +36 -0
  428. package/library/orm/Metadata/ModelBuilder.ts +299 -0
  429. package/library/orm/Metadata/OrmModel.ts +80 -0
  430. package/library/orm/Metadata/ValueConverter.ts +57 -0
  431. package/library/orm/Metadata/conventions.ts +54 -0
  432. package/library/orm/Metadata/decorators.ts +396 -0
  433. package/library/orm/Metadata/types.ts +140 -0
  434. package/library/orm/Providers/OwnedStoreCatalog.reader.ts +1802 -0
  435. package/library/orm/Providers/PostgresDialect.ts +234 -0
  436. package/library/orm/Providers/PostgresProvider.ts +1494 -0
  437. package/library/orm/Providers/PostgresSchema.decoder.ts +107 -0
  438. package/library/orm/Providers/limits.ts +29 -0
  439. package/library/orm/Providers/operationDeadline.ts +43 -0
  440. package/library/orm/Providers/ormOwnedStoreRuntime.ts +19 -0
  441. package/library/orm/Providers/ormTransactionRuntime.ts +117 -0
  442. package/library/orm/Providers/propertyConversion.ts +14 -0
  443. package/library/orm/Providers/resolvedForeignKey.ts +16 -0
  444. package/library/orm/Providers/transactionCallbacks.ts +45 -0
  445. package/library/orm/Providers/transactionOutcome.ts +82 -0
  446. package/library/orm/Providers/transactionScopes.ts +78 -0
  447. package/library/orm/Providers/types.ts +207 -0
  448. package/library/orm/Query/DbSet.ts +78 -0
  449. package/library/orm/Query/EntityQuery.ts +247 -0
  450. package/library/orm/Query/ImmediateMutations.ts +226 -0
  451. package/library/orm/Query/IncludeLoader.ts +200 -0
  452. package/library/orm/Query/QueryPlan.ts +55 -0
  453. package/library/orm/Query/SqlTranslator.ts +195 -0
  454. package/library/orm/Query/conditions.ts +127 -0
  455. package/library/orm/Query/materialize.ts +39 -0
  456. package/library/orm/README.md +256 -0
  457. package/library/orm/Repository/README.md +684 -0
  458. package/library/orm/Repository/Repository.ts +68 -0
  459. package/library/orm/Saving/CommandBuilder.ts +139 -0
  460. package/library/orm/Saving/ExecutionStrategy.ts +128 -0
  461. package/library/orm/Saving/SaveExecutor.ts +413 -0
  462. package/library/orm/Saving/applyConventions.ts +64 -0
  463. package/library/orm/Schema/CheckExpression.ts +327 -0
  464. package/library/orm/Schema/ExactSchemaVerifier.ts +168 -0
  465. package/library/orm/Schema/ExpectedSchema.ts +43 -0
  466. package/library/orm/Schema/MigrationRunner.ts +146 -0
  467. package/library/orm/Schema/Migrator.ts +73 -0
  468. package/library/orm/Schema/OrmOwnedStore.ts +123 -0
  469. package/library/orm/Schema/OwnedStoreAdmission.ts +752 -0
  470. package/library/orm/Schema/OwnedStoreCanonical.ts +273 -0
  471. package/library/orm/Schema/OwnedStoreCatalog.ts +1325 -0
  472. package/library/orm/Schema/SafeAdditiveSchema.ts +100 -0
  473. package/library/orm/Schema/SchemaAdmission.ts +62 -0
  474. package/library/orm/Schema/SchemaDiffer.ts +78 -0
  475. package/library/orm/Schema/formatSchemaDifferences.ts +62 -0
  476. package/library/orm/Schema/introspection.ts +52 -0
  477. package/library/orm/Schema/physicalColumnTypes.ts +37 -0
  478. package/library/orm/Schema/tableKey.ts +26 -0
  479. package/library/orm/Tracking/ChangeTracker.ts +448 -0
  480. package/library/orm/Tracking/EntityState.ts +13 -0
  481. package/library/orm/Transactions/OrmTransaction.ts +30 -0
  482. package/library/orm/Transactions/TransactionScopeCoordinator.ts +377 -0
  483. package/library/orm/errors.ts +190 -0
  484. package/library/orm/index.ts +162 -0
  485. package/library/orm/runtime.ts +14 -0
  486. package/library/redaction/index.ts +166 -0
  487. package/library/ui/UiProfile.ts +77 -0
  488. package/library/ui/compiled-v1.ts +131 -0
  489. package/library/ui/compiler/compileUiSurfaceV1.ts +717 -0
  490. package/library/ui/compiler/contracts-v1.ts +25 -0
  491. package/library/ui/compiler/index.ts +12 -0
  492. package/library/ui/compiler/openApiIndex.ts +246 -0
  493. package/library/ui/defineUiProfile.ts +340 -0
  494. package/library/ui/diagnostics-v1.ts +33 -0
  495. package/library/ui/index.ts +129 -0
  496. package/library/ui/profile-v1.ts +191 -0
  497. package/library/ui/serialization.ts +130 -0
  498. package/library/ui/uiProfileAuthoring.ts +289 -0
  499. package/library/ui/uiProfileAuthoringMetadata.ts +78 -0
  500. package/library/validation/MessageRegistry.ts +144 -0
  501. package/library/validation/README.md +62 -0
  502. package/library/validation/RuleCompiler.ts +183 -0
  503. package/library/validation/RuleEngine.ts +401 -0
  504. package/library/validation/SPEC.md +579 -0
  505. package/library/validation/ValidationError.ts +37 -0
  506. package/library/validation/ValidationResult.ts +63 -0
  507. package/library/validation/Validator.ts +101 -0
  508. package/library/validation/index.ts +16 -0
  509. package/library/validation/metadata.ts +69 -0
  510. package/library/validation/modelValidatorAdapter.ts +119 -0
  511. package/library/validation/types/ValidationCode.ts +69 -0
  512. package/library/validation/types/ValidatorOptions.ts +153 -0
  513. 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
+ }