insforge 0.3.3 → 1.3.0

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 (635) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.dockerignore +60 -57
  3. package/.env.example +84 -49
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -83
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -79
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +66 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -0
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +44 -3
  15. package/CLAUDE_PLUGIN.md +104 -0
  16. package/CODE_OF_CONDUCT.md +128 -0
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -27
  19. package/GITHUB_OAUTH_SETUP.md +49 -49
  20. package/GOOGLE_OAUTH_SETUP.md +148 -148
  21. package/LICENSE +201 -201
  22. package/README.md +182 -134
  23. package/assets/Dark.svg +23 -23
  24. package/assets/mcpInstallv2.png +0 -0
  25. package/assets/sampleResponse.png +0 -0
  26. package/auth/index.html +13 -0
  27. package/auth/package.json +28 -0
  28. package/auth/public/favicon.ico +0 -0
  29. package/auth/src/App.tsx +33 -0
  30. package/auth/src/components/ErrorCard.tsx +37 -0
  31. package/auth/src/components/Layout.tsx +13 -0
  32. package/auth/src/index.css +19 -0
  33. package/auth/src/lib/broadcastService.ts +117 -0
  34. package/auth/src/lib/utils.ts +11 -0
  35. package/auth/src/main.tsx +22 -0
  36. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  37. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  38. package/auth/src/pages/SignInPage.tsx +60 -0
  39. package/auth/src/pages/SignUpPage.tsx +60 -0
  40. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  41. package/auth/src/vite-env.d.ts +10 -0
  42. package/auth/tsconfig.json +32 -0
  43. package/auth/tsconfig.node.json +11 -0
  44. package/auth/vite.config.ts +25 -0
  45. package/backend/package.json +78 -75
  46. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  47. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  48. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +22 -26
  49. package/backend/src/api/routes/auth/index.routes.ts +667 -0
  50. package/backend/src/api/routes/auth/oauth.routes.ts +473 -0
  51. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +128 -65
  52. package/backend/src/api/routes/database/index.routes.ts +90 -0
  53. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +26 -12
  54. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +6 -23
  55. package/backend/src/api/routes/docs/index.routes.ts +75 -0
  56. package/backend/src/api/routes/email/index.routes.ts +35 -0
  57. package/backend/src/api/routes/functions/index.routes.ts +194 -0
  58. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  59. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +33 -31
  60. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  61. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  62. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  63. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  64. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  65. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +48 -61
  66. package/backend/src/api/routes/usage/index.routes.ts +91 -0
  67. package/backend/src/infra/config/app.config.ts +51 -0
  68. package/backend/src/infra/database/database.manager.ts +182 -0
  69. package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +141 -141
  70. package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +40 -40
  71. package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +29 -29
  72. package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +55 -55
  73. package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +23 -23
  74. package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +29 -29
  75. package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +24 -24
  76. package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +1 -1
  77. package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +76 -76
  78. package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +23 -23
  79. package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +93 -93
  80. package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +15 -15
  81. package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +7 -7
  82. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  83. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  84. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  85. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  86. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  87. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  88. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  89. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  90. package/backend/src/infra/security/token.manager.ts +219 -0
  91. package/backend/src/infra/socket/socket.manager.ts +522 -0
  92. package/backend/src/providers/ai/openrouter.provider.ts +380 -0
  93. package/backend/src/providers/email/base.provider.ts +38 -0
  94. package/backend/src/providers/email/cloud.provider.ts +271 -0
  95. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  96. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  97. package/backend/src/providers/logs/local.provider.ts +185 -0
  98. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  99. package/backend/src/providers/oauth/base.provider.ts +29 -0
  100. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  101. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  102. package/backend/src/providers/oauth/github.provider.ts +208 -0
  103. package/backend/src/providers/oauth/google.provider.ts +249 -0
  104. package/backend/src/providers/oauth/index.ts +8 -0
  105. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  106. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  107. package/backend/src/providers/oauth/x.provider.ts +202 -0
  108. package/backend/src/providers/storage/base.provider.ts +29 -0
  109. package/backend/src/providers/storage/local.provider.ts +103 -0
  110. package/backend/src/providers/storage/s3.provider.ts +313 -0
  111. package/backend/src/server.ts +317 -288
  112. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  113. package/backend/src/services/ai/ai-model.service.ts +60 -0
  114. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  115. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  116. package/backend/src/services/ai/helpers.ts +64 -0
  117. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  118. package/backend/src/services/ai/index.ts +13 -0
  119. package/backend/src/services/auth/auth-config.service.ts +250 -0
  120. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  121. package/backend/src/services/auth/auth.service.ts +1150 -0
  122. package/backend/src/services/auth/index.ts +4 -0
  123. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  124. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  125. package/backend/src/services/database/database-table.service.ts +802 -0
  126. package/backend/src/services/database/database.service.ts +127 -0
  127. package/backend/src/services/email/email.service.ts +73 -0
  128. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  129. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  130. package/backend/src/services/logs/log.service.ts +73 -0
  131. package/backend/src/services/realtime/index.ts +3 -0
  132. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  133. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  134. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  135. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  136. package/backend/src/services/storage/storage.service.ts +617 -0
  137. package/backend/src/services/usage/usage.service.ts +149 -0
  138. package/backend/src/types/auth.ts +77 -2
  139. package/backend/src/types/email.ts +8 -0
  140. package/backend/src/types/error-constants.ts +4 -0
  141. package/backend/src/types/logs.ts +0 -29
  142. package/backend/src/types/realtime.ts +18 -0
  143. package/backend/src/{core/socket/types.ts → types/socket.ts} +11 -36
  144. package/backend/src/utils/cookies.ts +35 -0
  145. package/backend/src/utils/environment.ts +9 -3
  146. package/backend/src/utils/logger.ts +20 -2
  147. package/backend/src/utils/s3-config-loader.ts +64 -0
  148. package/backend/src/utils/seed.ts +301 -205
  149. package/backend/src/utils/sql-parser.ts +91 -1
  150. package/backend/src/utils/utils.ts +114 -0
  151. package/backend/src/utils/validations.ts +40 -4
  152. package/backend/tests/README.md +133 -133
  153. package/backend/tests/cleanup-all-test-data.sh +230 -230
  154. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  155. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  156. package/backend/tests/local/test-ai-config.sh +129 -0
  157. package/backend/tests/local/test-ai-usage.sh +80 -0
  158. package/backend/tests/local/test-auth-router.sh +143 -143
  159. package/backend/tests/local/test-database-router.sh +222 -222
  160. package/backend/tests/local/test-e2e.sh +240 -240
  161. package/backend/tests/local/test-fk-errors.sh +96 -96
  162. package/backend/tests/local/test-functions.sh +123 -0
  163. package/backend/tests/local/test-id-field.sh +200 -200
  164. package/backend/tests/local/test-logs.sh +132 -0
  165. package/backend/tests/local/test-public-bucket.sh +264 -264
  166. package/backend/tests/local/test-secrets.sh +249 -247
  167. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  168. package/backend/tests/local/test-traditional-rest.sh +208 -208
  169. package/backend/tests/manual/README.md +50 -50
  170. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  171. package/backend/tests/manual/seed-large-table.sql +100 -100
  172. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  173. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  174. package/backend/tests/manual/test-database-advance.sh +296 -296
  175. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  176. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  177. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  178. package/backend/tests/manual/test-universal-storage.sh +263 -263
  179. package/backend/tests/manual/test-users.sql +17 -17
  180. package/backend/tests/run-all-tests.sh +139 -139
  181. package/backend/tests/setup.ts +0 -0
  182. package/backend/tests/test-config.sh +338 -302
  183. package/backend/tests/unit/analyze-query.test.ts +697 -0
  184. package/backend/tests/unit/cloud-token.test.ts +48 -0
  185. package/backend/tests/unit/constant.test.ts +8 -0
  186. package/backend/tests/unit/email.test.ts +372 -0
  187. package/backend/tests/unit/environment.test.ts +59 -0
  188. package/backend/tests/unit/helpers.test.ts +63 -0
  189. package/backend/tests/unit/logger.test.ts +22 -0
  190. package/backend/tests/unit/rate-limit.test.ts +154 -0
  191. package/backend/tests/unit/response.test.ts +58 -0
  192. package/backend/tests/unit/sql-parser.test.ts +74 -0
  193. package/backend/tests/unit/uuid.test.ts +21 -0
  194. package/backend/tests/unit/validations.test.ts +80 -0
  195. package/backend/tsconfig.json +22 -22
  196. package/backend/vitest.config.ts +11 -0
  197. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  198. package/claude-plugin/README.md +133 -0
  199. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  200. package/docker-compose.prod.yml +204 -144
  201. package/docker-compose.yml +232 -167
  202. package/docker-init/db/db-init.sql +97 -125
  203. package/docker-init/db/jwt.sql +5 -5
  204. package/docker-init/db/postgresql.conf +16 -16
  205. package/docker-init/logs/vector.yml +236 -0
  206. package/docs/README.md +44 -0
  207. package/docs/agent-docs/real-time.md +269 -0
  208. package/docs/changelog.mdx +119 -0
  209. package/docs/core-concepts/ai/architecture.mdx +373 -0
  210. package/docs/core-concepts/ai/sdk.mdx +213 -0
  211. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  212. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  213. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  214. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  215. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  216. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  217. package/docs/core-concepts/database/architecture.mdx +256 -0
  218. package/docs/core-concepts/database/sdk.mdx +382 -0
  219. package/docs/core-concepts/email/architecture.mdx +101 -0
  220. package/docs/core-concepts/email/sdk.mdx +53 -0
  221. package/docs/core-concepts/functions/architecture.mdx +105 -0
  222. package/docs/core-concepts/functions/sdk.mdx +184 -0
  223. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  224. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  225. package/docs/core-concepts/storage/architecture.mdx +243 -0
  226. package/docs/core-concepts/storage/sdk.mdx +253 -0
  227. package/docs/deployment/README.md +94 -0
  228. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  229. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  230. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  231. package/docs/deployment/deploy-to-render.md +441 -0
  232. package/docs/deprecated/insforge-auth-api.md +214 -214
  233. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  234. package/docs/deprecated/insforge-db-api.md +358 -358
  235. package/docs/deprecated/insforge-db-sdk.md +139 -139
  236. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  237. package/docs/deprecated/insforge-debug.md +64 -64
  238. package/docs/deprecated/insforge-instructions.md +123 -123
  239. package/docs/deprecated/insforge-project.md +117 -117
  240. package/docs/deprecated/insforge-storage-api.md +278 -278
  241. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  242. package/docs/docs.json +232 -0
  243. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  244. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  245. package/docs/examples/framework-guides/react.mdx +165 -0
  246. package/docs/examples/framework-guides/svelte.mdx +153 -0
  247. package/docs/examples/framework-guides/vue.mdx +159 -0
  248. package/docs/examples/overview.mdx +67 -0
  249. package/docs/favicon.svg +19 -0
  250. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  251. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  252. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  253. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  254. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  255. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  256. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  257. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  258. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  259. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  260. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  261. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  262. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  263. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  264. package/docs/images/checks-passed.png +0 -0
  265. package/docs/images/dashboard-connect-expanded.png +0 -0
  266. package/docs/images/dashboard-connect.png +0 -0
  267. package/docs/images/hero-dark.png +0 -0
  268. package/docs/images/hero-light.png +0 -0
  269. package/docs/images/icons/ai.svg +4 -0
  270. package/docs/images/icons/auth.svg +1 -0
  271. package/docs/images/icons/database.svg +1 -0
  272. package/docs/images/icons/function.svg +1 -0
  273. package/docs/images/icons/storage.svg +1 -0
  274. package/docs/images/logos/nextjs.svg +4 -0
  275. package/docs/images/logos/nuxt.svg +4 -0
  276. package/docs/images/logos/react.svg +5 -0
  277. package/docs/images/logos/svelte.svg +4 -0
  278. package/docs/images/logos/vue.svg +5 -0
  279. package/docs/images/mcp-install.png +0 -0
  280. package/docs/images/onboarding-mcp.png +0 -0
  281. package/docs/insforge-instructions-sdk.md +89 -407
  282. package/docs/introduction.mdx +45 -0
  283. package/docs/logo/dark.svg +22 -0
  284. package/docs/logo/light.svg +20 -0
  285. package/docs/partnership.mdx +652 -0
  286. package/docs/quickstart.mdx +83 -0
  287. package/docs/showcase/2048-arena.png +0 -0
  288. package/docs/showcase/framegen-cloud.png +0 -0
  289. package/docs/showcase/line-connect-race.png +0 -0
  290. package/docs/showcase/moment-vibe.png +0 -0
  291. package/docs/showcase/national-flags.png +0 -0
  292. package/docs/showcase/pokemon-vibe.png +0 -0
  293. package/docs/showcase/pure-browse-buy.png +0 -0
  294. package/docs/showcase.mdx +52 -0
  295. package/docs/snippets/sdk-installation.mdx +22 -0
  296. package/docs/snippets/service-icons.mdx +27 -0
  297. package/eslint.config.js +10 -3
  298. package/examples/oauth/frontend-oauth-example.html +250 -250
  299. package/examples/response-examples.md +443 -443
  300. package/frontend/components.json +17 -17
  301. package/frontend/package.json +69 -63
  302. package/frontend/src/App.tsx +13 -82
  303. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  304. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  305. package/frontend/src/assets/icons/checked.svg +3 -3
  306. package/frontend/src/assets/icons/connected.svg +3 -0
  307. package/frontend/src/assets/icons/error.svg +3 -3
  308. package/frontend/src/assets/icons/loader.svg +9 -0
  309. package/frontend/src/assets/icons/pencil.svg +4 -4
  310. package/frontend/src/assets/icons/refresh.svg +4 -4
  311. package/frontend/src/assets/icons/step_active.svg +3 -3
  312. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  313. package/frontend/src/assets/icons/warning.svg +3 -3
  314. package/frontend/src/assets/logos/apple.svg +4 -0
  315. package/frontend/src/assets/logos/claude_code.svg +3 -3
  316. package/frontend/src/assets/logos/cline.svg +6 -6
  317. package/frontend/src/assets/logos/cursor.svg +20 -20
  318. package/frontend/src/assets/logos/discord.svg +8 -8
  319. package/frontend/src/assets/logos/facebook.svg +3 -0
  320. package/frontend/src/assets/logos/gemini.svg +19 -19
  321. package/frontend/src/assets/logos/github.svg +5 -5
  322. package/frontend/src/assets/logos/google.svg +13 -13
  323. package/frontend/src/assets/logos/grok.svg +10 -10
  324. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  325. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  326. package/frontend/src/assets/logos/instagram.svg +2 -0
  327. package/frontend/src/assets/logos/linkedin.svg +3 -0
  328. package/frontend/src/assets/logos/microsoft.svg +1 -0
  329. package/frontend/src/assets/logos/openai.svg +10 -10
  330. package/frontend/src/assets/logos/roo_code.svg +9 -9
  331. package/frontend/src/assets/logos/spotify.svg +17 -0
  332. package/frontend/src/assets/logos/tiktok.svg +6 -0
  333. package/frontend/src/assets/logos/trae.svg +3 -3
  334. package/frontend/src/assets/logos/windsurf.svg +10 -10
  335. package/frontend/src/assets/logos/x.svg +3 -0
  336. package/frontend/src/components/Checkbox.tsx +27 -29
  337. package/frontend/src/components/CodeBlock.tsx +55 -2
  338. package/frontend/src/components/CodeEditor.tsx +92 -0
  339. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  340. package/frontend/src/components/ConnectCTA.tsx +38 -0
  341. package/frontend/src/components/CopyButton.tsx +52 -15
  342. package/frontend/src/components/ErrorState.tsx +1 -2
  343. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  344. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  345. package/frontend/src/components/JsonHighlight.tsx +21 -9
  346. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  347. package/frontend/src/components/PromptDialog.tsx +1 -4
  348. package/frontend/src/components/SearchInput.tsx +1 -2
  349. package/frontend/src/components/Stepper.tsx +53 -0
  350. package/frontend/src/components/ThemeToggle.tsx +3 -3
  351. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  352. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  353. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  354. package/frontend/src/components/datagrid/index.ts +23 -0
  355. package/frontend/src/components/index.ts +23 -30
  356. package/frontend/src/components/layout/AppHeader.tsx +131 -91
  357. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  358. package/frontend/src/components/layout/Layout.tsx +12 -23
  359. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  360. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  361. package/frontend/src/components/layout/index.ts +5 -0
  362. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  363. package/frontend/src/components/radix/index.ts +22 -0
  364. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  365. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  366. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  367. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  368. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  369. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  370. package/frontend/src/features/ai/components/index.ts +6 -0
  371. package/frontend/src/features/ai/helpers.ts +57 -71
  372. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  373. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  374. package/frontend/src/features/ai/pages/AIPage.tsx +166 -0
  375. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  376. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  377. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +54 -30
  378. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  379. package/frontend/src/features/auth/components/UsersDataGrid.tsx +50 -14
  380. package/frontend/src/features/auth/components/index.ts +5 -0
  381. package/frontend/src/features/auth/helpers.tsx +208 -0
  382. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  383. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  384. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  385. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  386. package/frontend/src/features/auth/index.ts +3 -2
  387. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -0
  388. package/frontend/src/features/auth/pages/ConfigurationPage.tsx +395 -0
  389. package/frontend/src/features/auth/pages/UsersPage.tsx +257 -0
  390. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  391. package/frontend/src/features/auth/services/config.service.ts +19 -0
  392. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  393. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  394. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  395. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  396. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  397. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  398. package/frontend/src/features/dashboard/components/index.ts +4 -0
  399. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +212 -0
  400. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  401. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  402. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  403. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  404. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  405. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  406. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  407. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  408. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  409. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  410. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  411. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  412. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  413. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  414. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  415. package/frontend/src/features/database/components/TableForm.tsx +28 -19
  416. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  417. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  418. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  419. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  420. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  421. package/frontend/src/features/database/components/index.ts +19 -0
  422. package/frontend/src/features/database/constants.ts +28 -2
  423. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  424. package/frontend/src/features/database/helpers.ts +2 -2
  425. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  426. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  427. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  428. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  429. package/frontend/src/features/database/hooks/useTables.ts +135 -0
  430. package/frontend/src/features/database/index.ts +7 -1
  431. package/frontend/src/features/database/pages/FunctionsPage.tsx +203 -0
  432. package/frontend/src/features/database/pages/IndexesPage.tsx +228 -0
  433. package/frontend/src/features/database/pages/PoliciesPage.tsx +237 -0
  434. package/frontend/src/features/database/pages/SQLEditorPage.tsx +382 -0
  435. package/frontend/src/features/database/{page/DatabasePage.tsx → pages/TablesPage.tsx} +168 -209
  436. package/frontend/src/features/database/pages/TemplatesPage.tsx +39 -0
  437. package/frontend/src/features/database/pages/TriggersPage.tsx +230 -0
  438. package/frontend/src/features/database/services/advance.service.ts +40 -0
  439. package/frontend/src/features/database/services/database.service.ts +33 -194
  440. package/frontend/src/features/database/services/record.service.ts +219 -0
  441. package/frontend/src/features/database/services/table.service.ts +58 -0
  442. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  443. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  444. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  445. package/frontend/src/features/database/templates/index.ts +34 -0
  446. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  447. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  448. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  449. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  450. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  451. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  452. package/frontend/src/features/functions/components/index.ts +5 -0
  453. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  454. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  455. package/frontend/src/features/functions/pages/FunctionsPage.tsx +148 -0
  456. package/frontend/src/features/functions/{components/SecretsContent.tsx → pages/SecretsPage.tsx} +19 -21
  457. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  458. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  459. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  460. package/frontend/src/features/login/pages/CloudLoginPage.tsx +118 -0
  461. package/frontend/src/features/login/{page → pages}/LoginPage.tsx +16 -23
  462. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  463. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  464. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  465. package/frontend/src/features/logs/components/index.ts +2 -0
  466. package/frontend/src/features/logs/helpers.ts +24 -0
  467. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  468. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  469. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  470. package/frontend/src/features/logs/hooks/useMcpUsage.ts +128 -0
  471. package/frontend/src/features/logs/index.ts +8 -2
  472. package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +91 -38
  473. package/frontend/src/features/logs/pages/LogsPage.tsx +152 -0
  474. package/frontend/src/features/logs/pages/MCPLogsPage.tsx +84 -0
  475. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  476. package/frontend/src/features/logs/services/log.service.ts +15 -110
  477. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  478. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  479. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  480. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  481. package/frontend/src/features/onboard/components/index.ts +4 -0
  482. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  483. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  484. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  485. package/frontend/src/features/onboard/index.ts +13 -3
  486. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  487. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  488. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  489. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  490. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  491. package/frontend/src/features/realtime/index.ts +11 -0
  492. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  493. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  494. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  495. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  496. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  497. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  498. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  499. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  500. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  501. package/frontend/src/features/storage/components/index.ts +12 -0
  502. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  503. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +41 -143
  504. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  505. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  506. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  507. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  508. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  509. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  510. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +97 -0
  511. package/frontend/src/index.css +1 -0
  512. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  513. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  514. package/frontend/src/lib/contexts/SocketContext.tsx +123 -80
  515. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  516. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  517. package/frontend/src/lib/routing/AppRoutes.tsx +99 -0
  518. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  519. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  520. package/frontend/src/lib/utils/menuItems.ts +207 -0
  521. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  522. package/frontend/src/lib/utils/utils.ts +32 -1
  523. package/frontend/src/vite-env.d.ts +1 -0
  524. package/frontend/tsconfig.json +25 -25
  525. package/frontend/tsconfig.node.json +9 -9
  526. package/frontend/vite.config.ts +5 -3
  527. package/functions/deno.json +24 -24
  528. package/functions/server.ts +315 -290
  529. package/functions/worker-template.js +15 -4
  530. package/i18n/README.ar.md +130 -0
  531. package/i18n/README.de.md +130 -0
  532. package/i18n/README.es.md +154 -0
  533. package/i18n/README.fr.md +134 -0
  534. package/i18n/README.hi.md +129 -0
  535. package/i18n/README.ja.md +174 -0
  536. package/i18n/README.ko.md +137 -0
  537. package/i18n/README.pt-BR.md +131 -0
  538. package/i18n/README.ru.md +129 -0
  539. package/i18n/README.zh-CN.md +133 -0
  540. package/openapi/ai.yaml +715 -688
  541. package/openapi/auth.yaml +1244 -563
  542. package/openapi/email.yaml +158 -0
  543. package/openapi/functions.yaml +475 -475
  544. package/openapi/health.yaml +29 -29
  545. package/openapi/logs.yaml +223 -223
  546. package/openapi/metadata.yaml +177 -177
  547. package/openapi/realtime.yaml +699 -0
  548. package/openapi/records.yaml +381 -381
  549. package/openapi/secrets.yaml +370 -370
  550. package/openapi/storage.yaml +875 -875
  551. package/openapi/tables.yaml +463 -463
  552. package/package.json +97 -88
  553. package/shared-schemas/package.json +31 -31
  554. package/shared-schemas/src/ai-api.schema.ts +34 -58
  555. package/shared-schemas/src/ai.schema.ts +63 -54
  556. package/shared-schemas/src/auth-api.schema.ts +352 -193
  557. package/shared-schemas/src/auth.schema.ts +43 -7
  558. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  559. package/shared-schemas/src/database-api.schema.ts +35 -4
  560. package/shared-schemas/src/database.schema.ts +40 -1
  561. package/shared-schemas/src/docs.schema.ts +26 -0
  562. package/shared-schemas/src/email-api.schema.ts +30 -0
  563. package/shared-schemas/src/index.ts +5 -0
  564. package/shared-schemas/src/logs-api.schema.ts +7 -1
  565. package/shared-schemas/src/logs.schema.ts +26 -0
  566. package/shared-schemas/src/metadata.schema.ts +18 -4
  567. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  568. package/shared-schemas/src/realtime.schema.ts +143 -0
  569. package/shared-schemas/tsconfig.json +21 -21
  570. package/tsconfig.json +7 -7
  571. package/zeabur/README.md +13 -0
  572. package/zeabur/template.yml +1032 -0
  573. package/.github/workflows/deploy-aws.yml +0 -130
  574. package/backend/src/api/routes/agent.ts +0 -29
  575. package/backend/src/api/routes/auth.oauth.ts +0 -482
  576. package/backend/src/api/routes/auth.ts +0 -386
  577. package/backend/src/api/routes/docs.ts +0 -66
  578. package/backend/src/api/routes/functions.ts +0 -183
  579. package/backend/src/api/routes/openapi.ts +0 -82
  580. package/backend/src/api/routes/usage.ts +0 -96
  581. package/backend/src/core/ai/client.ts +0 -242
  582. package/backend/src/core/ai/model.ts +0 -117
  583. package/backend/src/core/auth/auth.ts +0 -780
  584. package/backend/src/core/database/manager.ts +0 -178
  585. package/backend/src/core/database/table.ts +0 -772
  586. package/backend/src/core/documentation/agent.ts +0 -689
  587. package/backend/src/core/documentation/openapi.ts +0 -856
  588. package/backend/src/core/logs/analytics.ts +0 -76
  589. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  590. package/backend/src/core/socket/socket.ts +0 -388
  591. package/backend/src/core/storage/storage.ts +0 -923
  592. package/backend/src/utils/cloud-token.ts +0 -39
  593. package/backend/src/utils/helpers.ts +0 -49
  594. package/backend/src/utils/uuid.ts +0 -9
  595. package/backend/tests/manual/test-better-auth.sh +0 -303
  596. package/docker-init/db/logs.sql +0 -9
  597. package/frontend/README.md +0 -112
  598. package/frontend/src/components/datagrid/index.tsx +0 -20
  599. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  600. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  601. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  602. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  603. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  604. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  605. package/frontend/src/features/ai/page/AIPage.tsx +0 -178
  606. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  607. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  608. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  609. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  610. package/frontend/src/features/dashboard/page/DashboardPage.tsx +0 -194
  611. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  612. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  613. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  614. package/frontend/src/features/functions/page/FunctionsPage.tsx +0 -28
  615. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  616. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  617. package/frontend/src/features/login/page/CloudLoginPage.tsx +0 -93
  618. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  619. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  620. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  621. package/frontend/src/features/metadata/index.ts +0 -0
  622. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  623. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  624. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  625. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  626. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  627. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  628. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  629. package/frontend/src/features/onboard/types.ts +0 -8
  630. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +0 -127
  631. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  632. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  633. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  634. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  635. /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
