@useatlas/create 0.0.1 → 0.0.3

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 (298) hide show
  1. package/README.md +4 -18
  2. package/index.ts +191 -31
  3. package/package.json +1 -1
  4. package/templates/docker/.env.example +3 -3
  5. package/templates/docker/Dockerfile.sidecar +28 -0
  6. package/templates/docker/bin/__tests__/plugin-cli.test.ts +11 -11
  7. package/templates/docker/bin/atlas.ts +120 -56
  8. package/templates/docker/data/demo-semantic/catalog.yml +51 -27
  9. package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
  10. package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
  11. package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
  12. package/templates/docker/data/demo-semantic/glossary.yml +104 -8
  13. package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
  14. package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
  15. package/templates/docker/docker-compose.yml +1 -1
  16. package/templates/docker/docs/deploy.md +4 -41
  17. package/templates/docker/package.json +17 -1
  18. package/templates/docker/semantic/catalog.yml +62 -3
  19. package/templates/docker/semantic/entities/accounts.yml +162 -0
  20. package/templates/docker/semantic/entities/companies.yml +143 -0
  21. package/templates/docker/semantic/entities/people.yml +132 -0
  22. package/templates/docker/semantic/glossary.yml +116 -4
  23. package/templates/docker/semantic/metrics/accounts.yml +77 -0
  24. package/templates/docker/semantic/metrics/companies.yml +63 -0
  25. package/templates/docker/sidecar/Dockerfile +5 -6
  26. package/templates/docker/sidecar/railway.json +1 -2
  27. package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
  28. package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
  29. package/templates/docker/src/api/__tests__/health.test.ts +30 -8
  30. package/templates/docker/src/api/routes/admin.ts +549 -8
  31. package/templates/docker/src/api/routes/chat.ts +5 -20
  32. package/templates/docker/src/api/routes/health.ts +39 -27
  33. package/templates/docker/src/api/routes/openapi.ts +1329 -74
  34. package/templates/docker/src/api/routes/query.ts +2 -1
  35. package/templates/docker/src/api/server.ts +27 -0
  36. package/templates/docker/src/app/api/[...route]/route.ts +2 -2
  37. package/templates/docker/src/app/globals.css +13 -12
  38. package/templates/docker/src/app/layout.tsx +9 -2
  39. package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
  40. package/templates/docker/src/components/ui/badge.tsx +48 -0
  41. package/templates/docker/src/components/ui/button.tsx +64 -0
  42. package/templates/docker/src/components/ui/card.tsx +92 -0
  43. package/templates/docker/src/components/ui/collapsible.tsx +33 -0
  44. package/templates/docker/src/components/ui/command.tsx +184 -0
  45. package/templates/docker/src/components/ui/dialog.tsx +158 -0
  46. package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
  47. package/templates/docker/src/components/ui/input.tsx +21 -0
  48. package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
  49. package/templates/docker/src/components/ui/select.tsx +190 -0
  50. package/templates/docker/src/components/ui/separator.tsx +28 -0
  51. package/templates/docker/src/components/ui/sheet.tsx +143 -0
  52. package/templates/docker/src/components/ui/sidebar.tsx +726 -0
  53. package/templates/docker/src/components/ui/skeleton.tsx +13 -0
  54. package/templates/docker/src/components/ui/table.tsx +116 -0
  55. package/templates/docker/src/components/ui/tabs.tsx +91 -0
  56. package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
  57. package/templates/docker/src/components/ui/toggle.tsx +47 -0
  58. package/templates/docker/src/components/ui/tooltip.tsx +57 -0
  59. package/templates/docker/src/hooks/use-mobile.ts +19 -0
  60. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
  61. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
  62. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
  63. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
  64. package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
  65. package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
  66. package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
  67. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
  68. package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
  69. package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
  70. package/templates/docker/src/lib/agent-query.ts +5 -23
  71. package/templates/docker/src/lib/agent.ts +32 -112
  72. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
  73. package/templates/docker/src/lib/auth/middleware.ts +30 -4
  74. package/templates/docker/src/lib/auth/migrate.ts +97 -0
  75. package/templates/docker/src/lib/auth/server.ts +12 -1
  76. package/templates/docker/src/lib/config.ts +38 -40
  77. package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
  78. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
  79. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
  80. package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
  81. package/templates/docker/src/lib/db/connection.ts +87 -265
  82. package/templates/docker/src/lib/db/internal.ts +6 -1
  83. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
  84. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
  85. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
  86. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
  87. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
  88. package/templates/docker/src/lib/plugins/index.ts +4 -1
  89. package/templates/docker/src/lib/plugins/migrate.ts +104 -1
  90. package/templates/docker/src/lib/plugins/registry.ts +14 -8
  91. package/templates/docker/src/lib/plugins/wiring.ts +113 -4
  92. package/templates/docker/src/lib/providers.ts +6 -1
  93. package/templates/docker/src/lib/security.ts +24 -0
  94. package/templates/docker/src/lib/semantic.ts +2 -0
  95. package/templates/docker/src/lib/sidecar-types.ts +12 -1
  96. package/templates/docker/src/lib/startup.ts +71 -101
  97. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
  98. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
  99. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
  100. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
  101. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
  102. package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  103. package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  104. package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  105. package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
  106. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  107. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
  108. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  109. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  110. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  111. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
  112. package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
  113. package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
  114. package/templates/docker/src/lib/tools/explore.ts +28 -32
  115. package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
  116. package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
  117. package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
  118. package/templates/docker/src/lib/tools/python.ts +367 -0
  119. package/templates/docker/src/lib/tools/registry.ts +49 -22
  120. package/templates/docker/src/lib/tools/sql.ts +88 -88
  121. package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
  122. package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
  123. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
  124. package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
  125. package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
  126. package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  127. package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
  128. package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
  129. package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
  130. package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
  131. package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
  132. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
  133. package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
  134. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
  135. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
  136. package/templates/docker/src/ui/context.tsx +1 -1
  137. package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
  138. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
  139. package/templates/docker/tsconfig.json +2 -2
  140. package/templates/nextjs-standalone/.env.example +1 -1
  141. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +11 -11
  142. package/templates/nextjs-standalone/bin/atlas.ts +120 -56
  143. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
  144. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
  145. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
  146. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
  147. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
  148. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
  149. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
  150. package/templates/nextjs-standalone/docs/deploy.md +4 -41
  151. package/templates/nextjs-standalone/package.json +11 -2
  152. package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
  153. package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
  154. package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
  155. package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
  156. package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
  157. package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
  158. package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
  159. package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
  160. package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
  161. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
  162. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
  163. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
  164. package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
  165. package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
  166. package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
  167. package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
  168. package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
  169. package/templates/nextjs-standalone/src/api/server.ts +27 -0
  170. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
  171. package/templates/nextjs-standalone/src/app/globals.css +13 -12
  172. package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
  173. package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
  174. package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
  175. package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
  176. package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
  177. package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
  178. package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
  179. package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
  180. package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
  181. package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
  182. package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
  183. package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
  184. package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
  185. package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
  186. package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
  187. package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
  188. package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
  189. package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
  190. package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
  191. package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
  192. package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
  193. package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
  194. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
  195. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
  196. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
  197. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
  198. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
  199. package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
  200. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
  201. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
  202. package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
  203. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
  204. package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
  205. package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
  206. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
  207. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
  208. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
  209. package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
  210. package/templates/nextjs-standalone/src/lib/config.ts +38 -40
  211. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
  212. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
  213. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
  214. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
  215. package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
  216. package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
  217. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
  218. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
  219. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
  220. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
  221. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
  222. package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
  223. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +104 -1
  224. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +14 -8
  225. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
  226. package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
  227. package/templates/nextjs-standalone/src/lib/security.ts +24 -0
  228. package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
  229. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
  230. package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
  231. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
  232. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
  233. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
  234. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
  235. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
  236. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  237. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  238. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  239. package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
  240. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  241. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
  242. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  243. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  244. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  245. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
  246. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
  247. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
  248. package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
  249. package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
  250. package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
  251. package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
  252. package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
  253. package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
  254. package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
  255. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
  256. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
  257. package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
  258. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
  259. package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  260. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
  261. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
  262. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
  263. package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
  264. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
  265. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
  266. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
  267. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
  268. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
  269. package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
  270. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
  271. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
  272. package/templates/nextjs-standalone/tsconfig.json +0 -1
  273. package/templates/nextjs-standalone/vercel.json +4 -1
  274. package/templates/docker/render.yaml +0 -34
  275. package/templates/docker/semantic/entities/.gitkeep +0 -0
  276. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  277. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
  278. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
  279. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
  280. package/templates/docker/src/lib/db/duckdb.ts +0 -122
  281. package/templates/docker/src/lib/db/salesforce.ts +0 -342
  282. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  283. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  284. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  285. package/templates/docker/src/lib/tools/salesforce.ts +0 -138
  286. package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
  287. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  288. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  289. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
  290. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
  291. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
  292. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
  293. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
  294. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  295. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  296. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  297. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
  298. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
