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,1837 @@
1
+ import path from "node:path";
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { lstat, mkdir, readdir, rename, rm } from "node:fs/promises";
4
+ import ts from "typescript";
5
+ import {
6
+ createOpenApiCodegenAnalyzer,
7
+ type OpenApiCodegenOperationSpec,
8
+ type OpenApiCodegenSchema,
9
+ } from "../../library/openapi/codegen";
10
+ import { generateAgentMetadataCatalog, type AgentCodegenSource } from "./agent-codegen";
11
+ import {
12
+ analyzeRequestModelHydration,
13
+ type RequestModelHydration,
14
+ } from "./request-model-codegen";
15
+ import { collectTargetReachability, normalizeConfiguredPath, readCodegenConfig } from "./di-generate-target";
16
+ import { analyzeOrmPredicates } from "./orm-predicate-codegen";
17
+ import { hashSources } from "../generatedFingerprint";
18
+ const FRAMEWORK_DIR = realpathSync(path.resolve(import.meta.dir, "../.."));
19
+ /**
20
+ * Project-relative framework source when codegen runs in the framework's own
21
+ * checkout (`src/bazis` there); null when the framework is an installed
22
+ * dependency (node_modules/bazis, vendor/bazis) and not part of the project sources.
23
+ */
24
+ const FRAMEWORK_SOURCE = ((): string | null => {
25
+ const relative = path.relative(realpathSync(process.cwd()), FRAMEWORK_DIR).replaceAll("\\", "/");
26
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
27
+ return /(^|\/)(node_modules|vendor)(\/|$)/.test(relative) ? null : relative;
28
+ }
29
+ // A checkout linked into the project (src/bazis -> framework) is still its source.
30
+ try { return realpathSync("src/bazis") === FRAMEWORK_DIR ? "src/bazis" : null; } catch { return null; }
31
+ })();
32
+ const FRAMEWORK_ROOT = FRAMEWORK_SOURCE ?? "src/bazis";
33
+ const CORE_AGENT_CATALOG_FILE = `${FRAMEWORK_ROOT}/core/agent/generated/catalog.ts`;
34
+ const CORE_AGENT_GENERATED_DIR = `${FRAMEWORK_ROOT}/core/agent/generated`;
35
+ const isFrameworkSourcePath = (file: string): boolean => FRAMEWORK_SOURCE !== null && file.startsWith(`${FRAMEWORK_SOURCE}/`);
36
+ const APP_GENERATED_DIR = "src/generated/bazis";
37
+ let activeGeneratedDir = APP_GENERATED_DIR;
38
+ const plannedWrites = new Map<string, string>();
39
+ const plannedRemovals = new Set<string>();
40
+
41
+ // Test-only fault injection for the generator's private disk transaction. It
42
+ // deliberately lives at the IO boundary so production analysis/rendering has
43
+ // no alternate path or exported surface.
44
+ let renameCount = 0;
45
+ async function renameGeneratedFile(from: string, to: string): Promise<void> {
46
+ renameCount += 1;
47
+ const failAt = process.env.BAZIS_CODEGEN_TEST_FAIL_RENAME_AT;
48
+ if (failAt !== undefined && Number(failAt) === renameCount) {
49
+ throw new Error(`BAZIS_CODEGEN_TEST_RENAME_FAILURE:${renameCount}`);
50
+ }
51
+ await rename(from, to);
52
+ }
53
+
54
+ function stageWrite(filePath: string, content: string): void {
55
+ plannedWrites.set(filePath, content);
56
+ }
57
+
58
+ function stageRemove(filePath: string): void {
59
+ plannedRemovals.add(filePath);
60
+ }
61
+
62
+ async function commitPlannedWrites(): Promise<void> {
63
+ const transaction = `.bazis-codegen-stage-${crypto.randomUUID()}`;
64
+ const staged: { readonly final: string; readonly staged: string; readonly backup: string; hadFinal: boolean }[] = [];
65
+ const removed: { readonly final: string; readonly backup: string }[] = [];
66
+ renameCount = 0;
67
+ try {
68
+ for (const [final, content] of plannedWrites) {
69
+ const stagedFile = `${transaction}/${final}`;
70
+ await mkdir(path.dirname(stagedFile), { recursive: true });
71
+ await Bun.write(stagedFile, content);
72
+ staged.push({ final, staged: stagedFile, backup: `${transaction}/backup/${final}`, hadFinal: await pathExists(final) });
73
+ }
74
+ for (const final of plannedRemovals) {
75
+ if (!await pathExists(final)) continue;
76
+ const backup = `${transaction}/removed/${final}`;
77
+ await mkdir(path.dirname(backup), { recursive: true });
78
+ await renameGeneratedFile(final, backup);
79
+ removed.push({ final, backup });
80
+ }
81
+ for (const entry of staged) {
82
+ await mkdir(path.dirname(entry.final), { recursive: true });
83
+ if (entry.hadFinal) {
84
+ await mkdir(path.dirname(entry.backup), { recursive: true });
85
+ await renameGeneratedFile(entry.final, entry.backup);
86
+ }
87
+ await renameGeneratedFile(entry.staged, entry.final);
88
+ }
89
+ } catch (error) {
90
+ for (const entry of [...staged].reverse()) {
91
+ if (await pathExists(entry.backup)) {
92
+ if (await pathExists(entry.final)) await rm(entry.final, { recursive: true, force: true });
93
+ await mkdir(path.dirname(entry.final), { recursive: true });
94
+ await renameGeneratedFile(entry.backup, entry.final);
95
+ } else if (!entry.hadFinal && await pathExists(entry.final)) {
96
+ await rm(entry.final, { recursive: true, force: true });
97
+ }
98
+ }
99
+ for (const entry of [...removed].reverse()) {
100
+ if (!await pathExists(entry.backup)) continue;
101
+ if (await pathExists(entry.final)) await rm(entry.final, { recursive: true, force: true });
102
+ await mkdir(path.dirname(entry.final), { recursive: true });
103
+ await renameGeneratedFile(entry.backup, entry.final);
104
+ }
105
+ throw error;
106
+ } finally {
107
+ await rm(transaction, { recursive: true, force: true });
108
+ }
109
+ }
110
+
111
+ async function pathExists(filePath: string): Promise<boolean> {
112
+ try {
113
+ await lstat(filePath);
114
+ return true;
115
+ } catch (error) {
116
+ if ((error as { readonly code?: string }).code === "ENOENT") return false;
117
+ throw error;
118
+ }
119
+ }
120
+
121
+ async function planStaleGeneratedTargetRemoval(configuredNames: readonly string[]): Promise<void> {
122
+ const targetsDirectory = `${APP_GENERATED_DIR}/targets`;
123
+ if (!await pathExists(targetsDirectory)) return;
124
+ const configured = new Set(configuredNames);
125
+ for (const entry of await readdir(targetsDirectory, { withFileTypes: true })) {
126
+ if (!entry.isDirectory()) continue;
127
+ if (!/^[a-z][a-z0-9-]*$/.test(entry.name)) {
128
+ throw new Error(`BAZIS_CODEGEN_GENERATED_TARGET_INVALID: ${entry.name}`);
129
+ }
130
+ if (!configured.has(entry.name)) stageRemove(`${targetsDirectory}/${entry.name}`);
131
+ }
132
+ }
133
+
134
+ function readRequestedTarget(): string | undefined {
135
+ const args = Bun.argv.slice(2);
136
+ if (args.length === 0) return undefined;
137
+ if (args.length !== 2 || args[0] !== "--target") {
138
+ throw new Error("BAZIS_CODEGEN_CONFIG_INVALID: expected --target <name|all>");
139
+ }
140
+ const target = args[1];
141
+ if (target && (target === "all" || /^[a-z][a-z0-9-]*$/.test(target))) return target;
142
+ throw new Error(`BAZIS_CODEGEN_TARGET_UNKNOWN: ${target ?? ""}`);
143
+ }
144
+
145
+ // Framework internals: their classes are excluded from the auto-deps map (they
146
+ // wire their own services explicitly). Auto-deps magic targets user code only.
147
+ const FRAMEWORK_INTERNAL_PREFIXES = FRAMEWORK_SOURCE === null ? [] : [
148
+ "core/di/", "core/kernel/", "core/http/", "core/grpc/", "core/http-client/", "core/background/",
149
+ "core/agent/", "core/websocket/", "core/cache/", "library/validation/", "library/boundary/",
150
+ "library/orm/", "library/jwt/", "library/jsonapi/", "library/openapi/", "core/orm/", "core/infra/",
151
+ "core/app/", "library/http-client/",
152
+ ].map((prefix) => `${FRAMEWORK_SOURCE}/${prefix}`);
153
+
154
+ // A declaration is an identity; short names are local to their source/module.
155
+ interface CollectedDependency { readonly name: string; readonly target?: ts.ClassDeclaration; readonly lazy?: boolean; }
156
+ interface ConstructorDependencies {
157
+ readonly deps: CollectedDependency[];
158
+ readonly unsupportedInheritedParameter?: string;
159
+ }
160
+ let classDeps = new Map<ts.ClassDeclaration, readonly CollectedDependency[]>();
161
+ let inferredDiClasses = new Set<ts.ClassDeclaration>();
162
+ let explicitlyBoundDiClasses = new Set<ts.ClassDeclaration>();
163
+ // Known runtime token names: createToken descriptions + class names usable as class-tokens.
164
+ let tokenDescriptions = new Set<string>();
165
+ let knownClassNames = new Set<string>();
166
+ let warnings: string[] = [];
167
+ let fatalErrors: string[] = [];
168
+
169
+ // HTTP binding conventions inferred from controller method signatures.
170
+ interface HttpBindingSpec {
171
+ source: "route" | "query" | "body" | "context" | "request" | "response" | "list";
172
+ name?: string;
173
+ type?: "int" | "number" | "bool" | "string";
174
+ optional?: boolean;
175
+ model?: string;
176
+ }
177
+ let httpBindings: Record<string, Record<string, HttpBindingSpec[]>> = {};
178
+ interface GrpcRequestBinding {
179
+ readonly method: string;
180
+ readonly model: ts.ClassDeclaration;
181
+ readonly requestStream: boolean;
182
+ }
183
+ let grpcBindings = new Map<ts.ClassDeclaration, GrpcRequestBinding[]>();
184
+ let httpControllerFiles: Record<string, string> = {};
185
+ let openApiSchemas: Record<string, OpenApiCodegenSchema> = {};
186
+ let openApiOperations: Record<string, Record<string, OpenApiCodegenOperationSpec>> = {};
187
+ interface OpenApiSchemaModelImport {
188
+ readonly declaration: ts.ClassDeclaration;
189
+ readonly schemaName: string;
190
+ }
191
+ let uiProfileResponseModels = new Map<ts.ClassDeclaration, string>();
192
+ let openApiSchemaModels = new Map<ts.ClassDeclaration, string>();
193
+ let requestModelClassNames = new Set<string>();
194
+ // Names of classes extending `ListRequest` — bound as universal list-query
195
+ // params (source "list"), not as request bodies. Collected in a pre-pass so the
196
+ // decision is independent of file scan order.
197
+ let listRequestClassNames = new Set<string>();
198
+ // Declaration file of every scanned class (by name). Used to auto-import body
199
+ // models into the generated registry. A name seen in two files is ambiguous.
200
+ let classFilesByName = new Map<string, string>();
201
+ let classDeclarationsByName = new Map<string, ts.ClassDeclaration>();
202
+ let ambiguousClassNames = new Set<string>();
203
+ // Exact roots that do not need convention name resolution: @RequestModel()
204
+ // classes used by generated transport schemas.
205
+ let explicitRequestModelRoots = new Set<ts.ClassDeclaration>();
206
+ let agentCodegenSources: AgentCodegenSource[] = [];
207
+
208
+ const ROUTE_DECORATORS = new Set(["Get", "Post", "Put", "Patch", "Delete", "Options", "Head", "All"]);
209
+ const CODEGEN_CHANNEL_DECORATORS = new Set(["Controller", "GrpcController", "Agent", "Tool", "Prompt", "RequestModel", "UiProfile"]);
210
+ const PRIMITIVE_QUERY_TYPES: Record<string, HttpBindingSpec["type"]> = {
211
+ string: "string",
212
+ number: "number",
213
+ boolean: "bool",
214
+ };
215
+ const CONTEXT_TYPES: Record<string, HttpBindingSpec["source"]> = {
216
+ HttpContext: "context",
217
+ Request: "request",
218
+ ResponseBuilder: "response",
219
+ };
220
+ let program: ts.Program;
221
+ let programFiles = new Map<string, ts.SourceFile>();
222
+ // Framework sources outside the scanned project (node_modules/bazis, vendor/bazis).
223
+ // Only their class and token names are read, so app constructor dependencies on
224
+ // framework classes are not discarded as unknown; their own wiring ships with them.
225
+ let installedFrameworkSources: ts.SourceFile[] = [];
226
+ let sourceFilePathSet = new Set<string>();
227
+ let checker: ts.TypeChecker;
228
+ let sourceEntries: { filePath: string; source: ts.SourceFile }[] = [];
229
+ let openApiCodegen: ReturnType<typeof createOpenApiCodegenAnalyzer>;
230
+
231
+ // TypeScript / platform types that appear in constructors but are never DI tokens.
232
+ const BUILTIN_DEPENDENCY_NAMES = new Set([
233
+ "Array",
234
+ "Map",
235
+ "Partial",
236
+ "Promise",
237
+ "Readonly",
238
+ "ReadonlyArray",
239
+ "ReadonlyMap",
240
+ "Record",
241
+ "Required",
242
+ "Set",
243
+ ]);
244
+
245
+ function isScannableFile(filePath: string): boolean {
246
+ if (filePath.startsWith("../") || path.isAbsolute(filePath)) return false;
247
+ if (!filePath.endsWith(".ts") || filePath.endsWith(".d.ts") || /\.(test|spec)\.ts$/.test(filePath)) return false;
248
+ if (
249
+ filePath === CORE_AGENT_CATALOG_FILE ||
250
+ filePath.startsWith(`${APP_GENERATED_DIR}/`)
251
+ ) {
252
+ return false;
253
+ }
254
+ return !/(^|\/)(test|tests|__tests__|fixture|fixtures|__fixtures__|generated|node_modules|vendor|dist|build|bin|coverage|examples)(\/|$)/.test(filePath) && !filePath.startsWith("admin-ui/");
255
+ }
256
+
257
+ function isInstalledFrameworkSource(source: ts.SourceFile): boolean {
258
+ if (source.isDeclarationFile) return false;
259
+ let file: string;
260
+ try { file = realpathSync(source.fileName); } catch { return false; }
261
+ return file.startsWith(`${FRAMEWORK_DIR}${path.sep}`) && !/[\\/](test|tests|__tests__)[\\/]|\.(test|spec)\.ts$/.test(file);
262
+ }
263
+
264
+ function createTypeScriptProgram(): ts.Program {
265
+ const configPath = ts.findConfigFile(".", ts.sys.fileExists, "tsconfig.json");
266
+ if (configPath === undefined) {
267
+ return ts.createProgram([], {
268
+ target: ts.ScriptTarget.ESNext,
269
+ module: ts.ModuleKind.ESNext,
270
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
271
+ skipLibCheck: true,
272
+ noEmit: true,
273
+ });
274
+ }
275
+ const config = ts.readConfigFile(configPath, ts.sys.readFile);
276
+ const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
277
+ return ts.createProgram(parsed.fileNames, {
278
+ ...parsed.options,
279
+ noEmit: true,
280
+ });
281
+ }
282
+
283
+ function duplicateOpenApiSchemaName(input: { readonly name: string; readonly filePath: string }): string {
284
+ const normalized = input.filePath.replaceAll("\\", "/");
285
+ const parts = normalized.split("/");
286
+ const appIndex = parts.findIndex((part, index) => part === "modules" && parts[index - 1] === "app");
287
+ if (appIndex >= 0 && parts[appIndex + 1] !== undefined) {
288
+ return `App${toPascalIdentifier(parts[appIndex + 1] as string)}${input.name}`;
289
+ }
290
+ const context = parts.slice(Math.max(0, parts.length - 3), Math.max(0, parts.length - 1)).map(toPascalIdentifier).join("");
291
+ return context.length === 0 ? input.name : `${context}${input.name}`;
292
+ }
293
+
294
+ function toPascalIdentifier(value: string): string {
295
+ return value
296
+ .replace(/\.[^.]+$/, "")
297
+ .split(/[^A-Za-z0-9]+/)
298
+ .filter((part) => part.length > 0)
299
+ .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
300
+ .join("");
301
+ }
302
+
303
+ await runConfiguredTargets();
304
+
305
+ async function runConfiguredTargets(): Promise<void> {
306
+ const totalStartedAt = performance.now();
307
+ const configStartedAt = performance.now();
308
+ const config = await readCodegenConfig();
309
+ await planStaleGeneratedTargetRemoval(Object.keys(config.targets));
310
+ // Test sources remain excluded from the broad candidate scan. A test-only
311
+ // target can still opt a single fixture in explicitly through its declared
312
+ // entrypoint/application part, and receives the same isolated descriptor as
313
+ // every other target.
314
+ const explicitTargetSources = new Set<string>();
315
+ for (const target of Object.values(config.targets)) {
316
+ for (const source of [...target.entrypoints, ...(target.applicationParts ?? [])]) {
317
+ explicitTargetSources.add(normalizeConfiguredPath(source));
318
+ }
319
+ }
320
+ const configMs = performance.now() - configStartedAt;
321
+ const programStartedAt = performance.now();
322
+ program = createTypeScriptProgram();
323
+ programFiles = new Map();
324
+ installedFrameworkSources = [];
325
+ // In the framework's own checkout its sources are scanned project files.
326
+ const frameworkInstalled = FRAMEWORK_SOURCE === null;
327
+ for (const source of program.getSourceFiles()) {
328
+ const filePath = normalizeProjectPath(source.fileName);
329
+ if (isScannableFile(filePath) || explicitTargetSources.has(filePath)) {
330
+ programFiles.set(filePath, source);
331
+ } else if (frameworkInstalled && isInstalledFrameworkSource(source)) {
332
+ installedFrameworkSources.push(source);
333
+ }
334
+ }
335
+ checker = program.getTypeChecker();
336
+ const programMs = performance.now() - programStartedAt;
337
+ const discoveryStartedAt = performance.now();
338
+ const requested = readRequestedTarget() ?? config.defaultTarget;
339
+ if (requested !== "all" && !Object.hasOwn(config.targets, requested)) {
340
+ throw new Error(`BAZIS_CODEGEN_TARGET_UNKNOWN: ${requested}`);
341
+ }
342
+ const names = requested === "all"
343
+ ? Object.keys(config.targets).sort((left, right) => left.localeCompare(right))
344
+ : [requested];
345
+ const reachableByTarget = new Map<string, Set<string>>();
346
+ for (const name of Object.keys(config.targets)) {
347
+ const target = config.targets[name] as { entrypoints: readonly string[]; applicationParts?: readonly string[] };
348
+ const roots = [...target.entrypoints, ...(target.applicationParts ?? [])].map(normalizeConfiguredPath);
349
+ for (const root of roots) {
350
+ if (!programFiles.has(root)) throw new Error(`BAZIS_CODEGEN_ENTRY_NOT_IN_PROGRAM: ${name}:${root}`);
351
+ }
352
+ reachableByTarget.set(name, collectTargetReachability(program, programFiles, roots));
353
+ }
354
+ const owners = new Map<string, string[]>();
355
+ for (const [name, files] of reachableByTarget) for (const file of files) {
356
+ if (isFrameworkSourcePath(file)) continue;
357
+ const current = owners.get(file) ?? [];
358
+ current.push(name);
359
+ owners.set(file, current);
360
+ }
361
+ // Shared source is legitimate: every target gets its own immutable analysis
362
+ // slice and renderer state. Only a collision *inside* that slice is fatal.
363
+ const candidateIndex = createSourceCandidateIndex(programFiles);
364
+ const boundaryDiagnostics: string[] = [];
365
+ for (const [file] of programFiles) {
366
+ if (isFrameworkSourcePath(file)) continue;
367
+ if (!owners.has(file) && candidateIndex.get(file)) boundaryDiagnostics.push(`BAZIS_CODEGEN_SOURCE_UNASSIGNED: ${file}`);
368
+ }
369
+ for (const [name, reachable] of reachableByTarget) {
370
+ for (const file of reachable) {
371
+ const source = programFiles.get(file);
372
+ if (source !== undefined && !isFrameworkSourcePath(file)) collectDynamicImportDiagnostics(source, name, boundaryDiagnostics);
373
+ }
374
+ }
375
+ if (boundaryDiagnostics.length > 0) throw new Error(boundaryDiagnostics.sort((left, right) => left.localeCompare(right)).join("\n"));
376
+ const discoveryMs = performance.now() - discoveryStartedAt;
377
+ const targetStartedAt = performance.now();
378
+ let analysisMs = 0;
379
+ let renderMs = 0;
380
+ let eligible = 0;
381
+ let candidates = 0;
382
+ for (const name of names) {
383
+ const metrics = await generateTarget(name, reachableByTarget.get(name) as Set<string>, name === config.defaultTarget);
384
+ analysisMs += metrics.analysisMs;
385
+ renderMs += metrics.renderMs;
386
+ eligible += metrics.eligible;
387
+ candidates += metrics.candidates;
388
+ }
389
+ const targetMs = performance.now() - targetStartedAt;
390
+ const writeStartedAt = performance.now();
391
+ await commitPlannedWrites();
392
+ const writeMs = performance.now() - writeStartedAt;
393
+ console.log(`[di:generate] programFactories=1 programSources=${programFiles.size} targets=${names.join(",")} configMs=${configMs.toFixed(1)} programMs=${programMs.toFixed(1)} discoveryMs=${discoveryMs.toFixed(1)} analysisMs=${analysisMs.toFixed(1)} targetMs=${targetMs.toFixed(1)} renderMs=${renderMs.toFixed(1)} writeMs=${writeMs.toFixed(1)} totalMs=${(performance.now() - totalStartedAt).toFixed(1)} eligible=${eligible} candidates=${candidates} outputs=${plannedWrites.size}`);
394
+ }
395
+
396
+ function createSourceCandidateIndex(files: ReadonlyMap<string, ts.SourceFile>): ReadonlyMap<string, boolean> {
397
+ for (const [file, source] of files) {
398
+ if (!FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => file.startsWith(prefix))) collectDiClassRegistrations(source);
399
+ }
400
+ return new Map([...files].map(([file, source]) => [file, sourceHasCodegenCandidate(source)]));
401
+ }
402
+
403
+ /** Same target-owned marker vocabulary used by target collection/rendering. */
404
+ function sourceHasCodegenCandidate(source: ts.SourceFile): boolean {
405
+ let candidate = false;
406
+ const visit = (node: ts.Node): void => {
407
+ if (ts.isClassDeclaration(node)) {
408
+ const decorators = ts.getDecorators(node) ?? [];
409
+ const constructorDeps = readConstructorTypeNames(node);
410
+ // Shared all-channel boundary index: any class that feeds DI/HTTP,
411
+ // request/list, OpenAPI/UI, Agent/Tool/Prompt analysis must
412
+ // be owned by exactly one configured target before rendering begins.
413
+ if (
414
+ decorators.some((decorator) => {
415
+ const name = decoratorCall(decorator)?.name;
416
+ return name !== undefined && (CODEGEN_CHANNEL_DECORATORS.has(name)
417
+ || name === "Module");
418
+ })
419
+ || node.heritageClauses?.some((clause) => clause.types.some((type) => type.expression.getText(source) === "ListRequest"))
420
+ // Constructor metadata feeds the DI channel even when a class has no
421
+ // decorator of its own (providers may be registered by a module).
422
+ || ((constructorDeps.deps.length > 0
423
+ && (dependencyClassExportName(node) !== undefined || inferredDiClasses.has(node)))
424
+ || (inferredDiClasses.has(node) && constructorDeps.unsupportedInheritedParameter !== undefined))
425
+ ) {
426
+ candidate = true;
427
+ }
428
+ }
429
+ ts.forEachChild(node, visit);
430
+ };
431
+ visit(source);
432
+ return candidate;
433
+ }
434
+
435
+ function collectDynamicImportDiagnostics(source: ts.SourceFile, target: string, diagnostics: string[]): void {
436
+ const visit = (node: ts.Node): void => {
437
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && (node.arguments[0] === undefined || !ts.isStringLiteralLike(node.arguments[0]))) {
438
+ const location = source.getLineAndCharacterOfPosition(node.getStart(source));
439
+ diagnostics.push(`BAZIS_CODEGEN_DYNAMIC_EDGE_UNRESOLVED: ${target}:${normalizeProjectPath(source.fileName)}:${location.line + 1}`);
440
+ }
441
+ ts.forEachChild(node, visit);
442
+ };
443
+ visit(source);
444
+ }
445
+
446
+ function resetTargetAnalysis(files: Set<string>, generatedDir: string): void {
447
+ activeGeneratedDir = generatedDir;
448
+ classDeps = new Map(); inferredDiClasses = new Set(); explicitlyBoundDiClasses = new Set();
449
+ tokenDescriptions = new Set(); knownClassNames = new Set(); warnings = []; fatalErrors = [];
450
+ httpBindings = {}; httpControllerFiles = {}; openApiSchemas = {}; openApiOperations = {};
451
+ grpcBindings = new Map();
452
+ uiProfileResponseModels = new Map(); openApiSchemaModels = new Map(); requestModelClassNames = new Set(); listRequestClassNames = new Set();
453
+ classFilesByName = new Map(); classDeclarationsByName = new Map(); ambiguousClassNames = new Set(); explicitRequestModelRoots = new Set(); agentCodegenSources = [];
454
+ sourceEntries = [...files].sort((a, b) => a.localeCompare(b)).map((filePath) => ({ filePath, source: programFiles.get(filePath) as ts.SourceFile }));
455
+ sourceFilePathSet = new Set(sourceEntries.map((entry) => entry.filePath));
456
+ openApiCodegen = createOpenApiCodegenAnalyzer({ checker, sourceFiles: sourceEntries.map((entry) => entry.source), schemaNameForDuplicate: duplicateOpenApiSchemaName });
457
+ }
458
+
459
+ async function generateTarget(name: string, reachable: Set<string>, production: boolean): Promise<{ analysisMs: number; renderMs: number; eligible: number; candidates: number }> {
460
+ const startedAt = performance.now();
461
+ const generatedDir = production ? APP_GENERATED_DIR : `${APP_GENERATED_DIR}/targets/${name}`;
462
+ resetTargetAnalysis(reachable, generatedDir);
463
+ for (const { source } of sourceEntries) collectListRequestClasses(source);
464
+ for (const { filePath, source } of sourceEntries) {
465
+ if (!FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix))) collectDiClassRegistrations(source);
466
+ }
467
+ const prepassMs = performance.now() - startedAt;
468
+ for (const source of installedFrameworkSources) collectTokenDescriptions(source);
469
+ for (const { filePath, source } of sourceEntries) {
470
+ fatalErrors.push(...analyzeOrmPredicates(checker, source));
471
+ collectTokenDescriptions(source);
472
+ if (!FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
473
+ collectClassDeps(source);
474
+ collectGrpcBindings(source);
475
+ agentCodegenSources.push({ filePath, sourceFile: source });
476
+ }
477
+ collectHttpBindings(source); collectOpenApiSchemas(source); collectUiProfileResponseModels(source);
478
+ }
479
+ validateDepsAgainstKnownTokens();
480
+ const requestModelImports = resolveRequestModelImports();
481
+ const requestModelHydration = resolveRequestModelHydration(requestModelImports);
482
+ const listModelImports = resolveListModelImports();
483
+ const agent = generateAgentMetadataCatalog(agentCodegenSources, {
484
+ generatedDir, frameworkImports: "public", schemaNameForDeclaration: openApiCodegen.schemaNameForDeclaration,
485
+ });
486
+ const schemaModels = resolveOpenApiSchemaModelImports(agent.schemaModels, agent.schemaNames);
487
+ classifyAgentWarnings(agent.warnings); failOnFatalErrors();
488
+ const analysisMs = performance.now() - startedAt;
489
+ const renderStartedAt = performance.now();
490
+ const requestModels = renderRequestModels(requestModelImports, requestModelHydration);
491
+ const listModels = renderListModels(listModelImports);
492
+ const openApi = renderOpenApiMetadata(openApiSchemas, openApiOperations, "app", agent.schemaNames, schemaModels);
493
+ stageWrite(`${generatedDir}/deps.ts`, renderTargetDeps());
494
+ stageWrite(`${generatedDir}/bindings.ts`, renderTargetBindings());
495
+ stageWrite(`${generatedDir}/httpRequestModels.ts`, requestModels);
496
+ stageWrite(`${generatedDir}/httpListModels.ts`, listModels);
497
+ stageWrite(`${generatedDir}/openapi.ts`, openApi);
498
+ stageWrite(`${generatedDir}/agentCatalog.ts`, agent.output);
499
+ stageWrite(`${generatedDir}/runtime.ts`, renderGeneratedRuntime(name));
500
+ if (production) stageWrite(`${generatedDir}/fingerprint.ts`, renderSourceFingerprint());
501
+ // The framework's own empty agent catalog (a fixture of agent.metadata.test)
502
+ // is refreshed only when the framework source is part of this project.
503
+ if (production) {
504
+ if (FRAMEWORK_SOURCE !== null) {
505
+ stageWrite(CORE_AGENT_CATALOG_FILE, generateAgentMetadataCatalog([], CORE_AGENT_GENERATED_DIR).output);
506
+ }
507
+ } else {
508
+ stageWrite(`${generatedDir}/bootstrap.ts`, 'import { registerBazisGeneratedRuntime } from "./runtime";\n\nawait registerBazisGeneratedRuntime();\n');
509
+ }
510
+ for (const warning of warnings) console.warn(`[di:generate] WARNING: ${warning}`);
511
+ console.log(`[di:generate] target=${name} sources=${sourceEntries.length} deps=${classDeps.size} controllers=${Object.keys(httpBindings).length} requestModels=${requestModelImports.length} listModels=${listModelImports.length} agents=${agent.agentCount} prepassMs=${prepassMs.toFixed(1)} analysisMs=${analysisMs.toFixed(1)} totalMs=${(performance.now() - startedAt).toFixed(1)}`);
512
+ return {
513
+ analysisMs,
514
+ renderMs: performance.now() - renderStartedAt,
515
+ eligible: sourceEntries.length,
516
+ candidates: classDeps.size + Object.keys(httpBindings).length + requestModelImports.length + listModelImports.length + agent.agentCount,
517
+ };
518
+ }
519
+
520
+ function classifyAgentWarnings(agentWarnings: readonly string[]): void {
521
+ for (let index = 0; index < agentWarnings.length; index += 1) {
522
+ const warning = agentWarnings[index] as string;
523
+ if (warning.includes("ambiguous") || warning.includes("declared in more than one file")) {
524
+ fatalErrors.push(warning);
525
+ } else {
526
+ warnings.push(warning);
527
+ }
528
+ }
529
+ }
530
+
531
+ function failOnFatalErrors(): void {
532
+ if (fatalErrors.length === 0) {
533
+ return;
534
+ }
535
+ for (let index = 0; index < fatalErrors.length; index += 1) {
536
+ console.error(`[di:generate] ERROR: ${fatalErrors[index]}`);
537
+ }
538
+ throw new Error(`di:generate failed with ${fatalErrors.length} fatal codegen error(s).`);
539
+ }
540
+
541
+ function renderGeneratedRuntime(targetId: string): string {
542
+ if (activeGeneratedDir !== APP_GENERATED_DIR) {
543
+ return [
544
+ "// This file is auto-generated by `bun run di:generate`.",
545
+ "// Do not edit manually.",
546
+ "",
547
+ 'import { registerBazisGeneratedTargetDescriptor } from "bazis/core/generatedRuntime";',
548
+ "",
549
+ "let load: Promise<void> | undefined;",
550
+ "",
551
+ "export function registerBazisGeneratedRuntime(): Promise<void> {",
552
+ " load ??= loadTarget().catch((error) => { load = undefined; throw error; });",
553
+ " return load;",
554
+ "}",
555
+ "",
556
+ "async function loadTarget(): Promise<void> {",
557
+ " const [deps, bindings, requests, lists, openApi, agentCatalog] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\"), import(\"./agentCatalog\")]);",
558
+ ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS, agentMetadata: agentCatalog.GENERATED_AGENT_METADATA });`,
559
+ "}",
560
+ "",
561
+ ].join("\n");
562
+ }
563
+ return [
564
+ "// This file is auto-generated by `bun run di:generate`.",
565
+ "// Do not edit manually.",
566
+ "",
567
+ "let registered: Promise<void> | undefined;",
568
+ "",
569
+ "export async function registerBazisGeneratedRuntime(): Promise<void> {",
570
+ " registered ??= loadTarget().catch((error) => { registered = undefined; throw error; });",
571
+ " return registered;",
572
+ "}",
573
+ "",
574
+ "async function loadTarget(): Promise<void> {",
575
+ " // Test-only owner-private hook: it is inert unless an isolated test installs it before loading this generated module.",
576
+ ' (globalThis as { __bazisGeneratedRuntimeTestHook?: (target: string) => void }).__bazisGeneratedRuntimeTestHook?.("production");',
577
+ ' const { registerBazisGeneratedTargetDescriptor, warnIfGeneratedSourcesChanged } = await import("bazis/core/generatedRuntime");',
578
+ " const [deps, bindings, requests, lists, openApi, agentCatalog] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\"), import(\"./agentCatalog\")]);",
579
+ ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS, agentMetadata: agentCatalog.GENERATED_AGENT_METADATA });`,
580
+ " // Started from sources edited after codegen: say so instead of silently missing routes.",
581
+ ' warnIfGeneratedSourcesChanged(import.meta.dir, (await import("./fingerprint")).GENERATED_SOURCE_FINGERPRINT);',
582
+ "}",
583
+ "",
584
+ ].join("\n");
585
+ }
586
+
587
+ /** Sources of this target as codegen saw them; read back by the generated runtime. */
588
+ function renderSourceFingerprint(): string {
589
+ const files = sourceEntries.map((entry) => entry.filePath).sort();
590
+ const framework = JSON.parse(readFileSync(path.join(FRAMEWORK_DIR, "package.json"), "utf8")).version as string;
591
+ const fingerprint = { root: path.relative(activeGeneratedDir, ".").replaceAll("\\", "/"), framework, files, hash: hashSources(".", files) };
592
+ return [
593
+ "// This file is auto-generated by `bazis codegen`.",
594
+ "// Do not edit manually.",
595
+ "",
596
+ `export const GENERATED_SOURCE_FINGERPRINT = ${JSON.stringify(fingerprint, null, 2)} as const;`,
597
+ "",
598
+ ].join("\n");
599
+ }
600
+
601
+ /** Target-local descriptor renderer over the mature collector output. */
602
+ function renderTargetDeps(): string {
603
+ const entries = [...classDeps.entries()].sort(([left], [right]) =>
604
+ left.name!.text.localeCompare(right.name!.text)
605
+ || left.getSourceFile().fileName.localeCompare(right.getSourceFile().fileName)
606
+ || left.pos - right.pos);
607
+ const references = new Map<ts.ClassDeclaration, string>();
608
+ for (let index = 0; index < entries.length; index++) references.set(entries[index]![0], `TargetClass_${index}`);
609
+ for (const [, dependencies] of entries) {
610
+ for (const dependency of dependencies) {
611
+ if (dependency.target && !references.has(dependency.target)) references.set(dependency.target, `DependencyClass_${references.size}`);
612
+ }
613
+ }
614
+ const lines = ["// This file is auto-generated by `bun run di:generate`.", "// Do not edit manually."];
615
+ if (entries.some(([, deps]) => deps.some(dep => dep.target && dep.lazy))) lines.push('import { lazyDependency } from "bazis/core/di";');
616
+ for (const [declaration, reference] of references) {
617
+ const name = dependencyClassExportName(declaration);
618
+ if (!name) throw new Error(`BAZIS_DI_CLASS_UNIMPORTABLE: ${sourceLocation(declaration)}`);
619
+ lines.push(`import { ${name} as ${reference} } from ${JSON.stringify(toModuleSpecifierFrom(activeGeneratedDir, normalizeProjectPath(declaration.getSourceFile().fileName)))};`);
620
+ }
621
+ lines.push("export const GENERATED_TARGET_CLASS_DEPS = Object.freeze([");
622
+ for (const [declaration, dependencies] of entries) {
623
+ const values = dependencies.map(dependency => {
624
+ if (!dependency.target) return JSON.stringify(dependency.name);
625
+ const reference = references.get(dependency.target)!;
626
+ return dependency.lazy ? `lazyDependency(${reference})` : reference;
627
+ });
628
+ lines.push(` [${references.get(declaration)}, [${values.join(", ")}]] as const,`);
629
+ }
630
+ lines.push("]);" );
631
+ return `${lines.join("\n")}\n`;
632
+ }
633
+
634
+ /** Export aliases and default exports still refer to the same class symbol. */
635
+ function dependencyClassExportName(declaration: ts.ClassDeclaration): string | undefined {
636
+ const sourceSymbol = checker.getSymbolAtLocation(declaration.getSourceFile());
637
+ const classSymbol = declaration.name && checker.getSymbolAtLocation(declaration.name);
638
+ if (sourceSymbol && classSymbol) {
639
+ for (const exported of checker.getExportsOfModule(sourceSymbol)) {
640
+ // `export type { LocalClass }` exposes only a type: the generated runtime
641
+ // must skip it and keep looking for an actual value alias/default export.
642
+ if (exported.declarations?.every((entry) => ts.isExportSpecifier(entry)
643
+ && (entry.isTypeOnly || (ts.isExportDeclaration(entry.parent.parent) && entry.parent.parent.isTypeOnly)))) continue;
644
+ const target = exported.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported;
645
+ if (target === classSymbol) return exported.name;
646
+ }
647
+ }
648
+ return undefined;
649
+ }
650
+
651
+ /** Only executable, exported classes can supply an exact runtime constructor. */
652
+ function dependencyClass(node: ts.TypeNode): ts.ClassDeclaration | undefined {
653
+ if (!ts.isTypeReferenceNode(node)) return undefined;
654
+ let symbol = checker.getSymbolAtLocation(node.typeName);
655
+ if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
656
+ const declaration = symbol?.declarations?.find(ts.isClassDeclaration);
657
+ return declaration && !declaration.getSourceFile().isDeclarationFile && dependencyClassExportName(declaration)
658
+ ? declaration : undefined;
659
+ }
660
+
661
+ /** Target-local HTTP descriptor renderer over the mature binding collector output. */
662
+ function renderTargetBindings(): string {
663
+ // Controller-scoped OpenAPI metadata must include controllers which have no
664
+ // bindable parameters as well as ordinary binding-bearing controllers.
665
+ const entries = [...new Set([...Object.keys(httpBindings), ...Object.keys(openApiOperations)])]
666
+ .sort((a, b) => a.localeCompare(b));
667
+ const lines = ["// This file is auto-generated by `bun run di:generate`.", "// Do not edit manually."];
668
+ for (let index = 0; index < entries.length; index += 1) {
669
+ const name = entries[index] as string;
670
+ const file = httpControllerFiles[name] ?? classFilesByName.get(name);
671
+ if (file === undefined) throw new Error(`BAZIS_CODEGEN_CONTROLLER_SOURCE_MISSING: ${name}`);
672
+ lines.push(`import { ${name} as TargetController_${index} } from ${JSON.stringify(toModuleSpecifierFrom(activeGeneratedDir, normalizeProjectPath(file)))};`);
673
+ }
674
+ lines.push("export const GENERATED_TARGET_BINDINGS = Object.freeze([");
675
+ for (let index = 0; index < entries.length; index += 1) {
676
+ const name = entries[index] as string;
677
+ lines.push(` [TargetController_${index}, ${JSON.stringify(httpBindings[name] ?? {})}] as const,`);
678
+ }
679
+ lines.push("]);" );
680
+ lines.push(...renderGrpcBindings());
681
+ return `${lines.join("\n")}\n`;
682
+ }
683
+
684
+ function renderGrpcBindings(): string[] {
685
+ const entries = [...grpcBindings.entries()];
686
+ const lines: string[] = [];
687
+ const aliases = new Map<ts.ClassDeclaration, string>();
688
+ if (entries.length > 0) lines.push('import { GRPC_REQUEST_BINDINGS } from "bazis/core/grpc";');
689
+ for (const [controller, bindings] of entries) {
690
+ for (const declaration of [controller, ...bindings.map((binding) => binding.model)]) {
691
+ if (aliases.has(declaration)) continue;
692
+ const alias = `GrpcClass_${aliases.size}`;
693
+ aliases.set(declaration, alias);
694
+ lines.push(`import { ${declaration.name!.text} as ${alias} } from ${JSON.stringify(toModuleSpecifier(sourcePathForDeclaration(declaration)))};`);
695
+ }
696
+ }
697
+ lines.push("export const GENERATED_PROVIDER_ATTACHMENTS = Object.freeze([");
698
+ for (const [controller, bindings] of entries) {
699
+ const methods = bindings.map((binding) => `${JSON.stringify(binding.method)}: { model: ${aliases.get(binding.model)}, requestStream: ${binding.requestStream} }`);
700
+ lines.push(` { channel: GRPC_REQUEST_BINDINGS, target: ${aliases.get(controller)}, value: { ${methods.join(", ")} } },`);
701
+ }
702
+ lines.push("]);" );
703
+ return lines;
704
+ }
705
+
706
+ function collectTokenDescriptions(source: ts.SourceFile): void {
707
+ const visit = (node: ts.Node): void => {
708
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments.length > 0) {
709
+ const firstArg = node.arguments[0];
710
+ if (firstArg && ts.isStringLiteralLike(firstArg)) {
711
+ if (node.expression.text === "createToken" || node.expression.text === "createOpenGenericTokenFamily") {
712
+ tokenDescriptions.add(firstArg.text);
713
+ }
714
+ }
715
+ }
716
+ if (ts.isClassDeclaration(node) && node.name) {
717
+ knownClassNames.add(node.name.text);
718
+ }
719
+ ts.forEachChild(node, visit);
720
+ };
721
+
722
+ ts.forEachChild(source, visit);
723
+ }
724
+
725
+ function validateDepsAgainstKnownTokens(): void {
726
+ for (const [declaration, deps] of classDeps) {
727
+ for (let index = 0; index < deps.length; index += 1) {
728
+ const rawName = deps[index]!.name;
729
+ const depName = rawName.startsWith("lazy:") ? rawName.slice("lazy:".length) : rawName;
730
+ if (
731
+ tokenDescriptions.has(depName) ||
732
+ knownClassNames.has(depName) ||
733
+ depName.startsWith("IRepository<") ||
734
+ depName.startsWith("IRepository:")
735
+ ) {
736
+ continue;
737
+ }
738
+ classDeps.delete(declaration);
739
+ // A class DI must construct cannot lose a dependency silently: the app
740
+ // would fail later, far from the cause. Other classes need no entry.
741
+ if (needsInferredDiDeps(declaration)) {
742
+ fatalErrors.push(declaration.members.some(ts.isConstructorDeclaration)
743
+ ? `BAZIS_DI_DEPENDENCY_UNKNOWN: ${sourceLocation(declaration)}: constructor parameter ${index + 1} of "${declaration.name?.text}" has type "${depName}", which is neither a DI token (createToken) nor a class known to codegen. Register it, import it from the framework package, or bind the class with an explicit factory.`
744
+ : `BAZIS_DI_CONSTRUCTOR_UNRESOLVED: ${sourceLocation(declaration)}: inherited constructor dependency "${depName}" has no known DI token.`);
745
+ }
746
+ break;
747
+ }
748
+ }
749
+ }
750
+
751
+ function collectClassDeps(source: ts.SourceFile): void {
752
+ const visit = (node: ts.Node): void => {
753
+ if (ts.isClassDeclaration(node) && node.name) {
754
+ const result = readConstructorTypeNames(node);
755
+ if (result.unsupportedInheritedParameter !== undefined && needsInferredDiDeps(node)) {
756
+ fatalErrors.push(`BAZIS_DI_CONSTRUCTOR_UNRESOLVED: ${sourceLocation(node)}: cannot infer inherited constructor parameter "${result.unsupportedInheritedParameter}" for "${node.name.text}". The parameter needs a runtime DI token or an explicit value/factory binding.`);
757
+ }
758
+ if (result.deps.length > 0) {
759
+ if (dependencyClassExportName(node)) {
760
+ // Abstract bases supply inherited signatures, never concrete DI targets.
761
+ if (!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword)) {
762
+ classDeps.set(node, result.deps);
763
+ }
764
+ } else if (needsInferredDiDeps(node)) {
765
+ fatalErrors.push(`BAZIS_DI_CLASS_UNIMPORTABLE: ${sourceLocation(node)}: DI class "${node.name.text}" must be exported (an export alias is sufficient) so constructor dependencies can bind to its exact identity.`);
766
+ }
767
+ }
768
+ }
769
+ ts.forEachChild(node, visit);
770
+ };
771
+
772
+ ts.forEachChild(source, visit);
773
+ }
774
+
775
+ /** Only registrations that need inference require an importable constructor.
776
+ * Local helpers and deliberately explicit registrations need no generated entry.
777
+ */
778
+ function collectDiClassRegistrations(source: ts.SourceFile): void {
779
+ const record = (expression: ts.Expression | undefined, explicit = false): void => {
780
+ if (!expression) return;
781
+ const declaration = classDeclarationForExpression(expression)
782
+ ?? checker.getTypeAtLocation(expression).getConstructSignatures()[0]?.getReturnType().getSymbol()?.declarations?.find(ts.isClassDeclaration);
783
+ if (declaration) (explicit ? explicitlyBoundDiClasses : inferredDiClasses).add(declaration);
784
+ };
785
+ const objectFor = (expression: ts.Expression | undefined, seen = new Set<ts.Node>()): ts.ObjectLiteralExpression | undefined => {
786
+ if (!expression || seen.has(expression)) return undefined;
787
+ seen.add(expression);
788
+ if (ts.isObjectLiteralExpression(expression)) return expression;
789
+ if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)
790
+ || ts.isTypeAssertionExpression(expression) || ts.isSatisfiesExpression(expression)) return objectFor(expression.expression, seen);
791
+ let symbol = checker.getSymbolAtLocation(expression);
792
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) symbol = checker.getAliasedSymbol(symbol);
793
+ const declaration = symbol?.valueDeclaration;
794
+ return declaration && ts.isVariableDeclaration(declaration) ? objectFor(declaration.initializer, seen) : undefined;
795
+ };
796
+ const propertiesOf = (node: ts.ObjectLiteralExpression) => new Map(node.properties.filter(ts.isPropertyAssignment)
797
+ .map((property) => [property.name.getText().replace(/["']/g, ""), property.initializer]));
798
+ const recordProvider = (expression: ts.Expression | undefined): void => {
799
+ const object = objectFor(expression);
800
+ if (!object) return;
801
+ const properties = propertiesOf(object);
802
+ if (properties.has("provide") && properties.has("useClass")
803
+ && (!properties.has("deps") || properties.get("deps")!.getText() === "undefined")) record(properties.get("useClass"));
804
+ };
805
+ const visitedModules = new Set<ts.ObjectLiteralExpression>();
806
+ const recordModule = (expression: ts.Expression | undefined): void => {
807
+ const object = objectFor(expression);
808
+ if (!object || visitedModules.has(object)) return;
809
+ visitedModules.add(object);
810
+ const properties = propertiesOf(object);
811
+ for (const name of ["controllers", "grpcControllers", "background", "tools"]) {
812
+ const classes = properties.get(name);
813
+ if (classes && ts.isArrayLiteralExpression(classes)) for (const entry of classes.elements) record(entry);
814
+ }
815
+ const imports = properties.get("imports");
816
+ if (imports && ts.isArrayLiteralExpression(imports)) for (const entry of imports.elements) recordModule(entry);
817
+ };
818
+ const hasContextualProperty = (node: ts.ObjectLiteralExpression, property: string, suffix: string): boolean => {
819
+ const type = checker.getContextualType(node);
820
+ return (type?.isUnion() ? type.types : type ? [type] : []).some((part) =>
821
+ part.getProperty(property)?.declarations?.some((declaration) => declaration.getSourceFile().fileName.replace(/\\/g, "/").endsWith(suffix)));
822
+ };
823
+ const visit = (node: ts.Node): void => {
824
+ if (ts.isCallExpression(node)) {
825
+ let symbol = checker.getSymbolAtLocation(node.expression);
826
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) symbol = checker.getAliasedSymbol(symbol);
827
+ const declaration = symbol?.declarations?.find((entry) => ts.isFunctionDeclaration(entry) || ts.isMethodDeclaration(entry));
828
+ const file = declaration?.getSourceFile().fileName.replace(/\\/g, "/");
829
+ const name = declaration && "name" in declaration && declaration.name && ts.isIdentifier(declaration.name)
830
+ ? declaration.name.text : undefined;
831
+ if (file?.endsWith("/core/di/module/shortcuts.ts") && ["singleton", "scoped", "transient"].includes(name ?? "")) {
832
+ // Explicit deps apply to this registration, not every use of the class.
833
+ if (node.arguments[2] === undefined || node.arguments[2].getText(source) === "undefined") record(node.arguments[1] ?? node.arguments[0]);
834
+ } else if (file?.endsWith("/core/di/module/DI.ts")) {
835
+ if (name === "classProvider" && (node.arguments[2] === undefined || node.arguments[2].getText(source) === "undefined")) {
836
+ record(node.arguments[1]);
837
+ } else if (name === "bindDeps" || name === "injectFor") record(node.arguments[0], true);
838
+ else if (["singleton", "scoped", "transient", "keyedSingleton", "keyedScoped", "keyedTransient"].includes(name ?? "")) {
839
+ recordProvider(node.arguments[node.arguments.length - 1]);
840
+ }
841
+ } else if ((file?.endsWith("/core/di/module/Module.ts") && name === "Module")
842
+ || (file?.endsWith("/core/di/module/createContainer.ts") && name === "createContainer")
843
+ || (file?.endsWith("/core/kernel/Bazis.ts") && name === "createBuilder")) {
844
+ recordModule(node.arguments[0]);
845
+ } else if (file?.endsWith("/core/di/ServiceCollection.ts") || file?.endsWith("/core/di/module/ModuleRegistrar.ts")) {
846
+ for (const argument of node.arguments) recordProvider(argument);
847
+ }
848
+ } else if (ts.isObjectLiteralExpression(node)) {
849
+ if (hasContextualProperty(node, "useClass", "/core/di/provider/types/ClassProvider.ts")) recordProvider(node);
850
+ if (hasContextualProperty(node, "background", "/core/di/module/types/BazisModule.ts")) recordModule(node);
851
+ }
852
+ ts.forEachChild(node, visit);
853
+ };
854
+ visit(source);
855
+ }
856
+
857
+ function hasStaticClassDeps(node: ts.ClassDeclaration): boolean {
858
+ if (!node.name) return false;
859
+ const symbol = checker.getSymbolAtLocation(node.name);
860
+ if (!symbol) return false;
861
+ const type = checker.getTypeOfSymbolAtLocation(symbol, node);
862
+ return type.getProperty("inject") !== undefined || type.getProperty("deps") !== undefined;
863
+ }
864
+
865
+ function needsInferredDiDeps(node: ts.ClassDeclaration): boolean {
866
+ return inferredDiClasses.has(node) && !explicitlyBoundDiClasses.has(node) && !hasStaticClassDeps(node);
867
+ }
868
+
869
+ /** The checker substitutes type parameters across all inherited constructors.
870
+ * Reading only the base declaration would incorrectly bind T instead of X in Base<X>.
871
+ */
872
+ function readInheritedConstructorDeps(node: ts.ClassDeclaration): ConstructorDependencies {
873
+ if (!node.name || !node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword)) return { deps: [] };
874
+ const symbol = checker.getSymbolAtLocation(node.name);
875
+ const signature = symbol && checker.getTypeOfSymbolAtLocation(symbol, node).getConstructSignatures()[0];
876
+ if (!signature) return { deps: [] };
877
+ const parameters = signature.getParameters();
878
+ let lastRequired = -1;
879
+ for (let index = parameters.length - 1; index >= 0; index--) {
880
+ const declaration = parameters[index]!.valueDeclaration;
881
+ if (declaration && ts.isParameter(declaration) && !declaration.questionToken && !declaration.initializer) {
882
+ lastRequired = index;
883
+ break;
884
+ }
885
+ }
886
+ const deps: CollectedDependency[] = [];
887
+ for (let index = 0; index <= lastRequired; index++) {
888
+ const parameter = parameters[index]!;
889
+ let type = checker.getTypeOfSymbolAtLocation(parameter, node);
890
+ const declaration = parameter.valueDeclaration;
891
+ // A default before a required argument still occupies a DI position. The
892
+ // checker adds undefined to that parameter; the original annotation did not.
893
+ if (declaration && ts.isParameter(declaration) && declaration.initializer && type.isUnion()) {
894
+ const defined = type.types.filter((member) => !(member.flags & ts.TypeFlags.Undefined));
895
+ if (defined.length === 1) type = defined[0]!;
896
+ }
897
+ const dependency = dependencyFromType(type);
898
+ if (!dependency) return { deps: [], unsupportedInheritedParameter: parameter.name };
899
+ deps.push(dependency);
900
+ }
901
+ return { deps };
902
+ }
903
+
904
+ function dependencyFromType(type: ts.Type): CollectedDependency | undefined {
905
+ if (type.flags & ts.TypeFlags.TypeParameter) return undefined;
906
+ const symbol = type.getSymbol();
907
+ const declaration = symbol?.declarations?.find(ts.isClassDeclaration);
908
+ // Structural type aliases have the synthetic symbol __type; named DI tokens
909
+ // retain their declared alias. A concrete class still uses constructor identity.
910
+ const name = declaration?.name?.text ?? type.aliasSymbol?.name ?? symbol?.name;
911
+ if (!name || BUILTIN_DEPENDENCY_NAMES.has(name)) return undefined;
912
+ const arguments_ = type.flags & ts.TypeFlags.Object ? checker.getTypeArguments(type as ts.TypeReference) : [];
913
+ if (name === "Lazy" || name === "IRepository") {
914
+ const inner = arguments_[0] && dependencyFromType(arguments_[0]);
915
+ if (!inner) return undefined;
916
+ return name === "Lazy" ? { ...inner, name: `lazy:${inner.name}`, lazy: true } : { name: `IRepository<${inner.name}>` };
917
+ }
918
+ const target = declaration && !declaration.getSourceFile().isDeclarationFile && dependencyClassExportName(declaration) ? declaration : undefined;
919
+ return { name, target };
920
+ }
921
+
922
+ function readConstructorTypeNames(node: ts.ClassDeclaration): ConstructorDependencies {
923
+ const ctor = node.members.find((member): member is ts.ConstructorDeclaration => ts.isConstructorDeclaration(member));
924
+ if (!ctor) {
925
+ return readInheritedConstructorDeps(node);
926
+ }
927
+
928
+ const deps: CollectedDependency[] = [];
929
+ const lastRequiredParamIndex = findLastRequiredParameterIndex(ctor);
930
+ for (let index = 0; index <= lastRequiredParamIndex; index += 1) {
931
+ const param = ctor.parameters[index];
932
+ if (!param) {
933
+ continue;
934
+ }
935
+ const paramType = param.type;
936
+ const refName = paramType ? getDependencyTypeName(paramType) : undefined;
937
+ if (!refName || BUILTIN_DEPENDENCY_NAMES.has(refName)) {
938
+ return { deps: [] };
939
+ }
940
+
941
+ // `Lazy<X>` is a deferred dependency on X: encode it with a marker prefix
942
+ // so the runtime injects a Lazy wrapper instead of resolving X eagerly.
943
+ if (refName === "Lazy" && paramType && ts.isTypeReferenceNode(paramType)) {
944
+ const innerType = paramType.typeArguments?.[0];
945
+ const innerName = innerType ? getDependencyTypeName(innerType) : undefined;
946
+ if (!innerName) {
947
+ return { deps: [] };
948
+ }
949
+ deps.push({ name: `lazy:${innerName}`, target: dependencyClass(innerType!), lazy: true });
950
+ continue;
951
+ }
952
+
953
+ if (refName === "IRepository" && paramType && ts.isTypeReferenceNode(paramType)) {
954
+ const entityType = paramType.typeArguments?.[0];
955
+ const entityName = entityType ? getDependencyTypeName(entityType) : undefined;
956
+ if (entityName) {
957
+ deps.push({ name: `IRepository<${entityName}>` });
958
+ continue;
959
+ }
960
+ }
961
+
962
+ deps.push({ name: refName, target: dependencyClass(paramType!) });
963
+ }
964
+
965
+ return { deps };
966
+ }
967
+
968
+ /** Resolve imported type aliases/re-exports; runtime named binding stays module-scoped. */
969
+ function getDependencyTypeName(node: ts.TypeNode): string | undefined {
970
+ if (!ts.isTypeReferenceNode(node)) return undefined;
971
+ const symbol = checker.getSymbolAtLocation(node.typeName);
972
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
973
+ const target = checker.getAliasedSymbol(symbol);
974
+ const declaration = target.declarations?.find(ts.isClassDeclaration);
975
+ return declaration?.name?.text ?? target.name;
976
+ }
977
+ return getTypeReferenceName(node);
978
+ }
979
+
980
+ function findLastRequiredParameterIndex(ctor: ts.ConstructorDeclaration): number {
981
+ for (let index = ctor.parameters.length - 1; index >= 0; index -= 1) {
982
+ const param = ctor.parameters[index];
983
+ if (!param) {
984
+ continue;
985
+ }
986
+ if (param.questionToken === undefined && param.initializer === undefined) {
987
+ return index;
988
+ }
989
+ }
990
+ return -1;
991
+ }
992
+
993
+ // ── HTTP binding conventions ─────────────────────────────────────────────────
994
+
995
+ function decoratorCall(decorator: ts.Decorator): { name: string; call?: ts.CallExpression } | undefined {
996
+ const expression = decorator.expression;
997
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
998
+ return { name: expression.expression.text, call: expression };
999
+ }
1000
+ if (ts.isIdentifier(expression)) {
1001
+ return { name: expression.text };
1002
+ }
1003
+ return undefined;
1004
+ }
1005
+
1006
+ function hasDecorator(node: ts.HasDecorators, name: string): boolean {
1007
+ const decorators = ts.getDecorators(node);
1008
+ return decorators?.some((decorator) => decoratorCall(decorator)?.name === name) ?? false;
1009
+ }
1010
+
1011
+ /** Route parameter names from all route decorators of a method (":id(int)" -> "id"). */
1012
+ function routeParamNames(method: ts.MethodDeclaration): Set<string> {
1013
+ const names = new Set<string>();
1014
+ for (const decorator of ts.getDecorators(method) ?? []) {
1015
+ const info = decoratorCall(decorator);
1016
+ if (!info?.call || !ROUTE_DECORATORS.has(info.name)) {
1017
+ continue;
1018
+ }
1019
+ const firstArg = info.call.arguments[0];
1020
+ if (!firstArg || !ts.isStringLiteralLike(firstArg)) {
1021
+ continue;
1022
+ }
1023
+ for (const segment of firstArg.text.split("/")) {
1024
+ const match = /^:([A-Za-z_][A-Za-z0-9_]*)/.exec(segment);
1025
+ if (match) {
1026
+ names.add(match[1] as string);
1027
+ }
1028
+ }
1029
+ }
1030
+ return names;
1031
+ }
1032
+
1033
+ /** Reuses the request hydration graph; no reflection metadata/runtime TS imports. */
1034
+ function collectGrpcBindings(source: ts.SourceFile): void {
1035
+ const visit = (node: ts.Node): void => {
1036
+ if (ts.isClassDeclaration(node) && node.name && hasDecorator(node, "GrpcController")) {
1037
+ const bindings: GrpcRequestBinding[] = [];
1038
+ // Include inherited methods, resolving the most-derived signature.
1039
+ for (const property of checker.getTypeAtLocation(node).getProperties()) {
1040
+ const member = property.declarations?.find((declaration): declaration is ts.MethodDeclaration =>
1041
+ ts.isMethodDeclaration(declaration) && hasDecorator(declaration, "GrpcMethod"));
1042
+ if (!member) continue;
1043
+ const decorator = (ts.getDecorators(member) ?? []).map(decoratorCall).find((entry) => entry?.name === "GrpcMethod");
1044
+ const explicit = decorator?.call?.arguments[1];
1045
+ if (explicit !== undefined && explicit.getText() !== "undefined") {
1046
+ const declaration = classDeclarationForExpression(explicit);
1047
+ if (declaration) explicitRequestModelRoots.add(declaration);
1048
+ continue;
1049
+ }
1050
+ const parameter = member.parameters[0];
1051
+ if (!parameter?.type) continue;
1052
+ let type = checker.getNonNullableType(checker.getTypeAtLocation(parameter));
1053
+ const requestStream = ["AsyncIterable", "AsyncIterableIterator", "AsyncGenerator"].includes(type.getSymbol()?.name ?? "");
1054
+ if (requestStream) {
1055
+ const element = checker.getTypeArguments(type as ts.TypeReference)[0];
1056
+ if (!element) continue;
1057
+ type = checker.getNonNullableType(element);
1058
+ }
1059
+ if (type.isUnionOrIntersection() && type.types.some((part) => classDeclarationForSymbol(part.getSymbol()))) {
1060
+ fatalErrors.push(`${sourceLocation(member)}: gRPC request must have one concrete DTO class; bind it explicitly with @GrpcMethod(name, Model).`);
1061
+ continue;
1062
+ }
1063
+ const model = classDeclarationForSymbol(type.getSymbol());
1064
+ // Interfaces/inline objects retain their existing raw protobuf behavior.
1065
+ if (!model) continue;
1066
+ if (!isNamedExportedTopLevelClass(node) || !isNamedExportedTopLevelClass(model)
1067
+ || !isScannedProjectDeclaration(model) || isFrameworkInternalDeclaration(model)) {
1068
+ fatalErrors.push(`${sourceLocation(member)}: gRPC DTO/controller must be named project exports; use an explicit @GrpcMethod(name, Model) binding otherwise.`);
1069
+ continue;
1070
+ }
1071
+ if (!ts.isIdentifier(member.name) && !ts.isStringLiteral(member.name)) {
1072
+ fatalErrors.push(`${sourceLocation(member)}: gRPC DTO inference requires a named method; bind its DTO explicitly.`);
1073
+ continue;
1074
+ }
1075
+ bindings.push({ method: member.name.text, model, requestStream });
1076
+ }
1077
+ if (bindings.length > 0) grpcBindings.set(node, bindings);
1078
+ }
1079
+ ts.forEachChild(node, visit);
1080
+ };
1081
+ ts.forEachChild(source, visit);
1082
+ }
1083
+
1084
+ /** Infers HTTP route/query/body conventions from controller method signatures. */
1085
+ function collectHttpBindings(source: ts.SourceFile): void {
1086
+ const visit = (node: ts.Node): void => {
1087
+ if (ts.isClassDeclaration(node) && node.name) {
1088
+ recordClassDeclaration(node, source.fileName);
1089
+ if (hasDecorator(node, "RequestModel")) {
1090
+ requestModelClassNames.add(node.name.text);
1091
+ explicitRequestModelRoots.add(node);
1092
+ }
1093
+ if (hasDecorator(node, "Controller")) {
1094
+ collectControllerBindings(node, node.name.text, source.fileName);
1095
+ }
1096
+ }
1097
+ ts.forEachChild(node, visit);
1098
+ };
1099
+ ts.forEachChild(source, visit);
1100
+ }
1101
+
1102
+ function classDeclarationForExpression(expression: ts.Expression): ts.ClassDeclaration | undefined {
1103
+ let symbol = checker.getSymbolAtLocation(expression);
1104
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
1105
+ symbol = checker.getAliasedSymbol(symbol);
1106
+ }
1107
+ return classDeclarationForSymbol(symbol);
1108
+ }
1109
+
1110
+ function classDeclarationForSymbol(symbol: ts.Symbol | undefined): ts.ClassDeclaration | undefined {
1111
+ if (symbol === undefined) {
1112
+ return undefined;
1113
+ }
1114
+ return symbol.declarations?.find(ts.isClassDeclaration);
1115
+ }
1116
+
1117
+ /** Pre-pass: records names of classes that (directly) extend `ListRequest`. */
1118
+ function collectListRequestClasses(source: ts.SourceFile): void {
1119
+ const visit = (node: ts.Node): void => {
1120
+ if (ts.isClassDeclaration(node) && node.name && extendsListRequest(node)) {
1121
+ listRequestClassNames.add(node.name.text);
1122
+ }
1123
+ ts.forEachChild(node, visit);
1124
+ };
1125
+ ts.forEachChild(source, visit);
1126
+ }
1127
+
1128
+ function collectOpenApiSchemas(source: ts.SourceFile): void {
1129
+ const visit = (node: ts.Node): void => {
1130
+ if (ts.isClassDeclaration(node) && node.name) {
1131
+ const schema = openApiCodegen.schemaFromDeclaration(node);
1132
+ openApiSchemaModels.set(node, openApiCodegen.schemaNameForDeclaration(node.name.text, node));
1133
+ if (Object.keys(schema.properties as Record<string, unknown>).length > 0) {
1134
+ openApiSchemas[openApiCodegen.schemaNameForDeclaration(node.name.text, node)] = schema;
1135
+ }
1136
+ } else if (ts.isInterfaceDeclaration(node)) {
1137
+ const schema = openApiCodegen.schemaFromDeclaration(node);
1138
+ if (Object.keys(schema.properties as Record<string, unknown>).length > 0) {
1139
+ openApiSchemas[openApiCodegen.schemaNameForDeclaration(node.name.text, node)] = schema;
1140
+ }
1141
+ }
1142
+ ts.forEachChild(node, visit);
1143
+ };
1144
+ ts.forEachChild(source, visit);
1145
+ }
1146
+
1147
+ /**
1148
+ * Records runtime response constructors referenced by @UiProfile so generated
1149
+ * OpenAPI metadata can bind each constructor to its exact component schema.
1150
+ * This keeps code-first consumers nominal and avoids `class.name` matching.
1151
+ */
1152
+ function collectUiProfileResponseModels(source: ts.SourceFile): void {
1153
+ const visit = (node: ts.Node): void => {
1154
+ if (ts.isClassDeclaration(node)) {
1155
+ for (const decorator of ts.getDecorators(node) ?? []) {
1156
+ const call = decoratorCall(decorator);
1157
+ if (call?.name !== "UiProfile" || call.call === undefined) {
1158
+ continue;
1159
+ }
1160
+ const options = call.call.arguments[0];
1161
+ if (options === undefined || !ts.isObjectLiteralExpression(options)) {
1162
+ fatalErrors.push(
1163
+ `@UiProfile on ${node.name?.text ?? "<anonymous>"} must receive one object literal so codegen can resolve response identity.`,
1164
+ );
1165
+ continue;
1166
+ }
1167
+ const responseProperties: ts.PropertyAssignment[] = [];
1168
+ const topLevel = options.properties.find((property): property is ts.PropertyAssignment =>
1169
+ ts.isPropertyAssignment(property) && uiProfileOptionName(property.name) === "response");
1170
+ if (topLevel !== undefined) {
1171
+ responseProperties.push(topLevel);
1172
+ }
1173
+ const collectEndpointResponses = (candidate: ts.Node): void => {
1174
+ if (ts.isCallExpression(candidate) && expressionName(candidate.expression) === "uiEndpoint") {
1175
+ const assertions = candidate.arguments[2];
1176
+ if (assertions !== undefined && ts.isObjectLiteralExpression(assertions)) {
1177
+ const response = assertions.properties.find((property): property is ts.PropertyAssignment =>
1178
+ ts.isPropertyAssignment(property) && uiProfileOptionName(property.name) === "response");
1179
+ if (response !== undefined) {
1180
+ responseProperties.push(response);
1181
+ }
1182
+ }
1183
+ }
1184
+ ts.forEachChild(candidate, collectEndpointResponses);
1185
+ };
1186
+ collectEndpointResponses(options);
1187
+ for (const responseProperty of responseProperties) {
1188
+ const declaration = classDeclarationForExpression(responseProperty.initializer);
1189
+ if (declaration?.name === undefined) {
1190
+ fatalErrors.push(
1191
+ `@UiProfile response on ${node.name?.text ?? "<anonymous>"} must reference an exported runtime class.`,
1192
+ );
1193
+ continue;
1194
+ }
1195
+ if (!isNamedExportedTopLevelClass(declaration)) {
1196
+ fatalErrors.push(
1197
+ `@UiProfile response ${declaration.name.text} must be a named exported top-level class.`,
1198
+ );
1199
+ continue;
1200
+ }
1201
+ uiProfileResponseModels.set(
1202
+ declaration,
1203
+ openApiCodegen.schemaNameForDeclaration(declaration.name.text, declaration),
1204
+ );
1205
+ }
1206
+ }
1207
+ }
1208
+ ts.forEachChild(node, visit);
1209
+ };
1210
+ ts.forEachChild(source, visit);
1211
+ }
1212
+
1213
+ function expressionName(expression: ts.LeftHandSideExpression): string | undefined {
1214
+ if (ts.isIdentifier(expression)) {
1215
+ return expression.text;
1216
+ }
1217
+ if (ts.isPropertyAccessExpression(expression)) {
1218
+ return expression.name.text;
1219
+ }
1220
+ return undefined;
1221
+ }
1222
+
1223
+ function uiProfileOptionName(name: ts.PropertyName): string | undefined {
1224
+ if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) {
1225
+ return name.text;
1226
+ }
1227
+ return undefined;
1228
+ }
1229
+
1230
+ function resolveUiProfileResponseModelImports(): OpenApiSchemaModelImport[] {
1231
+ const out: OpenApiSchemaModelImport[] = [];
1232
+ for (const [declaration, schemaName] of uiProfileResponseModels) {
1233
+ if (openApiSchemas[schemaName] === undefined) {
1234
+ fatalErrors.push(
1235
+ `@UiProfile response ${declaration.name?.text ?? "<anonymous>"} has no generated OpenAPI schema ${schemaName}.`,
1236
+ );
1237
+ continue;
1238
+ }
1239
+ out.push({ declaration, schemaName });
1240
+ }
1241
+ return out.sort((left, right) =>
1242
+ left.schemaName.localeCompare(right.schemaName)
1243
+ || sourcePathForDeclaration(left.declaration).localeCompare(sourcePathForDeclaration(right.declaration)));
1244
+ }
1245
+
1246
+ /** Agent DTOs and their referenced classes share the existing nominal OpenAPI registry. */
1247
+ function resolveOpenApiSchemaModelImports(agentModels: readonly ts.ClassDeclaration[], agentNames: readonly string[]): OpenApiSchemaModelImport[] {
1248
+ const models = new Map(resolveUiProfileResponseModelImports().map((model) => [model.declaration, model.schemaName]));
1249
+ // Empty class contracts still have a real object schema and must survive filtering.
1250
+ for (const declaration of agentModels) {
1251
+ const name = openApiCodegen.schemaNameForDeclaration(declaration.name!.text, declaration);
1252
+ openApiSchemas[name] ??= openApiCodegen.schemaFromDeclaration(declaration);
1253
+ models.set(declaration, name);
1254
+ }
1255
+ const reachable = new Set<string>();
1256
+ const add = (name: string | undefined): void => {
1257
+ if (name === undefined || reachable.has(name)) return;
1258
+ reachable.add(name);
1259
+ collectSchemaRefs(openApiSchemas[name], add);
1260
+ };
1261
+ for (const name of agentNames) add(name);
1262
+ for (const [declaration, name] of openApiSchemaModels) {
1263
+ if (!reachable.has(name)) continue;
1264
+ if (!isNamedExportedTopLevelClass(declaration)) {
1265
+ fatalErrors.push(`BAZIS_AGENT_SCHEMA_MODEL_UNIMPORTABLE: Agent schema ${name} must be a named exported top-level class (${sourcePathForDeclaration(declaration)}).`);
1266
+ continue;
1267
+ }
1268
+ openApiSchemas[name] ??= openApiCodegen.schemaFromDeclaration(declaration);
1269
+ models.set(declaration, name);
1270
+ }
1271
+ return [...models].map(([declaration, schemaName]) => ({ declaration, schemaName })).sort((left, right) =>
1272
+ left.schemaName.localeCompare(right.schemaName)
1273
+ || sourcePathForDeclaration(left.declaration).localeCompare(sourcePathForDeclaration(right.declaration)));
1274
+ }
1275
+
1276
+ function collectOpenApiOperation(controllerName: string, method: ts.MethodDeclaration): void {
1277
+ if (!ts.isIdentifier(method.name)) {
1278
+ return;
1279
+ }
1280
+ const response = openApiCodegen.responseSchemaFromMethod(method);
1281
+ (openApiOperations[controllerName] ??= {})[method.name.text] = response === undefined ? {} : { response };
1282
+ }
1283
+
1284
+ /** True if the class has an `extends ListRequest` (with or without type args). */
1285
+ function extendsListRequest(node: ts.ClassDeclaration): boolean {
1286
+ for (const clause of node.heritageClauses ?? []) {
1287
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword) {
1288
+ continue;
1289
+ }
1290
+ for (const type of clause.types) {
1291
+ const expression = type.expression;
1292
+ if (ts.isIdentifier(expression) && expression.text === "ListRequest") {
1293
+ return true;
1294
+ }
1295
+ if (ts.isPropertyAccessExpression(expression) && expression.name.text === "ListRequest") {
1296
+ return true;
1297
+ }
1298
+ }
1299
+ }
1300
+ return false;
1301
+ }
1302
+
1303
+ function collectControllerBindings(node: ts.ClassDeclaration, controllerName: string, filePath: string): void {
1304
+ const previousFile = httpControllerFiles[controllerName];
1305
+ if (previousFile && previousFile !== filePath) {
1306
+ fatalErrors.push(
1307
+ `duplicate controller name "${controllerName}" in "${previousFile}" and "${filePath}". ` +
1308
+ `Generated bindings are keyed by class name; rename one of the controllers.`,
1309
+ );
1310
+ }
1311
+ httpControllerFiles[controllerName] = filePath;
1312
+
1313
+ for (const member of node.members) {
1314
+ if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) {
1315
+ continue;
1316
+ }
1317
+ const decorators = ts.getDecorators(member) ?? [];
1318
+ const hasRoute = decorators.some((decorator) => {
1319
+ const info = decoratorCall(decorator);
1320
+ return info !== undefined && ROUTE_DECORATORS.has(info.name);
1321
+ });
1322
+ if (hasRoute) {
1323
+ collectOpenApiOperation(controllerName, member);
1324
+ }
1325
+ if (!hasRoute) {
1326
+ continue;
1327
+ }
1328
+
1329
+ const routeParams = routeParamNames(member);
1330
+ const specs: HttpBindingSpec[] = [];
1331
+ let bodyCount = 0;
1332
+ let failed: string | undefined;
1333
+
1334
+ for (const param of member.parameters) {
1335
+ if (!ts.isIdentifier(param.name)) {
1336
+ failed = "destructured parameters are not supported";
1337
+ break;
1338
+ }
1339
+ const paramName = param.name.text;
1340
+ const optional = param.questionToken !== undefined || param.initializer !== undefined;
1341
+ const typeNode = param.type;
1342
+ const keywordType = typeNode ? typeNode.getText().trim() : undefined;
1343
+ const refName = typeNode ? getTypeReferenceName(typeNode) : undefined;
1344
+
1345
+ if (refName && CONTEXT_TYPES[refName] !== undefined) {
1346
+ specs.push({ source: CONTEXT_TYPES[refName] as HttpBindingSpec["source"] });
1347
+ continue;
1348
+ }
1349
+ if (routeParams.has(paramName)) {
1350
+ // Constraint conversion happens in the router; add type only for plain :params.
1351
+ const type = keywordType !== undefined ? PRIMITIVE_QUERY_TYPES[keywordType] : undefined;
1352
+ specs.push({ source: "route", name: paramName, type: type === "string" ? undefined : type, optional });
1353
+ continue;
1354
+ }
1355
+ const primitiveType =
1356
+ keywordType !== undefined ? PRIMITIVE_QUERY_TYPES[keywordType] : inferTypeFromInitializer(param.initializer);
1357
+ if (primitiveType !== undefined) {
1358
+ specs.push({ source: "query", name: paramName, type: primitiveType === "string" ? undefined : primitiveType, optional });
1359
+ continue;
1360
+ }
1361
+ if (refName !== undefined) {
1362
+ // Class extending ListRequest -> universal list-query param (not a body).
1363
+ if (listRequestClassNames.has(refName)) {
1364
+ specs.push({ source: "list", model: refName, optional });
1365
+ continue;
1366
+ }
1367
+ bodyCount += 1;
1368
+ if (bodyCount > 1) {
1369
+ failed = "more than one body (class-typed) parameter";
1370
+ break;
1371
+ }
1372
+ specs.push({ source: "body", model: refName, optional });
1373
+ continue;
1374
+ }
1375
+ failed = `parameter "${paramName}" has no type annotation usable for conventions`;
1376
+ break;
1377
+ }
1378
+
1379
+ if (failed) {
1380
+ fatalErrors.push(
1381
+ `BAZIS_HTTP_BINDING_UNRESOLVED: controller "${controllerName}.${member.name.text}" (${filePath}): cannot infer bindings (${failed}). ` +
1382
+ `Use supported parameter types; read headers or raw bodies through HttpContext, and inject services in the constructor.`,
1383
+ );
1384
+ continue;
1385
+ }
1386
+ (httpBindings[controllerName] ??= {})[member.name.text] = specs;
1387
+ }
1388
+ }
1389
+
1390
+ /** `limit = 100` has no type annotation — infer the primitive from the default value. */
1391
+ function inferTypeFromInitializer(initializer: ts.Expression | undefined): HttpBindingSpec["type"] | undefined {
1392
+ if (!initializer) {
1393
+ return undefined;
1394
+ }
1395
+ if (ts.isNumericLiteral(initializer)) {
1396
+ return "number";
1397
+ }
1398
+ if (
1399
+ ts.isPrefixUnaryExpression(initializer) &&
1400
+ initializer.operator === ts.SyntaxKind.MinusToken &&
1401
+ ts.isNumericLiteral(initializer.operand)
1402
+ ) {
1403
+ return "number";
1404
+ }
1405
+ if (ts.isStringLiteralLike(initializer)) {
1406
+ return "string";
1407
+ }
1408
+ if (initializer.kind === ts.SyntaxKind.TrueKeyword || initializer.kind === ts.SyntaxKind.FalseKeyword) {
1409
+ return "bool";
1410
+ }
1411
+ return undefined;
1412
+ }
1413
+
1414
+ /** Records the declaration file of a class by name; flags cross-file duplicates as ambiguous. */
1415
+ function recordClassDeclaration(declaration: ts.ClassDeclaration, filePath: string): void {
1416
+ const name = declaration.name?.text;
1417
+ if (name === undefined) {
1418
+ return;
1419
+ }
1420
+ const existing = classFilesByName.get(name);
1421
+ if (existing === undefined) {
1422
+ classFilesByName.set(name, filePath);
1423
+ classDeclarationsByName.set(name, declaration);
1424
+ } else if (existing !== filePath || classDeclarationsByName.get(name) !== declaration) {
1425
+ ambiguousClassNames.add(name);
1426
+ classDeclarationsByName.delete(name);
1427
+ }
1428
+ }
1429
+
1430
+ interface RequestModelImport {
1431
+ readonly className: string;
1432
+ readonly importPath: string;
1433
+ readonly declaration: ts.ClassDeclaration;
1434
+ }
1435
+
1436
+ /**
1437
+ * Resolves every convention body model to the file that declares it, so the
1438
+ * generated registry can import and register it automatically — no
1439
+ * `@RequestModel()` decorator and no manual side-effect import required.
1440
+ */
1441
+ function resolveRequestModelImports(): RequestModelImport[] {
1442
+ const modelNames = new Set<string>();
1443
+ for (const methods of Object.values(httpBindings)) {
1444
+ for (const specs of Object.values(methods)) {
1445
+ for (const spec of specs) {
1446
+ if (spec.source === "body" && spec.model !== undefined) {
1447
+ modelNames.add(spec.model);
1448
+ }
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ const imports: RequestModelImport[] = [];
1454
+ for (const className of [...modelNames].sort((a, b) => a.localeCompare(b))) {
1455
+ if (ambiguousClassNames.has(className)) {
1456
+ fatalErrors.push(
1457
+ `body model "${className}" is declared in more than one file; ` +
1458
+ `rename one of the classes.`,
1459
+ );
1460
+ continue;
1461
+ }
1462
+ const filePath = classFilesByName.get(className);
1463
+ const declaration = classDeclarationsByName.get(className);
1464
+ if (filePath === undefined) {
1465
+ // Not declared in scanned sources (e.g. external) — fall back to a
1466
+ // manual @RequestModel() so the body still resolves at runtime.
1467
+ if (!requestModelClassNames.has(className)) {
1468
+ warnings.push(
1469
+ `body model "${className}" was not found in scanned sources. ` +
1470
+ `Add @RequestModel() to the class.`,
1471
+ );
1472
+ }
1473
+ continue;
1474
+ }
1475
+ // Framework-internal models (e.g. test fixtures) self-register via their own
1476
+ // @RequestModel() decorator; the generated registry targets app code only.
1477
+ if (FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
1478
+ continue;
1479
+ }
1480
+ if (declaration === undefined) {
1481
+ fatalErrors.push(`body model "${className}" could not be resolved to one class declaration.`);
1482
+ continue;
1483
+ }
1484
+ imports.push({ className, importPath: toModuleSpecifier(filePath), declaration });
1485
+ }
1486
+ return imports;
1487
+ }
1488
+
1489
+ /**
1490
+ * Resolves every ListRequest subclass used as a list-query param to its
1491
+ * declaring file, so the generated registry can import and register it (the
1492
+ * binder resolves the class by name at startup).
1493
+ */
1494
+ function resolveListModelImports(): RequestModelImport[] {
1495
+ const modelNames = new Set<string>();
1496
+ for (const methods of Object.values(httpBindings)) {
1497
+ for (const specs of Object.values(methods)) {
1498
+ for (const spec of specs) {
1499
+ if (spec.source === "list" && spec.model !== undefined) {
1500
+ modelNames.add(spec.model);
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+
1506
+ const imports: RequestModelImport[] = [];
1507
+ for (const className of [...modelNames].sort((a, b) => a.localeCompare(b))) {
1508
+ if (ambiguousClassNames.has(className)) {
1509
+ fatalErrors.push(
1510
+ `list model "${className}" is declared in more than one file; rename one of the classes.`,
1511
+ );
1512
+ continue;
1513
+ }
1514
+ const filePath = classFilesByName.get(className);
1515
+ if (filePath === undefined || FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
1516
+ continue;
1517
+ }
1518
+ const declaration = classDeclarationsByName.get(className);
1519
+ if (declaration === undefined) {
1520
+ fatalErrors.push(`list model "${className}" could not be resolved to one class declaration.`);
1521
+ continue;
1522
+ }
1523
+ imports.push({ className, importPath: toModuleSpecifier(filePath), declaration });
1524
+ }
1525
+ return imports;
1526
+ }
1527
+
1528
+ /**
1529
+ * Builds the transitive DTO graph used by the runtime hydrator. TypeScript
1530
+ * source is the source of truth; no `emitDecoratorMetadata` or reflect runtime
1531
+ * dependency is required.
1532
+ */
1533
+ function resolveRequestModelHydration(conventionRoots: readonly RequestModelImport[]): RequestModelHydration {
1534
+ const requiredRoots = [...conventionRoots.map((entry) => entry.declaration), ...[...grpcBindings.values()].flatMap((bindings) => bindings.map((binding) => binding.model))];
1535
+ const roots = new Set<ts.ClassDeclaration>(requiredRoots);
1536
+ for (const declaration of explicitRequestModelRoots) {
1537
+ if (!isFrameworkInternalDeclaration(declaration) && isScannedProjectDeclaration(declaration)) {
1538
+ roots.add(declaration);
1539
+ }
1540
+ }
1541
+ const hydration = analyzeRequestModelHydration({
1542
+ checker,
1543
+ roots: [...roots],
1544
+ requiredRoots,
1545
+ isProjectDeclaration: isScannedProjectDeclaration,
1546
+ isExcludedDeclaration: isFrameworkInternalDeclaration,
1547
+ isNamedExportedTopLevelClass,
1548
+ sourcePathForDeclaration,
1549
+ sourceLocation,
1550
+ });
1551
+ fatalErrors.push(...hydration.errors);
1552
+ return hydration;
1553
+ }
1554
+
1555
+ function hasModifier(node: ts.HasModifiers, kind: ts.SyntaxKind): boolean {
1556
+ return ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false;
1557
+ }
1558
+
1559
+ function isNamedExportedTopLevelClass(declaration: ts.ClassDeclaration): boolean {
1560
+ if (declaration.name === undefined || !ts.isSourceFile(declaration.parent)) {
1561
+ return false;
1562
+ }
1563
+ if (
1564
+ hasModifier(declaration, ts.SyntaxKind.ExportKeyword) &&
1565
+ !hasModifier(declaration, ts.SyntaxKind.DefaultKeyword)
1566
+ ) {
1567
+ return true;
1568
+ }
1569
+ // Also support `class Dto {}; export { Dto };` without weakening the
1570
+ // generated named-import contract (renamed/default exports remain explicit
1571
+ // codegen errors).
1572
+ const moduleSymbol = checker.getSymbolAtLocation(declaration.parent);
1573
+ if (moduleSymbol === undefined) {
1574
+ return false;
1575
+ }
1576
+ return checker.getExportsOfModule(moduleSymbol).some((exported) => {
1577
+ if (exported.getName() !== declaration.name?.text) {
1578
+ return false;
1579
+ }
1580
+ const target = (exported.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(exported) : exported;
1581
+ return target.declarations?.includes(declaration) ?? false;
1582
+ });
1583
+ }
1584
+
1585
+ function sourcePathForDeclaration(declaration: ts.ClassDeclaration): string {
1586
+ const fileName = declaration.getSourceFile().fileName.replaceAll("\\", "/");
1587
+ return normalizeProjectPath(fileName);
1588
+ }
1589
+
1590
+ function normalizeProjectPath(fileName: string): string {
1591
+ const normalized = fileName.replaceAll("\\", "/");
1592
+ return path.isAbsolute(normalized) ? path.relative(".", normalized).replaceAll("\\", "/") : normalized;
1593
+ }
1594
+
1595
+ function isScannedProjectDeclaration(declaration: ts.ClassDeclaration): boolean {
1596
+ return sourceFilePathSet.has(sourcePathForDeclaration(declaration));
1597
+ }
1598
+
1599
+ function isFrameworkInternalDeclaration(declaration: ts.ClassDeclaration): boolean {
1600
+ const filePath = sourcePathForDeclaration(declaration);
1601
+ return FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix));
1602
+ }
1603
+
1604
+ function sourceLocation(node: ts.Node): string {
1605
+ const source = node.getSourceFile();
1606
+ const location = source.getLineAndCharacterOfPosition(node.getStart(source));
1607
+ const fileName = path.isAbsolute(source.fileName) ? path.relative(".", source.fileName) : source.fileName;
1608
+ return `${fileName.replaceAll("\\", "/")}:${location.line + 1}`;
1609
+ }
1610
+
1611
+ function renderListModels(imports: readonly RequestModelImport[]): string {
1612
+ const lines: string[] = [];
1613
+ lines.push("// This file is auto-generated by `bun run di:generate`.");
1614
+ lines.push("// Do not edit manually.");
1615
+ lines.push("//");
1616
+ lines.push("// Target-local immutable descriptor; runtime publication is staged by runtime.ts.");
1617
+ lines.push("");
1618
+ if (imports.length === 0) {
1619
+ lines.push("export const GENERATED_LIST_MODELS = Object.freeze([]);");
1620
+ lines.push("");
1621
+ return lines.join("\n");
1622
+ }
1623
+ for (const entry of imports) {
1624
+ lines.push(`import { ${entry.className} } from ${JSON.stringify(entry.importPath)};`);
1625
+ }
1626
+ lines.push("");
1627
+ lines.push("export const GENERATED_LIST_MODELS = Object.freeze([");
1628
+ for (const entry of imports) lines.push(` ${entry.className},`);
1629
+ lines.push("]);" );
1630
+ lines.push("");
1631
+ return lines.join("\n");
1632
+ }
1633
+
1634
+ /** Relative, extension-less module specifier from the generated dir to `filePath`. */
1635
+ function toModuleSpecifier(filePath: string): string {
1636
+ return toModuleSpecifierFrom(activeGeneratedDir, filePath);
1637
+ }
1638
+
1639
+ function toModuleSpecifierFrom(fromDir: string, filePath: string): string {
1640
+ const withoutExt = filePath.replace(/\.tsx?$/, "");
1641
+ let relative = path.relative(fromDir, withoutExt).replaceAll("\\", "/");
1642
+ if (!relative.startsWith(".")) {
1643
+ relative = `./${relative}`;
1644
+ }
1645
+ return relative;
1646
+ }
1647
+
1648
+ function renderRequestModels(
1649
+ imports: readonly RequestModelImport[],
1650
+ hydration: RequestModelHydration,
1651
+ ): string {
1652
+ const lines: string[] = [];
1653
+ lines.push("// This file is auto-generated by `bun run di:generate`.");
1654
+ lines.push("// Do not edit manually.");
1655
+ lines.push("//");
1656
+ lines.push("// Registers convention body models (class-typed action params) so they are");
1657
+ lines.push("// Target-local immutable descriptor; runtime publication is staged by runtime.ts.");
1658
+ lines.push("");
1659
+ if (imports.length === 0 && hydration.declarations.length === 0) {
1660
+ lines.push("export const GENERATED_REQUEST_MODELS = Object.freeze([]);");
1661
+ lines.push("export const GENERATED_REQUEST_MODEL_SHAPES = Object.freeze([]);");
1662
+ lines.push("");
1663
+ return lines.join("\n");
1664
+ }
1665
+ const aliases = new Map<ts.ClassDeclaration, string>();
1666
+ for (let index = 0; index < hydration.declarations.length; index += 1) {
1667
+ const declaration = hydration.declarations[index] as ts.ClassDeclaration;
1668
+ const alias = `RequestModel_${index}`;
1669
+ aliases.set(declaration, alias);
1670
+ lines.push(
1671
+ `import { ${declaration.name?.text ?? ""} as ${alias} } from ` +
1672
+ `${JSON.stringify(toModuleSpecifier(sourcePathForDeclaration(declaration)))};`,
1673
+ );
1674
+ }
1675
+ lines.push("");
1676
+ lines.push("export const GENERATED_REQUEST_MODELS = Object.freeze([");
1677
+ for (const entry of imports) {
1678
+ const alias = aliases.get(entry.declaration); if (alias !== undefined) lines.push(` ${alias},`);
1679
+ }
1680
+ lines.push("]);" );
1681
+ lines.push("export const GENERATED_REQUEST_MODEL_SHAPES = Object.freeze([");
1682
+ for (const declaration of hydration.declarations) {
1683
+ const alias = aliases.get(declaration);
1684
+ const shape = hydration.fields.get(declaration);
1685
+ if (alias !== undefined && shape !== undefined) {
1686
+ const fields = shape.map((field) => `${JSON.stringify(field.property)}: { ${field.primitive === undefined ? `model: ${aliases.get(field.model!)}` : `primitive: ${JSON.stringify(field.primitive)} as const`},${field.array ? " array: true," : ""}${field.nullable ? " nullable: true," : ""}${field.elementNullable ? " elementNullable: true," : ""} }`).join(", ");
1687
+ lines.push(` [${alias}, Object.freeze({ ${fields} })] as const,`);
1688
+ }
1689
+ }
1690
+ lines.push("]);" );
1691
+ lines.push("");
1692
+ return lines.join("\n");
1693
+ }
1694
+
1695
+ function renderOpenApiMetadata(
1696
+ schemas: Record<string, OpenApiCodegenSchema>,
1697
+ operations: Record<string, Record<string, OpenApiCodegenOperationSpec>>,
1698
+ target: "app" | "core",
1699
+ extraSchemaNames: readonly string[] = [],
1700
+ schemaModels: readonly OpenApiSchemaModelImport[] = [],
1701
+ ): string {
1702
+ const sortedSchemas = sortRecord(filterRecord(
1703
+ schemas,
1704
+ usedOpenApiSchemaNames(
1705
+ schemas,
1706
+ operations,
1707
+ [...extraSchemaNames, ...schemaModels.map((model) => model.schemaName)],
1708
+ ),
1709
+ ));
1710
+ const sortedOperations: Record<string, Record<string, OpenApiCodegenOperationSpec>> = {};
1711
+ for (const controllerName of Object.keys(operations).sort((a, b) => a.localeCompare(b))) {
1712
+ sortedOperations[controllerName] = sortRecord(operations[controllerName] as Record<string, OpenApiCodegenOperationSpec>);
1713
+ }
1714
+
1715
+ const lines: string[] = [];
1716
+ lines.push("// This file is auto-generated by `bun run di:generate`.");
1717
+ lines.push("// Do not edit manually.");
1718
+ lines.push("");
1719
+ lines.push(
1720
+ target === "app"
1721
+ ? 'import type { GeneratedOpenApiMetadata } from "bazis/library/openapi";'
1722
+ : 'import type { GeneratedOpenApiMetadata } from "../../../library/openapi";',
1723
+ );
1724
+ if (target === "app") {
1725
+ for (let index = 0; index < schemaModels.length; index += 1) {
1726
+ const model = schemaModels[index] as OpenApiSchemaModelImport;
1727
+ lines.push(
1728
+ `import { ${model.declaration.name?.text ?? ""} as UiSchemaModel_${index} } from ` +
1729
+ `${JSON.stringify(toModuleSpecifier(sourcePathForDeclaration(model.declaration)))};`,
1730
+ );
1731
+ }
1732
+ }
1733
+ lines.push("");
1734
+ lines.push("export const GENERATED_OPENAPI_METADATA: GeneratedOpenApiMetadata = {");
1735
+ lines.push(` schemas: ${JSON.stringify(sortedSchemas, null, 2).replaceAll("\n", "\n ")},`);
1736
+ lines.push(` operations: ${JSON.stringify(sortedOperations, null, 2).replaceAll("\n", "\n ")},`);
1737
+ lines.push("};");
1738
+ if (target === "app") {
1739
+ lines.push("");
1740
+ lines.push("export const GENERATED_OPENAPI_SCHEMA_MODELS = Object.freeze([");
1741
+ for (let index = 0; index < schemaModels.length; index += 1) lines.push(` [UiSchemaModel_${index}, ${JSON.stringify((schemaModels[index] as OpenApiSchemaModelImport).schemaName)}] as const,`);
1742
+ lines.push("]);" );
1743
+ } else lines.push("export const GENERATED_OPENAPI_SCHEMA_MODELS = Object.freeze([]);");
1744
+ lines.push("");
1745
+ return lines.join("\n");
1746
+ }
1747
+
1748
+ function sortRecord<T>(record: Record<string, T>): Record<string, T> {
1749
+ const out: Record<string, T> = {};
1750
+ for (const key of Object.keys(record).sort((a, b) => a.localeCompare(b))) {
1751
+ out[key] = record[key] as T;
1752
+ }
1753
+ return out;
1754
+ }
1755
+
1756
+ function filterRecord<T>(record: Record<string, T>, keys: ReadonlySet<string>): Record<string, T> {
1757
+ const out: Record<string, T> = {};
1758
+ for (const key of Object.keys(record).sort((a, b) => a.localeCompare(b))) {
1759
+ if (keys.has(key)) {
1760
+ out[key] = record[key] as T;
1761
+ }
1762
+ }
1763
+ return out;
1764
+ }
1765
+
1766
+ function usedOpenApiSchemaNames(
1767
+ schemas: Record<string, OpenApiCodegenSchema>,
1768
+ operations: Record<string, Record<string, OpenApiCodegenOperationSpec>>,
1769
+ extraSchemaNames: readonly string[] = [],
1770
+ ): Set<string> {
1771
+ const used = new Set<string>();
1772
+ const queue: string[] = [];
1773
+ const add = (name: string | undefined): void => {
1774
+ if (name !== undefined && schemas[name] !== undefined && !used.has(name)) {
1775
+ used.add(name);
1776
+ queue.push(name);
1777
+ }
1778
+ };
1779
+
1780
+ for (const methods of Object.values(httpBindings)) {
1781
+ for (const specs of Object.values(methods)) {
1782
+ for (const spec of specs) {
1783
+ if ((spec.source === "body" || spec.source === "list") && spec.model !== undefined) {
1784
+ add(spec.model);
1785
+ }
1786
+ }
1787
+ }
1788
+ }
1789
+ for (const methods of Object.values(operations)) {
1790
+ for (const operation of Object.values(methods)) {
1791
+ collectSchemaRefs(operation.response, add);
1792
+ }
1793
+ }
1794
+ for (const name of extraSchemaNames) add(name);
1795
+ for (let index = 0; index < queue.length; index += 1) {
1796
+ const schema = schemas[queue[index] as string];
1797
+ collectSchemaRefs(schema, add);
1798
+ }
1799
+ return used;
1800
+ }
1801
+
1802
+ function collectSchemaRefs(value: unknown, add: (name: string | undefined) => void): void {
1803
+ if (Array.isArray(value)) {
1804
+ for (const item of value) {
1805
+ collectSchemaRefs(item, add);
1806
+ }
1807
+ return;
1808
+ }
1809
+ if (value === null || typeof value !== "object") {
1810
+ return;
1811
+ }
1812
+ const record = value as Record<string, unknown>;
1813
+ const ref = record.$ref;
1814
+ if (typeof ref === "string") {
1815
+ const prefix = "#/components/schemas/";
1816
+ add(ref.startsWith(prefix) ? ref.slice(prefix.length) : undefined);
1817
+ }
1818
+ for (const item of Object.values(record)) {
1819
+ collectSchemaRefs(item, add);
1820
+ }
1821
+ }
1822
+
1823
+
1824
+
1825
+ function getTypeReferenceName(node: ts.TypeNode): string | undefined {
1826
+ if (!ts.isTypeReferenceNode(node)) {
1827
+ return undefined;
1828
+ }
1829
+ const typeName = node.typeName;
1830
+ if (ts.isIdentifier(typeName)) {
1831
+ return typeName.text;
1832
+ }
1833
+ if (ts.isQualifiedName(typeName)) {
1834
+ return typeName.right.text;
1835
+ }
1836
+ return undefined;
1837
+ }