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,93 @@
1
+ import type { HttpContext } from "../HttpContext/HttpContext";
2
+ import { HttpResult } from "./HttpResult";
3
+
4
+ /** Per-action response defaults coming from decorators. */
5
+ export interface ResponseDefaults {
6
+ /** `@HttpCode(...)` value. */
7
+ httpCode?: number;
8
+ /** `@Produces(...)` value. */
9
+ produces?: string;
10
+ }
11
+
12
+ type ResponseBody = ConstructorParameters<typeof Response>[0];
13
+
14
+ function isBinaryBody(body: unknown): body is Blob | Uint8Array | ArrayBuffer | ReadableStream {
15
+ return (
16
+ body instanceof Blob || body instanceof Uint8Array || body instanceof ArrayBuffer || body instanceof ReadableStream
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Turns whatever an action returned into a `Response` with automatic
22
+ * Content-Type negotiation:
23
+ *
24
+ * - `Response` — returned as-is (full manual control);
25
+ * - `HttpResult` — status/headers/body from the helper;
26
+ * - `string` — text/plain (unless `@Produces` says otherwise);
27
+ * - `Blob`/bytes/stream — binary passthrough;
28
+ * - any other object/array/number/boolean — JSON;
29
+ * - `undefined`/`null` — empty body, 204 (or `@HttpCode`).
30
+ *
31
+ * Status priority: result helper > `@HttpCode` > `ctx.res.status(...)` > default.
32
+ * Headers from `ctx.res.header(...)` are merged into every non-raw response.
33
+ */
34
+ export function normalizeResult(result: unknown, ctx: HttpContext, defaults: ResponseDefaults): Response {
35
+ if (result instanceof Response) {
36
+ return result;
37
+ }
38
+
39
+ const builderStatus = ctx.res.statusOverride;
40
+ const builderHeaders = ctx.res.headers;
41
+
42
+ let status: number;
43
+ let body: unknown;
44
+ let contentType: string | undefined = defaults.produces;
45
+ let extraHeaders: readonly [string, string][] | undefined;
46
+
47
+ if (result instanceof HttpResult) {
48
+ status = result.status;
49
+ body = result.body;
50
+ contentType = result.contentType ?? defaults.produces;
51
+ extraHeaders = result.headers;
52
+ } else {
53
+ body = result;
54
+ status = defaults.httpCode ?? builderStatus ?? (result === undefined || result === null ? 204 : 200);
55
+ }
56
+
57
+ const headers = new Headers();
58
+ if (extraHeaders) {
59
+ for (const [name, value] of extraHeaders) {
60
+ headers.append(name, value);
61
+ }
62
+ }
63
+ for (const [name, value] of builderHeaders) {
64
+ headers.append(name, value);
65
+ }
66
+
67
+ let payload: ResponseBody = null;
68
+ if (body === undefined || body === null) {
69
+ payload = null;
70
+ } else if (typeof body === "string") {
71
+ payload = body;
72
+ if (!headers.has("content-type")) {
73
+ headers.set("content-type", contentType ?? "text/plain; charset=utf-8");
74
+ }
75
+ } else if (isBinaryBody(body)) {
76
+ payload = body as ResponseBody;
77
+ if (contentType) {
78
+ headers.set("content-type", contentType);
79
+ }
80
+ } else {
81
+ payload = JSON.stringify(body);
82
+ if (!headers.has("content-type")) {
83
+ headers.set("content-type", contentType ?? "application/json; charset=utf-8");
84
+ }
85
+ }
86
+
87
+ // 204/304 must not carry a body.
88
+ if (status === 204 || status === 304) {
89
+ payload = null;
90
+ }
91
+
92
+ return new Response(payload, { status, headers });
93
+ }
@@ -0,0 +1,70 @@
1
+ import { HttpResult } from "./HttpResult";
2
+
3
+ /** 200 OK. Body optional: object -> JSON, string -> text. */
4
+ export function Ok(body?: unknown): HttpResult {
5
+ return new HttpResult(200, body);
6
+ }
7
+
8
+ /** 201 Created with optional Location header. */
9
+ export function Created(location?: string, body?: unknown): HttpResult {
10
+ return new HttpResult(201, body, location ? [["location", location]] : undefined);
11
+ }
12
+
13
+ /** 202 Accepted. */
14
+ export function Accepted(body?: unknown): HttpResult {
15
+ return new HttpResult(202, body);
16
+ }
17
+
18
+ /** 204 No Content. */
19
+ export function NoContent(): HttpResult {
20
+ return new HttpResult(204);
21
+ }
22
+
23
+ /** 400 Bad Request. */
24
+ export function BadRequest(body?: unknown): HttpResult {
25
+ return new HttpResult(400, body ?? { error: "Bad Request" });
26
+ }
27
+
28
+ /** 401 Unauthorized. */
29
+ export function Unauthorized(body?: unknown): HttpResult {
30
+ return new HttpResult(401, body ?? { error: "Unauthorized" });
31
+ }
32
+
33
+ /** 403 Forbidden. */
34
+ export function Forbidden(body?: unknown): HttpResult {
35
+ return new HttpResult(403, body ?? { error: "Forbidden" });
36
+ }
37
+
38
+ /** 404 Not Found. */
39
+ export function NotFound(body?: unknown): HttpResult {
40
+ return new HttpResult(404, body ?? { error: "Not Found" });
41
+ }
42
+
43
+ /** 409 Conflict. */
44
+ export function Conflict(body?: unknown): HttpResult {
45
+ return new HttpResult(409, body ?? { error: "Conflict" });
46
+ }
47
+
48
+ /** 500 Internal Server Error. */
49
+ export function InternalServerError(body?: unknown): HttpResult {
50
+ return new HttpResult(500, body ?? { error: "Internal Server Error" });
51
+ }
52
+
53
+ /** Redirect: 302 by default, 301 when `permanent`. */
54
+ export function Redirect(location: string, permanent = false): HttpResult {
55
+ return new HttpResult(permanent ? 301 : 302, undefined, [["location", location]]);
56
+ }
57
+
58
+ /** Arbitrary status with a JSON body. */
59
+ export function StatusCode(status: number, body?: unknown): HttpResult {
60
+ return new HttpResult(status, body);
61
+ }
62
+
63
+ /**
64
+ * File response backed by `Bun.file` (zero-copy streaming) or an explicit
65
+ * Blob/bytes. Content-Type is inferred by Bun for files on disk.
66
+ */
67
+ export function File(pathOrBlob: string | Blob | Uint8Array, contentType?: string): HttpResult {
68
+ const body = typeof pathOrBlob === "string" ? Bun.file(pathOrBlob) : pathOrBlob;
69
+ return new HttpResult(200, body, undefined, contentType);
70
+ }
@@ -0,0 +1,201 @@
1
+ import type { HttpMiddleware } from "../Middleware/types";
2
+ import { HttpSetupError } from "../Errors/HttpError";
3
+ import { INVALID } from "../Binding/convert";
4
+ import type { SegmentConstraint, TemplateSegment } from "./template";
5
+
6
+ /** A fully compiled route endpoint: its middleware chain runs the action. */
7
+ export interface RouteAction {
8
+ /** Precompiled pipeline: [server-level, global, controller, route, terminal]. */
9
+ readonly chain: readonly HttpMiddleware[];
10
+ /** Declared API version (diagnostics). */
11
+ readonly version?: string;
12
+ /** Debug name: Controller.method. */
13
+ readonly name: string;
14
+ /** Action-specific body cap in bytes; undefined inherits the server cap. */
15
+ readonly maxBodyBytes?: number;
16
+ }
17
+
18
+ interface RouteEntry {
19
+ readonly action: RouteAction;
20
+ /** Names belong to the selected declaration, never to shared tree edges. */
21
+ readonly bindings: readonly { readonly name: string; readonly index: number }[];
22
+ }
23
+
24
+ /** method -> (versionKey -> entry); "" is unversioned, "*" matches any method. */
25
+ type RouteLeaf = Map<string, Map<string, RouteEntry>>;
26
+
27
+ interface ParamEdge {
28
+ readonly constraint?: SegmentConstraint;
29
+ readonly node: RouteNode;
30
+ }
31
+
32
+ class RouteNode {
33
+ statics?: Map<string, RouteNode>;
34
+ params?: ParamEdge[];
35
+ wildcard?: { leaf: RouteLeaf };
36
+ leaf?: RouteLeaf;
37
+ }
38
+
39
+ export type RouteParamsBag = Record<string, string | number | boolean>;
40
+
41
+ export type MatchResult =
42
+ | { kind: "matched"; action: RouteAction; params: RouteParamsBag }
43
+ | { kind: "method-not-allowed"; allow: readonly string[] }
44
+ | { kind: "unsupported-version"; supported: readonly string[] }
45
+ | { kind: "not-found" };
46
+
47
+ /**
48
+ * Radix-style route tree. Segments are matched with priority
49
+ * static > constrained param > param > wildcard, with backtracking, so the
50
+ * most specific route always wins regardless of registration order.
51
+ * Captures are indexed by path position and named only after method/version
52
+ * selection, so overlapping declarations cannot change each other's params.
53
+ */
54
+ export class Router {
55
+ private readonly root = new RouteNode();
56
+
57
+ /** Registers a parsed template. Duplicate (path, method, version) -> startup error. */
58
+ register(segments: readonly TemplateSegment[], httpMethod: string, versionKey: string, action: RouteAction): void {
59
+ const entry: RouteEntry = {
60
+ action,
61
+ bindings: segments.flatMap((segment, index) => segment.kind === "static" ? [] : [{ name: segment.name, index }]),
62
+ };
63
+ let node = this.root;
64
+ for (let index = 0; index < segments.length; index += 1) {
65
+ const segment = segments[index]!;
66
+ if (segment.kind === "static") {
67
+ node.statics ??= new Map();
68
+ let child = node.statics.get(segment.value);
69
+ if (!child) {
70
+ child = new RouteNode();
71
+ node.statics.set(segment.value, child);
72
+ }
73
+ node = child;
74
+ } else if (segment.kind === "param") {
75
+ node.params ??= [];
76
+ let edge = node.params.find(
77
+ (candidate) => candidate.constraint?.name === segment.constraint?.name,
78
+ );
79
+ if (!edge) {
80
+ edge = { constraint: segment.constraint, node: new RouteNode() };
81
+ node.params.push(edge);
82
+ // Keep overlap resolution deterministic: constrained before plain,
83
+ // and integer before the broader number constraint.
84
+ node.params.sort((left, right) => constraintPriority(right.constraint) - constraintPriority(left.constraint));
85
+ }
86
+ node = edge.node;
87
+ } else {
88
+ node.wildcard ??= { leaf: new Map() };
89
+ this.addToLeaf(node.wildcard.leaf, httpMethod, versionKey, entry);
90
+ return;
91
+ }
92
+ }
93
+ node.leaf ??= new Map();
94
+ this.addToLeaf(node.leaf, httpMethod, versionKey, entry);
95
+ }
96
+
97
+ private addToLeaf(leaf: RouteLeaf, httpMethod: string, versionKey: string, entry: RouteEntry): void {
98
+ let versions = leaf.get(httpMethod);
99
+ if (!versions) {
100
+ versions = new Map();
101
+ leaf.set(httpMethod, versions);
102
+ }
103
+ if (versions.has(versionKey)) {
104
+ throw new HttpSetupError(
105
+ `Duplicate route: ${httpMethod} (version "${versionKey || "-"}") already mapped to ${versions.get(versionKey)!.action.name}, cannot map ${entry.action.name}.`,
106
+ );
107
+ }
108
+ versions.set(versionKey, entry);
109
+ }
110
+
111
+ /**
112
+ * @param requestedVersion Version resolved from the request (query/header
113
+ * sources) or `undefined`. URL-source versions are part of the path.
114
+ */
115
+ match(httpMethod: string, segments: readonly string[], requestedVersion?: string): MatchResult {
116
+ const captures: (string | number | boolean)[] = [];
117
+ const leaf = this.descend(this.root, segments, 0, captures);
118
+ if (!leaf) {
119
+ return { kind: "not-found" };
120
+ }
121
+ // HEAD falls back to a GET handler (the server strips the body); explicit
122
+ // HEAD routes still win. "*" (@All) matches any method last.
123
+ const versions =
124
+ leaf.get(httpMethod) ?? (httpMethod === "HEAD" ? leaf.get("GET") : undefined) ?? leaf.get("*");
125
+ if (!versions) {
126
+ const allow = [...leaf.keys()].filter((method) => method !== "*");
127
+ if (allow.includes("GET") && !allow.includes("HEAD")) {
128
+ allow.push("HEAD");
129
+ }
130
+ return { kind: "method-not-allowed", allow };
131
+ }
132
+ const entry =
133
+ requestedVersion !== undefined ? (versions.get(requestedVersion) ?? versions.get("")) : versions.get("");
134
+ if (!entry) {
135
+ return { kind: "unsupported-version", supported: [...versions.keys()].filter((key) => key !== "") };
136
+ }
137
+ const params: RouteParamsBag = Object.create(null) as RouteParamsBag;
138
+ for (const { name, index } of entry.bindings) {
139
+ params[name] = captures[index]!;
140
+ }
141
+ return { kind: "matched", action: entry.action, params };
142
+ }
143
+
144
+ private descend(node: RouteNode, segments: readonly string[], index: number, captures: (string | number | boolean)[]): RouteLeaf | undefined {
145
+ if (index === segments.length) {
146
+ if (node.leaf) {
147
+ return node.leaf;
148
+ }
149
+ // A trailing wildcard also matches the empty rest ("files" -> "files/*path").
150
+ if (node.wildcard) {
151
+ captures[index] = "";
152
+ return node.wildcard.leaf;
153
+ }
154
+ return undefined;
155
+ }
156
+ const segment = segments[index]!;
157
+
158
+ const staticChild = node.statics?.get(segment);
159
+ if (staticChild) {
160
+ const leaf = this.descend(staticChild, segments, index + 1, captures);
161
+ if (leaf) {
162
+ return leaf;
163
+ }
164
+ }
165
+
166
+ if (node.params) {
167
+ for (const edge of node.params) {
168
+ const value = edge.constraint ? edge.constraint.convert(segment) : segment;
169
+ if (value === INVALID) {
170
+ continue;
171
+ }
172
+ captures[index] = value;
173
+ const leaf = this.descend(edge.node, segments, index + 1, captures);
174
+ if (leaf) {
175
+ return leaf;
176
+ }
177
+ }
178
+ }
179
+
180
+ if (node.wildcard) {
181
+ captures[index] = segments.slice(index).join("/");
182
+ return node.wildcard.leaf;
183
+ }
184
+ return undefined;
185
+ }
186
+ }
187
+
188
+ function constraintPriority(constraint: SegmentConstraint | undefined): number {
189
+ switch (constraint?.name) {
190
+ case "int":
191
+ return 30;
192
+ case "uuid":
193
+ case "bool":
194
+ case "alpha":
195
+ return 20;
196
+ case "number":
197
+ return 10;
198
+ default:
199
+ return 0;
200
+ }
201
+ }
@@ -0,0 +1,184 @@
1
+ import type { Class, Token } from "../../di";
2
+ import { bindArguments } from "../Binding/ParameterBinder";
3
+ import { resolveGeneratedBindings } from "../Binding/autoBindings";
4
+ import { controllerMetaOf, type ActionMeta, type ControllerMeta } from "../Decorators/metadata";
5
+ import { HttpSetupError } from "../Errors/HttpError";
6
+ import type { HttpContext } from "../HttpContext/HttpContext";
7
+ import type { ActionFilterHooks, HttpMiddleware } from "../Middleware/types";
8
+ import { normalizeResult } from "../Results/normalize";
9
+ import type { ApiVersioningOptions, RouteMiddlewareComposer } from "../options";
10
+ import { createAuthorizeComposer } from "../Authorization/authorizeComposer";
11
+ import { Router, type RouteAction } from "./Router";
12
+ import { joinPaths, parseTemplate } from "./template";
13
+
14
+ type ControllerInstance = Record<string | symbol, (...args: unknown[]) => unknown>;
15
+
16
+ interface ActionPipeline {
17
+ /** Binding and ActionFilter.before: boundary checks that must also run on cache hits. */
18
+ readonly boundary: HttpMiddleware;
19
+ /** Controller/action execution and ActionFilter.after: skipped when middleware short-circuits. */
20
+ readonly terminal: HttpMiddleware;
21
+ }
22
+
23
+ /**
24
+ * Builds the radix router from controller metadata. Runs once at server
25
+ * startup: templates are parsed and the action boundary/terminal handlers are
26
+ * precompiled per route — request dispatch performs no metadata work at all.
27
+ */
28
+ export class RouterBuilder {
29
+ private readonly authorizeComposer = createAuthorizeComposer();
30
+
31
+ constructor(
32
+ /** Middleware prepended to every route: [logger?, cors?, errorHandler, ...global]. */
33
+ private readonly serverChain: readonly HttpMiddleware[],
34
+ private readonly globalPrefix: string | undefined,
35
+ private readonly versioning: ApiVersioningOptions | undefined,
36
+ private readonly routeMiddlewareComposer?: RouteMiddlewareComposer,
37
+ ) {}
38
+
39
+ build(controllers: readonly Class<object>[]): Router {
40
+ const router = new Router();
41
+ for (const controllerClass of controllers) {
42
+ const meta = controllerMetaOf(controllerClass);
43
+ if (!meta?.isController) {
44
+ throw new HttpSetupError(
45
+ `Class "${controllerClass.name}" is not a controller. Add @Controller() and route decorators.`,
46
+ );
47
+ }
48
+ this.registerController(router, controllerClass, meta);
49
+ }
50
+ return router;
51
+ }
52
+
53
+ private registerController(router: Router, controllerClass: Class<object>, meta: ControllerMeta): void {
54
+ const useUrlVersioning = this.versioning === undefined || this.versioning.source === "url";
55
+ for (const [methodName, action] of meta.actions) {
56
+ if (action.routes.length === 0) {
57
+ continue;
58
+ }
59
+ if (action.maxBodyBytes !== undefined
60
+ && (!Number.isSafeInteger(action.maxBodyBytes) || action.maxBodyBytes <= 0)) {
61
+ throw new HttpSetupError("HTTP route maxBodyBytes metadata must be a positive safe integer.");
62
+ }
63
+ const version = action.version ?? meta.version;
64
+ const pipeline = this.createActionPipeline(controllerClass, methodName, meta, action);
65
+ const authorization = this.authorizeComposer(controllerClass, methodName, meta, action);
66
+ const composed = this.routeMiddlewareComposer?.(controllerClass, methodName, meta, action) ?? [];
67
+ // Authorization must precede controller middleware so unauthenticated
68
+ // requests cannot trigger controller-level work or side effects. Route
69
+ // and method policies plus ActionFilter.before form the public action
70
+ // boundary and therefore run before short-circuiting composers such as
71
+ // output cache. The controller and action still remain lazy on a hit.
72
+ const chain = [
73
+ ...this.serverChain,
74
+ ...authorization,
75
+ ...meta.middleware,
76
+ ...action.middleware,
77
+ pipeline.boundary,
78
+ ...composed,
79
+ pipeline.terminal,
80
+ ];
81
+ const routeAction: RouteAction = {
82
+ chain,
83
+ version,
84
+ name: `${controllerClass.name}.${String(methodName)}`,
85
+ maxBodyBytes: action.maxBodyBytes,
86
+ };
87
+ for (const declaration of action.routes) {
88
+ const versionSegment = useUrlVersioning && version !== undefined ? `v${version}` : undefined;
89
+ const fullPath = joinPaths(this.globalPrefix, versionSegment, meta.prefix, declaration.template);
90
+ const versionKey = useUrlVersioning ? "" : (version ?? "");
91
+ router.register(parseTemplate(fullPath), declaration.httpMethod, versionKey, routeAction);
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Precompiles binding + ActionFilter.before as a boundary middleware and
98
+ * controller execution + ActionFilter.after as the terminal. This lets
99
+ * output cache short-circuit only the latter. Errors first get a chance at
100
+ * the controller's @Catch handlers, then bubble to the global error handler.
101
+ */
102
+ private createActionPipeline(
103
+ controllerClass: Class<object>,
104
+ methodName: string | symbol,
105
+ meta: ControllerMeta,
106
+ action: ActionMeta,
107
+ ): ActionPipeline {
108
+ const filters: readonly ActionFilterHooks[] = [...meta.filters, ...action.filters];
109
+ const catches = meta.catches;
110
+ const bindings = resolveGeneratedBindings(controllerClass, methodName);
111
+ const responseDefaults = { httpCode: action.httpCode, produces: action.produces };
112
+ const bindingDefaults = { consumes: action.consumes };
113
+ const token = controllerClass as unknown as Token<object>;
114
+ const boundArguments = new WeakMap<HttpContext, readonly unknown[]>();
115
+
116
+ const handleError = async (
117
+ error: unknown,
118
+ ctx: HttpContext,
119
+ resolvedController?: ControllerInstance,
120
+ ): Promise<boolean> => {
121
+ if (catches.length === 0) {
122
+ return false;
123
+ }
124
+ const controller = resolvedController ?? ctx.services.resolve(token) as ControllerInstance;
125
+ for (const handler of catches) {
126
+ if (handler.errorType !== undefined && !(error instanceof handler.errorType)) {
127
+ continue;
128
+ }
129
+ const handled: unknown = await controller[handler.methodName]!(error, ctx);
130
+ if (handled !== undefined) {
131
+ ctx.response = normalizeResult(handled, ctx, {});
132
+ return true;
133
+ }
134
+ }
135
+ return false;
136
+ };
137
+
138
+ const boundary: HttpMiddleware = async (ctx, next) => {
139
+ try {
140
+ const args = bindings ? await bindArguments(bindings, ctx, bindingDefaults) : [ctx];
141
+ for (const filter of filters) {
142
+ await filter.before?.(ctx);
143
+ }
144
+ boundArguments.set(ctx, args);
145
+ } catch (error) {
146
+ if (await handleError(error, ctx)) {
147
+ return;
148
+ }
149
+ throw error;
150
+ }
151
+
152
+ try {
153
+ await next();
154
+ } finally {
155
+ boundArguments.delete(ctx);
156
+ }
157
+ };
158
+
159
+ const terminal: HttpMiddleware = async (ctx: HttpContext) => {
160
+ const args = boundArguments.get(ctx);
161
+ if (args === undefined) {
162
+ throw new Error(`Action boundary was not executed for ${controllerClass.name}.${String(methodName)}`);
163
+ }
164
+ const controller = ctx.services.resolve(token) as ControllerInstance;
165
+ try {
166
+ let result: unknown = await controller[methodName]!(...args);
167
+ for (let index = filters.length - 1; index >= 0; index -= 1) {
168
+ const replaced = await filters[index]!.after?.(ctx, result);
169
+ if (replaced !== undefined) {
170
+ result = replaced;
171
+ }
172
+ }
173
+ ctx.response = normalizeResult(result, ctx, responseDefaults);
174
+ } catch (error) {
175
+ if (await handleError(error, ctx, controller)) {
176
+ return;
177
+ }
178
+ throw error;
179
+ }
180
+ };
181
+
182
+ return { boundary, terminal };
183
+ }
184
+ }
@@ -0,0 +1,112 @@
1
+ import { HttpSetupError } from "../Errors/HttpError";
2
+ import { INVALID, tryConvert } from "../Binding/convert";
3
+
4
+ /** Route constraint: validates and converts a path segment. */
5
+ export interface SegmentConstraint {
6
+ readonly name: string;
7
+ convert(segment: string): string | number | boolean | typeof INVALID;
8
+ }
9
+
10
+ const UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
+
12
+ const CONSTRAINTS: Record<string, SegmentConstraint> = {
13
+ int: { name: "int", convert: (s) => tryConvert(s, "int") },
14
+ number: { name: "number", convert: (s) => tryConvert(s, "number") },
15
+ bool: { name: "bool", convert: (s) => tryConvert(s, "bool") },
16
+ uuid: { name: "uuid", convert: (s) => (UUID_SEGMENT.test(s) ? s : INVALID) },
17
+ alpha: { name: "alpha", convert: (s) => (/^[a-z]+$/i.test(s) ? s : INVALID) },
18
+ };
19
+
20
+ export type TemplateSegment =
21
+ | { readonly kind: "static"; readonly value: string }
22
+ | { readonly kind: "param"; readonly name: string; readonly constraint?: SegmentConstraint }
23
+ | { readonly kind: "wildcard"; readonly name: string };
24
+
25
+ const PARAM_PATTERN = /^:([A-Za-z_][A-Za-z0-9_]*)(?:\(([a-z]+)\))?$/;
26
+ const FORBIDDEN_PARAM_NAMES = new Set(["__proto__", "constructor", "prototype"]);
27
+
28
+ /** Joins path fragments into one normalized template ("a", "/b/" -> "a/b"). */
29
+ export function joinPaths(...fragments: (string | undefined)[]): string {
30
+ const parts: string[] = [];
31
+ for (const fragment of fragments) {
32
+ if (!fragment) {
33
+ continue;
34
+ }
35
+ for (const piece of fragment.split("/")) {
36
+ if (piece !== "") {
37
+ parts.push(piece);
38
+ }
39
+ }
40
+ }
41
+ return parts.join("/");
42
+ }
43
+
44
+ /**
45
+ * Parses a route template into segments. Fails fast (at startup) on bad
46
+ * templates: unknown constraints, wildcard not in last position, parameter
47
+ * names that could pollute prototypes.
48
+ */
49
+ export function parseTemplate(template: string): TemplateSegment[] {
50
+ const segments: TemplateSegment[] = [];
51
+ const rawSegments = template.split("/").filter((segment) => segment !== "");
52
+ for (let index = 0; index < rawSegments.length; index += 1) {
53
+ const raw = rawSegments[index]!;
54
+ if (raw.startsWith(":")) {
55
+ const match = PARAM_PATTERN.exec(raw);
56
+ if (!match) {
57
+ throw new HttpSetupError(`Invalid route parameter "${raw}" in template "${template}".`);
58
+ }
59
+ const name = match[1]!;
60
+ if (FORBIDDEN_PARAM_NAMES.has(name)) {
61
+ throw new HttpSetupError(`Forbidden route parameter name "${name}" in template "${template}".`);
62
+ }
63
+ let constraint: SegmentConstraint | undefined;
64
+ if (match[2] !== undefined) {
65
+ constraint = CONSTRAINTS[match[2]];
66
+ if (!constraint) {
67
+ throw new HttpSetupError(
68
+ `Unknown route constraint "(${match[2]})" in template "${template}". Known: ${Object.keys(CONSTRAINTS).join(", ")}.`,
69
+ );
70
+ }
71
+ }
72
+ segments.push({ kind: "param", name, constraint });
73
+ } else if (raw.startsWith("*")) {
74
+ if (index !== rawSegments.length - 1) {
75
+ throw new HttpSetupError(`Wildcard "*" must be the last segment in template "${template}".`);
76
+ }
77
+ const name = raw.length > 1 ? raw.slice(1) : "rest";
78
+ if (FORBIDDEN_PARAM_NAMES.has(name)) {
79
+ throw new HttpSetupError(`Forbidden wildcard name "${name}" in template "${template}".`);
80
+ }
81
+ segments.push({ kind: "wildcard", name });
82
+ } else {
83
+ segments.push({ kind: "static", value: raw });
84
+ }
85
+ }
86
+ return segments;
87
+ }
88
+
89
+ /**
90
+ * Splits and decodes a request path into segments.
91
+ * Returns `undefined` for malformed/unsafe paths (bad percent-encoding,
92
+ * `..` traversal attempts) — the server answers 400.
93
+ */
94
+ export function parseRequestPath(pathname: string): string[] | undefined {
95
+ const segments: string[] = [];
96
+ for (const raw of pathname.split("/")) {
97
+ if (raw === "") {
98
+ continue;
99
+ }
100
+ let decoded: string;
101
+ try {
102
+ decoded = decodeURIComponent(raw);
103
+ } catch {
104
+ return undefined;
105
+ }
106
+ if (decoded === ".." || decoded === "." || decoded.includes("\0")) {
107
+ return undefined;
108
+ }
109
+ segments.push(decoded);
110
+ }
111
+ return segments;
112
+ }