@@ -1,141 +0,0 @@
1
- /**
2
- * Tests for the DuckDB adapter (duckdb.ts) and DuckDB URL parsing.
3
- *
4
- * Uses a real in-memory DuckDB instance (no mocks) to verify end-to-end
5
- * query execution, column extraction, and close behavior.
6
- */
7
- import { describe, it, expect, afterEach } from "bun:test";
8
- import { parseDuckDBUrl, createDuckDBConnection } from "../duckdb";
9
- import type { DBConnection } from "../connection";
10
- import * as fs from "fs";
11
- import * as path from "path";
12
- import * as os from "os";
13
-
14
- describe("parseDuckDBUrl", () => {
15
- it("parses duckdb://:memory: as in-memory", () => {
16
- const config = parseDuckDBUrl("duckdb://:memory:");
17
- expect(config.path).toBe(":memory:");
18
- });
19
-
20
- it("parses bare duckdb:// as in-memory", () => {
21
- const config = parseDuckDBUrl("duckdb://");
22
- expect(config.path).toBe(":memory:");
23
- });
24
-
25
- it("parses duckdb:///absolute/path.duckdb", () => {
26
- const config = parseDuckDBUrl("duckdb:///tmp/test.duckdb");
27
- expect(config.path).toBe("/tmp/test.duckdb");
28
- });
29
-
30
- it("parses duckdb://relative/path.duckdb", () => {
31
- const config = parseDuckDBUrl("duckdb://data/test.duckdb");
32
- expect(config.path).toBe("data/test.duckdb");
33
- });
34
-
35
- it("throws for non-duckdb:// URL", () => {
36
- expect(() => parseDuckDBUrl("postgresql://localhost/db")).toThrow("Invalid DuckDB URL");
37
- });
38
- });
39
-
40
- describe("createDuckDBConnection", () => {
41
- let conn: DBConnection | null = null;
42
-
43
- afterEach(async () => {
44
- if (conn) {
45
- await conn.close();
46
- conn = null;
47
- }
48
- });
49
-
50
- it("creates a connection and runs a simple query", async () => {
51
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
52
- const result = await conn.query("SELECT 42 AS answer");
53
- expect(result.columns).toEqual(["answer"]);
54
- expect(result.rows).toHaveLength(1);
55
- expect(result.rows[0].answer).toBe(42);
56
- });
57
-
58
- it("returns correct columns and rows for multi-column queries", async () => {
59
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
60
- const result = await conn.query("SELECT 1 AS a, 'hello' AS b, true AS c");
61
- expect(result.columns).toEqual(["a", "b", "c"]);
62
- expect(result.rows).toHaveLength(1);
63
- expect(result.rows[0].a).toBe(1);
64
- expect(result.rows[0].b).toBe("hello");
65
- expect(result.rows[0].c).toBe(true);
66
- });
67
-
68
- it("handles empty result sets", async () => {
69
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
70
- const result = await conn.query("SELECT 1 AS n WHERE false");
71
- expect(result.columns).toEqual(["n"]);
72
- expect(result.rows).toHaveLength(0);
73
- });
74
-
75
- it("supports CREATE TABLE and SELECT in read-write mode", async () => {
76
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
77
- await conn.query("CREATE TABLE test (id INTEGER, name VARCHAR)");
78
- await conn.query("INSERT INTO test VALUES (1, 'alice'), (2, 'bob')");
79
- const result = await conn.query("SELECT * FROM test ORDER BY id");
80
- expect(result.columns).toEqual(["id", "name"]);
81
- expect(result.rows).toHaveLength(2);
82
- expect(result.rows[0].name).toBe("alice");
83
- expect(result.rows[1].name).toBe("bob");
84
- });
85
-
86
- it("supports VALUES clause for inline data", async () => {
87
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
88
- const result = await conn.query(
89
- "SELECT * FROM (VALUES (1, 'a'), (2, 'b'), (3, 'c')) AS t(id, letter)"
90
- );
91
- expect(result.rows).toHaveLength(3);
92
- });
93
-
94
- it("close is idempotent", async () => {
95
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
96
- await conn.query("SELECT 1");
97
- await conn.close();
98
- await conn.close(); // Should not throw
99
- conn = null; // Prevent afterEach from closing again
100
- });
101
-
102
- it("enforces read-only mode for file-based databases", async () => {
103
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "atlas-duckdb-ro-"));
104
- try {
105
- const dbPath = path.join(tmpDir, "test.duckdb");
106
-
107
- // Create and populate the database first (read-write)
108
- const rwConn = createDuckDBConnection({ path: dbPath, readOnly: false });
109
- await rwConn.query("CREATE TABLE test (id INTEGER)");
110
- await rwConn.query("INSERT INTO test VALUES (1)");
111
- await rwConn.close();
112
-
113
- // Open in read-only mode (default for file-based)
114
- conn = createDuckDBConnection({ path: dbPath });
115
- const result = await conn.query("SELECT * FROM test");
116
- expect(result.rows).toHaveLength(1);
117
-
118
- // Write operations should fail
119
- expect(conn.query("INSERT INTO test VALUES (2)")).rejects.toThrow();
120
- } finally {
121
- fs.rmSync(tmpDir, { recursive: true, force: true });
122
- }
123
- });
124
-
125
- it("respects timeoutMs parameter", async () => {
126
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
127
- // A very short timeout on a query that takes some time
128
- await expect(
129
- conn.query("SELECT COUNT(*) FROM generate_series(1, 100000000)", 1)
130
- ).rejects.toThrow("timed out");
131
- });
132
-
133
- it("can recover after close by re-initializing", async () => {
134
- conn = createDuckDBConnection({ path: ":memory:", readOnly: false });
135
- await conn.query("SELECT 1");
136
- await conn.close();
137
- // After close + retry, lazy init should re-create the connection
138
- const result = await conn.query("SELECT 42 AS answer");
139
- expect(result.rows[0].answer).toBe(42);
140
- });
141
- });
@@ -1,339 +0,0 @@
1
- /**
2
- * Tests for the Salesforce DataSource adapter and registry.
3
- */
4
- import { describe, it, expect, beforeEach, mock } from "bun:test";
5
-
6
- // Mock jsforce before importing the module under test
7
- const mockLogin = mock(() => Promise.resolve());
8
- const mockLogout = mock(() => Promise.resolve());
9
- const mockQuery = mock(() =>
10
- Promise.resolve({
11
- records: [
12
- { attributes: { type: "Account" }, Id: "001", Name: "Acme" },
13
- { attributes: { type: "Account" }, Id: "002", Name: "Widget Co" },
14
- ],
15
- }),
16
- );
17
- const mockDescribe = mock(() =>
18
- Promise.resolve({
19
- name: "Account",
20
- label: "Account",
21
- fields: [
22
- {
23
- name: "Id",
24
- type: "id",
25
- label: "Account ID",
26
- picklistValues: [],
27
- referenceTo: [],
28
- nillable: false,
29
- length: 18,
30
- },
31
- {
32
- name: "Name",
33
- type: "string",
34
- label: "Account Name",
35
- picklistValues: [],
36
- referenceTo: [],
37
- nillable: false,
38
- length: 255,
39
- },
40
- {
41
- name: "Industry",
42
- type: "picklist",
43
- label: "Industry",
44
- picklistValues: [
45
- { value: "Technology", label: "Technology", active: true },
46
- { value: "Finance", label: "Finance", active: true },
47
- ],
48
- referenceTo: [],
49
- nillable: true,
50
- length: 255,
51
- },
52
- ],
53
- }),
54
- );
55
- const mockDescribeGlobal = mock(() =>
56
- Promise.resolve({
57
- sobjects: [
58
- { name: "Account", label: "Account", queryable: true },
59
- { name: "Contact", label: "Contact", queryable: true },
60
- { name: "ApexLog", label: "Apex Log", queryable: false },
61
- ],
62
- }),
63
- );
64
-
65
- mock.module("jsforce", () => ({
66
- Connection: class MockConnection {
67
- login = mockLogin;
68
- logout = mockLogout;
69
- query = mockQuery;
70
- describe = mockDescribe;
71
- describeGlobal = mockDescribeGlobal;
72
- },
73
- }));
74
-
75
- const {
76
- parseSalesforceURL,
77
- createSalesforceDataSource,
78
- registerSalesforceSource,
79
- getSalesforceSource,
80
- listSalesforceSources,
81
- describeSalesforceSources,
82
- _resetSalesforceSources,
83
- } = await import("@atlas/api/lib/db/salesforce");
84
-
85
- describe("parseSalesforceURL", () => {
86
- it("parses a full URL", () => {
87
- const config = parseSalesforceURL(
88
- "salesforce://user%40example.com:pass123@login.salesforce.com?token=SECTOKEN",
89
- );
90
- expect(config.loginUrl).toBe("https://login.salesforce.com");
91
- expect(config.username).toBe("user@example.com");
92
- expect(config.password).toBe("pass123");
93
- expect(config.securityToken).toBe("SECTOKEN");
94
- });
95
-
96
- it("parses minimal URL with defaults", () => {
97
- const config = parseSalesforceURL("salesforce://admin:secret@localhost");
98
- expect(config.loginUrl).toBe("https://localhost");
99
- expect(config.username).toBe("admin");
100
- expect(config.password).toBe("secret");
101
- expect(config.securityToken).toBeUndefined();
102
- });
103
-
104
- it("parses sandbox URL (test.salesforce.com)", () => {
105
- const config = parseSalesforceURL(
106
- "salesforce://user:pass@test.salesforce.com",
107
- );
108
- expect(config.loginUrl).toBe("https://test.salesforce.com");
109
- });
110
-
111
- it("parses OAuth params", () => {
112
- const config = parseSalesforceURL(
113
- "salesforce://user:pass@login.salesforce.com?clientId=CID&clientSecret=CSEC",
114
- );
115
- expect(config.clientId).toBe("CID");
116
- expect(config.clientSecret).toBe("CSEC");
117
- });
118
-
119
- it("throws for non-salesforce scheme", () => {
120
- expect(() => parseSalesforceURL("postgresql://user:pass@localhost")).toThrow(
121
- "expected salesforce://",
122
- );
123
- });
124
-
125
- it("throws for missing username", () => {
126
- expect(() =>
127
- parseSalesforceURL("salesforce://:pass@login.salesforce.com"),
128
- ).toThrow("missing username");
129
- });
130
-
131
- it("throws for missing password", () => {
132
- expect(() =>
133
- parseSalesforceURL("salesforce://user@login.salesforce.com"),
134
- ).toThrow("missing password");
135
- });
136
- });
137
-
138
- describe("createSalesforceDataSource", () => {
139
- beforeEach(() => {
140
- mockLogin.mockClear();
141
- mockLogout.mockClear();
142
- mockQuery.mockClear();
143
- mockDescribe.mockClear();
144
- mockDescribeGlobal.mockClear();
145
- });
146
-
147
- it("query returns columns and rows without attributes key", async () => {
148
- const source = createSalesforceDataSource({
149
- loginUrl: "https://login.salesforce.com",
150
- username: "user",
151
- password: "pass",
152
- });
153
-
154
- const result = await source.query("SELECT Id, Name FROM Account");
155
- expect(result.columns).toEqual(["Id", "Name"]);
156
- expect(result.rows).toEqual([
157
- { Id: "001", Name: "Acme" },
158
- { Id: "002", Name: "Widget Co" },
159
- ]);
160
- expect(mockLogin).toHaveBeenCalledTimes(1);
161
- });
162
-
163
- it("query returns empty result for no records", async () => {
164
- mockQuery.mockImplementationOnce(() =>
165
- Promise.resolve({ records: [] }),
166
- );
167
- const source = createSalesforceDataSource({
168
- loginUrl: "https://login.salesforce.com",
169
- username: "user",
170
- password: "pass",
171
- });
172
-
173
- const result = await source.query("SELECT Id FROM Account WHERE Id = 'none'");
174
- expect(result.columns).toEqual([]);
175
- expect(result.rows).toEqual([]);
176
- });
177
-
178
- it("describe returns mapped fields", async () => {
179
- const source = createSalesforceDataSource({
180
- loginUrl: "https://login.salesforce.com",
181
- username: "user",
182
- password: "pass",
183
- });
184
-
185
- const desc = await source.describe("Account");
186
- expect(desc.name).toBe("Account");
187
- expect(desc.fields).toHaveLength(3);
188
- expect(desc.fields[0].name).toBe("Id");
189
- expect(desc.fields[2].picklistValues).toHaveLength(2);
190
- });
191
-
192
- it("listObjects filters to queryable only", async () => {
193
- const source = createSalesforceDataSource({
194
- loginUrl: "https://login.salesforce.com",
195
- username: "user",
196
- password: "pass",
197
- });
198
-
199
- const objects = await source.listObjects();
200
- expect(objects).toHaveLength(2);
201
- expect(objects.map((o) => o.name)).toEqual(["Account", "Contact"]);
202
- });
203
-
204
- it("close calls logout", async () => {
205
- const source = createSalesforceDataSource({
206
- loginUrl: "https://login.salesforce.com",
207
- username: "user",
208
- password: "pass",
209
- });
210
-
211
- // Force login first
212
- await source.listObjects();
213
- await source.close();
214
- expect(mockLogout).toHaveBeenCalledTimes(1);
215
- });
216
-
217
- it("appends security token to password on login", async () => {
218
- const source = createSalesforceDataSource({
219
- loginUrl: "https://login.salesforce.com",
220
- username: "user",
221
- password: "pass",
222
- securityToken: "TOKEN123",
223
- });
224
-
225
- await source.listObjects();
226
- expect(mockLogin).toHaveBeenCalledWith("user", "passTOKEN123");
227
- });
228
-
229
- it("serializes concurrent login attempts (no duplicate logins)", async () => {
230
- // Make login take some time so concurrent calls overlap
231
- mockLogin.mockImplementation(
232
- () => new Promise((resolve) => setTimeout(resolve, 50)),
233
- );
234
-
235
- const source = createSalesforceDataSource({
236
- loginUrl: "https://login.salesforce.com",
237
- username: "user",
238
- password: "pass",
239
- });
240
-
241
- // Fire three concurrent queries — all need login
242
- await Promise.all([
243
- source.query("SELECT Id FROM Account"),
244
- source.listObjects(),
245
- source.describe("Account"),
246
- ]);
247
-
248
- // Login should have been called exactly once despite three concurrent callers
249
- expect(mockLogin).toHaveBeenCalledTimes(1);
250
- });
251
-
252
- it("close without prior login is a no-op", async () => {
253
- const source = createSalesforceDataSource({
254
- loginUrl: "https://login.salesforce.com",
255
- username: "user",
256
- password: "pass",
257
- });
258
-
259
- // Close without ever calling login — should not throw or call logout
260
- await source.close();
261
- expect(mockLogout).not.toHaveBeenCalled();
262
- expect(mockLogin).not.toHaveBeenCalled();
263
- });
264
- });
265
-
266
- describe("Salesforce source registry", () => {
267
- beforeEach(() => {
268
- _resetSalesforceSources();
269
- mockLogin.mockClear();
270
- mockLogout.mockClear();
271
- });
272
-
273
- it("register and get a source", () => {
274
- registerSalesforceSource("sf1", {
275
- loginUrl: "https://login.salesforce.com",
276
- username: "user",
277
- password: "pass",
278
- });
279
-
280
- const source = getSalesforceSource("sf1");
281
- expect(source).toBeDefined();
282
- expect(source.query).toBeDefined();
283
- });
284
-
285
- it("throws for unregistered source", () => {
286
- expect(() => getSalesforceSource("nonexistent")).toThrow(
287
- 'not registered',
288
- );
289
- });
290
-
291
- it("lists registered sources", () => {
292
- registerSalesforceSource("sf1", {
293
- loginUrl: "https://login.salesforce.com",
294
- username: "user",
295
- password: "pass",
296
- });
297
- registerSalesforceSource("sf2", {
298
- loginUrl: "https://test.salesforce.com",
299
- username: "admin",
300
- password: "secret",
301
- });
302
-
303
- expect(listSalesforceSources()).toEqual(["sf1", "sf2"]);
304
- });
305
-
306
- it("reset clears all sources", () => {
307
- registerSalesforceSource("sf1", {
308
- loginUrl: "https://login.salesforce.com",
309
- username: "user",
310
- password: "pass",
311
- });
312
-
313
- _resetSalesforceSources();
314
- expect(listSalesforceSources()).toEqual([]);
315
- });
316
-
317
- it("describeSalesforceSources returns metadata for registered sources", () => {
318
- registerSalesforceSource("sf1", {
319
- loginUrl: "https://login.salesforce.com",
320
- username: "user",
321
- password: "pass",
322
- });
323
- registerSalesforceSource("sf2", {
324
- loginUrl: "https://test.salesforce.com",
325
- username: "admin",
326
- password: "secret",
327
- });
328
-
329
- const meta = describeSalesforceSources();
330
- expect(meta).toEqual([
331
- { id: "sf1", dbType: "salesforce" },
332
- { id: "sf2", dbType: "salesforce" },
333
- ]);
334
- });
335
-
336
- it("describeSalesforceSources returns empty array when no sources registered", () => {
337
- expect(describeSalesforceSources()).toEqual([]);
338
- });
339
- });
@@ -1,217 +0,0 @@
1
- /**
2
- * Tests for Snowflake URL parsing, detectDBType integration, and connection behavior.
3
- */
4
- import { describe, it, expect, mock, beforeEach } from "bun:test";
5
- import { resolve } from "path";
6
-
7
- // Track all SQL statements executed via the mock connection.
8
- // Reset in each describe block's beforeEach to prevent cross-test pollution.
9
- let executedStatements: string[] = [];
10
-
11
- // Flag to make the QUERY_TAG ALTER SESSION fail in specific tests
12
- let queryTagShouldFail = false;
13
-
14
- // Mock logger to capture structured log output
15
- const mockWarn = mock(() => {});
16
- mock.module("@atlas/api/lib/logger", () => ({
17
- createLogger: () => ({
18
- info: mock(() => {}),
19
- warn: mockWarn,
20
- error: mock(() => {}),
21
- debug: mock(() => {}),
22
- child: () => ({ info: mock(() => {}), warn: mockWarn, error: mock(() => {}), debug: mock(() => {}) }),
23
- }),
24
- }));
25
-
26
- // Mock database drivers before importing connection module
27
- mock.module("pg", () => ({
28
- Pool: class MockPool {
29
- async query() { return { rows: [], fields: [] }; }
30
- async connect() { return { async query() { return { rows: [], fields: [] }; }, release() {} }; }
31
- async end() {}
32
- },
33
- }));
34
-
35
- mock.module("mysql2/promise", () => ({
36
- createPool: () => ({
37
- async getConnection() { return { async execute() { return [[], []]; }, release() {} }; },
38
- async end() {},
39
- }),
40
- }));
41
-
42
- mock.module("snowflake-sdk", () => ({
43
- configure: () => {},
44
- createPool: () => ({
45
- use: async (fn: (conn: unknown) => Promise<unknown>) => {
46
- const mockConn = {
47
- execute: (opts: { sqlText: string; complete: (err: Error | null, stmt: unknown, rows: unknown[]) => void }) => {
48
- executedStatements.push(opts.sqlText);
49
- if (queryTagShouldFail && opts.sqlText.includes("QUERY_TAG")) {
50
- opts.complete(new Error("Insufficient privileges to set QUERY_TAG"), null, []);
51
- return;
52
- }
53
- opts.complete(null, { getColumns: () => [] }, []);
54
- },
55
- };
56
- return fn(mockConn);
57
- },
58
- drain: async () => {},
59
- clear: async () => {},
60
- }),
61
- }));
62
-
63
- // Cache-busting import to get a fresh module instance, avoiding interference
64
- // from global mock.module("@atlas/api/lib/db/connection") in other test files
65
- // (e.g. sql.test.ts) that don't export parseSnowflakeURL.
66
- const connModPath = resolve(__dirname, "../connection.ts");
67
- const connMod = await import(`${connModPath}?t=${Date.now()}`);
68
- const parseSnowflakeURL = connMod.parseSnowflakeURL as typeof import("../connection").parseSnowflakeURL;
69
- const detectDBType = connMod.detectDBType as typeof import("../connection").detectDBType;
70
-
71
- describe("parseSnowflakeURL", () => {
72
- it("parses full Snowflake URL with all components", () => {
73
- const opts = parseSnowflakeURL(
74
- "snowflake://myuser:mypass@xy12345.us-east-1/mydb/myschema?warehouse=COMPUTE_WH&role=ANALYST"
75
- );
76
- expect(opts.account).toBe("xy12345.us-east-1");
77
- expect(opts.username).toBe("myuser");
78
- expect(opts.password).toBe("mypass");
79
- expect(opts.database).toBe("mydb");
80
- expect(opts.schema).toBe("myschema");
81
- expect(opts.warehouse).toBe("COMPUTE_WH");
82
- expect(opts.role).toBe("ANALYST");
83
- });
84
-
85
- it("parses URL with database only (no schema)", () => {
86
- const opts = parseSnowflakeURL("snowflake://user:pass@account123/mydb");
87
- expect(opts.account).toBe("account123");
88
- expect(opts.username).toBe("user");
89
- expect(opts.password).toBe("pass");
90
- expect(opts.database).toBe("mydb");
91
- expect(opts.schema).toBeUndefined();
92
- expect(opts.warehouse).toBeUndefined();
93
- expect(opts.role).toBeUndefined();
94
- });
95
-
96
- it("parses minimal URL (account only)", () => {
97
- const opts = parseSnowflakeURL("snowflake://user:pass@account123");
98
- expect(opts.account).toBe("account123");
99
- expect(opts.username).toBe("user");
100
- expect(opts.password).toBe("pass");
101
- expect(opts.database).toBeUndefined();
102
- expect(opts.schema).toBeUndefined();
103
- });
104
-
105
- it("decodes URL-encoded credentials", () => {
106
- const opts = parseSnowflakeURL("snowflake://my%40user:p%40ss%23word@account123/db");
107
- expect(opts.username).toBe("my@user");
108
- expect(opts.password).toBe("p@ss#word");
109
- });
110
-
111
- it("handles warehouse query parameter only", () => {
112
- const opts = parseSnowflakeURL("snowflake://user:pass@account123/db/schema?warehouse=WH");
113
- expect(opts.warehouse).toBe("WH");
114
- expect(opts.role).toBeUndefined();
115
- });
116
-
117
- it("throws for non-snowflake protocol", () => {
118
- expect(() => parseSnowflakeURL("postgresql://user:pass@localhost:5432/db")).toThrow(
119
- "Invalid Snowflake URL"
120
- );
121
- });
122
-
123
- it("accepts empty password (key-pair auth scenario)", () => {
124
- const opts = parseSnowflakeURL("snowflake://user:@account123/db");
125
- expect(opts.username).toBe("user");
126
- expect(opts.password).toBe("");
127
- expect(opts.database).toBe("db");
128
- });
129
-
130
- it("throws for missing username", () => {
131
- expect(() => parseSnowflakeURL("snowflake://:pass@account123/db")).toThrow(
132
- "missing username"
133
- );
134
- });
135
-
136
- it("throws for missing account (unparseable URL)", () => {
137
- // snowflake://user:pass@/db is not a valid URL — the URL constructor throws
138
- expect(() => parseSnowflakeURL("snowflake://user:pass@/db")).toThrow();
139
- });
140
- });
141
-
142
- describe("detectDBType — Snowflake", () => {
143
- it("returns 'snowflake' for snowflake:// URLs", () => {
144
- expect(detectDBType("snowflake://user:pass@account123/db")).toBe("snowflake");
145
- });
146
-
147
- it("still returns 'postgres' for postgresql:// URLs", () => {
148
- expect(detectDBType("postgresql://user:pass@localhost:5432/db")).toBe("postgres");
149
- });
150
-
151
- it("still returns 'mysql' for mysql:// URLs", () => {
152
- expect(detectDBType("mysql://user:pass@localhost:3306/db")).toBe("mysql");
153
- });
154
-
155
- it("throws for unsupported URL schemes", () => {
156
- expect(() => detectDBType("sqlite:///test.db")).toThrow("Unsupported database URL");
157
- });
158
- });
159
-
160
- describe("createSnowflakeDB — defense-in-depth", () => {
161
- const ConnectionRegistry = connMod.ConnectionRegistry as typeof import("../connection").ConnectionRegistry;
162
-
163
- beforeEach(() => {
164
- executedStatements = [];
165
- queryTagShouldFail = false;
166
- mockWarn.mockClear();
167
- });
168
-
169
- it("logs a startup warning recommending a SELECT-only role", () => {
170
- const registry = new ConnectionRegistry();
171
- registry.register("sf-test", { url: "snowflake://user:pass@account123/db" });
172
- expect(mockWarn).toHaveBeenCalledWith(
173
- expect.stringContaining("no session-level read-only mode"),
174
- );
175
- });
176
-
177
- it("sets QUERY_TAG before each query", async () => {
178
- const registry = new ConnectionRegistry();
179
- registry.register("sf-tag", { url: "snowflake://user:pass@account123/db" });
180
- const db = registry.get("sf-tag");
181
- await db.query("SELECT 1");
182
-
183
- expect(executedStatements).toContain("ALTER SESSION SET QUERY_TAG = 'atlas:readonly'");
184
- // QUERY_TAG should come after timeout and before the actual query
185
- const tagIdx = executedStatements.indexOf("ALTER SESSION SET QUERY_TAG = 'atlas:readonly'");
186
- const queryIdx = executedStatements.indexOf("SELECT 1");
187
- expect(tagIdx).toBeLessThan(queryIdx);
188
- });
189
-
190
- it("sets statement timeout before QUERY_TAG", async () => {
191
- const registry = new ConnectionRegistry();
192
- registry.register("sf-order", { url: "snowflake://user:pass@account123/db" });
193
- const db = registry.get("sf-order");
194
- await db.query("SELECT 1", 15000);
195
-
196
- const timeoutIdx = executedStatements.findIndex((s) => s.includes("STATEMENT_TIMEOUT_IN_SECONDS"));
197
- const tagIdx = executedStatements.indexOf("ALTER SESSION SET QUERY_TAG = 'atlas:readonly'");
198
- expect(timeoutIdx).toBeLessThan(tagIdx);
199
- });
200
-
201
- it("proceeds with the query when QUERY_TAG fails (best-effort)", async () => {
202
- queryTagShouldFail = true;
203
- const registry = new ConnectionRegistry();
204
- registry.register("sf-tag-fail", { url: "snowflake://user:pass@account123/db" });
205
- const db = registry.get("sf-tag-fail");
206
- const result = await db.query("SELECT 42");
207
-
208
- // The actual query should still execute despite QUERY_TAG failure
209
- expect(executedStatements).toContain("SELECT 42");
210
- expect(result).toEqual({ columns: [], rows: [] });
211
-
212
- // A warning should be logged
213
- expect(mockWarn).toHaveBeenCalledWith(
214
- expect.stringContaining("Failed to set QUERY_TAG"),
215
- );
216
- });
217
- });