@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,303 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { validateSOQL, appendSOQLLimit } from "../soql-validation";
3
-
4
- const ALLOWED = new Set(["Account", "Contact", "Opportunity", "Lead"]);
5
-
6
- describe("validateSOQL", () => {
7
- describe("Layer 0: Empty check", () => {
8
- it("rejects empty string", () => {
9
- const result = validateSOQL("", ALLOWED);
10
- expect(result.valid).toBe(false);
11
- expect(result.error).toContain("Empty");
12
- });
13
-
14
- it("rejects whitespace-only", () => {
15
- const result = validateSOQL(" \n\t ", ALLOWED);
16
- expect(result.valid).toBe(false);
17
- expect(result.error).toContain("Empty");
18
- });
19
- });
20
-
21
- describe("Layer 1: Mutation guard", () => {
22
- for (const keyword of ["INSERT", "UPDATE", "DELETE", "UPSERT", "MERGE", "UNDELETE"]) {
23
- it(`rejects ${keyword}`, () => {
24
- const result = validateSOQL(`${keyword} INTO Account`, ALLOWED);
25
- expect(result.valid).toBe(false);
26
- expect(result.error).toContain("Forbidden");
27
- });
28
-
29
- it(`rejects ${keyword.toLowerCase()}`, () => {
30
- const result = validateSOQL(`${keyword.toLowerCase()} into account`, ALLOWED);
31
- expect(result.valid).toBe(false);
32
- expect(result.error).toContain("Forbidden");
33
- });
34
- }
35
- });
36
-
37
- describe("Layer 2: SELECT-only", () => {
38
- it("accepts SELECT query", () => {
39
- const result = validateSOQL("SELECT Id FROM Account", ALLOWED);
40
- expect(result.valid).toBe(true);
41
- });
42
-
43
- it("rejects non-SELECT query", () => {
44
- const result = validateSOQL("DESCRIBE Account", ALLOWED);
45
- expect(result.valid).toBe(false);
46
- expect(result.error).toContain("Only SELECT");
47
- });
48
-
49
- it("rejects semicolons", () => {
50
- const result = validateSOQL("SELECT Id FROM Account;", ALLOWED);
51
- expect(result.valid).toBe(false);
52
- expect(result.error).toContain("Semicolons");
53
- });
54
-
55
- it("rejects multiple statements", () => {
56
- const result = validateSOQL("SELECT Id FROM Account; SELECT Id FROM Contact", ALLOWED);
57
- expect(result.valid).toBe(false);
58
- expect(result.error).toContain("Semicolons");
59
- });
60
- });
61
-
62
- describe("Layer 3: Object whitelist", () => {
63
- it("allows whitelisted objects", () => {
64
- const result = validateSOQL("SELECT Id, Name FROM Account", ALLOWED);
65
- expect(result.valid).toBe(true);
66
- });
67
-
68
- it("rejects non-whitelisted objects", () => {
69
- const result = validateSOQL("SELECT Id FROM CustomObject__c", ALLOWED);
70
- expect(result.valid).toBe(false);
71
- expect(result.error).toContain("not in the allowed list");
72
- });
73
-
74
- it("checks subquery objects", () => {
75
- const result = validateSOQL(
76
- "SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM CustomObject__c)",
77
- ALLOWED,
78
- );
79
- expect(result.valid).toBe(false);
80
- expect(result.error).toContain("CustomObject__c");
81
- });
82
-
83
- it("allows subquery with whitelisted objects", () => {
84
- const result = validateSOQL(
85
- "SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Contact)",
86
- ALLOWED,
87
- );
88
- expect(result.valid).toBe(true);
89
- });
90
-
91
- it("is case-insensitive", () => {
92
- const result = validateSOQL("SELECT Id FROM account", ALLOWED);
93
- expect(result.valid).toBe(true);
94
- });
95
-
96
- it("rejects queries with no FROM clause", () => {
97
- const result = validateSOQL("SELECT 1", ALLOWED);
98
- expect(result.valid).toBe(false);
99
- expect(result.error).toContain("No FROM");
100
- });
101
- });
102
-
103
- describe("Relationship subquery whitelist bypass (parent-to-child)", () => {
104
- it("accepts parent-to-child relationship subquery with plural relationship name", () => {
105
- // "Contacts" is the relationship name (plural), not in the whitelist.
106
- // Only "Contact" (singular) is whitelisted. This should pass because
107
- // relationship subqueries in SELECT are not whitelist-checked.
108
- const result = validateSOQL(
109
- "SELECT Id, Name, (SELECT LastName FROM Contacts) FROM Account",
110
- ALLOWED,
111
- );
112
- expect(result.valid).toBe(true);
113
- });
114
-
115
- it("accepts multiple relationship subqueries in SELECT", () => {
116
- const result = validateSOQL(
117
- "SELECT Id, (SELECT LastName FROM Contacts), (SELECT Amount FROM Opportunities) FROM Account",
118
- ALLOWED,
119
- );
120
- expect(result.valid).toBe(true);
121
- });
122
-
123
- it("accepts relationship subquery with unknown relationship name", () => {
124
- // Custom relationship names like "Cases" won't be in the whitelist
125
- const result = validateSOQL(
126
- "SELECT Id, (SELECT Subject FROM Cases) FROM Account",
127
- ALLOWED,
128
- );
129
- expect(result.valid).toBe(true);
130
- });
131
-
132
- it("still rejects non-whitelisted objects in WHERE semi-join subqueries", () => {
133
- // Semi-join subqueries in WHERE reference real object names — must be checked
134
- const result = validateSOQL(
135
- "SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM CustomObject__c)",
136
- ALLOWED,
137
- );
138
- expect(result.valid).toBe(false);
139
- expect(result.error).toContain("CustomObject__c");
140
- });
141
-
142
- it("allows whitelisted objects in WHERE semi-join subqueries", () => {
143
- const result = validateSOQL(
144
- "SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Contact)",
145
- ALLOWED,
146
- );
147
- expect(result.valid).toBe(true);
148
- });
149
-
150
- it("accepts relationship subquery AND valid WHERE subquery together", () => {
151
- const result = validateSOQL(
152
- "SELECT Id, (SELECT LastName FROM Contacts) FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity)",
153
- ALLOWED,
154
- );
155
- expect(result.valid).toBe(true);
156
- });
157
-
158
- it("rejects relationship subquery with invalid WHERE subquery", () => {
159
- const result = validateSOQL(
160
- "SELECT Id, (SELECT LastName FROM Contacts) FROM Account WHERE Id IN (SELECT AccountId FROM Forbidden__c)",
161
- ALLOWED,
162
- );
163
- expect(result.valid).toBe(false);
164
- expect(result.error).toContain("Forbidden__c");
165
- });
166
-
167
- it("still checks top-level FROM object", () => {
168
- const result = validateSOQL(
169
- "SELECT Id, (SELECT LastName FROM Contacts) FROM NotAllowed__c",
170
- ALLOWED,
171
- );
172
- expect(result.valid).toBe(false);
173
- expect(result.error).toContain("NotAllowed__c");
174
- });
175
- });
176
-
177
- describe("String literal false positives in mutation guard", () => {
178
- it("allows 'delete' inside a string literal", () => {
179
- const result = validateSOQL(
180
- "SELECT Id FROM Account WHERE Name = 'delete this'",
181
- ALLOWED,
182
- );
183
- expect(result.valid).toBe(true);
184
- });
185
-
186
- it("allows 'update' inside a string literal", () => {
187
- const result = validateSOQL(
188
- "SELECT Id FROM Account WHERE Description = 'please update record'",
189
- ALLOWED,
190
- );
191
- expect(result.valid).toBe(true);
192
- });
193
-
194
- it("allows 'insert' inside a string literal", () => {
195
- const result = validateSOQL(
196
- "SELECT Id FROM Contact WHERE Name = 'insert coin'",
197
- ALLOWED,
198
- );
199
- expect(result.valid).toBe(true);
200
- });
201
-
202
- it("allows 'merge' inside a string literal", () => {
203
- const result = validateSOQL(
204
- "SELECT Id FROM Lead WHERE Status = 'merge pending'",
205
- ALLOWED,
206
- );
207
- expect(result.valid).toBe(true);
208
- });
209
-
210
- it("allows 'upsert' inside a string literal", () => {
211
- const result = validateSOQL(
212
- "SELECT Id FROM Account WHERE Name = 'upsert test'",
213
- ALLOWED,
214
- );
215
- expect(result.valid).toBe(true);
216
- });
217
-
218
- it("allows LIKE pattern with forbidden keyword", () => {
219
- const result = validateSOQL(
220
- "SELECT Id FROM Account WHERE Name LIKE '%delete%'",
221
- ALLOWED,
222
- );
223
- expect(result.valid).toBe(true);
224
- });
225
-
226
- it("still rejects actual DELETE statements", () => {
227
- const result = validateSOQL("DELETE FROM Account", ALLOWED);
228
- expect(result.valid).toBe(false);
229
- expect(result.error).toContain("Forbidden");
230
- });
231
-
232
- it("still rejects forbidden keyword outside string literal even with strings present", () => {
233
- // The keyword DELETE appears outside the string
234
- const result = validateSOQL(
235
- "DELETE FROM Account WHERE Name = 'safe string'",
236
- ALLOWED,
237
- );
238
- expect(result.valid).toBe(false);
239
- expect(result.error).toContain("Forbidden");
240
- });
241
-
242
- it("handles multiple string literals with forbidden keywords", () => {
243
- const result = validateSOQL(
244
- "SELECT Id FROM Account WHERE Name = 'delete' AND Type = 'update this'",
245
- ALLOWED,
246
- );
247
- expect(result.valid).toBe(true);
248
- });
249
-
250
- it("handles empty string literals", () => {
251
- const result = validateSOQL(
252
- "SELECT Id FROM Account WHERE Name = ''",
253
- ALLOWED,
254
- );
255
- expect(result.valid).toBe(true);
256
- });
257
- });
258
-
259
- describe("valid queries", () => {
260
- it("accepts basic query", () => {
261
- const result = validateSOQL("SELECT Id, Name FROM Account LIMIT 10", ALLOWED);
262
- expect(result.valid).toBe(true);
263
- });
264
-
265
- it("accepts query with WHERE clause", () => {
266
- const result = validateSOQL(
267
- "SELECT Id, Name FROM Account WHERE Name = 'Test'",
268
- ALLOWED,
269
- );
270
- expect(result.valid).toBe(true);
271
- });
272
-
273
- it("accepts query with aggregate functions", () => {
274
- const result = validateSOQL(
275
- "SELECT COUNT(Id) FROM Opportunity GROUP BY StageName",
276
- ALLOWED,
277
- );
278
- expect(result.valid).toBe(true);
279
- });
280
- });
281
- });
282
-
283
- describe("appendSOQLLimit", () => {
284
- it("appends LIMIT when not present", () => {
285
- const result = appendSOQLLimit("SELECT Id FROM Account", 100);
286
- expect(result).toBe("SELECT Id FROM Account LIMIT 100");
287
- });
288
-
289
- it("does not append LIMIT when already present", () => {
290
- const result = appendSOQLLimit("SELECT Id FROM Account LIMIT 50", 100);
291
- expect(result).toBe("SELECT Id FROM Account LIMIT 50");
292
- });
293
-
294
- it("is case-insensitive for existing LIMIT", () => {
295
- const result = appendSOQLLimit("SELECT Id FROM Account limit 50", 100);
296
- expect(result).toBe("SELECT Id FROM Account limit 50");
297
- });
298
-
299
- it("trims whitespace", () => {
300
- const result = appendSOQLLimit(" SELECT Id FROM Account ", 100);
301
- expect(result).toBe("SELECT Id FROM Account LIMIT 100");
302
- });
303
- });
@@ -1,233 +0,0 @@
1
- /**
2
- * SQL validation tests specific to DuckDB queries.
3
- *
4
- * Verifies that:
5
- * - Standard SELECT queries pass validation in DuckDB mode
6
- * - DuckDB-specific forbidden operations (PRAGMA, ATTACH, etc.) are blocked
7
- * - Common DuckDB query patterns work with the PostgreSQL parser mode
8
- */
9
- import { describe, expect, it, beforeEach, afterEach, mock } from "bun:test";
10
-
11
- // Mock semantic layer
12
- mock.module("@atlas/api/lib/semantic", () => ({
13
- getWhitelistedTables: () =>
14
- new Set(["sales", "customers", "products", "orders"]),
15
- _resetWhitelists: () => {},
16
- }));
17
-
18
- // Mock the DB connection
19
- const mockDBConnection = {
20
- query: async () => ({ columns: [], rows: [] }),
21
- close: async () => {},
22
- };
23
-
24
- const mockDetectDBType = () => {
25
- const url = process.env.ATLAS_DATASOURCE_URL ?? "";
26
- if (url.startsWith("duckdb://")) return "duckdb";
27
- if (url.startsWith("postgresql://")) return "postgres";
28
- throw new Error(`Unsupported: ${url}`);
29
- };
30
-
31
- mock.module("@atlas/api/lib/db/connection", () => ({
32
- getDB: () => mockDBConnection,
33
- connections: {
34
- get: () => mockDBConnection,
35
- getDefault: () => mockDBConnection,
36
- getDBType: () => mockDetectDBType(),
37
- getValidator: () => undefined,
38
- list: () => ["default"],
39
- },
40
- detectDBType: mockDetectDBType,
41
- }));
42
-
43
- const { validateSQL } = await import("@atlas/api/lib/tools/sql");
44
-
45
- const origEnv = { ...process.env };
46
-
47
- describe("validateSQL — DuckDB mode", () => {
48
- beforeEach(() => {
49
- process.env.ATLAS_DATASOURCE_URL = "duckdb://:memory:";
50
- });
51
-
52
- afterEach(() => {
53
- if (origEnv.ATLAS_DATASOURCE_URL === undefined) {
54
- delete process.env.ATLAS_DATASOURCE_URL;
55
- } else {
56
- process.env.ATLAS_DATASOURCE_URL = origEnv.ATLAS_DATASOURCE_URL;
57
- }
58
- });
59
-
60
- // --- Valid queries ---
61
-
62
- it("allows simple SELECT", () => {
63
- const result = validateSQL("SELECT * FROM sales");
64
- expect(result.valid).toBe(true);
65
- });
66
-
67
- it("allows SELECT with aggregate functions", () => {
68
- const result = validateSQL(
69
- "SELECT COUNT(*), SUM(amount) FROM sales GROUP BY product_id"
70
- );
71
- expect(result.valid).toBe(true);
72
- });
73
-
74
- it("allows SELECT with CTE", () => {
75
- const result = validateSQL(
76
- "WITH totals AS (SELECT customer_id, SUM(amount) AS total FROM sales GROUP BY customer_id) SELECT * FROM totals"
77
- );
78
- expect(result.valid).toBe(true);
79
- });
80
-
81
- it("allows SELECT with window function", () => {
82
- const result = validateSQL(
83
- "SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn FROM sales"
84
- );
85
- expect(result.valid).toBe(true);
86
- });
87
-
88
- it("allows SELECT with JOIN", () => {
89
- const result = validateSQL(
90
- "SELECT s.*, c.name FROM sales s JOIN customers c ON s.customer_id = c.id"
91
- );
92
- expect(result.valid).toBe(true);
93
- });
94
-
95
- it("allows CAST expressions", () => {
96
- const result = validateSQL(
97
- "SELECT CAST(amount AS VARCHAR) FROM sales"
98
- );
99
- expect(result.valid).toBe(true);
100
- });
101
-
102
- it("allows COALESCE and CASE", () => {
103
- const result = validateSQL(
104
- "SELECT COALESCE(name, 'unknown'), CASE WHEN amount > 100 THEN 'high' ELSE 'low' END FROM sales"
105
- );
106
- expect(result.valid).toBe(true);
107
- });
108
-
109
- // --- Blocked operations ---
110
-
111
- it("blocks PRAGMA", () => {
112
- const result = validateSQL("PRAGMA database_list");
113
- expect(result.valid).toBe(false);
114
- expect(result.error).toContain("Forbidden");
115
- });
116
-
117
- it("blocks ATTACH", () => {
118
- const result = validateSQL("ATTACH '/tmp/other.duckdb' AS other");
119
- expect(result.valid).toBe(false);
120
- expect(result.error).toContain("Forbidden");
121
- });
122
-
123
- it("blocks DETACH", () => {
124
- const result = validateSQL("DETACH other");
125
- expect(result.valid).toBe(false);
126
- expect(result.error).toContain("Forbidden");
127
- });
128
-
129
- it("blocks INSTALL", () => {
130
- const result = validateSQL("INSTALL httpfs");
131
- expect(result.valid).toBe(false);
132
- expect(result.error).toContain("Forbidden");
133
- });
134
-
135
- it("blocks EXPORT", () => {
136
- const result = validateSQL("EXPORT DATABASE '/tmp/backup'");
137
- expect(result.valid).toBe(false);
138
- expect(result.error).toContain("Forbidden");
139
- });
140
-
141
- it("blocks IMPORT", () => {
142
- const result = validateSQL("IMPORT DATABASE '/tmp/backup'");
143
- expect(result.valid).toBe(false);
144
- expect(result.error).toContain("Forbidden");
145
- });
146
-
147
- it("blocks CHECKPOINT", () => {
148
- const result = validateSQL("CHECKPOINT");
149
- expect(result.valid).toBe(false);
150
- expect(result.error).toContain("Forbidden");
151
- });
152
-
153
- it("blocks INSERT", () => {
154
- const result = validateSQL("INSERT INTO sales VALUES (1, 100)");
155
- expect(result.valid).toBe(false);
156
- expect(result.error).toContain("Forbidden");
157
- });
158
-
159
- it("blocks CREATE TABLE", () => {
160
- const result = validateSQL("CREATE TABLE foo (id INT)");
161
- expect(result.valid).toBe(false);
162
- expect(result.error).toContain("Forbidden");
163
- });
164
-
165
- it("blocks DROP TABLE", () => {
166
- const result = validateSQL("DROP TABLE sales");
167
- expect(result.valid).toBe(false);
168
- expect(result.error).toContain("Forbidden");
169
- });
170
-
171
- it("blocks DESCRIBE", () => {
172
- const result = validateSQL("DESCRIBE sales");
173
- expect(result.valid).toBe(false);
174
- expect(result.error).toContain("Forbidden");
175
- });
176
-
177
- it("blocks SHOW", () => {
178
- const result = validateSQL("SHOW TABLES");
179
- expect(result.valid).toBe(false);
180
- expect(result.error).toContain("Forbidden");
181
- });
182
-
183
- // --- Table whitelist ---
184
-
185
- it("rejects queries on non-whitelisted tables", () => {
186
- const result = validateSQL("SELECT * FROM secret_data");
187
- expect(result.valid).toBe(false);
188
- expect(result.error).toContain("not in the allowed list");
189
- });
190
-
191
- it("allows queries on whitelisted tables", () => {
192
- const result = validateSQL("SELECT * FROM customers");
193
- expect(result.valid).toBe(true);
194
- });
195
-
196
- // --- File-reading function blocks ---
197
-
198
- it("blocks read_csv_auto", () => {
199
- const result = validateSQL("SELECT * FROM read_csv_auto('/etc/passwd')");
200
- expect(result.valid).toBe(false);
201
- expect(result.error).toContain("Forbidden");
202
- });
203
-
204
- it("blocks read_parquet", () => {
205
- const result = validateSQL("SELECT * FROM read_parquet('/data/secret.parquet')");
206
- expect(result.valid).toBe(false);
207
- expect(result.error).toContain("Forbidden");
208
- });
209
-
210
- it("blocks read_json_auto", () => {
211
- const result = validateSQL("SELECT * FROM read_json_auto('/tmp/data.json')");
212
- expect(result.valid).toBe(false);
213
- expect(result.error).toContain("Forbidden");
214
- });
215
-
216
- it("blocks SET", () => {
217
- const result = validateSQL("SET memory_limit='100GB'");
218
- expect(result.valid).toBe(false);
219
- expect(result.error).toContain("Forbidden");
220
- });
221
-
222
- it("blocks COPY ... TO (via base patterns)", () => {
223
- const result = validateSQL("COPY sales TO '/tmp/exfil.csv'");
224
- expect(result.valid).toBe(false);
225
- expect(result.error).toContain("Forbidden");
226
- });
227
-
228
- it("blocks LOAD (via base patterns)", () => {
229
- const result = validateSQL("LOAD httpfs");
230
- expect(result.valid).toBe(false);
231
- expect(result.error).toContain("Forbidden");
232
- });
233
- });
@@ -1,138 +0,0 @@
1
- /**
2
- * Salesforce SOQL query tool for the Atlas agent.
3
- *
4
- * Parallel to executeSQL but for Salesforce objects via SOQL.
5
- * Uses the SalesforceDataSource instead of DBConnection.
6
- */
7
-
8
- import { tool } from "ai";
9
- import { z } from "zod";
10
- import {
11
- getSalesforceSource,
12
- listSalesforceSources,
13
- } from "@atlas/api/lib/db/salesforce";
14
- import { validateSOQL, appendSOQLLimit } from "./soql-validation";
15
- import { getWhitelistedTables } from "@atlas/api/lib/semantic";
16
- import { logQueryAudit } from "@atlas/api/lib/auth/audit";
17
- import { SENSITIVE_PATTERNS } from "@atlas/api/lib/security";
18
- import { createLogger } from "@atlas/api/lib/logger";
19
-
20
- const log = createLogger("salesforce-tool");
21
-
22
- const ROW_LIMIT = parseInt(process.env.ATLAS_ROW_LIMIT ?? "1000", 10);
23
- const QUERY_TIMEOUT = parseInt(
24
- process.env.ATLAS_QUERY_TIMEOUT ?? "30000",
25
- 10,
26
- );
27
-
28
- export const querySalesforce = tool({
29
- description: `Execute a read-only SOQL query against Salesforce. Only SELECT queries are allowed.
30
-
31
- Rules:
32
- - Always read the relevant entity schema from the semantic layer BEFORE writing SOQL
33
- - Use exact field names from the schema — never guess
34
- - SOQL does not support JOINs — use relationship queries instead (e.g. Account.Name)
35
- - Include a LIMIT clause for large result sets
36
- - If a query fails, fix the issue — do not retry the same SOQL`,
37
-
38
- inputSchema: z.object({
39
- soql: z.string().describe("The SELECT SOQL query to execute"),
40
- explanation: z
41
- .string()
42
- .describe("Brief explanation of what this query does and why"),
43
- connectionId: z
44
- .string()
45
- .optional()
46
- .describe(
47
- "Target Salesforce connection ID. Omit for the default Salesforce connection.",
48
- ),
49
- }),
50
-
51
- execute: async ({ soql, explanation, connectionId }) => {
52
- // Resolve which Salesforce source to use
53
- const sources = listSalesforceSources();
54
- const connId = connectionId ?? (sources.length > 0 ? sources[0] : "default");
55
-
56
- let source;
57
- try {
58
- source = getSalesforceSource(connId);
59
- } catch {
60
- return {
61
- success: false,
62
- error: `Salesforce source "${connId}" is not registered. Available: ${sources.join(", ") || "(none)"}`,
63
- };
64
- }
65
-
66
- // Get whitelist for this connection
67
- const allowed = getWhitelistedTables(connId);
68
-
69
- // Validate SOQL
70
- const validation = validateSOQL(soql, allowed);
71
- if (!validation.valid) {
72
- logQueryAudit({
73
- sql: soql.slice(0, 2000),
74
- durationMs: 0,
75
- rowCount: null,
76
- success: false,
77
- error: `Validation rejected: ${validation.error}`,
78
- });
79
- return { success: false, error: validation.error };
80
- }
81
-
82
- // Auto-append LIMIT
83
- const querySoql = appendSOQLLimit(soql.trim(), ROW_LIMIT);
84
-
85
- const start = performance.now();
86
- try {
87
- const result = await source.query(querySoql, QUERY_TIMEOUT);
88
- const durationMs = Math.round(performance.now() - start);
89
- const truncated = result.rows.length >= ROW_LIMIT;
90
-
91
- try {
92
- logQueryAudit({
93
- sql: querySoql,
94
- durationMs,
95
- rowCount: result.rows.length,
96
- success: true,
97
- });
98
- } catch (auditErr) {
99
- log.warn({ err: auditErr }, "Failed to write query audit log");
100
- }
101
-
102
- return {
103
- success: true,
104
- explanation,
105
- row_count: result.rows.length,
106
- columns: result.columns,
107
- rows: result.rows,
108
- truncated,
109
- };
110
- } catch (err) {
111
- const durationMs = Math.round(performance.now() - start);
112
- const message =
113
- err instanceof Error ? err.message : "Unknown Salesforce error";
114
-
115
- try {
116
- logQueryAudit({
117
- sql: querySoql,
118
- durationMs,
119
- rowCount: null,
120
- success: false,
121
- error: message,
122
- });
123
- } catch (auditErr) {
124
- log.warn({ err: auditErr }, "Failed to write query audit log");
125
- }
126
-
127
- // Block errors that might expose connection details or internal state
128
- if (SENSITIVE_PATTERNS.test(message)) {
129
- return {
130
- success: false,
131
- error: "Salesforce query failed — check server logs for details.",
132
- };
133
- }
134
-
135
- return { success: false, error: message };
136
- }
137
- },
138
- });