@useatlas/create 0.0.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 (515) hide show
  1. package/README.md +231 -0
  2. package/index.ts +829 -0
  3. package/package.json +38 -0
  4. package/templates/docker/.env.example +67 -0
  5. package/templates/docker/Dockerfile +52 -0
  6. package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
  7. package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
  8. package/templates/docker/bin/__tests__/eval.test.ts +434 -0
  9. package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
  10. package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
  11. package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
  12. package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
  13. package/templates/docker/bin/__tests__/query.test.ts +240 -0
  14. package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
  15. package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
  16. package/templates/docker/bin/atlas.ts +5044 -0
  17. package/templates/docker/bin/benchmark.ts +695 -0
  18. package/templates/docker/bin/enrich.ts +559 -0
  19. package/templates/docker/bin/eval.ts +770 -0
  20. package/templates/docker/bin/smoke.ts +438 -0
  21. package/templates/docker/data/.gitkeep +0 -0
  22. package/templates/docker/data/cybersec.sql +1961 -0
  23. package/templates/docker/data/demo-semantic/catalog.yml +40 -0
  24. package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
  25. package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
  26. package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
  27. package/templates/docker/data/demo-semantic/glossary.yml +22 -0
  28. package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
  29. package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
  30. package/templates/docker/data/demo.sql +373 -0
  31. package/templates/docker/data/ecommerce.sql +1690 -0
  32. package/templates/docker/data/init-demo-db.sql +8 -0
  33. package/templates/docker/docker-compose.yml +34 -0
  34. package/templates/docker/docs/deploy.md +390 -0
  35. package/templates/docker/eslint.config.mjs +18 -0
  36. package/templates/docker/gitignore +5 -0
  37. package/templates/docker/next.config.ts +9 -0
  38. package/templates/docker/package.json +59 -0
  39. package/templates/docker/postcss.config.mjs +8 -0
  40. package/templates/docker/public/.gitkeep +0 -0
  41. package/templates/docker/public/favicon.svg +4 -0
  42. package/templates/docker/railway.json +13 -0
  43. package/templates/docker/render.yaml +34 -0
  44. package/templates/docker/semantic/catalog.yml +5 -0
  45. package/templates/docker/semantic/entities/.gitkeep +0 -0
  46. package/templates/docker/semantic/glossary.yml +6 -0
  47. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  48. package/templates/docker/sidecar/Dockerfile +28 -0
  49. package/templates/docker/sidecar/railway.json +14 -0
  50. package/templates/docker/sidecar/server.ts +188 -0
  51. package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
  52. package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
  53. package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
  54. package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
  55. package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
  56. package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
  57. package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
  58. package/templates/docker/src/api/__tests__/health.test.ts +261 -0
  59. package/templates/docker/src/api/__tests__/query.test.ts +891 -0
  60. package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  61. package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
  62. package/templates/docker/src/api/index.ts +117 -0
  63. package/templates/docker/src/api/routes/actions.ts +274 -0
  64. package/templates/docker/src/api/routes/admin.ts +757 -0
  65. package/templates/docker/src/api/routes/auth.ts +48 -0
  66. package/templates/docker/src/api/routes/chat.ts +465 -0
  67. package/templates/docker/src/api/routes/conversations.ts +266 -0
  68. package/templates/docker/src/api/routes/health.ts +287 -0
  69. package/templates/docker/src/api/routes/openapi.ts +390 -0
  70. package/templates/docker/src/api/routes/query.ts +318 -0
  71. package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
  72. package/templates/docker/src/api/routes/slack.ts +611 -0
  73. package/templates/docker/src/api/server.ts +226 -0
  74. package/templates/docker/src/app/api/[...route]/route.ts +33 -0
  75. package/templates/docker/src/app/error.tsx +24 -0
  76. package/templates/docker/src/app/globals.css +126 -0
  77. package/templates/docker/src/app/layout.tsx +19 -0
  78. package/templates/docker/src/app/page.tsx +14 -0
  79. package/templates/docker/src/global.d.ts +1 -0
  80. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
  81. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
  82. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  83. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
  84. package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
  85. package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
  86. package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
  87. package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
  88. package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
  89. package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
  90. package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
  91. package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  92. package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  93. package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
  94. package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  95. package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
  96. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
  97. package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
  98. package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
  99. package/templates/docker/src/lib/action-types.ts +95 -0
  100. package/templates/docker/src/lib/agent-query.ts +178 -0
  101. package/templates/docker/src/lib/agent.ts +505 -0
  102. package/templates/docker/src/lib/api-url.ts +2 -0
  103. package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
  104. package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  105. package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
  106. package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
  107. package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
  108. package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
  109. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
  110. package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
  111. package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
  112. package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  113. package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
  114. package/templates/docker/src/lib/auth/audit.ts +89 -0
  115. package/templates/docker/src/lib/auth/byot.ts +158 -0
  116. package/templates/docker/src/lib/auth/client.ts +35 -0
  117. package/templates/docker/src/lib/auth/detect.ts +83 -0
  118. package/templates/docker/src/lib/auth/managed.ts +73 -0
  119. package/templates/docker/src/lib/auth/middleware.ts +208 -0
  120. package/templates/docker/src/lib/auth/migrate.ts +111 -0
  121. package/templates/docker/src/lib/auth/permissions.ts +156 -0
  122. package/templates/docker/src/lib/auth/server.ts +142 -0
  123. package/templates/docker/src/lib/auth/simple-key.ts +92 -0
  124. package/templates/docker/src/lib/auth/types.ts +49 -0
  125. package/templates/docker/src/lib/config.ts +704 -0
  126. package/templates/docker/src/lib/conversation-types.ts +29 -0
  127. package/templates/docker/src/lib/conversations.ts +270 -0
  128. package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
  129. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
  130. package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
  131. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
  132. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  133. package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
  134. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
  135. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
  136. package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  137. package/templates/docker/src/lib/db/connection.ts +753 -0
  138. package/templates/docker/src/lib/db/duckdb.ts +122 -0
  139. package/templates/docker/src/lib/db/internal.ts +273 -0
  140. package/templates/docker/src/lib/db/salesforce.ts +342 -0
  141. package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
  142. package/templates/docker/src/lib/errors.ts +154 -0
  143. package/templates/docker/src/lib/logger.ts +98 -0
  144. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  145. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  146. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  147. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
  148. package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
  149. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  150. package/templates/docker/src/lib/plugins/hooks.ts +162 -0
  151. package/templates/docker/src/lib/plugins/index.ts +9 -0
  152. package/templates/docker/src/lib/plugins/migrate.ts +309 -0
  153. package/templates/docker/src/lib/plugins/registry.ts +231 -0
  154. package/templates/docker/src/lib/plugins/tools.ts +39 -0
  155. package/templates/docker/src/lib/plugins/wiring.ts +291 -0
  156. package/templates/docker/src/lib/providers.ts +102 -0
  157. package/templates/docker/src/lib/rls.ts +321 -0
  158. package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
  159. package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
  160. package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  161. package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  162. package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  163. package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  164. package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  165. package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
  166. package/templates/docker/src/lib/scheduler/engine.ts +317 -0
  167. package/templates/docker/src/lib/scheduler/executor.ts +73 -0
  168. package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
  169. package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
  170. package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
  171. package/templates/docker/src/lib/scheduler/index.ts +7 -0
  172. package/templates/docker/src/lib/security.ts +11 -0
  173. package/templates/docker/src/lib/semantic-index.ts +503 -0
  174. package/templates/docker/src/lib/semantic.ts +387 -0
  175. package/templates/docker/src/lib/sidecar-types.ts +16 -0
  176. package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
  177. package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
  178. package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
  179. package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
  180. package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
  181. package/templates/docker/src/lib/slack/api.ts +102 -0
  182. package/templates/docker/src/lib/slack/format.ts +209 -0
  183. package/templates/docker/src/lib/slack/store.ts +107 -0
  184. package/templates/docker/src/lib/slack/threads.ts +64 -0
  185. package/templates/docker/src/lib/slack/verify.ts +71 -0
  186. package/templates/docker/src/lib/startup.ts +730 -0
  187. package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  188. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  189. package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  190. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  191. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  192. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  193. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  194. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  195. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
  196. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  197. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  198. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  199. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  200. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  201. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  202. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
  203. package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  204. package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  205. package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  206. package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  207. package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
  208. package/templates/docker/src/lib/tools/actions/email.ts +191 -0
  209. package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
  210. package/templates/docker/src/lib/tools/actions/index.ts +23 -0
  211. package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
  212. package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
  213. package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
  214. package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
  215. package/templates/docker/src/lib/tools/explore.ts +379 -0
  216. package/templates/docker/src/lib/tools/registry.ts +221 -0
  217. package/templates/docker/src/lib/tools/salesforce.ts +138 -0
  218. package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
  219. package/templates/docker/src/lib/tools/sql.ts +680 -0
  220. package/templates/docker/src/lib/tracing.ts +40 -0
  221. package/templates/docker/src/lib/utils.ts +6 -0
  222. package/templates/docker/src/test-setup.ts +38 -0
  223. package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
  224. package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
  225. package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
  226. package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
  227. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
  228. package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
  229. package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
  230. package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
  231. package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
  232. package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
  233. package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
  234. package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
  235. package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
  236. package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
  237. package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
  238. package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
  239. package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
  240. package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
  241. package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
  242. package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
  243. package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
  244. package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
  245. package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
  246. package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
  247. package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
  248. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
  249. package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
  250. package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
  251. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
  252. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
  253. package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
  254. package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  255. package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  256. package/templates/docker/src/ui/context.tsx +78 -0
  257. package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
  258. package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
  259. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
  260. package/templates/docker/src/ui/lib/action-types.ts +63 -0
  261. package/templates/docker/src/ui/lib/helpers.ts +104 -0
  262. package/templates/docker/src/ui/lib/types.ts +145 -0
  263. package/templates/docker/tsconfig.json +41 -0
  264. package/templates/docker/vercel.json +3 -0
  265. package/templates/nextjs-standalone/.env.example +68 -0
  266. package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
  267. package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
  268. package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
  269. package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
  270. package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
  271. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
  272. package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
  273. package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
  274. package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
  275. package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
  276. package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
  277. package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
  278. package/templates/nextjs-standalone/bin/enrich.ts +559 -0
  279. package/templates/nextjs-standalone/bin/eval.ts +770 -0
  280. package/templates/nextjs-standalone/bin/smoke.ts +438 -0
  281. package/templates/nextjs-standalone/data/.gitkeep +0 -0
  282. package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
  283. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
  284. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
  285. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
  286. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
  287. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
  288. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
  289. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
  290. package/templates/nextjs-standalone/data/demo.sql +373 -0
  291. package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
  292. package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
  293. package/templates/nextjs-standalone/docs/deploy.md +390 -0
  294. package/templates/nextjs-standalone/eslint.config.mjs +18 -0
  295. package/templates/nextjs-standalone/gitignore +5 -0
  296. package/templates/nextjs-standalone/next.config.ts +10 -0
  297. package/templates/nextjs-standalone/package.json +63 -0
  298. package/templates/nextjs-standalone/postcss.config.mjs +8 -0
  299. package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
  300. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  301. package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
  302. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  303. package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
  304. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
  305. package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
  306. package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
  307. package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
  308. package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
  309. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
  310. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
  311. package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
  312. package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  313. package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
  314. package/templates/nextjs-standalone/src/api/index.ts +117 -0
  315. package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
  316. package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
  317. package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
  318. package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
  319. package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
  320. package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
  321. package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
  322. package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
  323. package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
  324. package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
  325. package/templates/nextjs-standalone/src/api/server.ts +226 -0
  326. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
  327. package/templates/nextjs-standalone/src/app/error.tsx +24 -0
  328. package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
  329. package/templates/nextjs-standalone/src/app/globals.css +126 -0
  330. package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
  331. package/templates/nextjs-standalone/src/app/page.tsx +14 -0
  332. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
  333. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
  334. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  335. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
  336. package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
  337. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
  338. package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
  339. package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
  340. package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
  341. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
  342. package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
  343. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  344. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  345. package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
  346. package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  347. package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
  348. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
  349. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
  350. package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
  351. package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
  352. package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
  353. package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
  354. package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
  355. package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
  356. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  357. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
  358. package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
  359. package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
  360. package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
  361. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
  362. package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
  363. package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
  364. package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  365. package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
  366. package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
  367. package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
  368. package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
  369. package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
  370. package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
  371. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
  372. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
  373. package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
  374. package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
  375. package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
  376. package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
  377. package/templates/nextjs-standalone/src/lib/config.ts +704 -0
  378. package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
  379. package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
  380. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
  381. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
  382. package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
  383. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
  384. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  385. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
  386. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
  387. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
  388. package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  389. package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
  390. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
  391. package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
  392. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
  393. package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
  394. package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
  395. package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
  396. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  397. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  398. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  399. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
  400. package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
  401. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  402. package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
  403. package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
  404. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
  405. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
  406. package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
  407. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
  408. package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
  409. package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
  410. package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
  411. package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
  412. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  413. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  414. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  415. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  416. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  417. package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
  418. package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
  419. package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
  420. package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
  421. package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
  422. package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
  423. package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
  424. package/templates/nextjs-standalone/src/lib/security.ts +11 -0
  425. package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
  426. package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
  427. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
  428. package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
  429. package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
  430. package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
  431. package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
  432. package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
  433. package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
  434. package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
  435. package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
  436. package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
  437. package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
  438. package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
  439. package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  440. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  441. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  442. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  443. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  444. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  445. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  446. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  447. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
  448. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  449. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  450. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  451. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  452. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  453. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  454. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
  455. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  456. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  457. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  458. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  459. package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
  460. package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
  461. package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
  462. package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
  463. package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
  464. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
  465. package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
  466. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
  467. package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
  468. package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
  469. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
  470. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
  471. package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
  472. package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
  473. package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
  474. package/templates/nextjs-standalone/src/test-setup.ts +38 -0
  475. package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
  476. package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
  477. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
  478. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
  479. package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
  480. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
  481. package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
  482. package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
  483. package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
  484. package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
  485. package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
  486. package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
  487. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
  488. package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
  489. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
  490. package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
  491. package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
  492. package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
  493. package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
  494. package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
  495. package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
  496. package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
  497. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
  498. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
  499. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
  500. package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
  501. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
  502. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
  503. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
  504. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
  505. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  506. package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  507. package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
  508. package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
  509. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
  510. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
  511. package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
  512. package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
  513. package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
  514. package/templates/nextjs-standalone/tsconfig.json +32 -0
  515. package/templates/nextjs-standalone/vercel.json +4 -0
