@useatlas/create 0.0.6 → 0.0.7

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 (952) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/index.ts +253 -36
  4. package/package.json +4 -4
  5. package/templates/docker/Dockerfile +1 -1
  6. package/templates/docker/Dockerfile.sidecar +1 -1
  7. package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +17 -14
  8. package/templates/docker/bin/__tests__/failure-threshold.test.ts +148 -0
  9. package/templates/docker/bin/__tests__/fatal-error-propagation.test.ts +267 -0
  10. package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +5 -5
  11. package/templates/docker/bin/__tests__/schema-drift.test.ts +39 -0
  12. package/templates/docker/bin/atlas.ts +981 -1819
  13. package/templates/docker/bin/benchmark.ts +14 -16
  14. package/templates/docker/bin/enrich.ts +7 -2
  15. package/templates/docker/brand.css +13 -0
  16. package/templates/docker/data/cybersec-semantic/catalog.yml +222 -0
  17. package/templates/docker/data/cybersec-semantic/entities/alerts.yml +195 -0
  18. package/templates/docker/data/cybersec-semantic/entities/assets.yml +191 -0
  19. package/templates/docker/data/cybersec-semantic/entities/compliance_assessments.yml +170 -0
  20. package/templates/docker/data/cybersec-semantic/entities/incidents.yml +219 -0
  21. package/templates/docker/data/cybersec-semantic/entities/organizations.yml +136 -0
  22. package/templates/docker/data/cybersec-semantic/entities/plans.yml +114 -0
  23. package/templates/docker/data/cybersec-semantic/entities/remediation_actions.yml +212 -0
  24. package/templates/docker/data/cybersec-semantic/entities/scan_results.yml +215 -0
  25. package/templates/docker/data/cybersec-semantic/entities/scans.yml +180 -0
  26. package/templates/docker/data/cybersec-semantic/entities/subscriptions.yml +184 -0
  27. package/templates/docker/data/cybersec-semantic/entities/users.yml +140 -0
  28. package/templates/docker/data/cybersec-semantic/entities/vulnerabilities.yml +154 -0
  29. package/templates/docker/data/cybersec-semantic/glossary.yml +207 -0
  30. package/templates/docker/data/cybersec-semantic/metrics/business.yml +148 -0
  31. package/templates/docker/data/cybersec-semantic/metrics/compliance.yml +138 -0
  32. package/templates/docker/data/cybersec-semantic/metrics/security.yml +181 -0
  33. package/templates/docker/data/cybersec.sql +8 -8
  34. package/templates/docker/data/demo.sql +3 -0
  35. package/templates/docker/data/ecommerce-semantic/catalog.yml +221 -0
  36. package/templates/docker/data/ecommerce-semantic/entities/categories.yml +91 -0
  37. package/templates/docker/data/ecommerce-semantic/entities/customers.yml +133 -0
  38. package/templates/docker/data/ecommerce-semantic/entities/email_campaigns.yml +119 -0
  39. package/templates/docker/data/ecommerce-semantic/entities/inventory_levels.yml +153 -0
  40. package/templates/docker/data/ecommerce-semantic/entities/order_items.yml +159 -0
  41. package/templates/docker/data/ecommerce-semantic/entities/orders.yml +199 -0
  42. package/templates/docker/data/ecommerce-semantic/entities/payments.yml +140 -0
  43. package/templates/docker/data/ecommerce-semantic/entities/product_reviews.yml +155 -0
  44. package/templates/docker/data/ecommerce-semantic/entities/products.yml +178 -0
  45. package/templates/docker/data/ecommerce-semantic/entities/promotions.yml +171 -0
  46. package/templates/docker/data/ecommerce-semantic/entities/returns.yml +144 -0
  47. package/templates/docker/data/ecommerce-semantic/entities/sellers.yml +124 -0
  48. package/templates/docker/data/ecommerce-semantic/entities/shipments.yml +159 -0
  49. package/templates/docker/data/ecommerce-semantic/glossary.yml +193 -0
  50. package/templates/docker/data/ecommerce-semantic/metrics/customers.yml +116 -0
  51. package/templates/docker/data/ecommerce-semantic/metrics/operations.yml +131 -0
  52. package/templates/docker/data/ecommerce-semantic/metrics/revenue.yml +120 -0
  53. package/templates/docker/docs/deploy.md +2 -1
  54. package/templates/docker/ee/src/__mocks__/internal.ts +170 -0
  55. package/templates/docker/ee/src/audit/purge-scheduler.ts +113 -0
  56. package/templates/docker/ee/src/audit/retention.ts +467 -0
  57. package/templates/docker/ee/src/auth/ip-allowlist.ts +367 -0
  58. package/templates/docker/ee/src/auth/roles.ts +562 -0
  59. package/templates/docker/ee/src/auth/scim.ts +343 -0
  60. package/templates/docker/ee/src/auth/sso.ts +538 -0
  61. package/templates/docker/ee/src/backups/engine.ts +355 -0
  62. package/templates/docker/ee/src/backups/index.ts +26 -0
  63. package/templates/docker/ee/src/backups/restore.ts +169 -0
  64. package/templates/docker/ee/src/backups/scheduler.ts +153 -0
  65. package/templates/docker/ee/src/backups/verify.ts +124 -0
  66. package/templates/docker/ee/src/branding/white-label.ts +228 -0
  67. package/templates/docker/ee/src/compliance/masking.ts +477 -0
  68. package/templates/docker/ee/src/compliance/patterns.ts +16 -0
  69. package/templates/docker/ee/src/compliance/pii-detection.ts +217 -0
  70. package/templates/docker/ee/src/compliance/reports.ts +402 -0
  71. package/templates/docker/ee/src/deploy-mode.ts +37 -0
  72. package/templates/docker/ee/src/governance/approval.ts +699 -0
  73. package/templates/docker/ee/src/index.ts +74 -0
  74. package/templates/docker/ee/src/platform/domains.ts +562 -0
  75. package/templates/docker/ee/src/platform/model-routing.ts +382 -0
  76. package/templates/docker/ee/src/platform/residency.ts +265 -0
  77. package/templates/docker/ee/src/sla/alerting.ts +382 -0
  78. package/templates/docker/ee/src/sla/index.ts +12 -0
  79. package/templates/docker/ee/src/sla/metrics.ts +275 -0
  80. package/templates/docker/ee/src/test-setup.ts +1 -0
  81. package/templates/docker/next.config.ts +4 -1
  82. package/templates/docker/package.json +49 -29
  83. package/templates/docker/sidecar/Dockerfile +1 -1
  84. package/templates/docker/src/api/index.ts +336 -24
  85. package/templates/docker/src/api/routes/actions.ts +443 -176
  86. package/templates/docker/src/api/routes/admin-abuse.ts +219 -0
  87. package/templates/docker/src/api/routes/admin-approval.ts +418 -0
  88. package/templates/docker/src/api/routes/admin-audit-retention.ts +405 -0
  89. package/templates/docker/src/api/routes/admin-auth.ts +122 -0
  90. package/templates/docker/src/api/routes/admin-branding.ts +252 -0
  91. package/templates/docker/src/api/routes/admin-compliance.ts +352 -0
  92. package/templates/docker/src/api/routes/admin-domains.ts +334 -0
  93. package/templates/docker/src/api/routes/admin-integrations.ts +2667 -0
  94. package/templates/docker/src/api/routes/admin-ip-allowlist.ts +261 -0
  95. package/templates/docker/src/api/routes/admin-learned-patterns.ts +525 -0
  96. package/templates/docker/src/api/routes/admin-model-config.ts +252 -0
  97. package/templates/docker/src/api/routes/admin-onboarding-emails.ts +145 -0
  98. package/templates/docker/src/api/routes/admin-orgs.ts +710 -0
  99. package/templates/docker/src/api/routes/admin-prompts.ts +694 -0
  100. package/templates/docker/src/api/routes/admin-residency.ts +570 -0
  101. package/templates/docker/src/api/routes/admin-roles.ts +296 -0
  102. package/templates/docker/src/api/routes/admin-router.ts +120 -0
  103. package/templates/docker/src/api/routes/admin-sandbox.ts +417 -0
  104. package/templates/docker/src/api/routes/admin-scim.ts +262 -0
  105. package/templates/docker/src/api/routes/admin-sso.ts +545 -0
  106. package/templates/docker/src/api/routes/admin-suggestions.ts +176 -0
  107. package/templates/docker/src/api/routes/admin-usage.ts +310 -0
  108. package/templates/docker/src/api/routes/admin.ts +4156 -898
  109. package/templates/docker/src/api/routes/auth-preamble.ts +105 -0
  110. package/templates/docker/src/api/routes/billing.ts +397 -0
  111. package/templates/docker/src/api/routes/chat.ts +597 -334
  112. package/templates/docker/src/api/routes/conversations.ts +987 -132
  113. package/templates/docker/src/api/routes/demo.ts +673 -0
  114. package/templates/docker/src/api/routes/discord.ts +274 -0
  115. package/templates/docker/src/api/routes/ee-error-handler.ts +32 -0
  116. package/templates/docker/src/api/routes/health.ts +129 -14
  117. package/templates/docker/src/api/routes/middleware.ts +244 -0
  118. package/templates/docker/src/api/routes/onboarding-emails.ts +134 -0
  119. package/templates/docker/src/api/routes/onboarding.ts +1109 -0
  120. package/templates/docker/src/api/routes/openapi.ts +184 -1597
  121. package/templates/docker/src/api/routes/platform-admin.ts +760 -0
  122. package/templates/docker/src/api/routes/platform-backups.ts +436 -0
  123. package/templates/docker/src/api/routes/platform-domains.ts +235 -0
  124. package/templates/docker/src/api/routes/platform-residency.ts +257 -0
  125. package/templates/docker/src/api/routes/platform-sla.ts +379 -0
  126. package/templates/docker/src/api/routes/prompts.ts +221 -0
  127. package/templates/docker/src/api/routes/public-branding.ts +106 -0
  128. package/templates/docker/src/api/routes/query.ts +330 -219
  129. package/templates/docker/src/api/routes/scheduled-tasks.ts +393 -297
  130. package/templates/docker/src/api/routes/semantic.ts +179 -0
  131. package/templates/docker/src/api/routes/sessions.ts +210 -0
  132. package/templates/docker/src/api/routes/shared-domains.ts +98 -0
  133. package/templates/docker/src/api/routes/shared-schemas.ts +139 -0
  134. package/templates/docker/src/api/routes/slack.ts +209 -52
  135. package/templates/docker/src/api/routes/suggestions.ts +233 -0
  136. package/templates/docker/src/api/routes/tables.ts +67 -0
  137. package/templates/docker/src/api/routes/teams.ts +222 -0
  138. package/templates/docker/src/api/routes/validate-sql.ts +188 -0
  139. package/templates/docker/src/api/routes/validation-hook.ts +62 -0
  140. package/templates/docker/src/api/routes/widget-loader.ts +356 -0
  141. package/templates/docker/src/api/routes/widget.ts +428 -0
  142. package/templates/docker/src/api/routes/wizard.ts +852 -0
  143. package/templates/docker/src/api/server.ts +187 -69
  144. package/templates/docker/src/app/error.tsx +5 -2
  145. package/templates/docker/src/app/globals.css +1 -1
  146. package/templates/docker/src/app/layout.tsx +7 -2
  147. package/templates/docker/src/app/page.tsx +39 -5
  148. package/templates/docker/src/components/data-table/data-table-column-header.tsx +99 -0
  149. package/templates/docker/src/components/data-table/data-table-date-filter.tsx +225 -0
  150. package/templates/docker/src/components/data-table/data-table-expandable.tsx +125 -0
  151. package/templates/docker/src/components/data-table/data-table-faceted-filter.tsx +189 -0
  152. package/templates/docker/src/components/data-table/data-table-pagination.tsx +112 -0
  153. package/templates/docker/src/components/data-table/data-table-range-filter.tsx +122 -0
  154. package/templates/docker/src/components/data-table/data-table-slider-filter.tsx +256 -0
  155. package/templates/docker/src/components/data-table/data-table-sort-list.tsx +407 -0
  156. package/templates/docker/src/components/data-table/data-table-toolbar.tsx +149 -0
  157. package/templates/docker/src/components/data-table/data-table-view-options.tsx +89 -0
  158. package/templates/docker/src/components/data-table/data-table.tsx +105 -0
  159. package/templates/docker/src/components/form-dialog.tsx +135 -0
  160. package/templates/docker/src/components/ui/accordion.tsx +66 -0
  161. package/templates/docker/src/components/ui/calendar.tsx +220 -0
  162. package/templates/docker/src/components/ui/checkbox.tsx +32 -0
  163. package/templates/docker/src/components/ui/faceted.tsx +283 -0
  164. package/templates/docker/src/components/ui/form.tsx +167 -0
  165. package/templates/docker/src/components/ui/label.tsx +24 -0
  166. package/templates/docker/src/components/ui/popover.tsx +89 -0
  167. package/templates/docker/src/components/ui/progress.tsx +31 -0
  168. package/templates/docker/src/components/ui/scroll-area.tsx +6 -2
  169. package/templates/docker/src/components/ui/slider.tsx +63 -0
  170. package/templates/docker/src/components/ui/sortable.tsx +581 -0
  171. package/templates/docker/src/components/ui/switch.tsx +35 -0
  172. package/templates/docker/src/components/ui/textarea.tsx +18 -0
  173. package/templates/docker/src/config/data-table.ts +82 -0
  174. package/templates/docker/src/env-check.ts +74 -0
  175. package/templates/docker/src/hooks/use-callback-ref.ts +27 -0
  176. package/templates/docker/src/hooks/use-data-table.ts +316 -0
  177. package/templates/docker/src/hooks/use-debounced-callback.ts +28 -0
  178. package/templates/docker/src/lib/action-types.ts +7 -41
  179. package/templates/docker/src/lib/agent-query.ts +4 -2
  180. package/templates/docker/src/lib/agent.ts +363 -31
  181. package/templates/docker/src/lib/auth/admin-permissions.ts +38 -0
  182. package/templates/docker/src/lib/auth/audit.ts +19 -4
  183. package/templates/docker/src/lib/auth/byot.ts +3 -3
  184. package/templates/docker/src/lib/auth/client.ts +33 -3
  185. package/templates/docker/src/lib/auth/detect.ts +29 -8
  186. package/templates/docker/src/lib/auth/managed.ts +104 -14
  187. package/templates/docker/src/lib/auth/middleware.ts +53 -6
  188. package/templates/docker/src/lib/auth/migrate.ts +140 -15
  189. package/templates/docker/src/lib/auth/oauth-state.ts +123 -0
  190. package/templates/docker/src/lib/auth/org-permissions.ts +55 -0
  191. package/templates/docker/src/lib/auth/permissions.ts +26 -19
  192. package/templates/docker/src/lib/auth/server.ts +355 -9
  193. package/templates/docker/src/lib/auth/simple-key.ts +3 -3
  194. package/templates/docker/src/lib/auth/types.ts +15 -21
  195. package/templates/docker/src/lib/billing/enforcement.ts +368 -0
  196. package/templates/docker/src/lib/billing/plans.ts +155 -0
  197. package/templates/docker/src/lib/cache/index.ts +92 -0
  198. package/templates/docker/src/lib/cache/keys.ts +30 -0
  199. package/templates/docker/src/lib/cache/lru.ts +79 -0
  200. package/templates/docker/src/lib/cache/types.ts +31 -0
  201. package/templates/docker/src/lib/compose-refs.ts +62 -0
  202. package/templates/docker/src/lib/config.ts +563 -11
  203. package/templates/docker/src/lib/connection-types.ts +9 -0
  204. package/templates/docker/src/lib/conversation-types.ts +1 -25
  205. package/templates/docker/src/lib/conversations.ts +345 -14
  206. package/templates/docker/src/lib/data-table.ts +61 -0
  207. package/templates/docker/src/lib/db/connection.ts +793 -39
  208. package/templates/docker/src/lib/db/internal.ts +985 -139
  209. package/templates/docker/src/lib/db/migrate.ts +295 -0
  210. package/templates/docker/src/lib/db/migrations/0000_baseline.sql +703 -0
  211. package/templates/docker/src/lib/db/migrations/0001_teams_installations.sql +14 -0
  212. package/templates/docker/src/lib/db/migrations/0002_discord_installations.sql +14 -0
  213. package/templates/docker/src/lib/db/migrations/0003_telegram_installations.sql +15 -0
  214. package/templates/docker/src/lib/db/migrations/0004_sandbox_credentials.sql +18 -0
  215. package/templates/docker/src/lib/db/migrations/0005_oauth_state.sql +16 -0
  216. package/templates/docker/src/lib/db/migrations/0006_byot_credentials.sql +14 -0
  217. package/templates/docker/src/lib/db/migrations/0007_gchat_installations.sql +15 -0
  218. package/templates/docker/src/lib/db/migrations/0008_github_installations.sql +14 -0
  219. package/templates/docker/src/lib/db/migrations/0009_linear_installations.sql +15 -0
  220. package/templates/docker/src/lib/db/migrations/0010_whatsapp_installations.sql +14 -0
  221. package/templates/docker/src/lib/db/migrations/0011_email_installations.sql +16 -0
  222. package/templates/docker/src/lib/db/migrations/0012_region_migrations.sql +25 -0
  223. package/templates/docker/src/lib/db/schema.ts +1120 -0
  224. package/templates/docker/src/lib/db/source-rate-limit.ts +89 -139
  225. package/templates/docker/src/lib/demo.ts +308 -0
  226. package/templates/docker/src/lib/discord/store.ts +225 -0
  227. package/templates/docker/src/lib/effect/ai.ts +243 -0
  228. package/templates/docker/src/lib/effect/errors.ts +234 -0
  229. package/templates/docker/src/lib/effect/hono.ts +454 -0
  230. package/templates/docker/src/lib/effect/index.ts +137 -0
  231. package/templates/docker/src/lib/effect/layers.ts +496 -0
  232. package/templates/docker/src/lib/effect/services.ts +776 -0
  233. package/templates/docker/src/lib/effect/sql.ts +178 -0
  234. package/templates/docker/src/lib/effect/toolkit.ts +123 -0
  235. package/templates/docker/src/lib/email/delivery.ts +232 -0
  236. package/templates/docker/src/lib/email/engine.ts +349 -0
  237. package/templates/docker/src/lib/email/hooks.ts +107 -0
  238. package/templates/docker/src/lib/email/index.ts +16 -0
  239. package/templates/docker/src/lib/email/scheduler.ts +72 -0
  240. package/templates/docker/src/lib/email/sequence.ts +73 -0
  241. package/templates/docker/src/lib/email/store.ts +163 -0
  242. package/templates/docker/src/lib/email/templates.ts +215 -0
  243. package/templates/docker/src/lib/format.ts +67 -0
  244. package/templates/docker/src/lib/gchat/store.ts +202 -0
  245. package/templates/docker/src/lib/github/store.ts +197 -0
  246. package/templates/docker/src/lib/id.ts +29 -0
  247. package/templates/docker/src/lib/integrations/types.ts +166 -0
  248. package/templates/docker/src/lib/learn/pattern-analyzer.ts +224 -0
  249. package/templates/docker/src/lib/learn/pattern-cache.ts +229 -0
  250. package/templates/docker/src/lib/learn/pattern-proposer.ts +87 -0
  251. package/templates/docker/src/lib/learn/suggestion-helpers.ts +34 -0
  252. package/templates/docker/src/lib/learn/suggestions.ts +139 -0
  253. package/templates/docker/src/lib/linear/store.ts +200 -0
  254. package/templates/docker/src/lib/logger.ts +35 -3
  255. package/templates/docker/src/lib/metering.ts +272 -0
  256. package/templates/docker/src/lib/parsers.ts +99 -0
  257. package/templates/docker/src/lib/plugins/hooks.ts +13 -11
  258. package/templates/docker/src/lib/plugins/index.ts +3 -1
  259. package/templates/docker/src/lib/plugins/registry.ts +58 -6
  260. package/templates/docker/src/lib/plugins/settings.ts +147 -0
  261. package/templates/docker/src/lib/plugins/wiring.ts +6 -9
  262. package/templates/docker/src/lib/profiler.ts +1665 -0
  263. package/templates/docker/src/lib/providers.ts +188 -13
  264. package/templates/docker/src/lib/rls.ts +172 -60
  265. package/templates/docker/src/lib/sandbox/credentials.ts +206 -0
  266. package/templates/docker/src/lib/sandbox/validate.ts +179 -0
  267. package/templates/docker/src/lib/scheduled-task-types.ts +26 -94
  268. package/templates/docker/src/lib/scheduled-tasks.ts +174 -34
  269. package/templates/docker/src/lib/scheduler/delivery.ts +248 -150
  270. package/templates/docker/src/lib/scheduler/engine.ts +190 -154
  271. package/templates/docker/src/lib/scheduler/executor.ts +74 -23
  272. package/templates/docker/src/lib/scheduler/preview.ts +72 -0
  273. package/templates/docker/src/lib/security/abuse.ts +463 -0
  274. package/templates/docker/src/lib/semantic/diff.ts +267 -0
  275. package/templates/docker/src/lib/semantic/entities.ts +167 -0
  276. package/templates/docker/src/lib/semantic/files.ts +283 -0
  277. package/templates/docker/src/lib/semantic/index.ts +27 -0
  278. package/templates/docker/src/lib/{semantic-index.ts → semantic/search.ts} +80 -9
  279. package/templates/docker/src/lib/semantic/sync.ts +581 -0
  280. package/templates/docker/src/lib/{semantic.ts → semantic/whitelist.ts} +189 -3
  281. package/templates/docker/src/lib/settings.ts +817 -0
  282. package/templates/docker/src/lib/sidecar-types.ts +13 -0
  283. package/templates/docker/src/lib/slack/store.ts +134 -25
  284. package/templates/docker/src/lib/startup.ts +528 -362
  285. package/templates/docker/src/lib/teams/store.ts +216 -0
  286. package/templates/docker/src/lib/telegram/store.ts +202 -0
  287. package/templates/docker/src/lib/telemetry.ts +40 -0
  288. package/templates/docker/src/lib/tools/actions/audit.ts +8 -5
  289. package/templates/docker/src/lib/tools/actions/email.ts +3 -1
  290. package/templates/docker/src/lib/tools/actions/handler.ts +276 -93
  291. package/templates/docker/src/lib/tools/actions/jira.ts +2 -2
  292. package/templates/docker/src/lib/tools/backends/detect.ts +16 -0
  293. package/templates/docker/src/lib/tools/backends/index.ts +11 -0
  294. package/templates/docker/src/lib/tools/backends/nsjail.ts +213 -0
  295. package/templates/docker/src/lib/tools/backends/shared.ts +103 -0
  296. package/templates/docker/src/lib/tools/backends/types.ts +26 -0
  297. package/templates/docker/src/lib/tools/explore-nsjail.ts +7 -228
  298. package/templates/docker/src/lib/tools/explore-sandbox.ts +4 -29
  299. package/templates/docker/src/lib/tools/explore-sidecar.ts +18 -2
  300. package/templates/docker/src/lib/tools/explore.ts +246 -54
  301. package/templates/docker/src/lib/tools/index.ts +17 -0
  302. package/templates/docker/src/lib/tools/python-nsjail.ts +11 -139
  303. package/templates/docker/src/lib/tools/python-sandbox.ts +9 -132
  304. package/templates/docker/src/lib/tools/python-sidecar.ts +184 -3
  305. package/templates/docker/src/lib/tools/python-stream.ts +33 -0
  306. package/templates/docker/src/lib/tools/python-wrapper.ts +129 -0
  307. package/templates/docker/src/lib/tools/python.ts +115 -15
  308. package/templates/docker/src/lib/tools/registry.ts +14 -2
  309. package/templates/docker/src/lib/tools/sql.ts +778 -362
  310. package/templates/docker/src/lib/tracing.ts +16 -0
  311. package/templates/docker/src/lib/whatsapp/store.ts +198 -0
  312. package/templates/docker/src/lib/workspace.ts +89 -0
  313. package/templates/docker/src/progress.ts +121 -0
  314. package/templates/docker/src/types/data-table.ts +48 -0
  315. package/templates/docker/src/ui/atlas-chat-reexport.ts +3 -0
  316. package/templates/docker/src/ui/components/actions/action-approval-card.tsx +26 -19
  317. package/templates/docker/src/ui/components/actions/action-status-badge.tsx +3 -3
  318. package/templates/docker/src/ui/components/admin/admin-layout.tsx +57 -39
  319. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +213 -35
  320. package/templates/docker/src/ui/components/admin/delivery-status-badge.tsx +53 -0
  321. package/templates/docker/src/ui/components/admin/empty-state.tsx +27 -6
  322. package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -52
  323. package/templates/docker/src/ui/components/admin/error-banner.tsx +2 -2
  324. package/templates/docker/src/ui/components/admin/feature-disabled.tsx +28 -5
  325. package/templates/docker/src/ui/components/admin-content-wrapper.tsx +87 -0
  326. package/templates/docker/src/ui/components/atlas-chat.tsx +449 -166
  327. package/templates/docker/src/ui/components/branding-head.tsx +41 -0
  328. package/templates/docker/src/ui/components/chart/chart-detection.ts +62 -5
  329. package/templates/docker/src/ui/components/chart/result-chart.tsx +316 -125
  330. package/templates/docker/src/ui/components/chat/api-key-bar.tsx +4 -4
  331. package/templates/docker/src/ui/components/chat/data-table.tsx +45 -4
  332. package/templates/docker/src/ui/components/chat/error-banner.tsx +86 -5
  333. package/templates/docker/src/ui/components/chat/follow-up-chips.tsx +29 -0
  334. package/templates/docker/src/ui/components/chat/markdown.tsx +24 -0
  335. package/templates/docker/src/ui/components/chat/prompt-library.tsx +206 -0
  336. package/templates/docker/src/ui/components/chat/python-result-card.tsx +106 -78
  337. package/templates/docker/src/ui/components/chat/result-card-base.tsx +101 -0
  338. package/templates/docker/src/ui/components/chat/share-dialog.tsx +377 -0
  339. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +94 -73
  340. package/templates/docker/src/ui/components/chat/suggestion-chips.tsx +46 -0
  341. package/templates/docker/src/ui/components/chat/tool-part.tsx +16 -4
  342. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +48 -17
  343. package/templates/docker/src/ui/components/conversations/conversation-list.tsx +38 -24
  344. package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +66 -7
  345. package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +9 -2
  346. package/templates/docker/src/ui/components/error-boundary.tsx +66 -0
  347. package/templates/docker/src/ui/components/notebook/delete-cell-dialog.tsx +48 -0
  348. package/templates/docker/src/ui/components/notebook/fork-branch-selector.tsx +68 -0
  349. package/templates/docker/src/ui/components/notebook/notebook-cell-input.tsx +76 -0
  350. package/templates/docker/src/ui/components/notebook/notebook-cell-output.tsx +58 -0
  351. package/templates/docker/src/ui/components/notebook/notebook-cell-toolbar.tsx +91 -0
  352. package/templates/docker/src/ui/components/notebook/notebook-cell.tsx +119 -0
  353. package/templates/docker/src/ui/components/notebook/notebook-empty-state.tsx +19 -0
  354. package/templates/docker/src/ui/components/notebook/notebook-export.ts +287 -0
  355. package/templates/docker/src/ui/components/notebook/notebook-input-bar.tsx +49 -0
  356. package/templates/docker/src/ui/components/notebook/notebook-shell.tsx +266 -0
  357. package/templates/docker/src/ui/components/notebook/notebook-text-cell.tsx +152 -0
  358. package/templates/docker/src/ui/components/notebook/types.ts +39 -0
  359. package/templates/docker/src/ui/components/notebook/use-keyboard-nav.ts +109 -0
  360. package/templates/docker/src/ui/components/notebook/use-notebook.ts +684 -0
  361. package/templates/docker/src/ui/components/org-switcher.tsx +111 -0
  362. package/templates/docker/src/ui/components/region-picker.tsx +103 -0
  363. package/templates/docker/src/ui/components/schema-explorer/schema-explorer.tsx +522 -0
  364. package/templates/docker/src/ui/components/social-icons.tsx +26 -0
  365. package/templates/docker/src/ui/components/tour/guided-tour.tsx +81 -0
  366. package/templates/docker/src/ui/components/tour/index.ts +5 -0
  367. package/templates/docker/src/ui/components/tour/nav-bar.tsx +100 -0
  368. package/templates/docker/src/ui/components/tour/tour-overlay.tsx +298 -0
  369. package/templates/docker/src/ui/components/tour/tour-steps.ts +43 -0
  370. package/templates/docker/src/ui/components/tour/types.ts +21 -0
  371. package/templates/docker/src/ui/components/tour/use-tour.ts +193 -0
  372. package/templates/docker/src/ui/context-reexport.ts +3 -0
  373. package/templates/docker/src/ui/hooks/theme-init-script.ts +17 -0
  374. package/templates/docker/src/ui/hooks/use-admin-fetch.ts +38 -30
  375. package/templates/docker/src/ui/hooks/use-admin-mutation.ts +188 -0
  376. package/templates/docker/src/ui/hooks/use-atlas-transport.ts +225 -0
  377. package/templates/docker/src/ui/hooks/use-branding.ts +68 -0
  378. package/templates/docker/src/ui/hooks/use-conversations.ts +106 -83
  379. package/templates/docker/src/ui/hooks/use-dark-mode.ts +134 -10
  380. package/templates/docker/src/ui/hooks/use-deploy-mode.ts +36 -0
  381. package/templates/docker/src/ui/hooks/use-platform-admin-guard.ts +49 -0
  382. package/templates/docker/src/ui/lib/action-types.ts +11 -63
  383. package/templates/docker/src/ui/lib/admin-schemas.ts +744 -0
  384. package/templates/docker/src/ui/lib/fetch-client.ts +84 -0
  385. package/templates/docker/src/ui/lib/fetch-error.ts +54 -0
  386. package/templates/docker/src/ui/lib/helpers.ts +94 -1
  387. package/templates/docker/src/ui/lib/types.ts +149 -140
  388. package/templates/docker/tsconfig.json +4 -2
  389. package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +17 -14
  390. package/templates/nextjs-standalone/bin/__tests__/failure-threshold.test.ts +148 -0
  391. package/templates/nextjs-standalone/bin/__tests__/fatal-error-propagation.test.ts +267 -0
  392. package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +5 -5
  393. package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +39 -0
  394. package/templates/nextjs-standalone/bin/atlas.ts +981 -1819
  395. package/templates/nextjs-standalone/bin/benchmark.ts +14 -16
  396. package/templates/nextjs-standalone/bin/enrich.ts +7 -2
  397. package/templates/nextjs-standalone/brand.css +13 -0
  398. package/templates/nextjs-standalone/data/cybersec-semantic/catalog.yml +222 -0
  399. package/templates/nextjs-standalone/data/cybersec-semantic/entities/alerts.yml +195 -0
  400. package/templates/nextjs-standalone/data/cybersec-semantic/entities/assets.yml +191 -0
  401. package/templates/nextjs-standalone/data/cybersec-semantic/entities/compliance_assessments.yml +170 -0
  402. package/templates/nextjs-standalone/data/cybersec-semantic/entities/incidents.yml +219 -0
  403. package/templates/nextjs-standalone/data/cybersec-semantic/entities/organizations.yml +136 -0
  404. package/templates/nextjs-standalone/data/cybersec-semantic/entities/plans.yml +114 -0
  405. package/templates/nextjs-standalone/data/cybersec-semantic/entities/remediation_actions.yml +212 -0
  406. package/templates/nextjs-standalone/data/cybersec-semantic/entities/scan_results.yml +215 -0
  407. package/templates/nextjs-standalone/data/cybersec-semantic/entities/scans.yml +180 -0
  408. package/templates/nextjs-standalone/data/cybersec-semantic/entities/subscriptions.yml +184 -0
  409. package/templates/nextjs-standalone/data/cybersec-semantic/entities/users.yml +140 -0
  410. package/templates/nextjs-standalone/data/cybersec-semantic/entities/vulnerabilities.yml +154 -0
  411. package/templates/nextjs-standalone/data/cybersec-semantic/glossary.yml +207 -0
  412. package/templates/nextjs-standalone/data/cybersec-semantic/metrics/business.yml +148 -0
  413. package/templates/nextjs-standalone/data/cybersec-semantic/metrics/compliance.yml +138 -0
  414. package/templates/nextjs-standalone/data/cybersec-semantic/metrics/security.yml +181 -0
  415. package/templates/nextjs-standalone/data/cybersec.sql +8 -8
  416. package/templates/nextjs-standalone/data/demo.sql +3 -0
  417. package/templates/nextjs-standalone/data/ecommerce-semantic/catalog.yml +221 -0
  418. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/categories.yml +91 -0
  419. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/customers.yml +133 -0
  420. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/email_campaigns.yml +119 -0
  421. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/inventory_levels.yml +153 -0
  422. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/order_items.yml +159 -0
  423. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/orders.yml +199 -0
  424. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/payments.yml +140 -0
  425. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/product_reviews.yml +155 -0
  426. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/products.yml +178 -0
  427. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/promotions.yml +171 -0
  428. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/returns.yml +144 -0
  429. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/sellers.yml +124 -0
  430. package/templates/nextjs-standalone/data/ecommerce-semantic/entities/shipments.yml +159 -0
  431. package/templates/nextjs-standalone/data/ecommerce-semantic/glossary.yml +193 -0
  432. package/templates/nextjs-standalone/data/ecommerce-semantic/metrics/customers.yml +116 -0
  433. package/templates/nextjs-standalone/data/ecommerce-semantic/metrics/operations.yml +131 -0
  434. package/templates/nextjs-standalone/data/ecommerce-semantic/metrics/revenue.yml +120 -0
  435. package/templates/nextjs-standalone/docs/deploy.md +2 -1
  436. package/templates/nextjs-standalone/ee/src/__mocks__/internal.ts +170 -0
  437. package/templates/nextjs-standalone/ee/src/audit/purge-scheduler.ts +113 -0
  438. package/templates/nextjs-standalone/ee/src/audit/retention.ts +467 -0
  439. package/templates/nextjs-standalone/ee/src/auth/ip-allowlist.ts +367 -0
  440. package/templates/nextjs-standalone/ee/src/auth/roles.ts +562 -0
  441. package/templates/nextjs-standalone/ee/src/auth/scim.ts +343 -0
  442. package/templates/nextjs-standalone/ee/src/auth/sso.ts +538 -0
  443. package/templates/nextjs-standalone/ee/src/backups/engine.ts +355 -0
  444. package/templates/nextjs-standalone/ee/src/backups/index.ts +26 -0
  445. package/templates/nextjs-standalone/ee/src/backups/restore.ts +169 -0
  446. package/templates/nextjs-standalone/ee/src/backups/scheduler.ts +153 -0
  447. package/templates/nextjs-standalone/ee/src/backups/verify.ts +124 -0
  448. package/templates/nextjs-standalone/ee/src/branding/white-label.ts +228 -0
  449. package/templates/nextjs-standalone/ee/src/compliance/masking.ts +477 -0
  450. package/templates/nextjs-standalone/ee/src/compliance/patterns.ts +16 -0
  451. package/templates/nextjs-standalone/ee/src/compliance/pii-detection.ts +217 -0
  452. package/templates/nextjs-standalone/ee/src/compliance/reports.ts +402 -0
  453. package/templates/nextjs-standalone/ee/src/deploy-mode.ts +37 -0
  454. package/templates/nextjs-standalone/ee/src/governance/approval.ts +699 -0
  455. package/templates/nextjs-standalone/ee/src/index.ts +74 -0
  456. package/templates/nextjs-standalone/ee/src/platform/domains.ts +562 -0
  457. package/templates/nextjs-standalone/ee/src/platform/model-routing.ts +382 -0
  458. package/templates/nextjs-standalone/ee/src/platform/residency.ts +265 -0
  459. package/templates/nextjs-standalone/ee/src/sla/alerting.ts +382 -0
  460. package/templates/nextjs-standalone/ee/src/sla/index.ts +12 -0
  461. package/templates/nextjs-standalone/ee/src/sla/metrics.ts +275 -0
  462. package/templates/nextjs-standalone/ee/src/test-setup.ts +1 -0
  463. package/templates/nextjs-standalone/next.config.ts +1 -1
  464. package/templates/nextjs-standalone/package.json +50 -30
  465. package/templates/nextjs-standalone/src/api/index.ts +336 -24
  466. package/templates/nextjs-standalone/src/api/routes/actions.ts +443 -176
  467. package/templates/nextjs-standalone/src/api/routes/admin-abuse.ts +219 -0
  468. package/templates/nextjs-standalone/src/api/routes/admin-approval.ts +418 -0
  469. package/templates/nextjs-standalone/src/api/routes/admin-audit-retention.ts +405 -0
  470. package/templates/nextjs-standalone/src/api/routes/admin-auth.ts +122 -0
  471. package/templates/nextjs-standalone/src/api/routes/admin-branding.ts +252 -0
  472. package/templates/nextjs-standalone/src/api/routes/admin-compliance.ts +352 -0
  473. package/templates/nextjs-standalone/src/api/routes/admin-domains.ts +334 -0
  474. package/templates/nextjs-standalone/src/api/routes/admin-integrations.ts +2667 -0
  475. package/templates/nextjs-standalone/src/api/routes/admin-ip-allowlist.ts +261 -0
  476. package/templates/nextjs-standalone/src/api/routes/admin-learned-patterns.ts +525 -0
  477. package/templates/nextjs-standalone/src/api/routes/admin-model-config.ts +252 -0
  478. package/templates/nextjs-standalone/src/api/routes/admin-onboarding-emails.ts +145 -0
  479. package/templates/nextjs-standalone/src/api/routes/admin-orgs.ts +710 -0
  480. package/templates/nextjs-standalone/src/api/routes/admin-prompts.ts +694 -0
  481. package/templates/nextjs-standalone/src/api/routes/admin-residency.ts +570 -0
  482. package/templates/nextjs-standalone/src/api/routes/admin-roles.ts +296 -0
  483. package/templates/nextjs-standalone/src/api/routes/admin-router.ts +120 -0
  484. package/templates/nextjs-standalone/src/api/routes/admin-sandbox.ts +417 -0
  485. package/templates/nextjs-standalone/src/api/routes/admin-scim.ts +262 -0
  486. package/templates/nextjs-standalone/src/api/routes/admin-sso.ts +545 -0
  487. package/templates/nextjs-standalone/src/api/routes/admin-suggestions.ts +176 -0
  488. package/templates/nextjs-standalone/src/api/routes/admin-usage.ts +310 -0
  489. package/templates/nextjs-standalone/src/api/routes/admin.ts +4156 -898
  490. package/templates/nextjs-standalone/src/api/routes/auth-preamble.ts +105 -0
  491. package/templates/nextjs-standalone/src/api/routes/billing.ts +397 -0
  492. package/templates/nextjs-standalone/src/api/routes/chat.ts +597 -334
  493. package/templates/nextjs-standalone/src/api/routes/conversations.ts +987 -132
  494. package/templates/nextjs-standalone/src/api/routes/demo.ts +673 -0
  495. package/templates/nextjs-standalone/src/api/routes/discord.ts +274 -0
  496. package/templates/nextjs-standalone/src/api/routes/ee-error-handler.ts +32 -0
  497. package/templates/nextjs-standalone/src/api/routes/health.ts +129 -14
  498. package/templates/nextjs-standalone/src/api/routes/middleware.ts +244 -0
  499. package/templates/nextjs-standalone/src/api/routes/onboarding-emails.ts +134 -0
  500. package/templates/nextjs-standalone/src/api/routes/onboarding.ts +1109 -0
  501. package/templates/nextjs-standalone/src/api/routes/openapi.ts +184 -1597
  502. package/templates/nextjs-standalone/src/api/routes/platform-admin.ts +760 -0
  503. package/templates/nextjs-standalone/src/api/routes/platform-backups.ts +436 -0
  504. package/templates/nextjs-standalone/src/api/routes/platform-domains.ts +235 -0
  505. package/templates/nextjs-standalone/src/api/routes/platform-residency.ts +257 -0
  506. package/templates/nextjs-standalone/src/api/routes/platform-sla.ts +379 -0
  507. package/templates/nextjs-standalone/src/api/routes/prompts.ts +221 -0
  508. package/templates/nextjs-standalone/src/api/routes/public-branding.ts +106 -0
  509. package/templates/nextjs-standalone/src/api/routes/query.ts +330 -219
  510. package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +393 -297
  511. package/templates/nextjs-standalone/src/api/routes/semantic.ts +179 -0
  512. package/templates/nextjs-standalone/src/api/routes/sessions.ts +210 -0
  513. package/templates/nextjs-standalone/src/api/routes/shared-domains.ts +98 -0
  514. package/templates/nextjs-standalone/src/api/routes/shared-schemas.ts +139 -0
  515. package/templates/nextjs-standalone/src/api/routes/slack.ts +209 -52
  516. package/templates/nextjs-standalone/src/api/routes/suggestions.ts +233 -0
  517. package/templates/nextjs-standalone/src/api/routes/tables.ts +67 -0
  518. package/templates/nextjs-standalone/src/api/routes/teams.ts +222 -0
  519. package/templates/nextjs-standalone/src/api/routes/validate-sql.ts +188 -0
  520. package/templates/nextjs-standalone/src/api/routes/validation-hook.ts +62 -0
  521. package/templates/nextjs-standalone/src/api/routes/widget-loader.ts +356 -0
  522. package/templates/nextjs-standalone/src/api/routes/widget.ts +428 -0
  523. package/templates/nextjs-standalone/src/api/routes/wizard.ts +852 -0
  524. package/templates/nextjs-standalone/src/api/server.ts +187 -69
  525. package/templates/nextjs-standalone/src/app/error.tsx +5 -2
  526. package/templates/nextjs-standalone/src/app/globals.css +1 -1
  527. package/templates/nextjs-standalone/src/app/layout.tsx +7 -2
  528. package/templates/nextjs-standalone/src/app/page.tsx +39 -5
  529. package/templates/nextjs-standalone/src/components/data-table/data-table-column-header.tsx +99 -0
  530. package/templates/nextjs-standalone/src/components/data-table/data-table-date-filter.tsx +225 -0
  531. package/templates/nextjs-standalone/src/components/data-table/data-table-expandable.tsx +125 -0
  532. package/templates/nextjs-standalone/src/components/data-table/data-table-faceted-filter.tsx +189 -0
  533. package/templates/nextjs-standalone/src/components/data-table/data-table-pagination.tsx +112 -0
  534. package/templates/nextjs-standalone/src/components/data-table/data-table-range-filter.tsx +122 -0
  535. package/templates/nextjs-standalone/src/components/data-table/data-table-slider-filter.tsx +256 -0
  536. package/templates/nextjs-standalone/src/components/data-table/data-table-sort-list.tsx +407 -0
  537. package/templates/nextjs-standalone/src/components/data-table/data-table-toolbar.tsx +149 -0
  538. package/templates/nextjs-standalone/src/components/data-table/data-table-view-options.tsx +89 -0
  539. package/templates/nextjs-standalone/src/components/data-table/data-table.tsx +105 -0
  540. package/templates/nextjs-standalone/src/components/form-dialog.tsx +135 -0
  541. package/templates/nextjs-standalone/src/components/ui/accordion.tsx +66 -0
  542. package/templates/nextjs-standalone/src/components/ui/calendar.tsx +220 -0
  543. package/templates/nextjs-standalone/src/components/ui/checkbox.tsx +32 -0
  544. package/templates/nextjs-standalone/src/components/ui/faceted.tsx +283 -0
  545. package/templates/nextjs-standalone/src/components/ui/form.tsx +167 -0
  546. package/templates/nextjs-standalone/src/components/ui/label.tsx +24 -0
  547. package/templates/nextjs-standalone/src/components/ui/popover.tsx +89 -0
  548. package/templates/nextjs-standalone/src/components/ui/progress.tsx +31 -0
  549. package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +6 -2
  550. package/templates/nextjs-standalone/src/components/ui/slider.tsx +63 -0
  551. package/templates/nextjs-standalone/src/components/ui/sortable.tsx +581 -0
  552. package/templates/nextjs-standalone/src/components/ui/switch.tsx +35 -0
  553. package/templates/nextjs-standalone/src/components/ui/textarea.tsx +18 -0
  554. package/templates/nextjs-standalone/src/config/data-table.ts +82 -0
  555. package/templates/nextjs-standalone/src/env-check.ts +74 -0
  556. package/templates/nextjs-standalone/src/hooks/use-callback-ref.ts +27 -0
  557. package/templates/nextjs-standalone/src/hooks/use-data-table.ts +316 -0
  558. package/templates/nextjs-standalone/src/hooks/use-debounced-callback.ts +28 -0
  559. package/templates/nextjs-standalone/src/lib/action-types.ts +7 -41
  560. package/templates/nextjs-standalone/src/lib/agent-query.ts +4 -2
  561. package/templates/nextjs-standalone/src/lib/agent.ts +363 -31
  562. package/templates/nextjs-standalone/src/lib/api-url.ts +2 -3
  563. package/templates/nextjs-standalone/src/lib/auth/admin-permissions.ts +38 -0
  564. package/templates/nextjs-standalone/src/lib/auth/audit.ts +19 -4
  565. package/templates/nextjs-standalone/src/lib/auth/byot.ts +3 -3
  566. package/templates/nextjs-standalone/src/lib/auth/detect.ts +29 -8
  567. package/templates/nextjs-standalone/src/lib/auth/managed.ts +104 -14
  568. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +53 -6
  569. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +140 -15
  570. package/templates/nextjs-standalone/src/lib/auth/oauth-state.ts +123 -0
  571. package/templates/nextjs-standalone/src/lib/auth/org-permissions.ts +55 -0
  572. package/templates/nextjs-standalone/src/lib/auth/permissions.ts +26 -19
  573. package/templates/nextjs-standalone/src/lib/auth/server.ts +355 -9
  574. package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +3 -3
  575. package/templates/nextjs-standalone/src/lib/auth/types.ts +15 -21
  576. package/templates/nextjs-standalone/src/lib/billing/enforcement.ts +368 -0
  577. package/templates/nextjs-standalone/src/lib/billing/plans.ts +155 -0
  578. package/templates/nextjs-standalone/src/lib/cache/index.ts +92 -0
  579. package/templates/nextjs-standalone/src/lib/cache/keys.ts +30 -0
  580. package/templates/nextjs-standalone/src/lib/cache/lru.ts +79 -0
  581. package/templates/nextjs-standalone/src/lib/cache/types.ts +31 -0
  582. package/templates/nextjs-standalone/src/lib/compose-refs.ts +62 -0
  583. package/templates/nextjs-standalone/src/lib/config.ts +563 -11
  584. package/templates/nextjs-standalone/src/lib/connection-types.ts +9 -0
  585. package/templates/nextjs-standalone/src/lib/conversation-types.ts +1 -25
  586. package/templates/nextjs-standalone/src/lib/conversations.ts +345 -14
  587. package/templates/nextjs-standalone/src/lib/data-table.ts +61 -0
  588. package/templates/nextjs-standalone/src/lib/db/connection.ts +793 -39
  589. package/templates/nextjs-standalone/src/lib/db/internal.ts +985 -139
  590. package/templates/nextjs-standalone/src/lib/db/migrate.ts +295 -0
  591. package/templates/nextjs-standalone/src/lib/db/migrations/0000_baseline.sql +703 -0
  592. package/templates/nextjs-standalone/src/lib/db/migrations/0001_teams_installations.sql +14 -0
  593. package/templates/nextjs-standalone/src/lib/db/migrations/0002_discord_installations.sql +14 -0
  594. package/templates/nextjs-standalone/src/lib/db/migrations/0003_telegram_installations.sql +15 -0
  595. package/templates/nextjs-standalone/src/lib/db/migrations/0004_sandbox_credentials.sql +18 -0
  596. package/templates/nextjs-standalone/src/lib/db/migrations/0005_oauth_state.sql +16 -0
  597. package/templates/nextjs-standalone/src/lib/db/migrations/0006_byot_credentials.sql +14 -0
  598. package/templates/nextjs-standalone/src/lib/db/migrations/0007_gchat_installations.sql +15 -0
  599. package/templates/nextjs-standalone/src/lib/db/migrations/0008_github_installations.sql +14 -0
  600. package/templates/nextjs-standalone/src/lib/db/migrations/0009_linear_installations.sql +15 -0
  601. package/templates/nextjs-standalone/src/lib/db/migrations/0010_whatsapp_installations.sql +14 -0
  602. package/templates/nextjs-standalone/src/lib/db/migrations/0011_email_installations.sql +16 -0
  603. package/templates/nextjs-standalone/src/lib/db/migrations/0012_region_migrations.sql +25 -0
  604. package/templates/nextjs-standalone/src/lib/db/schema.ts +1120 -0
  605. package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +89 -139
  606. package/templates/nextjs-standalone/src/lib/demo.ts +308 -0
  607. package/templates/nextjs-standalone/src/lib/discord/store.ts +225 -0
  608. package/templates/nextjs-standalone/src/lib/effect/ai.ts +243 -0
  609. package/templates/nextjs-standalone/src/lib/effect/errors.ts +234 -0
  610. package/templates/nextjs-standalone/src/lib/effect/hono.ts +454 -0
  611. package/templates/nextjs-standalone/src/lib/effect/index.ts +137 -0
  612. package/templates/nextjs-standalone/src/lib/effect/layers.ts +496 -0
  613. package/templates/nextjs-standalone/src/lib/effect/services.ts +776 -0
  614. package/templates/nextjs-standalone/src/lib/effect/sql.ts +178 -0
  615. package/templates/nextjs-standalone/src/lib/effect/toolkit.ts +123 -0
  616. package/templates/nextjs-standalone/src/lib/email/delivery.ts +232 -0
  617. package/templates/nextjs-standalone/src/lib/email/engine.ts +349 -0
  618. package/templates/nextjs-standalone/src/lib/email/hooks.ts +107 -0
  619. package/templates/nextjs-standalone/src/lib/email/index.ts +16 -0
  620. package/templates/nextjs-standalone/src/lib/email/scheduler.ts +72 -0
  621. package/templates/nextjs-standalone/src/lib/email/sequence.ts +73 -0
  622. package/templates/nextjs-standalone/src/lib/email/store.ts +163 -0
  623. package/templates/nextjs-standalone/src/lib/email/templates.ts +215 -0
  624. package/templates/nextjs-standalone/src/lib/format.test.ts +117 -0
  625. package/templates/nextjs-standalone/src/lib/format.ts +67 -0
  626. package/templates/nextjs-standalone/src/lib/gchat/store.ts +202 -0
  627. package/templates/nextjs-standalone/src/lib/github/store.ts +197 -0
  628. package/templates/nextjs-standalone/src/lib/id.ts +29 -0
  629. package/templates/nextjs-standalone/src/lib/integrations/types.ts +166 -0
  630. package/templates/nextjs-standalone/src/lib/learn/pattern-analyzer.ts +224 -0
  631. package/templates/nextjs-standalone/src/lib/learn/pattern-cache.ts +229 -0
  632. package/templates/nextjs-standalone/src/lib/learn/pattern-proposer.ts +87 -0
  633. package/templates/nextjs-standalone/src/lib/learn/suggestion-helpers.ts +34 -0
  634. package/templates/nextjs-standalone/src/lib/learn/suggestions.ts +139 -0
  635. package/templates/nextjs-standalone/src/lib/linear/store.ts +200 -0
  636. package/templates/nextjs-standalone/src/lib/logger.ts +35 -3
  637. package/templates/nextjs-standalone/src/lib/metering.ts +272 -0
  638. package/templates/nextjs-standalone/src/lib/parsers.ts +99 -0
  639. package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +13 -11
  640. package/templates/nextjs-standalone/src/lib/plugins/index.ts +3 -1
  641. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +58 -6
  642. package/templates/nextjs-standalone/src/lib/plugins/settings.ts +147 -0
  643. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +6 -9
  644. package/templates/nextjs-standalone/src/lib/profiler.ts +1665 -0
  645. package/templates/nextjs-standalone/src/lib/providers.ts +188 -13
  646. package/templates/nextjs-standalone/src/lib/rls.ts +172 -60
  647. package/templates/nextjs-standalone/src/lib/sandbox/credentials.ts +206 -0
  648. package/templates/nextjs-standalone/src/lib/sandbox/validate.ts +179 -0
  649. package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +26 -94
  650. package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +174 -34
  651. package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -150
  652. package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +190 -154
  653. package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +74 -23
  654. package/templates/nextjs-standalone/src/lib/scheduler/preview.ts +72 -0
  655. package/templates/nextjs-standalone/src/lib/security/abuse.ts +463 -0
  656. package/templates/nextjs-standalone/src/lib/semantic/diff.ts +267 -0
  657. package/templates/nextjs-standalone/src/lib/semantic/entities.ts +167 -0
  658. package/templates/nextjs-standalone/src/lib/semantic/files.ts +283 -0
  659. package/templates/nextjs-standalone/src/lib/semantic/index.ts +27 -0
  660. package/templates/nextjs-standalone/src/lib/{semantic-index.ts → semantic/search.ts} +80 -9
  661. package/templates/nextjs-standalone/src/lib/semantic/sync.ts +581 -0
  662. package/templates/nextjs-standalone/src/lib/{semantic.ts → semantic/whitelist.ts} +189 -3
  663. package/templates/nextjs-standalone/src/lib/settings.ts +817 -0
  664. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +13 -0
  665. package/templates/nextjs-standalone/src/lib/slack/store.ts +134 -25
  666. package/templates/nextjs-standalone/src/lib/startup.ts +528 -362
  667. package/templates/nextjs-standalone/src/lib/teams/store.ts +216 -0
  668. package/templates/nextjs-standalone/src/lib/telegram/store.ts +202 -0
  669. package/templates/nextjs-standalone/src/lib/telemetry.ts +40 -0
  670. package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +8 -5
  671. package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +3 -1
  672. package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +276 -93
  673. package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +2 -2
  674. package/templates/nextjs-standalone/src/lib/tools/backends/detect.ts +16 -0
  675. package/templates/nextjs-standalone/src/lib/tools/backends/index.ts +11 -0
  676. package/templates/nextjs-standalone/src/lib/tools/backends/nsjail.ts +213 -0
  677. package/templates/nextjs-standalone/src/lib/tools/backends/shared.ts +103 -0
  678. package/templates/nextjs-standalone/src/lib/tools/backends/types.ts +26 -0
  679. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +7 -228
  680. package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +4 -29
  681. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +18 -2
  682. package/templates/nextjs-standalone/src/lib/tools/explore.ts +246 -54
  683. package/templates/nextjs-standalone/src/lib/tools/index.ts +17 -0
  684. package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +11 -139
  685. package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +9 -132
  686. package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +184 -3
  687. package/templates/nextjs-standalone/src/lib/tools/python-stream.ts +33 -0
  688. package/templates/nextjs-standalone/src/lib/tools/python-wrapper.ts +129 -0
  689. package/templates/nextjs-standalone/src/lib/tools/python.ts +115 -15
  690. package/templates/nextjs-standalone/src/lib/tools/registry.ts +14 -2
  691. package/templates/nextjs-standalone/src/lib/tools/sql.ts +778 -362
  692. package/templates/nextjs-standalone/src/lib/tracing.ts +16 -0
  693. package/templates/nextjs-standalone/src/lib/whatsapp/store.ts +198 -0
  694. package/templates/nextjs-standalone/src/lib/workspace.ts +89 -0
  695. package/templates/nextjs-standalone/src/progress.ts +121 -0
  696. package/templates/nextjs-standalone/src/types/data-table.ts +48 -0
  697. package/templates/nextjs-standalone/src/ui/atlas-chat-reexport.ts +3 -0
  698. package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +26 -19
  699. package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +3 -3
  700. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +57 -39
  701. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +213 -35
  702. package/templates/nextjs-standalone/src/ui/components/admin/delivery-status-badge.tsx +53 -0
  703. package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +27 -6
  704. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -52
  705. package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +2 -2
  706. package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +28 -5
  707. package/templates/nextjs-standalone/src/ui/components/admin-content-wrapper.tsx +87 -0
  708. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +449 -166
  709. package/templates/nextjs-standalone/src/ui/components/branding-head.tsx +41 -0
  710. package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +62 -5
  711. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +316 -125
  712. package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +4 -4
  713. package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +45 -4
  714. package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +86 -5
  715. package/templates/nextjs-standalone/src/ui/components/chat/follow-up-chips.tsx +29 -0
  716. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +24 -0
  717. package/templates/nextjs-standalone/src/ui/components/chat/prompt-library.tsx +206 -0
  718. package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +106 -78
  719. package/templates/nextjs-standalone/src/ui/components/chat/result-card-base.tsx +101 -0
  720. package/templates/nextjs-standalone/src/ui/components/chat/share-dialog.tsx +377 -0
  721. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +94 -73
  722. package/templates/nextjs-standalone/src/ui/components/chat/suggestion-chips.tsx +46 -0
  723. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +16 -4
  724. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +48 -17
  725. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +38 -24
  726. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +66 -7
  727. package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +9 -2
  728. package/templates/nextjs-standalone/src/ui/components/error-boundary.tsx +66 -0
  729. package/templates/nextjs-standalone/src/ui/components/notebook/delete-cell-dialog.tsx +48 -0
  730. package/templates/nextjs-standalone/src/ui/components/notebook/fork-branch-selector.tsx +68 -0
  731. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-cell-input.tsx +76 -0
  732. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-cell-output.tsx +58 -0
  733. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-cell-toolbar.tsx +91 -0
  734. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-cell.tsx +119 -0
  735. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-empty-state.tsx +19 -0
  736. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-export.ts +287 -0
  737. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-input-bar.tsx +49 -0
  738. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-shell.tsx +266 -0
  739. package/templates/nextjs-standalone/src/ui/components/notebook/notebook-text-cell.tsx +152 -0
  740. package/templates/nextjs-standalone/src/ui/components/notebook/types.ts +39 -0
  741. package/templates/nextjs-standalone/src/ui/components/notebook/use-keyboard-nav.ts +109 -0
  742. package/templates/nextjs-standalone/src/ui/components/notebook/use-notebook.ts +684 -0
  743. package/templates/nextjs-standalone/src/ui/components/org-switcher.tsx +111 -0
  744. package/templates/nextjs-standalone/src/ui/components/region-picker.tsx +103 -0
  745. package/templates/nextjs-standalone/src/ui/components/schema-explorer/schema-explorer.tsx +522 -0
  746. package/templates/nextjs-standalone/src/ui/components/social-icons.tsx +26 -0
  747. package/templates/nextjs-standalone/src/ui/components/tour/guided-tour.tsx +81 -0
  748. package/templates/nextjs-standalone/src/ui/components/tour/index.ts +5 -0
  749. package/templates/nextjs-standalone/src/ui/components/tour/nav-bar.tsx +100 -0
  750. package/templates/nextjs-standalone/src/ui/components/tour/tour-overlay.tsx +298 -0
  751. package/templates/nextjs-standalone/src/ui/components/tour/tour-steps.ts +43 -0
  752. package/templates/nextjs-standalone/src/ui/components/tour/types.ts +21 -0
  753. package/templates/nextjs-standalone/src/ui/components/tour/use-tour.ts +193 -0
  754. package/templates/nextjs-standalone/src/ui/context-reexport.ts +3 -0
  755. package/templates/nextjs-standalone/src/ui/hooks/theme-init-script.ts +17 -0
  756. package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +38 -30
  757. package/templates/nextjs-standalone/src/ui/hooks/use-admin-mutation.ts +188 -0
  758. package/templates/nextjs-standalone/src/ui/hooks/use-atlas-transport.ts +225 -0
  759. package/templates/nextjs-standalone/src/ui/hooks/use-branding.ts +68 -0
  760. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +106 -83
  761. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +134 -10
  762. package/templates/nextjs-standalone/src/ui/hooks/use-deploy-mode.ts +36 -0
  763. package/templates/nextjs-standalone/src/ui/hooks/use-platform-admin-guard.ts +49 -0
  764. package/templates/nextjs-standalone/src/ui/lib/action-types.ts +11 -63
  765. package/templates/nextjs-standalone/src/ui/lib/admin-schemas.ts +744 -0
  766. package/templates/nextjs-standalone/src/ui/lib/fetch-client.ts +84 -0
  767. package/templates/nextjs-standalone/src/ui/lib/fetch-error.ts +54 -0
  768. package/templates/nextjs-standalone/src/ui/lib/helpers.ts +94 -1
  769. package/templates/nextjs-standalone/src/ui/lib/types.ts +149 -140
  770. package/templates/nextjs-standalone/tsconfig.json +3 -2
  771. package/templates/docker/src/api/__tests__/actions.test.ts +0 -683
  772. package/templates/docker/src/api/__tests__/admin.test.ts +0 -820
  773. package/templates/docker/src/api/__tests__/auth.test.ts +0 -165
  774. package/templates/docker/src/api/__tests__/chat.test.ts +0 -376
  775. package/templates/docker/src/api/__tests__/conversations.test.ts +0 -555
  776. package/templates/docker/src/api/__tests__/cors.test.ts +0 -135
  777. package/templates/docker/src/api/__tests__/health-plugin.test.ts +0 -176
  778. package/templates/docker/src/api/__tests__/health.test.ts +0 -283
  779. package/templates/docker/src/api/__tests__/query.test.ts +0 -891
  780. package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +0 -601
  781. package/templates/docker/src/api/__tests__/slack.test.ts +0 -847
  782. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +0 -439
  783. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +0 -131
  784. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +0 -166
  785. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +0 -516
  786. package/templates/docker/src/lib/__tests__/config-actions.test.ts +0 -166
  787. package/templates/docker/src/lib/__tests__/config.test.ts +0 -1113
  788. package/templates/docker/src/lib/__tests__/conversations.test.ts +0 -589
  789. package/templates/docker/src/lib/__tests__/errors.test.ts +0 -256
  790. package/templates/docker/src/lib/__tests__/logger.test.ts +0 -200
  791. package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +0 -321
  792. package/templates/docker/src/lib/__tests__/providers.test.ts +0 -130
  793. package/templates/docker/src/lib/__tests__/rls.test.ts +0 -435
  794. package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +0 -124
  795. package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +0 -550
  796. package/templates/docker/src/lib/__tests__/semantic-index.test.ts +0 -547
  797. package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +0 -544
  798. package/templates/docker/src/lib/__tests__/semantic.test.ts +0 -363
  799. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +0 -461
  800. package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +0 -429
  801. package/templates/docker/src/lib/__tests__/startup.test.ts +0 -470
  802. package/templates/docker/src/lib/__tests__/tracing.test.ts +0 -28
  803. package/templates/docker/src/lib/auth/__tests__/audit.test.ts +0 -418
  804. package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +0 -222
  805. package/templates/docker/src/lib/auth/__tests__/byot.test.ts +0 -366
  806. package/templates/docker/src/lib/auth/__tests__/detect.test.ts +0 -190
  807. package/templates/docker/src/lib/auth/__tests__/managed.test.ts +0 -173
  808. package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +0 -456
  809. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +0 -203
  810. package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +0 -225
  811. package/templates/docker/src/lib/auth/__tests__/server.test.ts +0 -34
  812. package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +0 -176
  813. package/templates/docker/src/lib/auth/__tests__/types.test.ts +0 -44
  814. package/templates/docker/src/lib/db/__tests__/connection.test.ts +0 -144
  815. package/templates/docker/src/lib/db/__tests__/internal.test.ts +0 -387
  816. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +0 -190
  817. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -137
  818. package/templates/docker/src/lib/db/__tests__/registry.test.ts +0 -398
  819. package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +0 -130
  820. package/templates/docker/src/lib/errors.ts +0 -154
  821. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +0 -204
  822. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +0 -529
  823. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +0 -875
  824. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +0 -373
  825. package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +0 -49
  826. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +0 -799
  827. package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +0 -192
  828. package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +0 -248
  829. package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +0 -96
  830. package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +0 -78
  831. package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +0 -78
  832. package/templates/docker/src/lib/scheduler/index.ts +0 -7
  833. package/templates/docker/src/lib/slack/__tests__/api.test.ts +0 -160
  834. package/templates/docker/src/lib/slack/__tests__/format.test.ts +0 -237
  835. package/templates/docker/src/lib/slack/__tests__/store.test.ts +0 -188
  836. package/templates/docker/src/lib/slack/__tests__/threads.test.ts +0 -112
  837. package/templates/docker/src/lib/slack/__tests__/verify.test.ts +0 -111
  838. package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +0 -594
  839. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +0 -240
  840. package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +0 -267
  841. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +0 -506
  842. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +0 -374
  843. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +0 -82
  844. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +0 -210
  845. package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +0 -515
  846. package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +0 -397
  847. package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +0 -365
  848. package/templates/docker/src/lib/tools/__tests__/python.test.ts +0 -331
  849. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +0 -132
  850. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +0 -242
  851. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +0 -227
  852. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +0 -100
  853. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +0 -227
  854. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +0 -709
  855. package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +0 -211
  856. package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +0 -378
  857. package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +0 -681
  858. package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +0 -427
  859. package/templates/docker/src/test-setup.ts +0 -38
  860. package/templates/docker/src/types/vercel-sandbox.d.ts +0 -61
  861. package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +0 -116
  862. package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +0 -683
  863. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +0 -820
  864. package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +0 -165
  865. package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +0 -376
  866. package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +0 -555
  867. package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +0 -135
  868. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +0 -176
  869. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +0 -283
  870. package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +0 -891
  871. package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +0 -601
  872. package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +0 -847
  873. package/templates/nextjs-standalone/src/app/global-error.tsx +0 -68
  874. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +0 -439
  875. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +0 -131
  876. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +0 -166
  877. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +0 -516
  878. package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +0 -166
  879. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +0 -1113
  880. package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +0 -589
  881. package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +0 -256
  882. package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +0 -200
  883. package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +0 -321
  884. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +0 -130
  885. package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +0 -435
  886. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +0 -124
  887. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +0 -550
  888. package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +0 -547
  889. package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +0 -544
  890. package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +0 -363
  891. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +0 -461
  892. package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +0 -429
  893. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +0 -470
  894. package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +0 -28
  895. package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +0 -418
  896. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +0 -222
  897. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +0 -366
  898. package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +0 -190
  899. package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +0 -173
  900. package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +0 -456
  901. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +0 -203
  902. package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +0 -225
  903. package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +0 -34
  904. package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +0 -176
  905. package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +0 -44
  906. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +0 -144
  907. package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +0 -387
  908. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +0 -190
  909. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -137
  910. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +0 -398
  911. package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +0 -130
  912. package/templates/nextjs-standalone/src/lib/errors.ts +0 -154
  913. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +0 -204
  914. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +0 -529
  915. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +0 -875
  916. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +0 -373
  917. package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +0 -49
  918. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +0 -799
  919. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +0 -192
  920. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +0 -248
  921. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +0 -96
  922. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +0 -78
  923. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +0 -78
  924. package/templates/nextjs-standalone/src/lib/scheduler/index.ts +0 -7
  925. package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +0 -160
  926. package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +0 -237
  927. package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +0 -188
  928. package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +0 -112
  929. package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +0 -111
  930. package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +0 -594
  931. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +0 -240
  932. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +0 -267
  933. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +0 -506
  934. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +0 -374
  935. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +0 -82
  936. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +0 -210
  937. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +0 -515
  938. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +0 -397
  939. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +0 -365
  940. package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +0 -331
  941. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +0 -132
  942. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +0 -242
  943. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +0 -227
  944. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +0 -100
  945. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +0 -227
  946. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +0 -709
  947. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +0 -211
  948. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +0 -378
  949. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +0 -681
  950. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +0 -427
  951. package/templates/nextjs-standalone/src/test-setup.ts +0 -38
  952. package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +0 -116