@@ -0,0 +1,697 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { analyzeQuery, initSqlParser } from '../../src/utils/sql-parser.js';
3
+
4
+ beforeAll(async () => {
5
+ await initSqlParser();
6
+ });
7
+
8
+ describe('analyzeQuery', () => {
9
+ // ===================
10
+ // RECORDS (DML) - with table name
11
+ // ===================
12
+ describe('records - INSERT', () => {
13
+ it('simple insert', () => {
14
+ expect(analyzeQuery('INSERT INTO users (name) VALUES ("john")')).toEqual([
15
+ { type: 'records', name: 'users' },
16
+ ]);
17
+ });
18
+
19
+ it('insert with schema', () => {
20
+ expect(analyzeQuery('INSERT INTO public.users (name) VALUES ("john")')).toEqual([
21
+ { type: 'records', name: 'users' },
22
+ ]);
23
+ });
24
+
25
+ it('insert multiple rows', () => {
26
+ expect(analyzeQuery("INSERT INTO products (name) VALUES ('a'), ('b'), ('c')")).toEqual([
27
+ { type: 'records', name: 'products' },
28
+ ]);
29
+ });
30
+
31
+ it('insert with returning', () => {
32
+ expect(analyzeQuery('INSERT INTO orders (total) VALUES (100) RETURNING id')).toEqual([
33
+ { type: 'records', name: 'orders' },
34
+ ]);
35
+ });
36
+
37
+ it('insert with on conflict', () => {
38
+ expect(
39
+ analyzeQuery(
40
+ 'INSERT INTO users (id, name) VALUES (1, "john") ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name'
41
+ )
42
+ ).toEqual([{ type: 'records', name: 'users' }]);
43
+ });
44
+
45
+ it('insert from select', () => {
46
+ expect(
47
+ analyzeQuery('INSERT INTO archive SELECT * FROM logs WHERE created_at < NOW()')
48
+ ).toEqual([{ type: 'records', name: 'archive' }]);
49
+ });
50
+ });
51
+
52
+ describe('records - UPDATE', () => {
53
+ it('simple update', () => {
54
+ expect(analyzeQuery('UPDATE users SET name = "jane" WHERE id = 1')).toEqual([
55
+ { type: 'records', name: 'users' },
56
+ ]);
57
+ });
58
+
59
+ it('update with schema', () => {
60
+ expect(analyzeQuery('UPDATE public.posts SET title = "new" WHERE id = 1')).toEqual([
61
+ { type: 'records', name: 'posts' },
62
+ ]);
63
+ });
64
+
65
+ it('update multiple columns', () => {
66
+ expect(
67
+ analyzeQuery(
68
+ 'UPDATE users SET name = "jane", email = "jane@test.com", updated_at = NOW() WHERE id = 1'
69
+ )
70
+ ).toEqual([{ type: 'records', name: 'users' }]);
71
+ });
72
+
73
+ it('update with subquery', () => {
74
+ expect(
75
+ analyzeQuery(
76
+ 'UPDATE orders SET status = "shipped" WHERE id IN (SELECT order_id FROM shipments)'
77
+ )
78
+ ).toEqual([{ type: 'records', name: 'orders' }]);
79
+ });
80
+
81
+ it('update with returning', () => {
82
+ expect(analyzeQuery('UPDATE products SET price = price * 1.1 RETURNING id, price')).toEqual([
83
+ { type: 'records', name: 'products' },
84
+ ]);
85
+ });
86
+ });
87
+
88
+ describe('records - DELETE', () => {
89
+ it('simple delete', () => {
90
+ expect(analyzeQuery('DELETE FROM users WHERE id = 1')).toEqual([
91
+ { type: 'records', name: 'users' },
92
+ ]);
93
+ });
94
+
95
+ it('delete with schema', () => {
96
+ expect(analyzeQuery('DELETE FROM public.logs WHERE created_at < NOW()')).toEqual([
97
+ { type: 'records', name: 'logs' },
98
+ ]);
99
+ });
100
+
101
+ it('delete all', () => {
102
+ expect(analyzeQuery('DELETE FROM temp_table')).toEqual([
103
+ { type: 'records', name: 'temp_table' },
104
+ ]);
105
+ });
106
+
107
+ it('delete with subquery', () => {
108
+ expect(
109
+ analyzeQuery(
110
+ 'DELETE FROM orders WHERE user_id IN (SELECT id FROM users WHERE banned = true)'
111
+ )
112
+ ).toEqual([{ type: 'records', name: 'orders' }]);
113
+ });
114
+
115
+ it('delete with returning', () => {
116
+ expect(analyzeQuery('DELETE FROM sessions WHERE expired = true RETURNING id')).toEqual([
117
+ { type: 'records', name: 'sessions' },
118
+ ]);
119
+ });
120
+ });
121
+
122
+ // ===================
123
+ // TABLES (CREATE/DROP) - no name
124
+ // ===================
125
+ describe('tables - CREATE/DROP TABLE', () => {
126
+ it('create table', () => {
127
+ expect(analyzeQuery('CREATE TABLE users (id INT)')).toEqual([{ type: 'tables' }]);
128
+ });
129
+
130
+ it('create table if not exists', () => {
131
+ expect(analyzeQuery('CREATE TABLE IF NOT EXISTS posts (id UUID PRIMARY KEY)')).toEqual([
132
+ { type: 'tables' },
133
+ ]);
134
+ });
135
+
136
+ it('create table with schema', () => {
137
+ expect(analyzeQuery('CREATE TABLE public.comments (id SERIAL PRIMARY KEY)')).toEqual([
138
+ { type: 'tables' },
139
+ ]);
140
+ });
141
+
142
+ it('create table with constraints', () => {
143
+ expect(
144
+ analyzeQuery(
145
+ 'CREATE TABLE orders (id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), total DECIMAL NOT NULL)'
146
+ )
147
+ ).toEqual([{ type: 'tables' }]);
148
+ });
149
+
150
+ it('drop table', () => {
151
+ expect(analyzeQuery('DROP TABLE users')).toEqual([{ type: 'tables' }]);
152
+ });
153
+
154
+ it('drop table if exists', () => {
155
+ expect(analyzeQuery('DROP TABLE IF EXISTS temp_data')).toEqual([{ type: 'tables' }]);
156
+ });
157
+
158
+ it('drop table cascade', () => {
159
+ expect(analyzeQuery('DROP TABLE orders CASCADE')).toEqual([{ type: 'tables' }]);
160
+ });
161
+ });
162
+
163
+ // ===================
164
+ // TABLE (ALTER) - with name
165
+ // ===================
166
+ describe('table - ALTER TABLE', () => {
167
+ it('add column', () => {
168
+ expect(analyzeQuery('ALTER TABLE users ADD COLUMN email VARCHAR(255)')).toEqual([
169
+ { type: 'table', name: 'users' },
170
+ ]);
171
+ });
172
+
173
+ it('drop column', () => {
174
+ expect(analyzeQuery('ALTER TABLE posts DROP COLUMN temp_field')).toEqual([
175
+ { type: 'table', name: 'posts' },
176
+ ]);
177
+ });
178
+
179
+ it('rename column', () => {
180
+ expect(analyzeQuery('ALTER TABLE users RENAME COLUMN name TO full_name')).toEqual([
181
+ { type: 'table', name: 'users' },
182
+ ]);
183
+ });
184
+
185
+ it('alter column type', () => {
186
+ expect(analyzeQuery('ALTER TABLE products ALTER COLUMN price TYPE NUMERIC(10,2)')).toEqual([
187
+ { type: 'table', name: 'products' },
188
+ ]);
189
+ });
190
+
191
+ it('add constraint', () => {
192
+ expect(
193
+ analyzeQuery(
194
+ 'ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)'
195
+ )
196
+ ).toEqual([{ type: 'table', name: 'orders' }]);
197
+ });
198
+
199
+ it('enable RLS', () => {
200
+ expect(analyzeQuery('ALTER TABLE users ENABLE ROW LEVEL SECURITY')).toEqual([
201
+ { type: 'table', name: 'users' },
202
+ ]);
203
+ });
204
+
205
+ it('disable RLS', () => {
206
+ expect(analyzeQuery('ALTER TABLE users DISABLE ROW LEVEL SECURITY')).toEqual([
207
+ { type: 'table', name: 'users' },
208
+ ]);
209
+ });
210
+
211
+ it('force RLS', () => {
212
+ expect(analyzeQuery('ALTER TABLE users FORCE ROW LEVEL SECURITY')).toEqual([
213
+ { type: 'table', name: 'users' },
214
+ ]);
215
+ });
216
+
217
+ it('set not null', () => {
218
+ expect(analyzeQuery('ALTER TABLE users ALTER COLUMN email SET NOT NULL')).toEqual([
219
+ { type: 'table', name: 'users' },
220
+ ]);
221
+ });
222
+
223
+ it('set default', () => {
224
+ expect(analyzeQuery('ALTER TABLE users ALTER COLUMN created_at SET DEFAULT NOW()')).toEqual([
225
+ { type: 'table', name: 'users' },
226
+ ]);
227
+ });
228
+ });
229
+
230
+ // ===================
231
+ // INDEX - no name
232
+ // ===================
233
+ describe('index', () => {
234
+ it('create index', () => {
235
+ expect(analyzeQuery('CREATE INDEX idx_users_email ON users (email)')).toEqual([
236
+ { type: 'index' },
237
+ ]);
238
+ });
239
+
240
+ it('create unique index', () => {
241
+ expect(analyzeQuery('CREATE UNIQUE INDEX idx_users_email ON users (email)')).toEqual([
242
+ { type: 'index' },
243
+ ]);
244
+ });
245
+
246
+ it('create index concurrently', () => {
247
+ expect(
248
+ analyzeQuery('CREATE INDEX CONCURRENTLY idx_orders_date ON orders (created_at)')
249
+ ).toEqual([{ type: 'index' }]);
250
+ });
251
+
252
+ it('create partial index', () => {
253
+ expect(
254
+ analyzeQuery('CREATE INDEX idx_active_users ON users (id) WHERE active = true')
255
+ ).toEqual([{ type: 'index' }]);
256
+ });
257
+
258
+ it('create index with expression', () => {
259
+ expect(analyzeQuery('CREATE INDEX idx_users_lower_email ON users (LOWER(email))')).toEqual([
260
+ { type: 'index' },
261
+ ]);
262
+ });
263
+
264
+ it('drop index', () => {
265
+ expect(analyzeQuery('DROP INDEX idx_users_email')).toEqual([{ type: 'index' }]);
266
+ });
267
+
268
+ it('drop index if exists', () => {
269
+ expect(analyzeQuery('DROP INDEX IF EXISTS idx_old')).toEqual([{ type: 'index' }]);
270
+ });
271
+ });
272
+
273
+ // ===================
274
+ // TRIGGER - no name
275
+ // ===================
276
+ describe('trigger', () => {
277
+ it('create trigger before insert', () => {
278
+ expect(
279
+ analyzeQuery(
280
+ 'CREATE TRIGGER set_timestamp BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION update_modified()'
281
+ )
282
+ ).toEqual([{ type: 'trigger' }]);
283
+ });
284
+
285
+ it('create trigger after update', () => {
286
+ expect(
287
+ analyzeQuery(
288
+ 'CREATE TRIGGER audit_log AFTER UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION log_changes()'
289
+ )
290
+ ).toEqual([{ type: 'trigger' }]);
291
+ });
292
+
293
+ it('create trigger before delete', () => {
294
+ expect(
295
+ analyzeQuery(
296
+ 'CREATE TRIGGER prevent_delete BEFORE DELETE ON users FOR EACH ROW EXECUTE FUNCTION check_delete()'
297
+ )
298
+ ).toEqual([{ type: 'trigger' }]);
299
+ });
300
+
301
+ it('create or replace trigger', () => {
302
+ expect(
303
+ analyzeQuery(
304
+ 'CREATE OR REPLACE TRIGGER update_ts BEFORE UPDATE ON posts FOR EACH ROW EXECUTE FUNCTION update_modified()'
305
+ )
306
+ ).toEqual([{ type: 'trigger' }]);
307
+ });
308
+
309
+ it('drop trigger', () => {
310
+ expect(analyzeQuery('DROP TRIGGER update_timestamp ON users')).toEqual([{ type: 'trigger' }]);
311
+ });
312
+
313
+ it('drop trigger if exists', () => {
314
+ expect(analyzeQuery('DROP TRIGGER IF EXISTS old_trigger ON users')).toEqual([
315
+ { type: 'trigger' },
316
+ ]);
317
+ });
318
+ });
319
+
320
+ // ===================
321
+ // POLICY - no name
322
+ // ===================
323
+ describe('policy', () => {
324
+ it('create policy for select', () => {
325
+ expect(
326
+ analyzeQuery('CREATE POLICY user_select ON users FOR SELECT USING (id = current_user_id())')
327
+ ).toEqual([{ type: 'policy' }]);
328
+ });
329
+
330
+ it('create policy for insert', () => {
331
+ expect(
332
+ analyzeQuery('CREATE POLICY user_insert ON users FOR INSERT WITH CHECK (true)')
333
+ ).toEqual([{ type: 'policy' }]);
334
+ });
335
+
336
+ it('create policy for update', () => {
337
+ expect(
338
+ analyzeQuery(
339
+ 'CREATE POLICY user_update ON users FOR UPDATE USING (id = current_user_id()) WITH CHECK (id = current_user_id())'
340
+ )
341
+ ).toEqual([{ type: 'policy' }]);
342
+ });
343
+
344
+ it('create policy for delete', () => {
345
+ expect(
346
+ analyzeQuery('CREATE POLICY user_delete ON users FOR DELETE USING (id = current_user_id())')
347
+ ).toEqual([{ type: 'policy' }]);
348
+ });
349
+
350
+ it('create policy for all', () => {
351
+ expect(
352
+ analyzeQuery(
353
+ 'CREATE POLICY full_access ON orders FOR ALL USING (user_id = current_user_id())'
354
+ )
355
+ ).toEqual([{ type: 'policy' }]);
356
+ });
357
+
358
+ it('create permissive policy', () => {
359
+ expect(
360
+ analyzeQuery(
361
+ 'CREATE POLICY admin_access ON users AS PERMISSIVE FOR ALL TO admin USING (true)'
362
+ )
363
+ ).toEqual([{ type: 'policy' }]);
364
+ });
365
+
366
+ it('create restrictive policy', () => {
367
+ expect(
368
+ analyzeQuery(
369
+ 'CREATE POLICY tenant_isolation ON orders AS RESTRICTIVE FOR ALL USING (tenant_id = current_tenant())'
370
+ )
371
+ ).toEqual([{ type: 'policy' }]);
372
+ });
373
+
374
+ it('alter policy', () => {
375
+ expect(
376
+ analyzeQuery(
377
+ 'ALTER POLICY user_select ON users USING (id = current_user_id() OR is_admin())'
378
+ )
379
+ ).toEqual([{ type: 'policy' }]);
380
+ });
381
+
382
+ it('drop policy', () => {
383
+ expect(analyzeQuery('DROP POLICY user_policy ON users')).toEqual([{ type: 'policy' }]);
384
+ });
385
+
386
+ it('drop policy if exists', () => {
387
+ expect(analyzeQuery('DROP POLICY IF EXISTS old_policy ON users')).toEqual([
388
+ { type: 'policy' },
389
+ ]);
390
+ });
391
+ });
392
+
393
+ // ===================
394
+ // FUNCTION - no name
395
+ // ===================
396
+ describe('function', () => {
397
+ it('create function sql', () => {
398
+ expect(
399
+ analyzeQuery(
400
+ 'CREATE FUNCTION get_user(id INT) RETURNS TEXT AS $$ SELECT name FROM users WHERE id = $1 $$ LANGUAGE sql'
401
+ )
402
+ ).toEqual([{ type: 'function' }]);
403
+ });
404
+
405
+ it('create function plpgsql', () => {
406
+ expect(
407
+ analyzeQuery(
408
+ 'CREATE FUNCTION update_modified() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql'
409
+ )
410
+ ).toEqual([{ type: 'function' }]);
411
+ });
412
+
413
+ it('create or replace function', () => {
414
+ expect(
415
+ analyzeQuery(
416
+ 'CREATE OR REPLACE FUNCTION current_user_id() RETURNS UUID AS $$ SELECT auth.uid() $$ LANGUAGE sql'
417
+ )
418
+ ).toEqual([{ type: 'function' }]);
419
+ });
420
+
421
+ it('create function with security definer', () => {
422
+ expect(
423
+ analyzeQuery(
424
+ 'CREATE FUNCTION admin_action() RETURNS VOID AS $$ UPDATE users SET role = "admin" $$ LANGUAGE sql SECURITY DEFINER'
425
+ )
426
+ ).toEqual([{ type: 'function' }]);
427
+ });
428
+
429
+ it('drop function', () => {
430
+ expect(analyzeQuery('DROP FUNCTION get_user')).toEqual([{ type: 'function' }]);
431
+ });
432
+
433
+ it('drop function with args', () => {
434
+ expect(analyzeQuery('DROP FUNCTION get_user(INT)')).toEqual([{ type: 'function' }]);
435
+ });
436
+
437
+ it('drop function if exists', () => {
438
+ expect(analyzeQuery('DROP FUNCTION IF EXISTS old_func')).toEqual([{ type: 'function' }]);
439
+ });
440
+ });
441
+
442
+ // ===================
443
+ // EXTENSION - no name
444
+ // ===================
445
+ describe('extension', () => {
446
+ it('create extension', () => {
447
+ expect(analyzeQuery('CREATE EXTENSION "uuid-ossp"')).toEqual([{ type: 'extension' }]);
448
+ });
449
+
450
+ it('create extension if not exists', () => {
451
+ expect(analyzeQuery('CREATE EXTENSION IF NOT EXISTS pgcrypto')).toEqual([
452
+ { type: 'extension' },
453
+ ]);
454
+ });
455
+
456
+ it('create extension with schema', () => {
457
+ expect(analyzeQuery('CREATE EXTENSION hstore WITH SCHEMA public')).toEqual([
458
+ { type: 'extension' },
459
+ ]);
460
+ });
461
+
462
+ it('drop extension', () => {
463
+ expect(analyzeQuery('DROP EXTENSION "uuid-ossp"')).toEqual([{ type: 'extension' }]);
464
+ });
465
+
466
+ it('drop extension cascade', () => {
467
+ expect(analyzeQuery('DROP EXTENSION pgcrypto CASCADE')).toEqual([{ type: 'extension' }]);
468
+ });
469
+ });
470
+
471
+ // ===================
472
+ // SELECT - ignored
473
+ // ===================
474
+ describe('SELECT (ignored)', () => {
475
+ it('simple select', () => {
476
+ expect(analyzeQuery('SELECT * FROM users')).toEqual([]);
477
+ });
478
+
479
+ it('select with joins', () => {
480
+ expect(
481
+ analyzeQuery('SELECT u.*, p.title FROM users u JOIN posts p ON u.id = p.user_id')
482
+ ).toEqual([]);
483
+ });
484
+
485
+ it('select with subquery', () => {
486
+ expect(analyzeQuery('SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)')).toEqual(
487
+ []
488
+ );
489
+ });
490
+
491
+ it('select with cte', () => {
492
+ expect(
493
+ analyzeQuery(
494
+ 'WITH active_users AS (SELECT * FROM users WHERE active) SELECT * FROM active_users'
495
+ )
496
+ ).toEqual([]);
497
+ });
498
+
499
+ it('select for update (still ignored)', () => {
500
+ expect(analyzeQuery('SELECT * FROM users WHERE id = 1 FOR UPDATE')).toEqual([]);
501
+ });
502
+ });
503
+
504
+ // ===================
505
+ // MULTI-STATEMENT
506
+ // ===================
507
+ describe('multi-statement', () => {
508
+ it('multiple inserts', () => {
509
+ const result = analyzeQuery(
510
+ "INSERT INTO users (name) VALUES ('a'); INSERT INTO posts (title) VALUES ('b');"
511
+ );
512
+ expect(result).toEqual([
513
+ { type: 'records', name: 'users' },
514
+ { type: 'records', name: 'posts' },
515
+ ]);
516
+ });
517
+
518
+ it('create table + insert', () => {
519
+ const result = analyzeQuery('CREATE TABLE temp (id INT); INSERT INTO temp VALUES (1);');
520
+ expect(result).toEqual([{ type: 'tables' }, { type: 'records', name: 'temp' }]);
521
+ });
522
+
523
+ it('full table setup with RLS', () => {
524
+ const result = analyzeQuery(`
525
+ CREATE TABLE orders (id UUID PRIMARY KEY, user_id UUID NOT NULL);
526
+ ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
527
+ CREATE POLICY orders_select ON orders FOR SELECT USING (user_id = auth.uid());
528
+ CREATE INDEX idx_orders_user ON orders (user_id);
529
+ `);
530
+ expect(result).toHaveLength(4);
531
+ expect(result[0]).toEqual({ type: 'tables' });
532
+ expect(result[1]).toEqual({ type: 'table', name: 'orders' });
533
+ expect(result[2]).toEqual({ type: 'policy' });
534
+ expect(result[3]).toEqual({ type: 'index' });
535
+ });
536
+
537
+ it('migration: drop and recreate', () => {
538
+ const result = analyzeQuery(`
539
+ DROP TABLE IF EXISTS old_data;
540
+ DROP INDEX IF EXISTS idx_old;
541
+ DROP POLICY IF EXISTS old_policy ON users;
542
+ CREATE TABLE new_data (id INT);
543
+ ALTER TABLE users ADD COLUMN new_col TEXT;
544
+ `);
545
+ // tables is deduplicated (DROP + CREATE both emit 'tables')
546
+ expect(result).toHaveLength(4);
547
+ expect(result).toContainEqual({ type: 'tables' });
548
+ expect(result).toContainEqual({ type: 'index' });
549
+ expect(result).toContainEqual({ type: 'policy' });
550
+ expect(result).toContainEqual({ type: 'table', name: 'users' });
551
+ });
552
+
553
+ it('batch DML operations', () => {
554
+ const result = analyzeQuery(`
555
+ INSERT INTO products (name) VALUES ('new');
556
+ UPDATE products SET price = 100 WHERE name = 'new';
557
+ DELETE FROM products WHERE stock = 0;
558
+ `);
559
+ // Deduplicated to single entry
560
+ expect(result).toEqual([{ type: 'records', name: 'products' }]);
561
+ });
562
+
563
+ it('mixed with selects (selects ignored)', () => {
564
+ const result = analyzeQuery(`
565
+ SELECT * FROM users;
566
+ INSERT INTO logs (action) VALUES ('viewed');
567
+ SELECT COUNT(*) FROM logs;
568
+ `);
569
+ expect(result).toEqual([{ type: 'records', name: 'logs' }]);
570
+ });
571
+
572
+ it('complete app setup', () => {
573
+ const result = analyzeQuery(`
574
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
575
+ CREATE TABLE users (id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, email TEXT UNIQUE);
576
+ CREATE TABLE posts (id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), title TEXT);
577
+ CREATE INDEX idx_posts_user ON posts (user_id);
578
+ ALTER TABLE users ENABLE ROW LEVEL SECURITY;
579
+ ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
580
+ CREATE POLICY users_policy ON users FOR ALL USING (id = auth.uid());
581
+ CREATE POLICY posts_policy ON posts FOR ALL USING (user_id = auth.uid());
582
+ CREATE FUNCTION auth.uid() RETURNS UUID AS $$ SELECT current_setting('app.user_id')::UUID $$ LANGUAGE sql;
583
+ `);
584
+ // Deduplicated: 2 CREATE TABLEs -> 1 'tables', 2 CREATE POLICYs -> 1 'policy'
585
+ expect(result).toHaveLength(7);
586
+ expect(result.filter((r) => r.type === 'extension')).toHaveLength(1);
587
+ expect(result.filter((r) => r.type === 'tables')).toHaveLength(1);
588
+ expect(result.filter((r) => r.type === 'index')).toHaveLength(1);
589
+ expect(result.filter((r) => r.type === 'table')).toHaveLength(2); // ALTER users + ALTER posts (different names)
590
+ expect(result.filter((r) => r.type === 'policy')).toHaveLength(1);
591
+ expect(result.filter((r) => r.type === 'function')).toHaveLength(1);
592
+ });
593
+ });
594
+
595
+ // ===================
596
+ // EDGE CASES
597
+ // ===================
598
+ describe('edge cases', () => {
599
+ it('empty string', () => {
600
+ expect(analyzeQuery('')).toEqual([]);
601
+ });
602
+
603
+ it('whitespace only', () => {
604
+ expect(analyzeQuery(' \n\t ')).toEqual([]);
605
+ });
606
+
607
+ it('comment only', () => {
608
+ expect(analyzeQuery('-- this is a comment')).toEqual([]);
609
+ });
610
+
611
+ it('block comment only', () => {
612
+ expect(analyzeQuery('/* block comment */')).toEqual([]);
613
+ });
614
+
615
+ it('invalid SQL', () => {
616
+ expect(analyzeQuery('THIS IS NOT SQL')).toEqual([]);
617
+ });
618
+
619
+ it('table name with underscore', () => {
620
+ expect(analyzeQuery('INSERT INTO user_profiles (name) VALUES ("test")')).toEqual([
621
+ { type: 'records', name: 'user_profiles' },
622
+ ]);
623
+ });
624
+
625
+ it('table name with quotes', () => {
626
+ expect(analyzeQuery('INSERT INTO "user-data" (name) VALUES ("test")')).toEqual([
627
+ { type: 'records', name: 'user-data' },
628
+ ]);
629
+ });
630
+
631
+ it('schema qualified table', () => {
632
+ expect(analyzeQuery('UPDATE myschema.mytable SET x = 1')).toEqual([
633
+ { type: 'records', name: 'mytable' },
634
+ ]);
635
+ });
636
+
637
+ it('semicolon in string literal', () => {
638
+ expect(analyzeQuery("INSERT INTO logs (msg) VALUES ('hello; world')")).toEqual([
639
+ { type: 'records', name: 'logs' },
640
+ ]);
641
+ });
642
+ });
643
+
644
+ // ===================
645
+ // DEDUPLICATION
646
+ // ===================
647
+ describe('deduplication', () => {
648
+ it('deduplicates multiple inserts to same table', () => {
649
+ const result = analyzeQuery(`
650
+ INSERT INTO users (name) VALUES ('a');
651
+ INSERT INTO users (name) VALUES ('b');
652
+ INSERT INTO users (name) VALUES ('c');
653
+ `);
654
+ expect(result).toEqual([{ type: 'records', name: 'users' }]);
655
+ });
656
+
657
+ it('deduplicates multiple updates to same table', () => {
658
+ const result = analyzeQuery(`
659
+ UPDATE products SET price = 10 WHERE id = 1;
660
+ UPDATE products SET price = 20 WHERE id = 2;
661
+ `);
662
+ expect(result).toEqual([{ type: 'records', name: 'products' }]);
663
+ });
664
+
665
+ it('keeps different tables separate', () => {
666
+ const result = analyzeQuery(`
667
+ INSERT INTO users (name) VALUES ('a');
668
+ INSERT INTO posts (title) VALUES ('b');
669
+ INSERT INTO users (name) VALUES ('c');
670
+ `);
671
+ expect(result).toEqual([
672
+ { type: 'records', name: 'users' },
673
+ { type: 'records', name: 'posts' },
674
+ ]);
675
+ });
676
+
677
+ it('keeps different types separate', () => {
678
+ const result = analyzeQuery(`
679
+ INSERT INTO users (name) VALUES ('a');
680
+ ALTER TABLE users ADD COLUMN foo TEXT;
681
+ `);
682
+ expect(result).toEqual([
683
+ { type: 'records', name: 'users' },
684
+ { type: 'table', name: 'users' },
685
+ ]);
686
+ });
687
+
688
+ it('deduplicates DDL without names', () => {
689
+ const result = analyzeQuery(`
690
+ CREATE INDEX idx1 ON users (a);
691
+ CREATE INDEX idx2 ON users (b);
692
+ CREATE INDEX idx3 ON posts (c);
693
+ `);
694
+ expect(result).toEqual([{ type: 'index' }]);
695
+ });
696
+ });
697
+ });