@@ -0,0 +1,1063 @@
1
+ /**
2
+ * Tests for atlas.config.ts declarative configuration.
3
+ *
4
+ * Covers:
5
+ * - defineConfig() type-safe helper
6
+ * - configFromEnv() env var fallback
7
+ * - Zod validation (validateAndResolve)
8
+ * - loadConfig() from file vs env var fallback
9
+ * - applyDatasources() wiring into ConnectionRegistry
10
+ * - validateToolConfig() warnings
11
+ * - initializeConfig() integration
12
+ * - Backward compatibility
13
+ */
14
+ import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
15
+ import { resolve } from "path";
16
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from "fs";
17
+
18
+ // Mock database drivers so ConnectionRegistry.register() works without real DBs
19
+ mock.module("pg", () => ({
20
+ Pool: class MockPool {
21
+ async query() { return { rows: [], fields: [] }; }
22
+ async connect() {
23
+ return { async query() { return { rows: [], fields: [] }; }, release() {} };
24
+ }
25
+ async end() {}
26
+ },
27
+ }));
28
+
29
+ mock.module("mysql2/promise", () => ({
30
+ createPool: () => ({
31
+ async getConnection() {
32
+ return { async execute() { return [[], []]; }, release() {} };
33
+ },
34
+ async end() {},
35
+ }),
36
+ }));
37
+
38
+ // Cache-busting imports
39
+ const configModPath = resolve(__dirname, "../config.ts");
40
+ const configMod = await import(`${configModPath}?t=${Date.now()}`);
41
+ const {
42
+ defineConfig,
43
+ configFromEnv,
44
+ validateAndResolve,
45
+ loadConfig,
46
+ getConfig,
47
+ applyDatasources,
48
+ validateToolConfig,
49
+ initializeConfig,
50
+ _resetConfig,
51
+ } = configMod as typeof import("../config");
52
+
53
+ const connModPath = resolve(__dirname, "../db/connection.ts");
54
+ const connMod = await import(`${connModPath}?t=${Date.now()}`);
55
+ const ConnectionRegistry = connMod.ConnectionRegistry as typeof import("../db/connection").ConnectionRegistry;
56
+
57
+ // Temporary directory for config file tests — uses unique names per test
58
+ const tmpBase = resolve(__dirname, ".tmp-config-test");
59
+
60
+ function ensureTmpDir(subdir: string): string {
61
+ const dir = resolve(tmpBase, subdir);
62
+ mkdirSync(dir, { recursive: true });
63
+ return dir;
64
+ }
65
+
66
+ function cleanTmpBase() {
67
+ if (existsSync(tmpBase)) {
68
+ rmSync(tmpBase, { recursive: true, force: true });
69
+ }
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // defineConfig
74
+ // ---------------------------------------------------------------------------
75
+
76
+ describe("defineConfig", () => {
77
+ it("returns the config object unchanged (pass-through)", () => {
78
+ const config = defineConfig({
79
+ datasources: {
80
+ default: { url: "postgresql://localhost/db" },
81
+ },
82
+ });
83
+ expect(config.datasources).toEqual({
84
+ default: { url: "postgresql://localhost/db" },
85
+ });
86
+ });
87
+
88
+ it("allows minimal config (empty object)", () => {
89
+ const config = defineConfig({});
90
+ expect(config).toEqual({});
91
+ });
92
+
93
+ it("accepts all fields", () => {
94
+ const config = defineConfig({
95
+ datasources: {
96
+ default: { url: "postgresql://host/data" },
97
+ warehouse: { url: "postgresql://host/wh", schema: "analytics", description: "Data warehouse" },
98
+ },
99
+ tools: ["explore", "executeSQL"],
100
+ auth: "api-key",
101
+ semanticLayer: "./custom-semantic",
102
+ });
103
+ expect(config.tools).toEqual(["explore", "executeSQL"]);
104
+ expect(config.auth).toBe("api-key");
105
+ expect(config.semanticLayer).toBe("./custom-semantic");
106
+ });
107
+
108
+ it("preserves description through defineConfig", () => {
109
+ const config = defineConfig({
110
+ datasources: {
111
+ warehouse: { url: "postgresql://host/wh", description: "Analytics warehouse" },
112
+ },
113
+ });
114
+ expect(config.datasources!.warehouse.description).toBe("Analytics warehouse");
115
+ });
116
+
117
+ it("accepts pool config fields (maxConnections, idleTimeoutMs, rateLimit)", () => {
118
+ const config = defineConfig({
119
+ datasources: {
120
+ default: {
121
+ url: "postgresql://host/db",
122
+ maxConnections: 20,
123
+ idleTimeoutMs: 60000,
124
+ rateLimit: { queriesPerMinute: 30, concurrency: 3 },
125
+ },
126
+ },
127
+ maxTotalConnections: 200,
128
+ });
129
+ expect(config.datasources!.default.maxConnections).toBe(20);
130
+ expect(config.datasources!.default.idleTimeoutMs).toBe(60000);
131
+ expect(config.datasources!.default.rateLimit!.queriesPerMinute).toBe(30);
132
+ expect(config.maxTotalConnections).toBe(200);
133
+ });
134
+ });
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // configFromEnv
138
+ // ---------------------------------------------------------------------------
139
+
140
+ describe("configFromEnv", () => {
141
+ const origUrl = process.env.ATLAS_DATASOURCE_URL;
142
+ const origSchema = process.env.ATLAS_SCHEMA;
143
+
144
+ beforeEach(() => {
145
+ delete process.env.ATLAS_DATASOURCE_URL;
146
+ delete process.env.ATLAS_SCHEMA;
147
+ });
148
+
149
+ afterEach(() => {
150
+ if (origUrl !== undefined) process.env.ATLAS_DATASOURCE_URL = origUrl;
151
+ else delete process.env.ATLAS_DATASOURCE_URL;
152
+ if (origSchema !== undefined) process.env.ATLAS_SCHEMA = origSchema;
153
+ else delete process.env.ATLAS_SCHEMA;
154
+ });
155
+
156
+ it("builds config from ATLAS_DATASOURCE_URL", () => {
157
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/test";
158
+ const config = configFromEnv();
159
+ expect(config.source).toBe("env");
160
+ expect(config.datasources.default).toEqual({ url: "postgresql://host/test" });
161
+ expect(config.tools).toEqual(["explore", "executeSQL"]);
162
+ expect(config.auth).toBe("auto");
163
+ expect(config.semanticLayer).toBe("./semantic");
164
+ });
165
+
166
+ it("includes schema when ATLAS_SCHEMA is set", () => {
167
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/db";
168
+ process.env.ATLAS_SCHEMA = "analytics";
169
+ const config = configFromEnv();
170
+ expect(config.datasources.default).toEqual({
171
+ url: "postgresql://host/db",
172
+ schema: "analytics",
173
+ });
174
+ });
175
+
176
+ it("returns empty datasources when ATLAS_DATASOURCE_URL is not set", () => {
177
+ const config = configFromEnv();
178
+ expect(config.datasources).toEqual({});
179
+ });
180
+ });
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // validateAndResolve (Zod validation)
184
+ // ---------------------------------------------------------------------------
185
+
186
+ describe("validateAndResolve", () => {
187
+ it("validates and resolves a minimal config", () => {
188
+ const resolved = validateAndResolve({});
189
+ expect(resolved.source).toBe("file");
190
+ expect(resolved.datasources).toEqual({});
191
+ expect(resolved.tools).toEqual(["explore", "executeSQL"]);
192
+ expect(resolved.auth).toBe("auto");
193
+ expect(resolved.semanticLayer).toBe("./semantic");
194
+ });
195
+
196
+ it("resolves a full config", () => {
197
+ const resolved = validateAndResolve({
198
+ datasources: {
199
+ default: { url: "postgresql://host/data" },
200
+ warehouse: { url: "postgresql://host/wh", schema: "sales" },
201
+ },
202
+ tools: ["explore"],
203
+ auth: "managed",
204
+ semanticLayer: "./custom",
205
+ });
206
+ expect(resolved.datasources.warehouse).toEqual({
207
+ url: "postgresql://host/wh",
208
+ schema: "sales",
209
+ });
210
+ expect(resolved.tools).toEqual(["explore"]);
211
+ expect(resolved.auth).toBe("managed");
212
+ expect(resolved.semanticLayer).toBe("./custom");
213
+ });
214
+
215
+ it("preserves description through validateAndResolve", () => {
216
+ const resolved = validateAndResolve({
217
+ datasources: {
218
+ default: { url: "postgresql://host/data" },
219
+ warehouse: { url: "postgresql://host/wh", description: "Analytics warehouse" },
220
+ },
221
+ });
222
+ expect(resolved.datasources.warehouse.description).toBe("Analytics warehouse");
223
+ expect(resolved.datasources.default.description).toBeUndefined();
224
+ });
225
+
226
+ it("throws on invalid auth value", () => {
227
+ expect(() => validateAndResolve({ auth: "invalid-mode" })).toThrow(
228
+ "Invalid atlas.config.ts",
229
+ );
230
+ });
231
+
232
+ it("throws on empty datasource URL", () => {
233
+ expect(() =>
234
+ validateAndResolve({ datasources: { default: { url: "" } } }),
235
+ ).toThrow("Invalid atlas.config.ts");
236
+ });
237
+
238
+ it("throws on non-string datasource URL", () => {
239
+ expect(() =>
240
+ validateAndResolve({ datasources: { default: { url: 123 } } }),
241
+ ).toThrow("Invalid atlas.config.ts");
242
+ });
243
+
244
+ it("throws when tools is not an array", () => {
245
+ expect(() => validateAndResolve({ tools: "explore" })).toThrow(
246
+ "Invalid atlas.config.ts",
247
+ );
248
+ });
249
+
250
+ it("accepts all valid auth modes", () => {
251
+ const validModes = ["auto", "none", "api-key", "managed", "byot"] as const;
252
+ for (const auth of validModes) {
253
+ const resolved = validateAndResolve({ auth });
254
+ expect(resolved.auth).toBe(auth);
255
+ }
256
+ });
257
+
258
+ it("throws on function input (type guard)", () => {
259
+ expect(() => validateAndResolve(() => ({}))).toThrow(
260
+ "must export a plain object. Got function",
261
+ );
262
+ });
263
+
264
+ it("throws on array input (type guard)", () => {
265
+ expect(() => validateAndResolve([{ datasources: {} }])).toThrow(
266
+ "must export a plain object. Got array",
267
+ );
268
+ });
269
+
270
+ it("throws on string input (type guard)", () => {
271
+ expect(() => validateAndResolve("not an object")).toThrow(
272
+ "must export a plain object. Got string",
273
+ );
274
+ });
275
+
276
+ it("accepts and resolves pool/rate-limit fields", () => {
277
+ const resolved = validateAndResolve({
278
+ datasources: {
279
+ default: {
280
+ url: "postgresql://host/data",
281
+ maxConnections: 20,
282
+ idleTimeoutMs: 60000,
283
+ rateLimit: { queriesPerMinute: 30, concurrency: 3 },
284
+ },
285
+ },
286
+ maxTotalConnections: 200,
287
+ });
288
+ expect(resolved.datasources.default.maxConnections).toBe(20);
289
+ expect(resolved.datasources.default.idleTimeoutMs).toBe(60000);
290
+ expect(resolved.datasources.default.rateLimit!.queriesPerMinute).toBe(30);
291
+ expect(resolved.maxTotalConnections).toBe(200);
292
+ });
293
+
294
+ it("defaults work when pool/rate-limit fields are omitted", () => {
295
+ const resolved = validateAndResolve({
296
+ datasources: {
297
+ default: { url: "postgresql://host/data" },
298
+ },
299
+ });
300
+ expect(resolved.datasources.default.maxConnections).toBeUndefined();
301
+ expect(resolved.datasources.default.idleTimeoutMs).toBeUndefined();
302
+ expect(resolved.datasources.default.rateLimit).toBeUndefined();
303
+ expect(resolved.maxTotalConnections).toBe(100);
304
+ });
305
+
306
+ it("includes field path in error messages", () => {
307
+ try {
308
+ validateAndResolve({ datasources: { bad: { url: "" } } });
309
+ expect.unreachable("should have thrown");
310
+ } catch (err) {
311
+ expect((err as Error).message).toContain("datasources.bad.url");
312
+ }
313
+ });
314
+ });
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // loadConfig (file loading)
318
+ // ---------------------------------------------------------------------------
319
+
320
+ describe("loadConfig", () => {
321
+ const origUrl = process.env.ATLAS_DATASOURCE_URL;
322
+ const origSchema = process.env.ATLAS_SCHEMA;
323
+ let testCounter = 0;
324
+
325
+ beforeEach(() => {
326
+ _resetConfig();
327
+ delete process.env.ATLAS_DATASOURCE_URL;
328
+ delete process.env.ATLAS_SCHEMA;
329
+ testCounter++;
330
+ });
331
+
332
+ afterEach(() => {
333
+ _resetConfig();
334
+ if (origUrl !== undefined) process.env.ATLAS_DATASOURCE_URL = origUrl;
335
+ else delete process.env.ATLAS_DATASOURCE_URL;
336
+ if (origSchema !== undefined) process.env.ATLAS_SCHEMA = origSchema;
337
+ else delete process.env.ATLAS_SCHEMA;
338
+ });
339
+
340
+ afterEach(cleanTmpBase);
341
+
342
+ it("falls back to env vars when no config file exists", async () => {
343
+ const dir = ensureTmpDir(`load-env-${testCounter}`);
344
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/test";
345
+ process.env.ATLAS_SCHEMA = "custom";
346
+
347
+ const config = await loadConfig(dir);
348
+ expect(config.source).toBe("env");
349
+ expect(config.datasources.default).toEqual({
350
+ url: "postgresql://host/test",
351
+ schema: "custom",
352
+ });
353
+ });
354
+
355
+ it("loads config from atlas.config.ts file", async () => {
356
+ const dir = ensureTmpDir(`load-ts-${testCounter}`);
357
+ writeFileSync(
358
+ resolve(dir, "atlas.config.ts"),
359
+ `export default {
360
+ datasources: {
361
+ default: { url: "postgresql://host/data" },
362
+ warehouse: { url: "postgresql://host/wh", schema: "sales" },
363
+ },
364
+ tools: ["explore", "executeSQL"],
365
+ auth: "api-key",
366
+ semanticLayer: "./my-semantic",
367
+ };`,
368
+ );
369
+
370
+ const config = await loadConfig(dir);
371
+ expect(config.source).toBe("file");
372
+ expect(Object.keys(config.datasources)).toEqual(["default", "warehouse"]);
373
+ expect(config.datasources.warehouse).toEqual({
374
+ url: "postgresql://host/wh",
375
+ schema: "sales",
376
+ });
377
+ expect(config.auth).toBe("api-key");
378
+ expect(config.semanticLayer).toBe("./my-semantic");
379
+ });
380
+
381
+ it("loads config from atlas.config.js file", async () => {
382
+ const dir = ensureTmpDir(`load-js-${testCounter}`);
383
+ writeFileSync(
384
+ resolve(dir, "atlas.config.js"),
385
+ `module.exports = {
386
+ datasources: {
387
+ default: { url: "postgresql://host/fallback" },
388
+ },
389
+ };`,
390
+ );
391
+
392
+ const config = await loadConfig(dir);
393
+ expect(config.source).toBe("file");
394
+ expect(config.datasources.default.url).toBe("postgresql://host/fallback");
395
+ });
396
+
397
+ it("populates getConfig() after loading", async () => {
398
+ const dir = ensureTmpDir(`load-getconfig-${testCounter}`);
399
+ expect(getConfig()).toBeNull();
400
+
401
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/test";
402
+ const config = await loadConfig(dir);
403
+ expect(getConfig()).toBe(config);
404
+ });
405
+
406
+ it("applies defaults for omitted fields in config file", async () => {
407
+ const dir = ensureTmpDir(`load-defaults-${testCounter}`);
408
+ writeFileSync(resolve(dir, "atlas.config.ts"), `export default {};`);
409
+
410
+ const config = await loadConfig(dir);
411
+ expect(config.source).toBe("file");
412
+ expect(config.datasources).toEqual({});
413
+ expect(config.tools).toEqual(["explore", "executeSQL"]);
414
+ expect(config.auth).toBe("auto");
415
+ expect(config.semanticLayer).toBe("./semantic");
416
+ });
417
+
418
+ it("throws on invalid auth value in config file", async () => {
419
+ const dir = ensureTmpDir(`load-bad-auth-${testCounter}`);
420
+ writeFileSync(
421
+ resolve(dir, "atlas.config.ts"),
422
+ `export default { auth: "invalid-mode" };`,
423
+ );
424
+
425
+ await expect(loadConfig(dir)).rejects.toThrow("Invalid atlas.config.ts");
426
+ });
427
+
428
+ it("throws on empty datasource URL in config file", async () => {
429
+ const dir = ensureTmpDir(`load-bad-url-${testCounter}`);
430
+ writeFileSync(
431
+ resolve(dir, "atlas.config.ts"),
432
+ `export default { datasources: { default: { url: "" } } };`,
433
+ );
434
+
435
+ await expect(loadConfig(dir)).rejects.toThrow("Invalid atlas.config.ts");
436
+ });
437
+
438
+ it("throws on malformed config file (syntax error)", async () => {
439
+ const dir = ensureTmpDir(`load-syntax-${testCounter}`);
440
+ writeFileSync(
441
+ resolve(dir, "atlas.config.ts"),
442
+ `export default {{{ broken`,
443
+ );
444
+
445
+ await expect(loadConfig(dir)).rejects.toThrow("Failed to load config file");
446
+ });
447
+
448
+ it("throws when config file has only named exports (no default)", async () => {
449
+ const dir = ensureTmpDir(`load-named-only-${testCounter}`);
450
+ writeFileSync(
451
+ resolve(dir, "atlas.config.ts"),
452
+ `export const config = { datasources: { default: { url: "postgresql://host/data" } } };`,
453
+ );
454
+
455
+ await expect(loadConfig(dir)).rejects.toThrow(
456
+ "does not have a default export",
457
+ );
458
+ });
459
+
460
+ it("throws when config file exports a function instead of an object", async () => {
461
+ const dir = ensureTmpDir(`load-function-${testCounter}`);
462
+ writeFileSync(
463
+ resolve(dir, "atlas.config.ts"),
464
+ `export default () => ({ datasources: {} });`,
465
+ );
466
+
467
+ await expect(loadConfig(dir)).rejects.toThrow(
468
+ "must export a plain object",
469
+ );
470
+ });
471
+
472
+ it("throws when config file exports an array instead of an object", async () => {
473
+ const dir = ensureTmpDir(`load-array-${testCounter}`);
474
+ writeFileSync(
475
+ resolve(dir, "atlas.config.ts"),
476
+ `export default [{ datasources: {} }];`,
477
+ );
478
+
479
+ await expect(loadConfig(dir)).rejects.toThrow(
480
+ "must export a plain object. Got array",
481
+ );
482
+ });
483
+
484
+ it("prefers atlas.config.ts over atlas.config.js", async () => {
485
+ const dir = ensureTmpDir(`load-prefer-ts-${testCounter}`);
486
+ writeFileSync(
487
+ resolve(dir, "atlas.config.ts"),
488
+ `export default { auth: "api-key" };`,
489
+ );
490
+ writeFileSync(
491
+ resolve(dir, "atlas.config.js"),
492
+ `module.exports = { auth: "managed" };`,
493
+ );
494
+
495
+ const config = await loadConfig(dir);
496
+ expect(config.auth).toBe("api-key");
497
+ });
498
+ });
499
+
500
+ // ---------------------------------------------------------------------------
501
+ // applyDatasources (wiring into ConnectionRegistry)
502
+ // ---------------------------------------------------------------------------
503
+
504
+ describe("applyDatasources", () => {
505
+ let testRegistry: InstanceType<typeof ConnectionRegistry>;
506
+
507
+ beforeEach(() => {
508
+ testRegistry = new ConnectionRegistry();
509
+ });
510
+
511
+ afterEach(() => {
512
+ testRegistry._reset();
513
+ });
514
+
515
+ it("registers datasources from config into ConnectionRegistry", async () => {
516
+ await applyDatasources(
517
+ {
518
+ datasources: {
519
+ analytics: { url: "postgresql://host/analytics" },
520
+ reporting: { url: "mysql://host/reporting" },
521
+ },
522
+ tools: ["explore", "executeSQL"],
523
+ auth: "auto",
524
+ semanticLayer: "./semantic",
525
+ maxTotalConnections: 100,
526
+ source: "file",
527
+ },
528
+ testRegistry,
529
+ );
530
+
531
+ expect(testRegistry.list()).toContain("analytics");
532
+ expect(testRegistry.list()).toContain("reporting");
533
+ expect(testRegistry.get("analytics")).toBeDefined();
534
+ expect(testRegistry.get("reporting")).toBeDefined();
535
+ });
536
+
537
+ it("skips registration when datasources is empty (env var fallback)", async () => {
538
+ await applyDatasources(
539
+ {
540
+ datasources: {},
541
+ tools: ["explore", "executeSQL"],
542
+ auth: "auto",
543
+ semanticLayer: "./semantic",
544
+ maxTotalConnections: 100,
545
+ source: "env",
546
+ },
547
+ testRegistry,
548
+ );
549
+
550
+ expect(testRegistry.list()).toEqual([]);
551
+ });
552
+
553
+ it("registers default datasource from config file", async () => {
554
+ await applyDatasources(
555
+ {
556
+ datasources: {
557
+ default: { url: "postgresql://host/main" },
558
+ },
559
+ tools: ["explore", "executeSQL"],
560
+ auth: "auto",
561
+ semanticLayer: "./semantic",
562
+ maxTotalConnections: 100,
563
+ source: "file",
564
+ },
565
+ testRegistry,
566
+ );
567
+
568
+ expect(testRegistry.list()).toContain("default");
569
+ const conn = testRegistry.get("default");
570
+ expect(conn).toBeDefined();
571
+ expect(conn.query).toBeFunction();
572
+ });
573
+
574
+ it("registers multiple datasource types", async () => {
575
+ await applyDatasources(
576
+ {
577
+ datasources: {
578
+ pg: { url: "postgresql://host/db", schema: "sales" },
579
+ mysql: { url: "mysql://host/db" },
580
+ },
581
+ tools: [],
582
+ auth: "auto",
583
+ semanticLayer: "./semantic",
584
+ maxTotalConnections: 100,
585
+ source: "file",
586
+ },
587
+ testRegistry,
588
+ );
589
+
590
+ expect(testRegistry.list().sort()).toEqual(["mysql", "pg"]);
591
+ expect(testRegistry.getDBType("pg")).toBe("postgres");
592
+ expect(testRegistry.getDBType("mysql")).toBe("mysql");
593
+ });
594
+
595
+ it("passes description through to ConnectionRegistry", async () => {
596
+ await applyDatasources(
597
+ {
598
+ datasources: {
599
+ main: { url: "postgresql://user:pass@host:5432/main", description: "Primary DB" },
600
+ reporting: { url: "mysql://user:pass@host:3306/reporting" },
601
+ },
602
+ tools: [],
603
+ auth: "auto",
604
+ semanticLayer: "./semantic",
605
+ maxTotalConnections: 100,
606
+ source: "file",
607
+ },
608
+ testRegistry,
609
+ );
610
+
611
+ const meta = testRegistry.describe();
612
+ const mainMeta = meta.find((m) => m.id === "main");
613
+ expect(mainMeta!.description).toBe("Primary DB");
614
+ const reportingMeta = meta.find((m) => m.id === "reporting");
615
+ expect(reportingMeta!.description).toBeUndefined();
616
+ });
617
+
618
+ it("passes pool config (maxConnections, idleTimeoutMs) to ConnectionRegistry", async () => {
619
+ await applyDatasources(
620
+ {
621
+ datasources: {
622
+ default: {
623
+ url: "postgresql://host/main",
624
+ maxConnections: 25,
625
+ idleTimeoutMs: 45000,
626
+ },
627
+ },
628
+ tools: [],
629
+ auth: "auto",
630
+ semanticLayer: "./semantic",
631
+ maxTotalConnections: 100,
632
+ source: "file",
633
+ },
634
+ testRegistry,
635
+ );
636
+
637
+ expect(testRegistry.list()).toContain("default");
638
+ expect(testRegistry.get("default")).toBeDefined();
639
+ });
640
+
641
+ it("throws with datasource ID when registration fails", async () => {
642
+ // An invalid schema name (contains SQL injection chars) triggers
643
+ // the regex guard in createPostgresDB, causing register() to throw.
644
+ await expect(
645
+ applyDatasources(
646
+ {
647
+ datasources: {
648
+ good: { url: "postgresql://host/ok" },
649
+ broken: { url: "postgresql://host/db", schema: "bad; DROP TABLE" },
650
+ },
651
+ tools: [],
652
+ auth: "auto",
653
+ semanticLayer: "./semantic",
654
+ maxTotalConnections: 100,
655
+ source: "file",
656
+ },
657
+ testRegistry,
658
+ ),
659
+ ).rejects.toThrow(/Failed to register datasource "broken"/);
660
+ });
661
+ });
662
+
663
+ // ---------------------------------------------------------------------------
664
+ // validateToolConfig
665
+ // ---------------------------------------------------------------------------
666
+
667
+ describe("validateToolConfig", () => {
668
+ it("does not throw for valid tool names", async () => {
669
+ // Use the real defaultRegistry (from the non-cache-busted import path)
670
+ const { defaultRegistry } = await import("@atlas/api/lib/tools/registry");
671
+ await expect(
672
+ validateToolConfig(
673
+ {
674
+ datasources: {},
675
+ tools: ["explore", "executeSQL"],
676
+ auth: "auto",
677
+ semanticLayer: "./semantic",
678
+ maxTotalConnections: 100,
679
+ source: "env",
680
+ },
681
+ defaultRegistry,
682
+ ),
683
+ ).resolves.toBeUndefined();
684
+ });
685
+
686
+ it("throws for unknown tool names", async () => {
687
+ const { defaultRegistry } = await import("@atlas/api/lib/tools/registry");
688
+ await expect(
689
+ validateToolConfig(
690
+ {
691
+ datasources: {},
692
+ tools: ["explore", "unknownTool"],
693
+ auth: "auto",
694
+ semanticLayer: "./semantic",
695
+ maxTotalConnections: 100,
696
+ source: "env",
697
+ },
698
+ defaultRegistry,
699
+ ),
700
+ ).rejects.toThrow("Unknown tool(s) in config: unknownTool");
701
+ });
702
+
703
+ it("includes available tools in unknown tool error message", async () => {
704
+ const { defaultRegistry } = await import("@atlas/api/lib/tools/registry");
705
+ try {
706
+ await validateToolConfig(
707
+ {
708
+ datasources: {},
709
+ tools: ["typoTool"],
710
+ auth: "auto",
711
+ semanticLayer: "./semantic",
712
+ maxTotalConnections: 100,
713
+ source: "env",
714
+ },
715
+ defaultRegistry,
716
+ );
717
+ expect.unreachable("should have thrown");
718
+ } catch (err) {
719
+ const msg = (err as Error).message;
720
+ expect(msg).toContain("Available:");
721
+ expect(msg).toContain("explore");
722
+ expect(msg).toContain("executeSQL");
723
+ }
724
+ });
725
+ });
726
+
727
+ // ---------------------------------------------------------------------------
728
+ // initializeConfig (integration)
729
+ // ---------------------------------------------------------------------------
730
+
731
+ describe("initializeConfig", () => {
732
+ const origUrl = process.env.ATLAS_DATASOURCE_URL;
733
+ let testCounter = 0;
734
+ let testConnRegistry: InstanceType<typeof ConnectionRegistry>;
735
+
736
+ beforeEach(() => {
737
+ _resetConfig();
738
+ testConnRegistry = new ConnectionRegistry();
739
+ delete process.env.ATLAS_DATASOURCE_URL;
740
+ testCounter++;
741
+ });
742
+
743
+ afterEach(() => {
744
+ _resetConfig();
745
+ testConnRegistry._reset();
746
+ cleanTmpBase();
747
+ if (origUrl !== undefined) process.env.ATLAS_DATASOURCE_URL = origUrl;
748
+ else delete process.env.ATLAS_DATASOURCE_URL;
749
+ });
750
+
751
+ it("works with env vars when no config file", async () => {
752
+ const dir = ensureTmpDir(`init-env-${testCounter}`);
753
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/env";
754
+
755
+ const config = await initializeConfig(dir, {
756
+ connectionRegistry: testConnRegistry,
757
+ });
758
+ expect(config.source).toBe("env");
759
+ // env-synthesized config includes default datasource
760
+ expect(testConnRegistry.list()).toContain("default");
761
+ });
762
+
763
+ it("throws on invalid config file", async () => {
764
+ const dir = ensureTmpDir(`init-invalid-${testCounter}`);
765
+ writeFileSync(
766
+ resolve(dir, "atlas.config.ts"),
767
+ `export default { auth: 12345 };`,
768
+ );
769
+
770
+ await expect(
771
+ initializeConfig(dir, { connectionRegistry: testConnRegistry }),
772
+ ).rejects.toThrow("Invalid atlas.config.ts");
773
+ });
774
+
775
+ it("loads file config and registers datasources", async () => {
776
+ const dir = ensureTmpDir(`init-file-${testCounter}`);
777
+ writeFileSync(
778
+ resolve(dir, "atlas.config.ts"),
779
+ `export default {
780
+ datasources: {
781
+ main: { url: "postgresql://host/main" },
782
+ secondary: { url: "mysql://host/sec" },
783
+ },
784
+ };`,
785
+ );
786
+
787
+ const config = await initializeConfig(dir, {
788
+ connectionRegistry: testConnRegistry,
789
+ });
790
+ expect(config.source).toBe("file");
791
+ expect(testConnRegistry.list()).toContain("main");
792
+ expect(testConnRegistry.list()).toContain("secondary");
793
+ });
794
+ });
795
+
796
+ // ---------------------------------------------------------------------------
797
+ // Backward compatibility
798
+ // ---------------------------------------------------------------------------
799
+
800
+ describe("backward compatibility", () => {
801
+ const origUrl = process.env.ATLAS_DATASOURCE_URL;
802
+ let testCounter = 0;
803
+ let testConnRegistry: InstanceType<typeof ConnectionRegistry>;
804
+
805
+ beforeEach(() => {
806
+ _resetConfig();
807
+ testConnRegistry = new ConnectionRegistry();
808
+ delete process.env.ATLAS_DATASOURCE_URL;
809
+ testCounter++;
810
+ });
811
+
812
+ afterEach(() => {
813
+ _resetConfig();
814
+ testConnRegistry._reset();
815
+ cleanTmpBase();
816
+ if (origUrl !== undefined) process.env.ATLAS_DATASOURCE_URL = origUrl;
817
+ else delete process.env.ATLAS_DATASOURCE_URL;
818
+ });
819
+
820
+ it("env-var-only deploy: default datasource registered from env", async () => {
821
+ const dir = ensureTmpDir(`compat-env-${testCounter}`);
822
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/legacy";
823
+
824
+ // Simulate the no-config-file path
825
+ const config = await loadConfig(dir);
826
+ expect(config.source).toBe("env");
827
+ // configFromEnv includes the default datasource from ATLAS_DATASOURCE_URL
828
+ expect(config.datasources.default.url).toBe("postgresql://host/legacy");
829
+
830
+ // applyDatasources registers the default from the env-synthesized config
831
+ await applyDatasources(config, testConnRegistry);
832
+ expect(testConnRegistry.list()).toContain("default");
833
+
834
+ // getDefault() returns the pre-registered connection (no lazy-init needed)
835
+ const conn = testConnRegistry.getDefault();
836
+ expect(conn).toBeDefined();
837
+ });
838
+
839
+ it("env-var-only deploy without ATLAS_DATASOURCE_URL: getDefault() lazy-inits", async () => {
840
+ const dir = ensureTmpDir(`compat-no-env-${testCounter}`);
841
+ // ATLAS_DATASOURCE_URL is not set
842
+
843
+ const config = await loadConfig(dir);
844
+ expect(config.source).toBe("env");
845
+ expect(config.datasources).toEqual({});
846
+
847
+ // applyDatasources skips (no datasources in env config)
848
+ await applyDatasources(config, testConnRegistry);
849
+ expect(testConnRegistry.list()).toEqual([]);
850
+
851
+ // getDefault() would throw since no default registered and no env var
852
+ expect(() => testConnRegistry.getDefault()).toThrow(
853
+ "No analytics datasource configured",
854
+ );
855
+ });
856
+
857
+ it("config file with default datasource pre-registers, blocking env var lookup", async () => {
858
+ const dir = ensureTmpDir(`compat-file-${testCounter}`);
859
+ process.env.ATLAS_DATASOURCE_URL = "postgresql://host/should-not-use";
860
+
861
+ writeFileSync(
862
+ resolve(dir, "atlas.config.ts"),
863
+ `export default {
864
+ datasources: {
865
+ default: { url: "postgresql://host/from-config" },
866
+ },
867
+ };`,
868
+ );
869
+
870
+ const config = await loadConfig(dir);
871
+ expect(config.source).toBe("file");
872
+
873
+ await applyDatasources(config, testConnRegistry);
874
+ expect(testConnRegistry.list()).toContain("default");
875
+ // The connection was registered from config, not from env var
876
+ expect(config.datasources.default.url).toBe("postgresql://host/from-config");
877
+ });
878
+ });
879
+
880
+ // ---------------------------------------------------------------------------
881
+ // Plugin validation in validateAndResolve
882
+ // ---------------------------------------------------------------------------
883
+
884
+ describe("plugin validation", () => {
885
+ it("accepts valid plugin objects", () => {
886
+ const resolved = validateAndResolve({
887
+ plugins: [
888
+ {
889
+ id: "my-plugin",
890
+ type: "datasource",
891
+ version: "1.0.0",
892
+ connection: { create: () => ({}), dbType: "postgres" },
893
+ },
894
+ {
895
+ id: "ctx-plugin",
896
+ type: "context",
897
+ version: "2.0.0",
898
+ contextProvider: { load: async () => "" },
899
+ },
900
+ ],
901
+ });
902
+ expect(resolved.plugins).toHaveLength(2);
903
+ });
904
+
905
+ it("accepts empty plugins array", () => {
906
+ const resolved = validateAndResolve({ plugins: [] });
907
+ expect(resolved.plugins).toBeUndefined();
908
+ });
909
+
910
+ it("throws when plugin is missing id", () => {
911
+ expect(() =>
912
+ validateAndResolve({
913
+ plugins: [
914
+ { type: "datasource", version: "1.0.0" },
915
+ ],
916
+ }),
917
+ ).toThrow('missing "id"');
918
+ });
919
+
920
+ it("throws when plugin has empty id", () => {
921
+ expect(() =>
922
+ validateAndResolve({
923
+ plugins: [
924
+ { id: " ", type: "datasource", version: "1.0.0" },
925
+ ],
926
+ }),
927
+ ).toThrow('empty "id"');
928
+ });
929
+
930
+ it("throws when plugin is missing type", () => {
931
+ expect(() =>
932
+ validateAndResolve({
933
+ plugins: [
934
+ { id: "my-plugin", version: "1.0.0" },
935
+ ],
936
+ }),
937
+ ).toThrow('missing "type"');
938
+ });
939
+
940
+ it("throws when plugin has invalid type", () => {
941
+ expect(() =>
942
+ validateAndResolve({
943
+ plugins: [
944
+ { id: "my-plugin", type: "invalid", version: "1.0.0" },
945
+ ],
946
+ }),
947
+ ).toThrow('invalid type "invalid"');
948
+ });
949
+
950
+ it("throws when plugin is missing version", () => {
951
+ expect(() =>
952
+ validateAndResolve({
953
+ plugins: [
954
+ { id: "my-plugin", type: "datasource" },
955
+ ],
956
+ }),
957
+ ).toThrow('missing "version"');
958
+ });
959
+
960
+ it("throws when plugin has empty version", () => {
961
+ expect(() =>
962
+ validateAndResolve({
963
+ plugins: [
964
+ { id: "my-plugin", type: "datasource", version: "" },
965
+ ],
966
+ }),
967
+ ).toThrow('empty "version"');
968
+ });
969
+
970
+ it("throws when plugin is null", () => {
971
+ expect(() =>
972
+ validateAndResolve({
973
+ plugins: [null],
974
+ }),
975
+ ).toThrow("expected a plugin object, got null");
976
+ });
977
+
978
+ it("throws when plugin is undefined", () => {
979
+ expect(() =>
980
+ validateAndResolve({
981
+ plugins: [undefined],
982
+ }),
983
+ ).toThrow("expected a plugin object, got undefined");
984
+ });
985
+
986
+ it("throws when plugin is a string", () => {
987
+ expect(() =>
988
+ validateAndResolve({
989
+ plugins: ["not-a-plugin"],
990
+ }),
991
+ ).toThrow("expected a plugin object, got string");
992
+ });
993
+
994
+ it("throws on duplicate plugin IDs", () => {
995
+ expect(() =>
996
+ validateAndResolve({
997
+ plugins: [
998
+ { id: "my-plugin", type: "datasource", version: "1.0.0" },
999
+ { id: "my-plugin", type: "context", version: "2.0.0" },
1000
+ ],
1001
+ }),
1002
+ ).toThrow('duplicate id "my-plugin"');
1003
+ });
1004
+
1005
+ it("uses generic label for whitespace-only id (not the whitespace as name)", () => {
1006
+ try {
1007
+ validateAndResolve({
1008
+ plugins: [
1009
+ { id: " ", type: "datasource", version: "1.0.0" },
1010
+ ],
1011
+ });
1012
+ expect.unreachable("should have thrown");
1013
+ } catch (err) {
1014
+ const msg = (err as Error).message;
1015
+ expect(msg).toContain("plugin at index 0");
1016
+ expect(msg).toContain('empty "id"');
1017
+ }
1018
+ });
1019
+
1020
+ it("includes plugin id in error message when available", () => {
1021
+ try {
1022
+ validateAndResolve({
1023
+ plugins: [
1024
+ { id: "good", type: "datasource", version: "1.0.0" },
1025
+ { id: "bad-plugin", type: "invalid", version: "1.0.0" },
1026
+ ],
1027
+ });
1028
+ expect.unreachable("should have thrown");
1029
+ } catch (err) {
1030
+ expect((err as Error).message).toContain('"bad-plugin"');
1031
+ }
1032
+ });
1033
+
1034
+ it("collects multiple errors across plugins", () => {
1035
+ try {
1036
+ validateAndResolve({
1037
+ plugins: [
1038
+ { type: "datasource", version: "1.0.0" }, // missing id
1039
+ { id: "p2", version: "1.0.0" }, // missing type
1040
+ ],
1041
+ });
1042
+ expect.unreachable("should have thrown");
1043
+ } catch (err) {
1044
+ const msg = (err as Error).message;
1045
+ expect(msg).toContain('missing "id"');
1046
+ expect(msg).toContain('missing "type"');
1047
+ }
1048
+ });
1049
+
1050
+ it("accepts plugins produced by createPlugin factory", () => {
1051
+ // Simulate what createPlugin returns — a plain object with id, type, version, config
1052
+ const factoryPlugin = {
1053
+ id: "bigquery",
1054
+ type: "datasource",
1055
+ version: "1.0.0",
1056
+ config: { projectId: "my-proj", dataset: "analytics" },
1057
+ connection: { create: () => ({}), dbType: "bigquery" },
1058
+ };
1059
+
1060
+ const resolved = validateAndResolve({ plugins: [factoryPlugin] });
1061
+ expect(resolved.plugins).toHaveLength(1);
1062
+ });
1063
+ });