@@ -0,0 +1,1665 @@
1
+ /**
2
+ * Shared profiler library — used by the wizard API for database profiling.
3
+ *
4
+ * Contains type mapping, YAML generation, heuristics, and DB-specific
5
+ * profiling. Canonical type definitions live in @useatlas/types and are
6
+ * re-exported here for convenience.
7
+ */
8
+
9
+ import * as yaml from "js-yaml";
10
+ import type { DBType } from "@atlas/api/lib/db/connection";
11
+ import { createLogger } from "@atlas/api/lib/logger";
12
+
13
+ // Re-export canonical types so existing consumers of @atlas/api/lib/profiler
14
+ // continue to work without import path changes.
15
+ export {
16
+ OBJECT_TYPES,
17
+ FK_SOURCES,
18
+ PARTITION_STRATEGIES,
19
+ } from "@useatlas/types";
20
+ export type {
21
+ ObjectType,
22
+ ColumnProfile,
23
+ DatabaseObject,
24
+ ForeignKey,
25
+ ForeignKeySource,
26
+ PartitionStrategy,
27
+ PartitionInfo,
28
+ TableFlags,
29
+ TableProfile,
30
+ ProfileError,
31
+ ProfilingResult,
32
+ } from "@useatlas/types";
33
+
34
+ // Also import locally for use within this module's function signatures.
35
+ import type {
36
+ ColumnProfile,
37
+ DatabaseObject,
38
+ ForeignKey,
39
+ TableProfile,
40
+ ProfileError,
41
+ ProfilingResult,
42
+ } from "@useatlas/types";
43
+
44
+ /** Minimal structured logger interface — compatible with pino's (obj, msg) calling convention. */
45
+ export interface ProfileLogger {
46
+ info(obj: Record<string, unknown>, msg: string): void;
47
+ warn(obj: Record<string, unknown>, msg: string): void;
48
+ error(obj: Record<string, unknown>, msg: string): void;
49
+ }
50
+
51
+ const defaultLog: ProfileLogger = createLogger("profiler");
52
+
53
+ /** Callbacks for progress reporting during profiling. */
54
+ export interface ProfileProgressCallbacks {
55
+ onStart(total: number): void;
56
+ onTableStart(name: string, index: number, total: number): void;
57
+ onTableDone(name: string, index: number, total: number): void;
58
+ onTableError(name: string, error: string, index: number, total: number): void;
59
+ onComplete(count: number, elapsedMs: number): void;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Fatal error detection
64
+ // ---------------------------------------------------------------------------
65
+
66
+ export const FATAL_ERROR_PATTERN = /\bECONNRESET\b|\bECONNREFUSED\b|\bEHOSTUNREACH\b|\bENOTFOUND\b|\bEPIPE\b|\bETIMEDOUT\b/i;
67
+
68
+ export function isFatalConnectionError(err: unknown): boolean {
69
+ if (!(err instanceof Error)) return FATAL_ERROR_PATTERN.test(String(err));
70
+ if (FATAL_ERROR_PATTERN.test(err.message)) return true;
71
+ const code = (err as NodeJS.ErrnoException).code;
72
+ if (code && FATAL_ERROR_PATTERN.test(code)) return true;
73
+ if (err.cause) return isFatalConnectionError(err.cause);
74
+ return false;
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Failure threshold
79
+ // ---------------------------------------------------------------------------
80
+
81
+ const FAILURE_THRESHOLD = 0.2;
82
+
83
+ export function checkFailureThreshold(
84
+ result: ProfilingResult,
85
+ force: boolean
86
+ ): { shouldAbort: boolean; failureRate: number } {
87
+ if (result.errors.length === 0) return { shouldAbort: false, failureRate: 0 };
88
+ const total = result.profiles.length + result.errors.length;
89
+ const failureRate = result.errors.length / total;
90
+ return { shouldAbort: failureRate > FAILURE_THRESHOLD && !force, failureRate };
91
+ }
92
+
93
+ export function logProfilingErrors(errors: ProfileError[], total: number, log: ProfileLogger = defaultLog): void {
94
+ if (total === 0) return;
95
+ const pct = Math.round((errors.length / total) * 100);
96
+ log.warn(
97
+ { errorCount: errors.length, total, pct, tables: errors.slice(0, 5).map((e) => e.table) },
98
+ `${errors.length}/${total} tables (${pct}%) failed to profile`,
99
+ );
100
+ for (const e of errors.slice(0, 5)) {
101
+ log.warn({ table: e.table }, e.error);
102
+ }
103
+ if (errors.length > 5) {
104
+ log.warn({ remaining: errors.length - 5 }, `... and ${errors.length - 5} more`);
105
+ }
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // View helpers
110
+ // ---------------------------------------------------------------------------
111
+
112
+ export function isView(profile: TableProfile): boolean {
113
+ return profile.object_type === "view";
114
+ }
115
+
116
+ export function isMatView(profile: TableProfile): boolean {
117
+ return profile.object_type === "materialized_view";
118
+ }
119
+
120
+ export function isViewLike(profile: TableProfile): boolean {
121
+ return profile.object_type === "view" || profile.object_type === "materialized_view";
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Type mapping
126
+ // ---------------------------------------------------------------------------
127
+
128
+ export function mapSQLType(sqlType: string): string {
129
+ const unwrapped = sqlType.replace(/Nullable\((.+)\)/g, "$1").replace(/LowCardinality\((.+)\)/g, "$1");
130
+ const t = unwrapped.toLowerCase();
131
+ if (t.includes("interval") || t.includes("money")) return "string";
132
+ if (
133
+ t.includes("int") ||
134
+ t.includes("float") ||
135
+ t.includes("real") ||
136
+ t.includes("numeric") ||
137
+ t.includes("decimal") ||
138
+ t.includes("double") ||
139
+ t === "currency" ||
140
+ t === "percent" ||
141
+ t === "long"
142
+ )
143
+ return "number";
144
+ if (t.startsWith("bool")) return "boolean";
145
+ if (t.includes("date") || t.includes("time") || t.includes("timestamp"))
146
+ return "date";
147
+ return "string";
148
+ }
149
+
150
+ export function mapSalesforceFieldType(sfType: string): string {
151
+ const lower = sfType.toLowerCase();
152
+ switch (lower) {
153
+ case "int":
154
+ case "long":
155
+ return "integer";
156
+ case "double":
157
+ case "currency":
158
+ case "percent":
159
+ return "real";
160
+ case "boolean":
161
+ return "boolean";
162
+ case "date":
163
+ case "datetime":
164
+ case "time":
165
+ return "date";
166
+ default:
167
+ return "string";
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Pluralization helpers
173
+ // ---------------------------------------------------------------------------
174
+
175
+ const IRREGULAR_PLURALS: Record<string, string> = {
176
+ people: "person",
177
+ children: "child",
178
+ men: "man",
179
+ women: "woman",
180
+ mice: "mouse",
181
+ data: "datum",
182
+ criteria: "criterion",
183
+ analyses: "analysis",
184
+ };
185
+
186
+ const IRREGULAR_SINGULARS_TO_PLURALS: Record<string, string> = Object.fromEntries(
187
+ Object.entries(IRREGULAR_PLURALS).map(([plural, singular]) => [singular, plural])
188
+ );
189
+
190
+ export function pluralize(word: string): string {
191
+ const lower = word.toLowerCase();
192
+ if (IRREGULAR_SINGULARS_TO_PLURALS[lower]) return IRREGULAR_SINGULARS_TO_PLURALS[lower];
193
+ if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower))
194
+ return word.slice(0, -1) + "ies";
195
+ if (lower.endsWith("s") || lower.endsWith("x") || lower.endsWith("z") || lower.endsWith("sh") || lower.endsWith("ch"))
196
+ return word + "es";
197
+ return word + "s";
198
+ }
199
+
200
+ export function singularize(word: string): string {
201
+ const lower = word.toLowerCase();
202
+ if (IRREGULAR_PLURALS[lower]) return IRREGULAR_PLURALS[lower];
203
+ if (lower.endsWith("ies")) return word.slice(0, -3) + "y";
204
+ if (lower.endsWith("ses") || lower.endsWith("xes") || lower.endsWith("zes"))
205
+ return word.slice(0, -2);
206
+ if (lower.endsWith("s") && !lower.endsWith("ss") && !lower.endsWith("us") && !lower.endsWith("is")) return word.slice(0, -1);
207
+ return word;
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Profiler heuristics (pure functions on TableProfile[])
212
+ // ---------------------------------------------------------------------------
213
+
214
+ export function inferForeignKeys(profiles: TableProfile[]): void {
215
+ const tableMap = new Map(
216
+ profiles.filter((p) => !isViewLike(p)).map((p) => [p.table_name, p])
217
+ );
218
+
219
+ for (const profile of profiles) {
220
+ if (isViewLike(profile)) continue;
221
+
222
+ const constrainedCols = new Set(profile.foreign_keys.map((fk) => fk.from_column));
223
+
224
+ for (const col of profile.columns) {
225
+ if (!col.name.endsWith("_id")) continue;
226
+ if (constrainedCols.has(col.name)) continue;
227
+ if (col.is_primary_key) continue;
228
+
229
+ const prefix = col.name.slice(0, -3);
230
+ if (!prefix) continue;
231
+
232
+ const candidates = [prefix, pluralize(prefix), singularize(prefix)];
233
+ let targetTable: TableProfile | undefined;
234
+ for (const candidate of candidates) {
235
+ targetTable = tableMap.get(candidate);
236
+ if (targetTable) break;
237
+ }
238
+
239
+ if (!targetTable) continue;
240
+
241
+ const hasPkId = targetTable.primary_key_columns.includes("id");
242
+ if (!hasPkId) continue;
243
+
244
+ const inferredFK: ForeignKey = {
245
+ from_column: col.name,
246
+ to_table: targetTable.table_name,
247
+ to_column: "id",
248
+ source: "inferred",
249
+ };
250
+
251
+ profile.inferred_foreign_keys.push(inferredFK);
252
+
253
+ col.profiler_notes.push(
254
+ `Likely FK to ${targetTable.table_name}.id (inferred from column name, no constraint exists)`
255
+ );
256
+ }
257
+ }
258
+ }
259
+
260
+ const ABANDONED_NAME_PATTERNS = [
261
+ /^old_/,
262
+ /^temp_/,
263
+ /^legacy_/,
264
+ /_legacy$/,
265
+ /_backup$/,
266
+ /_archive$/,
267
+ /_v\d+$/,
268
+ ];
269
+
270
+ export function detectAbandonedTables(profiles: TableProfile[]): void {
271
+ const referencedTables = new Set<string>();
272
+ for (const p of profiles) {
273
+ for (const fk of p.foreign_keys) referencedTables.add(fk.to_table);
274
+ for (const fk of p.inferred_foreign_keys) referencedTables.add(fk.to_table);
275
+ }
276
+
277
+ for (const profile of profiles) {
278
+ if (isViewLike(profile)) continue;
279
+
280
+ const nameMatches = ABANDONED_NAME_PATTERNS.some((pat) =>
281
+ pat.test(profile.table_name)
282
+ );
283
+ if (!nameMatches) continue;
284
+
285
+ const hasInboundFKs = referencedTables.has(profile.table_name);
286
+ if (hasInboundFKs) continue;
287
+
288
+ profile.table_flags.possibly_abandoned = true;
289
+ profile.profiler_notes.push(
290
+ `Possibly abandoned: name matches legacy/temp pattern and no other tables reference it`
291
+ );
292
+ }
293
+ }
294
+
295
+ export function detectEnumInconsistency(profiles: TableProfile[]): void {
296
+ for (const profile of profiles) {
297
+ for (const col of profile.columns) {
298
+ if (!col.is_enum_like) continue;
299
+ if (col.sample_values.length === 0) continue;
300
+
301
+ const groups = new Map<string, string[]>();
302
+ for (const val of col.sample_values) {
303
+ const lower = val.toLowerCase();
304
+ const existing = groups.get(lower) ?? [];
305
+ existing.push(val);
306
+ groups.set(lower, existing);
307
+ }
308
+
309
+ const inconsistencies: string[] = [];
310
+ for (const [, originals] of groups) {
311
+ if (originals.length > 1) {
312
+ inconsistencies.push(originals.join(", "));
313
+ }
314
+ }
315
+
316
+ if (inconsistencies.length > 0) {
317
+ col.profiler_notes.push(
318
+ `Case-inconsistent enum values: [${inconsistencies.join("; ")}]. Consider using LOWER() for grouping`
319
+ );
320
+ }
321
+ }
322
+ }
323
+ }
324
+
325
+ const DENORMALIZED_NAME_PATTERNS = [
326
+ /_denormalized$/,
327
+ /_cache$/,
328
+ /_summary$/,
329
+ /_stats$/,
330
+ /_rollup$/,
331
+ ];
332
+
333
+ export function detectDenormalizedTables(profiles: TableProfile[]): void {
334
+ for (const profile of profiles) {
335
+ if (isViewLike(profile)) continue;
336
+
337
+ const nameMatches = DENORMALIZED_NAME_PATTERNS.some((pat) =>
338
+ pat.test(profile.table_name)
339
+ );
340
+ if (!nameMatches) continue;
341
+
342
+ profile.table_flags.possibly_denormalized = true;
343
+ profile.profiler_notes.push(
344
+ `Possibly denormalized/materialized table: name matches reporting pattern. Data may duplicate other tables`
345
+ );
346
+ }
347
+ }
348
+
349
+ export function analyzeTableProfiles(profiles: readonly TableProfile[]): TableProfile[] {
350
+ // Create fresh copies with reset analysis fields (no mutation of input).
351
+ // Deep-clone foreign_keys and partition_info to fully isolate from input.
352
+ const analyzed: TableProfile[] = profiles.map((p) => ({
353
+ ...p,
354
+ foreign_keys: p.foreign_keys.map((fk) => ({ ...fk })),
355
+ inferred_foreign_keys: [],
356
+ profiler_notes: [],
357
+ table_flags: { possibly_abandoned: false, possibly_denormalized: false },
358
+ columns: p.columns.map((col) => ({ ...col, profiler_notes: [] })),
359
+ partition_info: p.partition_info
360
+ ? { ...p.partition_info, children: [...p.partition_info.children] }
361
+ : undefined,
362
+ }));
363
+
364
+ inferForeignKeys(analyzed);
365
+ detectAbandonedTables(analyzed);
366
+ detectEnumInconsistency(analyzed);
367
+ detectDenormalizedTables(analyzed);
368
+
369
+ return analyzed;
370
+ }
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Entity name helper
374
+ // ---------------------------------------------------------------------------
375
+
376
+ export function entityName(tableName: string): string {
377
+ return tableName
378
+ .split("_")
379
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
380
+ .join("");
381
+ }
382
+
383
+ // ---------------------------------------------------------------------------
384
+ // YAML generation
385
+ // ---------------------------------------------------------------------------
386
+
387
+ export function generateEntityYAML(
388
+ profile: TableProfile,
389
+ allProfiles: TableProfile[],
390
+ dbType: DBType,
391
+ schema: string = "public",
392
+ source?: string,
393
+ ): string {
394
+ const name = entityName(profile.table_name);
395
+ const qualifiedTable = schema !== "public" && schema !== "main" ? `${schema}.${profile.table_name}` : profile.table_name;
396
+
397
+ // Build dimensions
398
+ const dimensions: Record<string, unknown>[] = profile.columns.map((col) => {
399
+ const dim: Record<string, unknown> = {
400
+ name: col.name,
401
+ sql: col.name,
402
+ type: dbType === "salesforce" ? mapSalesforceFieldType(col.type) : mapSQLType(col.type),
403
+ };
404
+
405
+ if (col.is_primary_key) {
406
+ dim.description = `Primary key`;
407
+ dim.primary_key = true;
408
+ } else if (col.is_foreign_key) {
409
+ dim.description = `Foreign key to ${col.fk_target_table}`;
410
+ }
411
+
412
+ if (col.unique_count !== null) dim.unique_count = col.unique_count;
413
+ if (col.null_count !== null && col.null_count > 0)
414
+ dim.null_count = col.null_count;
415
+ if (col.sample_values.length > 0) {
416
+ dim.sample_values = col.is_enum_like
417
+ ? col.sample_values
418
+ : col.sample_values.slice(0, 8);
419
+ }
420
+
421
+ return dim;
422
+ });
423
+
424
+ // Build virtual dimensions
425
+ const virtualDims: Record<string, unknown>[] = [];
426
+ for (const col of profile.columns) {
427
+ if (col.is_primary_key || col.is_foreign_key) continue;
428
+ const mappedType = dbType === "salesforce" ? mapSalesforceFieldType(col.type) : mapSQLType(col.type);
429
+
430
+ if (mappedType === "number" && !col.name.endsWith("_id") && dbType !== "salesforce") {
431
+ const label = col.name.replace(/_/g, " ");
432
+ if (dbType === "mysql") {
433
+ virtualDims.push({
434
+ name: `${col.name}_bucket`,
435
+ sql: `CASE\n WHEN ${col.name} IS NULL THEN 'Unknown'\n WHEN ${col.name} < (SELECT AVG(${col.name}) * 0.5 FROM ${qualifiedTable}) THEN 'Low'\n WHEN ${col.name} < (SELECT AVG(${col.name}) * 1.5 FROM ${qualifiedTable}) THEN 'Medium'\n ELSE 'High'\nEND`,
436
+ type: "string",
437
+ description: `${label} bucketed into Low/Medium/High`,
438
+ virtual: true,
439
+ sample_values: ["Low", "Medium", "High"],
440
+ });
441
+ } else if (dbType === "clickhouse") {
442
+ virtualDims.push({
443
+ name: `${col.name}_bucket`,
444
+ sql: `CASE\n WHEN ${col.name} < (SELECT quantile(0.33)(${col.name}) FROM ${qualifiedTable}) THEN 'Low'\n WHEN ${col.name} < (SELECT quantile(0.66)(${col.name}) FROM ${qualifiedTable}) THEN 'Medium'\n ELSE 'High'\nEND`,
445
+ type: "string",
446
+ description: `${label} bucketed into Low/Medium/High terciles`,
447
+ virtual: true,
448
+ sample_values: ["Low", "Medium", "High"],
449
+ });
450
+ } else {
451
+ virtualDims.push({
452
+ name: `${col.name}_bucket`,
453
+ sql: `CASE\n WHEN ${col.name} < (SELECT PERCENTILE_CONT(0.33) WITHIN GROUP (ORDER BY ${col.name}) FROM ${qualifiedTable}) THEN 'Low'\n WHEN ${col.name} < (SELECT PERCENTILE_CONT(0.66) WITHIN GROUP (ORDER BY ${col.name}) FROM ${qualifiedTable}) THEN 'Medium'\n ELSE 'High'\nEND`,
454
+ type: "string",
455
+ description: `${label} bucketed into Low/Medium/High terciles`,
456
+ virtual: true,
457
+ sample_values: ["Low", "Medium", "High"],
458
+ });
459
+ }
460
+ }
461
+
462
+ if (mappedType === "date") {
463
+ if (dbType === "mysql") {
464
+ virtualDims.push({
465
+ name: `${col.name}_year`,
466
+ sql: `YEAR(${col.name})`,
467
+ type: "number",
468
+ description: `Year extracted from ${col.name}`,
469
+ virtual: true,
470
+ });
471
+ virtualDims.push({
472
+ name: `${col.name}_month`,
473
+ sql: `DATE_FORMAT(${col.name}, '%Y-%m')`,
474
+ type: "string",
475
+ description: `Year-month extracted from ${col.name}`,
476
+ virtual: true,
477
+ });
478
+ } else if (dbType === "clickhouse") {
479
+ virtualDims.push({
480
+ name: `${col.name}_year`,
481
+ sql: `toYear(${col.name})`,
482
+ type: "number",
483
+ description: `Year extracted from ${col.name}`,
484
+ virtual: true,
485
+ });
486
+ virtualDims.push({
487
+ name: `${col.name}_month`,
488
+ sql: `formatDateTime(${col.name}, '%Y-%m')`,
489
+ type: "string",
490
+ description: `Year-month extracted from ${col.name}`,
491
+ virtual: true,
492
+ });
493
+ } else if (dbType === "salesforce") {
494
+ virtualDims.push({
495
+ name: `${col.name}_year`,
496
+ sql: `CALENDAR_YEAR(${col.name})`,
497
+ type: "number",
498
+ description: `Year extracted from ${col.name}`,
499
+ virtual: true,
500
+ });
501
+ virtualDims.push({
502
+ name: `${col.name}_month`,
503
+ sql: `CALENDAR_MONTH(${col.name})`,
504
+ type: "number",
505
+ description: `Month extracted from ${col.name}`,
506
+ virtual: true,
507
+ });
508
+ } else {
509
+ virtualDims.push({
510
+ name: `${col.name}_year`,
511
+ sql: `EXTRACT(YEAR FROM ${col.name})`,
512
+ type: "number",
513
+ description: `Year extracted from ${col.name}`,
514
+ virtual: true,
515
+ });
516
+ virtualDims.push({
517
+ name: `${col.name}_month`,
518
+ sql: `TO_CHAR(${col.name}, 'YYYY-MM')`,
519
+ type: "string",
520
+ description: `Year-month extracted from ${col.name}`,
521
+ virtual: true,
522
+ });
523
+ }
524
+ }
525
+ }
526
+
527
+ // Profiler notes on dimensions
528
+ for (const dim of dimensions) {
529
+ const col = profile.columns.find((c) => c.name === dim.name);
530
+ if (col?.profiler_notes && col.profiler_notes.length > 0) {
531
+ dim.profiler_notes = col.profiler_notes;
532
+ }
533
+ }
534
+
535
+ // Build joins from constraint FKs
536
+ const joins: Record<string, unknown>[] = profile.foreign_keys.map((fk) => ({
537
+ target_entity: entityName(fk.to_table),
538
+ relationship: "many_to_one",
539
+ join_columns: {
540
+ from: fk.from_column,
541
+ to: fk.to_column,
542
+ },
543
+ description: `Each ${singularize(profile.table_name)} belongs to one ${singularize(fk.to_table)}`,
544
+ }));
545
+
546
+ // Add inferred joins
547
+ for (const fk of profile.inferred_foreign_keys) {
548
+ joins.push({
549
+ target_entity: entityName(fk.to_table),
550
+ relationship: "many_to_one",
551
+ join_columns: {
552
+ from: fk.from_column,
553
+ to: fk.to_column,
554
+ },
555
+ inferred: true,
556
+ note: `No FK constraint exists — inferred from column name ${fk.from_column}`,
557
+ description: `Each ${singularize(profile.table_name)} likely belongs to one ${singularize(fk.to_table)}`,
558
+ });
559
+ }
560
+
561
+ // Build measures (skip for views/matviews)
562
+ const measures: Record<string, unknown>[] = [];
563
+
564
+ if (!isViewLike(profile)) {
565
+ const pkCol = profile.columns.find((c) => c.is_primary_key);
566
+ if (pkCol) {
567
+ measures.push({
568
+ name: `${singularize(profile.table_name)}_count`,
569
+ sql: pkCol.name,
570
+ type: "count_distinct",
571
+ });
572
+ }
573
+
574
+ for (const col of profile.columns) {
575
+ if (col.is_primary_key || col.is_foreign_key) continue;
576
+ if (col.name.endsWith("_id")) continue;
577
+ const mappedType = mapSQLType(col.type);
578
+ if (mappedType !== "number") continue;
579
+
580
+ measures.push({
581
+ name: `total_${col.name}`,
582
+ sql: col.name,
583
+ type: "sum",
584
+ description: `Sum of ${col.name.replace(/_/g, " ")}`,
585
+ });
586
+ measures.push({
587
+ name: `avg_${col.name}`,
588
+ sql: col.name,
589
+ type: "avg",
590
+ description: `Average ${col.name.replace(/_/g, " ")}`,
591
+ });
592
+ }
593
+ }
594
+
595
+ // Build use_cases
596
+ const useCases: string[] = [];
597
+
598
+ if (isView(profile)) {
599
+ useCases.push(`This is a database view — it may encapsulate complex joins or aggregations. Query it directly rather than recreating its logic`);
600
+ }
601
+
602
+ if (isMatView(profile)) {
603
+ useCases.push(`WARNING: This is a materialized view — data may be stale. Check with the user about refresh frequency before relying on real-time accuracy`);
604
+ if (profile.matview_populated === false) {
605
+ useCases.push(`WARNING: This materialized view has never been refreshed and contains no data`);
606
+ }
607
+ }
608
+
609
+ if (profile.partition_info) {
610
+ useCases.push(`This table is partitioned by ${profile.partition_info.strategy} on (${profile.partition_info.key}). Always include ${profile.partition_info.key} in WHERE clauses for optimal query performance`);
611
+ }
612
+
613
+ if (profile.table_flags.possibly_abandoned) {
614
+ useCases.push(`WARNING: This table appears to be abandoned/legacy. Verify with the user before querying`);
615
+ }
616
+ if (profile.table_flags.possibly_denormalized) {
617
+ useCases.push(`WARNING: This is a denormalized/materialized table. Data may be stale or duplicate other tables`);
618
+ }
619
+
620
+ const enumCols = profile.columns.filter((c) => c.is_enum_like);
621
+ const numericCols = profile.columns.filter(
622
+ (c) =>
623
+ mapSQLType(c.type) === "number" && !c.is_primary_key && !c.is_foreign_key && !c.name.endsWith("_id")
624
+ );
625
+ const dateCols = profile.columns.filter(
626
+ (c) => mapSQLType(c.type) === "date"
627
+ );
628
+
629
+ if (enumCols.length > 0)
630
+ useCases.push(
631
+ `Use for segmentation analysis by ${enumCols.map((c) => c.name).join(", ")}`
632
+ );
633
+ if (numericCols.length > 0)
634
+ useCases.push(
635
+ `Use for aggregation and trends on ${numericCols.map((c) => c.name).join(", ")}`
636
+ );
637
+ if (dateCols.length > 0)
638
+ useCases.push(`Use for time-series analysis using ${dateCols.map((c) => c.name).join(", ")}`);
639
+
640
+ const allFKs = [...profile.foreign_keys, ...profile.inferred_foreign_keys];
641
+ if (joins.length > 0) {
642
+ const targets = allFKs.map((fk) => fk.to_table);
643
+ const uniqueTargets = [...new Set(targets)];
644
+ useCases.push(
645
+ `Join with ${uniqueTargets.join(", ")} for cross-entity analysis`
646
+ );
647
+ }
648
+ const tablesPointingHere = allProfiles.filter((p) =>
649
+ [...p.foreign_keys, ...p.inferred_foreign_keys].some((fk) => fk.to_table === profile.table_name)
650
+ );
651
+ if (tablesPointingHere.length > 0) {
652
+ useCases.push(
653
+ `Avoid for row-level ${tablesPointingHere.map((p) => p.table_name).join("/")} queries — use those entities directly`
654
+ );
655
+ }
656
+ if (useCases.length === 0) {
657
+ useCases.push(`Use for querying ${profile.table_name} data`);
658
+ }
659
+
660
+ // Build query patterns (skip for views/matviews)
661
+ const queryPatterns: Record<string, unknown>[] = [];
662
+
663
+ if (!isViewLike(profile)) {
664
+ for (const col of enumCols.slice(0, 2)) {
665
+ queryPatterns.push({
666
+ description: `${entityName(profile.table_name)} by ${col.name}`,
667
+ sql: `SELECT ${col.name}, COUNT(*) as count\nFROM ${qualifiedTable}\nGROUP BY ${col.name}\nORDER BY count DESC`,
668
+ });
669
+ }
670
+
671
+ if (numericCols.length > 0 && enumCols.length > 0) {
672
+ const numCol = numericCols[0];
673
+ const enumCol = enumCols[0];
674
+ queryPatterns.push({
675
+ description: `Total ${numCol.name} by ${enumCol.name}`,
676
+ sql: `SELECT ${enumCol.name}, SUM(${numCol.name}) as total_${numCol.name}, COUNT(*) as count\nFROM ${qualifiedTable}\nGROUP BY ${enumCol.name}\nORDER BY total_${numCol.name} DESC`,
677
+ });
678
+ }
679
+ }
680
+
681
+ // Build description
682
+ let description: string;
683
+ if (isMatView(profile)) {
684
+ description = `Materialized view: ${profile.table_name} (${profile.row_count.toLocaleString()} rows). Contains ${profile.columns.length} columns.`;
685
+ } else if (isView(profile)) {
686
+ description = `Database view: ${profile.table_name} (${profile.row_count.toLocaleString()} rows). Contains ${profile.columns.length} columns.`;
687
+ } else {
688
+ description = `Auto-profiled schema for ${profile.table_name} (${profile.row_count.toLocaleString()} rows). Contains ${profile.columns.length} columns${allFKs.length > 0 ? `, linked to ${[...new Set(allFKs.map((fk) => fk.to_table))].join(", ")}` : ""}.`;
689
+ }
690
+ if (profile.table_flags.possibly_abandoned) {
691
+ description += ` POSSIBLY ABANDONED — name matches legacy/temp pattern and no tables reference it.`;
692
+ }
693
+ if (profile.table_flags.possibly_denormalized) {
694
+ description += ` DENORMALIZED — data may duplicate other tables.`;
695
+ }
696
+
697
+ // Entity type
698
+ let entityType: string;
699
+ if (isMatView(profile)) {
700
+ entityType = "materialized_view";
701
+ } else if (isView(profile)) {
702
+ entityType = "view";
703
+ } else {
704
+ entityType = "fact_table";
705
+ }
706
+
707
+ // Assemble entity
708
+ const entity: Record<string, unknown> = {
709
+ name,
710
+ type: entityType,
711
+ table: qualifiedTable,
712
+ ...(source ? { connection: source } : {}),
713
+ grain: isMatView(profile)
714
+ ? `one row per result from ${profile.table_name} materialized view`
715
+ : isViewLike(profile)
716
+ ? `one row per result from ${profile.table_name} view`
717
+ : `one row per ${singularize(profile.table_name).replace(/_/g, " ")} record`,
718
+ description,
719
+ dimensions: [...dimensions, ...virtualDims],
720
+ };
721
+
722
+ if (profile.partition_info) {
723
+ entity.partitioned = true;
724
+ entity.partition_strategy = profile.partition_info.strategy;
725
+ entity.partition_key = profile.partition_info.key;
726
+ }
727
+
728
+ if (measures.length > 0) entity.measures = measures;
729
+ if (joins.length > 0) entity.joins = joins;
730
+ entity.use_cases = useCases;
731
+ if (queryPatterns.length > 0) entity.query_patterns = queryPatterns;
732
+
733
+ if (profile.profiler_notes.length > 0) {
734
+ entity.profiler_notes = profile.profiler_notes;
735
+ }
736
+
737
+ return yaml.dump(entity, { lineWidth: 120, noRefs: true });
738
+ }
739
+
740
+ export function generateCatalogYAML(profiles: TableProfile[]): string {
741
+ const catalog: Record<string, unknown> = {
742
+ version: "1.0",
743
+ entities: profiles.map((p) => {
744
+ const enumCols = p.columns.filter((c) => c.is_enum_like);
745
+ const numericCols = p.columns.filter(
746
+ (c) =>
747
+ mapSQLType(c.type) === "number" && !c.is_primary_key && !c.is_foreign_key && !c.name.endsWith("_id")
748
+ );
749
+
750
+ const useFor: string[] = [];
751
+ if (enumCols.length > 0) {
752
+ useFor.push(
753
+ `Segmentation by ${enumCols.map((c) => c.name).join(", ")}`
754
+ );
755
+ }
756
+ if (numericCols.length > 0) {
757
+ useFor.push(
758
+ `Aggregation on ${numericCols.map((c) => c.name).join(", ")}`
759
+ );
760
+ }
761
+ const allFKs = [...p.foreign_keys, ...p.inferred_foreign_keys];
762
+ if (allFKs.length > 0) {
763
+ useFor.push(
764
+ `Cross-entity analysis via ${[...new Set(allFKs.map((fk) => fk.to_table))].join(", ")}`
765
+ );
766
+ }
767
+ if (useFor.length === 0) {
768
+ useFor.push(`General queries on ${p.table_name}`);
769
+ }
770
+
771
+ const questions: string[] = [];
772
+ for (const col of enumCols.slice(0, 2)) {
773
+ questions.push(
774
+ `How many ${p.table_name} by ${col.name}?`
775
+ );
776
+ }
777
+ if (numericCols.length > 0) {
778
+ questions.push(
779
+ `What is the average ${numericCols[0].name} across ${p.table_name}?`
780
+ );
781
+ }
782
+ if (allFKs.length > 0) {
783
+ const fk = allFKs[0];
784
+ questions.push(
785
+ `How are ${p.table_name} distributed across ${fk.to_table}?`
786
+ );
787
+ }
788
+ if (questions.length === 0) {
789
+ questions.push(`What data is in ${p.table_name}?`);
790
+ }
791
+
792
+ const entryIsMatView = isMatView(p);
793
+ const entryIsViewLike = isViewLike(p);
794
+
795
+ let catalogDesc: string;
796
+ if (entryIsMatView) {
797
+ catalogDesc = `${p.table_name} [materialized view] (${p.row_count.toLocaleString()} rows, ${p.columns.length} columns)`;
798
+ } else if (isView(p)) {
799
+ catalogDesc = `${p.table_name} [view] (${p.row_count.toLocaleString()} rows, ${p.columns.length} columns)`;
800
+ } else {
801
+ catalogDesc = `${p.table_name} (${p.row_count.toLocaleString()} rows, ${p.columns.length} columns)`;
802
+ }
803
+ if (p.partition_info) {
804
+ catalogDesc += ` [partitioned by ${p.partition_info.strategy}]`;
805
+ }
806
+
807
+ return {
808
+ name: entityName(p.table_name),
809
+ file: `entities/${p.table_name}.yml`,
810
+ grain: entryIsMatView
811
+ ? `one row per result from ${p.table_name} materialized view`
812
+ : entryIsViewLike
813
+ ? `one row per result from ${p.table_name} view`
814
+ : `one row per ${singularize(p.table_name).replace(/_/g, " ")} record`,
815
+ description: catalogDesc,
816
+ use_for: useFor,
817
+ common_questions: questions,
818
+ };
819
+ }),
820
+ glossary: "glossary.yml",
821
+ };
822
+
823
+ const tablesWithNumericCols = profiles.filter((p) =>
824
+ !isViewLike(p) &&
825
+ p.columns.some(
826
+ (c) =>
827
+ mapSQLType(c.type) === "number" && !c.is_primary_key && !c.is_foreign_key && !c.name.endsWith("_id")
828
+ )
829
+ );
830
+ if (tablesWithNumericCols.length > 0) {
831
+ catalog.metrics = tablesWithNumericCols.map((p) => ({
832
+ file: `metrics/${p.table_name}.yml`,
833
+ description: `Auto-generated metrics for ${p.table_name}`,
834
+ }));
835
+ }
836
+
837
+ const flaggedTables: { table: string; issues: string[] }[] = [];
838
+ for (const p of profiles) {
839
+ const issues: string[] = [];
840
+ if (p.table_flags.possibly_abandoned) issues.push("possibly_abandoned");
841
+ if (p.table_flags.possibly_denormalized) issues.push("possibly_denormalized");
842
+ if (p.inferred_foreign_keys.length > 0) issues.push("missing_fk_constraints");
843
+ const hasEnumIssues = p.columns.some((c) =>
844
+ c.profiler_notes.some((n) => n.startsWith("Case-inconsistent"))
845
+ );
846
+ if (hasEnumIssues) issues.push("inconsistent_enums");
847
+ if (issues.length > 0) flaggedTables.push({ table: p.table_name, issues });
848
+ }
849
+ if (flaggedTables.length > 0) {
850
+ catalog.tech_debt = flaggedTables;
851
+ }
852
+
853
+ return yaml.dump(catalog, { lineWidth: 120, noRefs: true });
854
+ }
855
+
856
+ export function generateMetricYAML(profile: TableProfile, schema: string = "public"): string | null {
857
+ if (isViewLike(profile)) return null;
858
+
859
+ const numericCols = profile.columns.filter(
860
+ (c) =>
861
+ mapSQLType(c.type) === "number" &&
862
+ !c.is_primary_key &&
863
+ !c.is_foreign_key &&
864
+ !c.name.endsWith("_id")
865
+ );
866
+
867
+ if (numericCols.length === 0) return null;
868
+
869
+ const pkCol = profile.columns.find((c) => c.is_primary_key);
870
+ const enumCols = profile.columns.filter((c) => c.is_enum_like);
871
+ const qualifiedTable = schema !== "public" ? `${schema}.${profile.table_name}` : profile.table_name;
872
+
873
+ const metrics: Record<string, unknown>[] = [];
874
+
875
+ if (pkCol) {
876
+ metrics.push({
877
+ id: `${profile.table_name}_count`,
878
+ label: `Total ${entityName(profile.table_name)}`,
879
+ description: `Count of distinct ${profile.table_name} records.`,
880
+ type: "atomic",
881
+ sql: `SELECT COUNT(DISTINCT ${pkCol.name}) as count\nFROM ${qualifiedTable}`,
882
+ aggregation: "count_distinct",
883
+ });
884
+ }
885
+
886
+ for (const col of numericCols) {
887
+ metrics.push({
888
+ id: `total_${col.name}`,
889
+ label: `Total ${col.name.replace(/_/g, " ")}`,
890
+ description: `Sum of ${col.name} across all ${profile.table_name}.`,
891
+ type: "atomic",
892
+ source: {
893
+ entity: entityName(profile.table_name),
894
+ measure: `total_${col.name}`,
895
+ },
896
+ sql: `SELECT SUM(${col.name}) as total_${col.name}\nFROM ${qualifiedTable}`,
897
+ aggregation: "sum",
898
+ objective: "maximize",
899
+ });
900
+
901
+ metrics.push({
902
+ id: `avg_${col.name}`,
903
+ label: `Average ${col.name.replace(/_/g, " ")}`,
904
+ description: `Average ${col.name} per ${singularize(profile.table_name)}.`,
905
+ type: "atomic",
906
+ sql: `SELECT AVG(${col.name}) as avg_${col.name}\nFROM ${qualifiedTable}`,
907
+ aggregation: "avg",
908
+ });
909
+
910
+ if (enumCols.length > 0) {
911
+ const enumCol = enumCols[0];
912
+ metrics.push({
913
+ id: `${col.name}_by_${enumCol.name}`,
914
+ label: `${col.name.replace(/_/g, " ")} by ${enumCol.name}`,
915
+ description: `${col.name} broken down by ${enumCol.name}.`,
916
+ type: "atomic",
917
+ sql: `SELECT ${enumCol.name}, SUM(${col.name}) as total_${col.name}, AVG(${col.name}) as avg_${col.name}, COUNT(*) as count\nFROM ${qualifiedTable}\nGROUP BY ${enumCol.name}\nORDER BY total_${col.name} DESC`,
918
+ });
919
+ }
920
+ }
921
+
922
+ return yaml.dump({ metrics }, { lineWidth: 120, noRefs: true });
923
+ }
924
+
925
+ export function generateGlossaryYAML(profiles: TableProfile[]): string {
926
+ const terms: Record<string, unknown> = {};
927
+
928
+ const columnToTables = new Map<string, string[]>();
929
+ for (const p of profiles) {
930
+ for (const col of p.columns) {
931
+ if (col.is_primary_key || col.is_foreign_key) continue;
932
+ const existing = columnToTables.get(col.name) ?? [];
933
+ existing.push(p.table_name);
934
+ columnToTables.set(col.name, existing);
935
+ }
936
+ }
937
+
938
+ for (const [colName, tables] of columnToTables) {
939
+ if (tables.length > 1) {
940
+ terms[colName] = {
941
+ status: "ambiguous",
942
+ note: `"${colName}" appears in multiple tables: ${tables.join(", ")}. ASK the user which table they mean.`,
943
+ possible_mappings: tables.map((t) => `${t}.${colName}`),
944
+ };
945
+ }
946
+ }
947
+
948
+ for (const p of profiles) {
949
+ for (const fk of p.foreign_keys) {
950
+ const termName = fk.from_column.replace(/_id$/, "");
951
+ if (!terms[termName]) {
952
+ terms[termName] = {
953
+ status: "defined",
954
+ definition: `Refers to the ${fk.to_table} entity. Linked via ${p.table_name}.${fk.from_column} → ${fk.to_table}.${fk.to_column}.`,
955
+ };
956
+ }
957
+ }
958
+ }
959
+
960
+ for (const p of profiles) {
961
+ for (const col of p.columns) {
962
+ if (col.is_enum_like && !terms[col.name]) {
963
+ terms[col.name] = {
964
+ status: "defined",
965
+ definition: `Categorical field on ${p.table_name}. Possible values: ${col.sample_values.join(", ")}.`,
966
+ };
967
+ }
968
+ }
969
+ }
970
+
971
+ for (const p of profiles) {
972
+ for (const col of p.columns) {
973
+ if (!col.is_enum_like) continue;
974
+ const inconsistencyNote = col.profiler_notes.find((n) =>
975
+ n.startsWith("Case-inconsistent")
976
+ );
977
+ if (!inconsistencyNote) continue;
978
+
979
+ const termKey = `${p.table_name}.${col.name}`;
980
+ terms[termKey] = {
981
+ status: "ambiguous",
982
+ note: `${col.name} on ${p.table_name} has case-inconsistent values. Use LOWER(${col.name}) when grouping or filtering.`,
983
+ guidance: `Always wrap in LOWER() for reliable aggregation: GROUP BY LOWER(${col.name})`,
984
+ };
985
+ }
986
+ }
987
+
988
+ if (Object.keys(terms).length === 0) {
989
+ terms["example_term"] = {
990
+ status: "defined",
991
+ definition: "Replace this with your own business terms",
992
+ };
993
+ }
994
+
995
+ return yaml.dump({ terms }, { lineWidth: 120, noRefs: true });
996
+ }
997
+
998
+ // ---------------------------------------------------------------------------
999
+ // Output directory helpers
1000
+ // ---------------------------------------------------------------------------
1001
+
1002
+ import * as path from "path";
1003
+
1004
+ const SEMANTIC_DIR = path.resolve("semantic");
1005
+
1006
+ export function outputDirForDatasource(id: string, orgId?: string): string {
1007
+ const base = orgId ? path.join(SEMANTIC_DIR, ".orgs", orgId) : SEMANTIC_DIR;
1008
+ return id === "default" ? base : path.join(base, id);
1009
+ }
1010
+
1011
+ // ---------------------------------------------------------------------------
1012
+ // PostgreSQL profiler — list objects and profile tables
1013
+ // ---------------------------------------------------------------------------
1014
+
1015
+ export async function listPostgresObjects(connectionString: string, schema: string = "public", log: ProfileLogger = defaultLog): Promise<DatabaseObject[]> {
1016
+ const { Pool } = await import("pg");
1017
+ const pool = new Pool({ connectionString, max: 1, connectionTimeoutMillis: 5000 });
1018
+ try {
1019
+ const result = await pool.query(
1020
+ `SELECT table_name, table_type FROM information_schema.tables
1021
+ WHERE table_schema = $1 AND table_type IN ('BASE TABLE', 'VIEW')
1022
+ ORDER BY table_name`,
1023
+ [schema]
1024
+ );
1025
+ const objects: DatabaseObject[] = result.rows.map((r: { table_name: string; table_type: string }) => ({
1026
+ name: r.table_name,
1027
+ type: r.table_type === "VIEW" ? "view" as const : "table" as const,
1028
+ }));
1029
+
1030
+ try {
1031
+ const matviewResult = await pool.query(
1032
+ `SELECT c.relname AS table_name
1033
+ FROM pg_class c
1034
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1035
+ WHERE n.nspname = $1 AND c.relkind = 'm'
1036
+ ORDER BY c.relname`,
1037
+ [schema]
1038
+ );
1039
+ for (const r of matviewResult.rows as { table_name: string }[]) {
1040
+ objects.push({ name: r.table_name, type: "materialized_view" });
1041
+ }
1042
+ } catch (mvErr) {
1043
+ if (isFatalConnectionError(mvErr)) throw mvErr;
1044
+ log.warn({ err: mvErr instanceof Error ? mvErr.message : String(mvErr) }, "Could not discover materialized views");
1045
+ }
1046
+
1047
+ return objects.sort((a, b) => a.name.localeCompare(b.name));
1048
+ } finally {
1049
+ await pool.end().catch((err: unknown) => {
1050
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "Postgres pool cleanup warning");
1051
+ });
1052
+ }
1053
+ }
1054
+
1055
+ export async function listMySQLObjects(connectionString: string, log: ProfileLogger = defaultLog): Promise<DatabaseObject[]> {
1056
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1057
+ const mysql = require("mysql2/promise");
1058
+ const pool = mysql.createPool({
1059
+ uri: connectionString,
1060
+ connectionLimit: 1,
1061
+ connectTimeout: 5000,
1062
+ });
1063
+ try {
1064
+ const [rows] = await pool.execute(
1065
+ `SELECT TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES
1066
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE IN ('BASE TABLE', 'VIEW')
1067
+ ORDER BY TABLE_NAME`
1068
+ );
1069
+ return (rows as { TABLE_NAME: string; TABLE_TYPE: string }[]).map((r) => ({
1070
+ name: r.TABLE_NAME,
1071
+ type: r.TABLE_TYPE === "VIEW" ? "view" as const : "table" as const,
1072
+ }));
1073
+ } finally {
1074
+ await pool.end().catch((err: unknown) => {
1075
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "MySQL pool cleanup warning");
1076
+ });
1077
+ }
1078
+ }
1079
+
1080
+ // ---------------------------------------------------------------------------
1081
+ // PostgreSQL profiler — full table profiling
1082
+ // ---------------------------------------------------------------------------
1083
+
1084
+ /** Schema-qualified table reference for SQL queries. */
1085
+ function pgTableRef(tableName: string, schema: string): string {
1086
+ const safeTable = tableName.replace(/"/g, '""');
1087
+ const safeSchema = schema.replace(/"/g, '""');
1088
+ return schema === "public" ? `"${safeTable}"` : `"${safeSchema}"."${safeTable}"`;
1089
+ }
1090
+
1091
+ async function queryPrimaryKeys(
1092
+ pool: import("pg").Pool,
1093
+ tableName: string,
1094
+ schema: string = "public"
1095
+ ): Promise<string[]> {
1096
+ const result = await pool.query(
1097
+ `
1098
+ SELECT a.attname AS column_name
1099
+ FROM pg_constraint c
1100
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
1101
+ WHERE c.contype = 'p'
1102
+ AND c.conrelid = $1::regclass
1103
+ ORDER BY a.attnum
1104
+ `,
1105
+ [pgTableRef(tableName, schema)]
1106
+ );
1107
+ return result.rows.map((r: { column_name: string }) => r.column_name);
1108
+ }
1109
+
1110
+ async function queryForeignKeys(
1111
+ pool: import("pg").Pool,
1112
+ tableName: string,
1113
+ schema: string = "public"
1114
+ ): Promise<ForeignKey[]> {
1115
+ const result = await pool.query(
1116
+ `
1117
+ SELECT
1118
+ a.attname AS from_column,
1119
+ cl.relname AS to_table,
1120
+ af.attname AS to_column,
1121
+ ns.nspname AS to_schema
1122
+ FROM pg_constraint c
1123
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
1124
+ JOIN pg_class cl ON cl.oid = c.confrelid
1125
+ JOIN pg_namespace ns ON ns.oid = cl.relnamespace
1126
+ JOIN pg_attribute af ON af.attrelid = c.confrelid AND af.attnum = ANY(c.confkey)
1127
+ WHERE c.contype = 'f'
1128
+ AND c.conrelid = $1::regclass
1129
+ ORDER BY a.attnum
1130
+ `,
1131
+ [pgTableRef(tableName, schema)]
1132
+ );
1133
+ return result.rows.map((r: { from_column: string; to_table: string; to_column: string; to_schema: string }) => ({
1134
+ from_column: r.from_column,
1135
+ to_table: r.to_schema !== schema ? `${r.to_schema}.${r.to_table}` : r.to_table,
1136
+ to_column: r.to_column,
1137
+ source: "constraint" as const,
1138
+ }));
1139
+ }
1140
+
1141
+ export async function profilePostgres(
1142
+ connectionString: string,
1143
+ filterTables?: string[],
1144
+ prefetchedObjects?: DatabaseObject[],
1145
+ schema: string = "public",
1146
+ progress?: ProfileProgressCallbacks,
1147
+ log: ProfileLogger = defaultLog,
1148
+ ): Promise<ProfilingResult> {
1149
+ const { Pool } = await import("pg");
1150
+ const pool = new Pool({ connectionString, max: 3 });
1151
+ try {
1152
+ const profiles: TableProfile[] = [];
1153
+ const errors: ProfileError[] = [];
1154
+
1155
+ let allObjects: DatabaseObject[];
1156
+ if (prefetchedObjects) {
1157
+ allObjects = prefetchedObjects;
1158
+ } else {
1159
+ const tablesResult = await pool.query(
1160
+ `SELECT table_name, table_type FROM information_schema.tables
1161
+ WHERE table_schema = $1 AND table_type IN ('BASE TABLE', 'VIEW')
1162
+ ORDER BY table_name`,
1163
+ [schema]
1164
+ );
1165
+ allObjects = tablesResult.rows.map((r: { table_name: string; table_type: string }) => ({
1166
+ name: r.table_name,
1167
+ type: r.table_type === "VIEW" ? "view" as const : "table" as const,
1168
+ }));
1169
+
1170
+ try {
1171
+ const matviewResult = await pool.query(
1172
+ `SELECT c.relname AS table_name
1173
+ FROM pg_class c
1174
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1175
+ WHERE n.nspname = $1 AND c.relkind = 'm'
1176
+ ORDER BY c.relname`,
1177
+ [schema]
1178
+ );
1179
+ for (const r of matviewResult.rows as { table_name: string }[]) {
1180
+ allObjects.push({ name: r.table_name, type: "materialized_view" });
1181
+ }
1182
+ } catch (mvErr) {
1183
+ if (isFatalConnectionError(mvErr)) throw mvErr;
1184
+ log.warn({ err: mvErr instanceof Error ? mvErr.message : String(mvErr) }, "Could not discover materialized views");
1185
+ }
1186
+ allObjects.sort((a, b) => a.name.localeCompare(b.name));
1187
+ }
1188
+
1189
+ const objectsToProfile = filterTables
1190
+ ? allObjects.filter((o) => filterTables.includes(o.name))
1191
+ : allObjects;
1192
+
1193
+ progress?.onStart(objectsToProfile.length);
1194
+
1195
+ for (const [i, obj] of objectsToProfile.entries()) {
1196
+ const table_name = obj.name;
1197
+ const objectType = obj.type;
1198
+ const objectLabel = objectType === "view" ? " [view]" : objectType === "materialized_view" ? " [matview]" : "";
1199
+ if (progress) {
1200
+ progress.onTableStart(table_name + objectLabel, i, objectsToProfile.length);
1201
+ } else {
1202
+ log.info({ table: table_name, index: i + 1, total: objectsToProfile.length }, `Profiling ${table_name}${objectLabel}`);
1203
+ }
1204
+
1205
+ try {
1206
+ let matview_populated: boolean | undefined;
1207
+ if (objectType === "materialized_view") {
1208
+ try {
1209
+ const mvResult = await pool.query(
1210
+ `SELECT ispopulated FROM pg_matviews WHERE schemaname = $1 AND matviewname = $2`,
1211
+ [schema, table_name]
1212
+ );
1213
+ if (mvResult.rows.length > 0) {
1214
+ matview_populated = mvResult.rows[0].ispopulated;
1215
+ }
1216
+ } catch (mvErr) {
1217
+ if (isFatalConnectionError(mvErr)) throw mvErr;
1218
+ log.warn({ err: mvErr instanceof Error ? mvErr.message : String(mvErr), table: table_name }, "Could not read matview status");
1219
+ }
1220
+ }
1221
+
1222
+ let rowCount: number;
1223
+ if (matview_populated === false) {
1224
+ rowCount = 0;
1225
+ log.info({ table: table_name }, "Materialized view is not populated — skipping data profiling");
1226
+ } else {
1227
+ const countResult = await pool.query(
1228
+ `SELECT COUNT(*) as c FROM ${pgTableRef(table_name, schema)}`
1229
+ );
1230
+ rowCount = parseInt(countResult.rows[0].c, 10);
1231
+ }
1232
+
1233
+ let primaryKeyColumns: string[] = [];
1234
+ let foreignKeys: ForeignKey[] = [];
1235
+ if (objectType === "table") {
1236
+ try {
1237
+ primaryKeyColumns = await queryPrimaryKeys(pool, table_name, schema);
1238
+ } catch (pkErr) {
1239
+ if (isFatalConnectionError(pkErr)) throw pkErr;
1240
+ log.warn({ err: pkErr instanceof Error ? pkErr.message : String(pkErr), table: table_name }, "Could not read PK constraints");
1241
+ }
1242
+ try {
1243
+ foreignKeys = await queryForeignKeys(pool, table_name, schema);
1244
+ } catch (fkErr) {
1245
+ if (isFatalConnectionError(fkErr)) throw fkErr;
1246
+ log.warn({ err: fkErr instanceof Error ? fkErr.message : String(fkErr), table: table_name }, "Could not read FK constraints");
1247
+ }
1248
+ }
1249
+
1250
+ const fkLookup = new Map(
1251
+ foreignKeys.map((fk) => [fk.from_column, fk])
1252
+ );
1253
+
1254
+ const colResult = objectType === "materialized_view"
1255
+ ? await pool.query(
1256
+ `
1257
+ SELECT a.attname AS column_name,
1258
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
1259
+ CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable
1260
+ FROM pg_attribute a
1261
+ JOIN pg_class c ON c.oid = a.attrelid
1262
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1263
+ WHERE n.nspname = $2
1264
+ AND c.relname = $1
1265
+ AND a.attnum > 0
1266
+ AND NOT a.attisdropped
1267
+ ORDER BY a.attnum
1268
+ `,
1269
+ [table_name, schema]
1270
+ )
1271
+ : await pool.query(
1272
+ `
1273
+ SELECT column_name, data_type, is_nullable
1274
+ FROM information_schema.columns
1275
+ WHERE table_name = $1 AND table_schema = $2
1276
+ ORDER BY ordinal_position
1277
+ `,
1278
+ [table_name, schema]
1279
+ );
1280
+
1281
+ const columns: ColumnProfile[] = [];
1282
+
1283
+ for (const col of colResult.rows) {
1284
+ let unique_count: number | null = null;
1285
+ let null_count: number | null = null;
1286
+ let sample_values: string[] = [];
1287
+ let isEnumLike = false;
1288
+
1289
+ const isPK = primaryKeyColumns.includes(col.column_name);
1290
+ const fkInfo = fkLookup.get(col.column_name);
1291
+ const isFK = !!fkInfo;
1292
+
1293
+ if (matview_populated !== false) {
1294
+ try {
1295
+ const tableRef = pgTableRef(table_name, schema);
1296
+ const uq = await pool.query(
1297
+ `SELECT COUNT(DISTINCT "${col.column_name}") as c FROM ${tableRef}`
1298
+ );
1299
+ unique_count = parseInt(uq.rows[0].c, 10);
1300
+
1301
+ const nc = await pool.query(
1302
+ `SELECT COUNT(*) as c FROM ${tableRef} WHERE "${col.column_name}" IS NULL`
1303
+ );
1304
+ null_count = parseInt(nc.rows[0].c, 10);
1305
+
1306
+ const isTextType =
1307
+ col.data_type === "text" ||
1308
+ col.data_type === "character varying" ||
1309
+ col.data_type === "character";
1310
+ isEnumLike =
1311
+ isTextType &&
1312
+ unique_count !== null &&
1313
+ unique_count < 20 &&
1314
+ rowCount > 0 &&
1315
+ unique_count / rowCount <= 0.05;
1316
+
1317
+ const sampleLimit = isEnumLike ? 100 : 10;
1318
+ const sv = await pool.query(
1319
+ `SELECT DISTINCT "${col.column_name}" as v FROM ${tableRef} WHERE "${col.column_name}" IS NOT NULL ORDER BY "${col.column_name}" LIMIT ${sampleLimit}`
1320
+ );
1321
+ sample_values = sv.rows.map((r: { v: unknown }) => String(r.v));
1322
+ } catch (colErr) {
1323
+ if (isFatalConnectionError(colErr)) throw colErr;
1324
+ log.warn({ err: colErr instanceof Error ? colErr.message : String(colErr), table: table_name, column: col.column_name }, "Could not profile column");
1325
+ }
1326
+ }
1327
+
1328
+ columns.push({
1329
+ name: col.column_name,
1330
+ type: col.data_type,
1331
+ nullable: col.is_nullable === "YES",
1332
+ unique_count,
1333
+ null_count,
1334
+ sample_values,
1335
+ is_primary_key: isPK,
1336
+ is_foreign_key: isFK,
1337
+ fk_target_table: fkInfo?.to_table ?? null,
1338
+ fk_target_column: fkInfo?.to_column ?? null,
1339
+ is_enum_like: isEnumLike,
1340
+ profiler_notes: [],
1341
+ });
1342
+ }
1343
+
1344
+ profiles.push({
1345
+ table_name,
1346
+ object_type: objectType,
1347
+ row_count: rowCount,
1348
+ columns,
1349
+ primary_key_columns: primaryKeyColumns,
1350
+ foreign_keys: foreignKeys,
1351
+ inferred_foreign_keys: [],
1352
+ profiler_notes: [],
1353
+ table_flags: { possibly_abandoned: false, possibly_denormalized: false },
1354
+ ...(matview_populated !== undefined ? { matview_populated } : {}),
1355
+ });
1356
+ progress?.onTableDone(table_name, i, objectsToProfile.length);
1357
+ } catch (err) {
1358
+ const msg = err instanceof Error ? err.message : String(err);
1359
+ if (isFatalConnectionError(err)) {
1360
+ throw new Error(`Fatal database error while profiling ${table_name}: ${msg}`, { cause: err });
1361
+ }
1362
+ if (progress) {
1363
+ progress.onTableError(table_name, msg, i, objectsToProfile.length);
1364
+ } else {
1365
+ log.warn({ err: msg, table: table_name }, "Failed to profile table");
1366
+ }
1367
+ errors.push({ table: table_name, error: msg });
1368
+ continue;
1369
+ }
1370
+ }
1371
+
1372
+ // Batch-query partition metadata
1373
+ const partitionMap = new Map<string, { strategy: "range" | "list" | "hash"; key: string }>();
1374
+ try {
1375
+ const partResult = await pool.query(
1376
+ `SELECT c.relname,
1377
+ CASE pt.partstrat WHEN 'r' THEN 'range' WHEN 'l' THEN 'list' WHEN 'h' THEN 'hash' ELSE pt.partstrat END as strategy,
1378
+ pg_get_partkeydef(c.oid) as partition_key
1379
+ FROM pg_partitioned_table pt
1380
+ JOIN pg_class c ON c.oid = pt.partrelid
1381
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1382
+ WHERE n.nspname = $1`,
1383
+ [schema]
1384
+ );
1385
+
1386
+ for (const r of partResult.rows as { relname: string; strategy: string; partition_key: string }[]) {
1387
+ if (r.strategy !== "range" && r.strategy !== "list" && r.strategy !== "hash") {
1388
+ log.warn({ table: r.relname, strategy: r.strategy }, "Unrecognized partition strategy — skipping");
1389
+ continue;
1390
+ }
1391
+ partitionMap.set(r.relname, { strategy: r.strategy, key: r.partition_key });
1392
+ }
1393
+ } catch (partErr) {
1394
+ if (isFatalConnectionError(partErr)) throw partErr;
1395
+ log.warn({ err: partErr instanceof Error ? partErr.message : String(partErr) }, "Could not read partition metadata");
1396
+ }
1397
+
1398
+ const childrenMap = new Map<string, string[]>();
1399
+ try {
1400
+ const childResult = await pool.query(
1401
+ `SELECT p.relname as parent, c.relname as child
1402
+ FROM pg_inherits i
1403
+ JOIN pg_class c ON c.oid = i.inhrelid
1404
+ JOIN pg_class p ON p.oid = i.inhparent
1405
+ JOIN pg_namespace n ON n.oid = p.relnamespace
1406
+ WHERE n.nspname = $1
1407
+ ORDER BY p.relname, c.relname`,
1408
+ [schema]
1409
+ );
1410
+ for (const r of childResult.rows as { parent: string; child: string }[]) {
1411
+ const children = childrenMap.get(r.parent) ?? [];
1412
+ children.push(r.child);
1413
+ childrenMap.set(r.parent, children);
1414
+ }
1415
+ } catch (childErr) {
1416
+ if (isFatalConnectionError(childErr)) throw childErr;
1417
+ log.warn({ err: childErr instanceof Error ? childErr.message : String(childErr) }, "Could not read partition children");
1418
+ }
1419
+
1420
+ for (const profile of profiles) {
1421
+ const partInfo = partitionMap.get(profile.table_name);
1422
+ if (partInfo) {
1423
+ profile.partition_info = {
1424
+ strategy: partInfo.strategy,
1425
+ key: partInfo.key,
1426
+ children: childrenMap.get(profile.table_name) ?? [],
1427
+ };
1428
+ }
1429
+ }
1430
+
1431
+ return { profiles, errors };
1432
+ } finally {
1433
+ await pool.end().catch((err: unknown) => {
1434
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "Postgres pool cleanup warning");
1435
+ });
1436
+ }
1437
+ }
1438
+
1439
+ // ---------------------------------------------------------------------------
1440
+ // MySQL profiler — full table profiling
1441
+ // ---------------------------------------------------------------------------
1442
+
1443
+ /** Backtick-quoted MySQL identifier with embedded backticks escaped. */
1444
+ export function mysqlQuoteIdent(name: string): string {
1445
+ return `\`${name.replace(/`/g, "``")}\``;
1446
+ }
1447
+
1448
+ async function queryMySQLPrimaryKeys(
1449
+ pool: { execute: (sql: string, params?: unknown[]) => Promise<[unknown[], unknown]> },
1450
+ tableName: string,
1451
+ ): Promise<string[]> {
1452
+ const [rows] = await pool.execute(
1453
+ `SELECT COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE
1454
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND CONSTRAINT_NAME = 'PRIMARY'
1455
+ ORDER BY ORDINAL_POSITION`,
1456
+ [tableName]
1457
+ );
1458
+ return (rows as { COLUMN_NAME: string }[]).map((r) => r.COLUMN_NAME);
1459
+ }
1460
+
1461
+ async function queryMySQLForeignKeys(
1462
+ pool: { execute: (sql: string, params?: unknown[]) => Promise<[unknown[], unknown]> },
1463
+ tableName: string,
1464
+ ): Promise<ForeignKey[]> {
1465
+ const [rows] = await pool.execute(
1466
+ `SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
1467
+ FROM information_schema.KEY_COLUMN_USAGE
1468
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
1469
+ AND REFERENCED_TABLE_NAME IS NOT NULL
1470
+ ORDER BY ORDINAL_POSITION`,
1471
+ [tableName]
1472
+ );
1473
+ return (rows as { COLUMN_NAME: string; REFERENCED_TABLE_NAME: string; REFERENCED_COLUMN_NAME: string }[]).map((r) => ({
1474
+ from_column: r.COLUMN_NAME,
1475
+ to_table: r.REFERENCED_TABLE_NAME,
1476
+ to_column: r.REFERENCED_COLUMN_NAME,
1477
+ source: "constraint" as const,
1478
+ }));
1479
+ }
1480
+
1481
+ export async function profileMySQL(
1482
+ connectionString: string,
1483
+ filterTables?: string[],
1484
+ prefetchedObjects?: DatabaseObject[],
1485
+ progress?: ProfileProgressCallbacks,
1486
+ log: ProfileLogger = defaultLog,
1487
+ ): Promise<ProfilingResult> {
1488
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1489
+ const mysql = require("mysql2/promise");
1490
+ const pool = mysql.createPool({
1491
+ uri: connectionString,
1492
+ connectionLimit: 3,
1493
+ supportBigNumbers: true,
1494
+ bigNumberStrings: true,
1495
+ });
1496
+ const profiles: TableProfile[] = [];
1497
+ const errors: ProfileError[] = [];
1498
+
1499
+ try {
1500
+ let allObjects: DatabaseObject[];
1501
+ if (prefetchedObjects) {
1502
+ allObjects = prefetchedObjects;
1503
+ } else {
1504
+ const [tablesRows] = await pool.execute(
1505
+ `SELECT TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES
1506
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE IN ('BASE TABLE', 'VIEW')
1507
+ ORDER BY TABLE_NAME`
1508
+ );
1509
+ allObjects = (tablesRows as { TABLE_NAME: string; TABLE_TYPE: string }[]).map((r) => ({
1510
+ name: r.TABLE_NAME,
1511
+ type: r.TABLE_TYPE === "VIEW" ? "view" as const : "table" as const,
1512
+ }));
1513
+ }
1514
+
1515
+ const objectsToProfile = filterTables
1516
+ ? allObjects.filter((o) => filterTables.includes(o.name))
1517
+ : allObjects;
1518
+
1519
+ progress?.onStart(objectsToProfile.length);
1520
+
1521
+ for (const [i, obj] of objectsToProfile.entries()) {
1522
+ const table_name = obj.name;
1523
+ const objectType = obj.type;
1524
+ const objectLabel = objectType === "view" ? " [view]" : "";
1525
+ if (progress) {
1526
+ progress.onTableStart(table_name + objectLabel, i, objectsToProfile.length);
1527
+ } else {
1528
+ log.info({ table: table_name, index: i + 1, total: objectsToProfile.length }, `Profiling ${table_name}${objectLabel}`);
1529
+ }
1530
+
1531
+ try {
1532
+ const [countRows] = await pool.execute(
1533
+ `SELECT COUNT(*) as c FROM ${mysqlQuoteIdent(table_name)}`
1534
+ );
1535
+ const rowCount = parseInt(String((countRows as { c: number }[])[0].c), 10);
1536
+
1537
+ let primaryKeyColumns: string[] = [];
1538
+ let foreignKeys: ForeignKey[] = [];
1539
+ if (objectType === "table") {
1540
+ try {
1541
+ primaryKeyColumns = await queryMySQLPrimaryKeys(pool, table_name);
1542
+ } catch (pkErr) {
1543
+ if (isFatalConnectionError(pkErr)) throw pkErr;
1544
+ log.warn({ err: pkErr instanceof Error ? pkErr.message : String(pkErr), table: table_name }, "Could not read PK constraints");
1545
+ }
1546
+ try {
1547
+ foreignKeys = await queryMySQLForeignKeys(pool, table_name);
1548
+ } catch (fkErr) {
1549
+ if (isFatalConnectionError(fkErr)) throw fkErr;
1550
+ log.warn({ err: fkErr instanceof Error ? fkErr.message : String(fkErr), table: table_name }, "Could not read FK constraints");
1551
+ }
1552
+ }
1553
+
1554
+ const fkLookup = new Map(
1555
+ foreignKeys.map((fk) => [fk.from_column, fk])
1556
+ );
1557
+
1558
+ const [colRows] = await pool.execute(
1559
+ `SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_TYPE
1560
+ FROM information_schema.COLUMNS
1561
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
1562
+ ORDER BY ORDINAL_POSITION`,
1563
+ [table_name]
1564
+ );
1565
+
1566
+ const columns: ColumnProfile[] = [];
1567
+
1568
+ for (const col of colRows as { COLUMN_NAME: string; DATA_TYPE: string; IS_NULLABLE: string; COLUMN_TYPE: string }[]) {
1569
+ let unique_count: number | null = null;
1570
+ let null_count: number | null = null;
1571
+ let sample_values: string[] = [];
1572
+ let isEnumLike = false;
1573
+
1574
+ const isPK = primaryKeyColumns.includes(col.COLUMN_NAME);
1575
+ const fkInfo = fkLookup.get(col.COLUMN_NAME);
1576
+ const isFK = !!fkInfo;
1577
+
1578
+ try {
1579
+ const [uqRows] = await pool.execute(
1580
+ `SELECT COUNT(DISTINCT ${mysqlQuoteIdent(col.COLUMN_NAME)}) as c FROM ${mysqlQuoteIdent(table_name)}`
1581
+ );
1582
+ unique_count = parseInt(String((uqRows as { c: number }[])[0].c), 10);
1583
+
1584
+ const [ncRows] = await pool.execute(
1585
+ `SELECT COUNT(*) as c FROM ${mysqlQuoteIdent(table_name)} WHERE ${mysqlQuoteIdent(col.COLUMN_NAME)} IS NULL`
1586
+ );
1587
+ null_count = parseInt(String((ncRows as { c: number }[])[0].c), 10);
1588
+
1589
+ const dataType = col.DATA_TYPE.toLowerCase();
1590
+ const isTextType =
1591
+ dataType === "varchar" ||
1592
+ dataType === "char" ||
1593
+ dataType === "text" ||
1594
+ dataType === "tinytext" ||
1595
+ dataType === "mediumtext" ||
1596
+ dataType === "longtext" ||
1597
+ dataType === "enum" ||
1598
+ dataType === "set";
1599
+ isEnumLike =
1600
+ isTextType &&
1601
+ unique_count !== null &&
1602
+ unique_count < 20 &&
1603
+ rowCount > 0 &&
1604
+ unique_count / rowCount <= 0.05;
1605
+
1606
+ const sampleLimit = isEnumLike ? 100 : 10;
1607
+ const [svRows] = await pool.execute(
1608
+ `SELECT DISTINCT ${mysqlQuoteIdent(col.COLUMN_NAME)} as v FROM ${mysqlQuoteIdent(table_name)} WHERE ${mysqlQuoteIdent(col.COLUMN_NAME)} IS NOT NULL ORDER BY ${mysqlQuoteIdent(col.COLUMN_NAME)} LIMIT ${sampleLimit}`
1609
+ );
1610
+ sample_values = (svRows as { v: unknown }[]).map((r) => String(r.v));
1611
+ } catch (colErr) {
1612
+ if (isFatalConnectionError(colErr)) throw colErr;
1613
+ log.warn({ err: colErr instanceof Error ? colErr.message : String(colErr), table: table_name, column: col.COLUMN_NAME }, "Could not profile column");
1614
+ }
1615
+
1616
+ columns.push({
1617
+ name: col.COLUMN_NAME,
1618
+ type: col.DATA_TYPE,
1619
+ nullable: col.IS_NULLABLE === "YES",
1620
+ unique_count,
1621
+ null_count,
1622
+ sample_values,
1623
+ is_primary_key: isPK,
1624
+ is_foreign_key: isFK,
1625
+ fk_target_table: fkInfo?.to_table ?? null,
1626
+ fk_target_column: fkInfo?.to_column ?? null,
1627
+ is_enum_like: isEnumLike,
1628
+ profiler_notes: [],
1629
+ });
1630
+ }
1631
+
1632
+ profiles.push({
1633
+ table_name,
1634
+ object_type: objectType,
1635
+ row_count: rowCount,
1636
+ columns,
1637
+ primary_key_columns: primaryKeyColumns,
1638
+ foreign_keys: foreignKeys,
1639
+ inferred_foreign_keys: [],
1640
+ profiler_notes: [],
1641
+ table_flags: { possibly_abandoned: false, possibly_denormalized: false },
1642
+ });
1643
+ progress?.onTableDone(table_name, i, objectsToProfile.length);
1644
+ } catch (err) {
1645
+ const msg = err instanceof Error ? err.message : String(err);
1646
+ if (isFatalConnectionError(err) || /PROTOCOL_CONNECTION_LOST|ER_SERVER_SHUTDOWN|ER_NET_READ_ERROR|ER_NET_WRITE_ERROR/i.test(msg)) {
1647
+ throw new Error(`Fatal database error while profiling ${table_name}: ${msg}`, { cause: err });
1648
+ }
1649
+ if (progress) {
1650
+ progress.onTableError(table_name, msg, i, objectsToProfile.length);
1651
+ } else {
1652
+ log.warn({ err: msg, table: table_name }, "Failed to profile table");
1653
+ }
1654
+ errors.push({ table: table_name, error: msg });
1655
+ continue;
1656
+ }
1657
+ }
1658
+ } finally {
1659
+ await pool.end().catch((err: unknown) => {
1660
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "MySQL pool cleanup warning");
1661
+ });
1662
+ }
1663
+
1664
+ return { profiles, errors };
1665
+ }