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.
- package/.claude-plugin/marketplace.json +20 -0
- package/.dockerignore +60 -57
- package/.env.example +84 -49
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -83
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -79
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +66 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -0
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +44 -3
- package/CLAUDE_PLUGIN.md +104 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -27
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -134
- package/assets/Dark.svg +23 -23
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/auth/index.html +13 -0
- package/auth/package.json +28 -0
- package/auth/public/favicon.ico +0 -0
- package/auth/src/App.tsx +33 -0
- package/auth/src/components/ErrorCard.tsx +37 -0
- package/auth/src/components/Layout.tsx +13 -0
- package/auth/src/index.css +19 -0
- package/auth/src/lib/broadcastService.ts +117 -0
- package/auth/src/lib/utils.ts +11 -0
- package/auth/src/main.tsx +22 -0
- package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
- package/auth/src/pages/ResetPasswordPage.tsx +11 -0
- package/auth/src/pages/SignInPage.tsx +60 -0
- package/auth/src/pages/SignUpPage.tsx +60 -0
- package/auth/src/pages/VerifyEmailPage.tsx +20 -0
- package/auth/src/vite-env.d.ts +10 -0
- package/auth/tsconfig.json +32 -0
- package/auth/tsconfig.node.json +11 -0
- package/auth/vite.config.ts +25 -0
- package/backend/package.json +78 -75
- package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
- package/backend/src/api/middlewares/rate-limiters.ts +127 -0
- package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +22 -26
- package/backend/src/api/routes/auth/index.routes.ts +667 -0
- package/backend/src/api/routes/auth/oauth.routes.ts +473 -0
- package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +128 -65
- package/backend/src/api/routes/database/index.routes.ts +90 -0
- package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +26 -12
- package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +6 -23
- package/backend/src/api/routes/docs/index.routes.ts +75 -0
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +194 -0
- package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
- package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +33 -31
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
- package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +48 -61
- package/backend/src/api/routes/usage/index.routes.ts +91 -0
- package/backend/src/infra/config/app.config.ts +51 -0
- package/backend/src/infra/database/database.manager.ts +182 -0
- package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +141 -141
- package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +40 -40
- package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +29 -29
- package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +55 -55
- package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +23 -23
- package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +24 -24
- package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +1 -1
- package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +76 -76
- package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +23 -23
- package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +93 -93
- package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +15 -15
- package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
- package/backend/src/infra/security/token.manager.ts +219 -0
- package/backend/src/infra/socket/socket.manager.ts +522 -0
- package/backend/src/providers/ai/openrouter.provider.ts +380 -0
- package/backend/src/providers/email/base.provider.ts +38 -0
- package/backend/src/providers/email/cloud.provider.ts +271 -0
- package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
- package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
- package/backend/src/providers/logs/local.provider.ts +185 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/base.provider.ts +29 -0
- package/backend/src/providers/oauth/discord.provider.ts +195 -0
- package/backend/src/providers/oauth/facebook.provider.ts +194 -0
- package/backend/src/providers/oauth/github.provider.ts +208 -0
- package/backend/src/providers/oauth/google.provider.ts +249 -0
- package/backend/src/providers/oauth/index.ts +8 -0
- package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
- package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
- package/backend/src/providers/oauth/x.provider.ts +202 -0
- package/backend/src/providers/storage/base.provider.ts +29 -0
- package/backend/src/providers/storage/local.provider.ts +103 -0
- package/backend/src/providers/storage/s3.provider.ts +313 -0
- package/backend/src/server.ts +317 -288
- package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
- package/backend/src/services/ai/ai-model.service.ts +60 -0
- package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
- package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
- package/backend/src/services/ai/helpers.ts +64 -0
- package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
- package/backend/src/services/ai/index.ts +13 -0
- package/backend/src/services/auth/auth-config.service.ts +250 -0
- package/backend/src/services/auth/auth-otp.service.ts +424 -0
- package/backend/src/services/auth/auth.service.ts +1150 -0
- package/backend/src/services/auth/index.ts +4 -0
- package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
- package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
- package/backend/src/services/database/database-table.service.ts +802 -0
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/email/email.service.ts +73 -0
- package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
- package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
- package/backend/src/services/logs/log.service.ts +73 -0
- package/backend/src/services/realtime/index.ts +3 -0
- package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
- package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
- package/backend/src/services/realtime/realtime-message.service.ts +260 -0
- package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
- package/backend/src/services/storage/storage.service.ts +617 -0
- package/backend/src/services/usage/usage.service.ts +149 -0
- package/backend/src/types/auth.ts +77 -2
- package/backend/src/types/email.ts +8 -0
- package/backend/src/types/error-constants.ts +4 -0
- package/backend/src/types/logs.ts +0 -29
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/{core/socket/types.ts → types/socket.ts} +11 -36
- package/backend/src/utils/cookies.ts +35 -0
- package/backend/src/utils/environment.ts +9 -3
- package/backend/src/utils/logger.ts +20 -2
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +301 -205
- package/backend/src/utils/sql-parser.ts +91 -1
- package/backend/src/utils/utils.ts +114 -0
- package/backend/src/utils/validations.ts +40 -4
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -0
- package/backend/tests/local/test-ai-usage.sh +80 -0
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -0
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -0
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-secrets.sh +249 -247
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -0
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -302
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tests/unit/cloud-token.test.ts +48 -0
- package/backend/tests/unit/constant.test.ts +8 -0
- package/backend/tests/unit/email.test.ts +372 -0
- package/backend/tests/unit/environment.test.ts +59 -0
- package/backend/tests/unit/helpers.test.ts +63 -0
- package/backend/tests/unit/logger.test.ts +22 -0
- package/backend/tests/unit/rate-limit.test.ts +154 -0
- package/backend/tests/unit/response.test.ts +58 -0
- package/backend/tests/unit/sql-parser.test.ts +74 -0
- package/backend/tests/unit/uuid.test.ts +21 -0
- package/backend/tests/unit/validations.test.ts +80 -0
- package/backend/tsconfig.json +22 -22
- package/backend/vitest.config.ts +11 -0
- package/claude-plugin/.claude-plugin/plugin.json +24 -0
- package/claude-plugin/README.md +133 -0
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
- package/docker-compose.prod.yml +204 -144
- package/docker-compose.yml +232 -167
- package/docker-init/db/db-init.sql +97 -125
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -0
- package/docs/README.md +44 -0
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +119 -0
- package/docs/core-concepts/ai/architecture.mdx +373 -0
- package/docs/core-concepts/ai/sdk.mdx +213 -0
- package/docs/core-concepts/authentication/architecture.mdx +278 -0
- package/docs/core-concepts/authentication/sdk.mdx +414 -0
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
- package/docs/core-concepts/database/architecture.mdx +256 -0
- package/docs/core-concepts/database/sdk.mdx +382 -0
- package/docs/core-concepts/email/architecture.mdx +101 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -0
- package/docs/core-concepts/functions/sdk.mdx +184 -0
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -0
- package/docs/core-concepts/storage/sdk.mdx +253 -0
- package/docs/deployment/README.md +94 -0
- package/docs/deployment/deploy-to-aws-ec2.md +565 -0
- package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
- package/docs/deployment/deploy-to-render.md +441 -0
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +232 -0
- package/docs/examples/framework-guides/nextjs.mdx +131 -0
- package/docs/examples/framework-guides/nuxt.mdx +165 -0
- package/docs/examples/framework-guides/react.mdx +165 -0
- package/docs/examples/framework-guides/svelte.mdx +153 -0
- package/docs/examples/framework-guides/vue.mdx +159 -0
- package/docs/examples/overview.mdx +67 -0
- package/docs/favicon.svg +19 -0
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
- package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
- package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
- package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
- package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
- package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
- package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
- package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
- package/docs/images/checks-passed.png +0 -0
- package/docs/images/dashboard-connect-expanded.png +0 -0
- package/docs/images/dashboard-connect.png +0 -0
- package/docs/images/hero-dark.png +0 -0
- package/docs/images/hero-light.png +0 -0
- package/docs/images/icons/ai.svg +4 -0
- package/docs/images/icons/auth.svg +1 -0
- package/docs/images/icons/database.svg +1 -0
- package/docs/images/icons/function.svg +1 -0
- package/docs/images/icons/storage.svg +1 -0
- package/docs/images/logos/nextjs.svg +4 -0
- package/docs/images/logos/nuxt.svg +4 -0
- package/docs/images/logos/react.svg +5 -0
- package/docs/images/logos/svelte.svg +4 -0
- package/docs/images/logos/vue.svg +5 -0
- package/docs/images/mcp-install.png +0 -0
- package/docs/images/onboarding-mcp.png +0 -0
- package/docs/insforge-instructions-sdk.md +89 -407
- package/docs/introduction.mdx +45 -0
- package/docs/logo/dark.svg +22 -0
- package/docs/logo/light.svg +20 -0
- package/docs/partnership.mdx +652 -0
- package/docs/quickstart.mdx +83 -0
- package/docs/showcase/2048-arena.png +0 -0
- package/docs/showcase/framegen-cloud.png +0 -0
- package/docs/showcase/line-connect-race.png +0 -0
- package/docs/showcase/moment-vibe.png +0 -0
- package/docs/showcase/national-flags.png +0 -0
- package/docs/showcase/pokemon-vibe.png +0 -0
- package/docs/showcase/pure-browse-buy.png +0 -0
- package/docs/showcase.mdx +52 -0
- package/docs/snippets/sdk-installation.mdx +22 -0
- package/docs/snippets/service-icons.mdx +27 -0
- package/eslint.config.js +10 -3
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -63
- package/frontend/src/App.tsx +13 -82
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -0
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/apple.svg +4 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -0
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +2 -0
- package/frontend/src/assets/logos/linkedin.svg +3 -0
- package/frontend/src/assets/logos/microsoft.svg +1 -0
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +17 -0
- package/frontend/src/assets/logos/tiktok.svg +6 -0
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -0
- package/frontend/src/components/Checkbox.tsx +27 -29
- package/frontend/src/components/CodeBlock.tsx +55 -2
- package/frontend/src/components/CodeEditor.tsx +92 -0
- package/frontend/src/components/ConfirmDialog.tsx +1 -1
- package/frontend/src/components/ConnectCTA.tsx +38 -0
- package/frontend/src/components/CopyButton.tsx +52 -15
- package/frontend/src/components/ErrorState.tsx +1 -2
- package/frontend/src/components/FeatureSidebar.tsx +6 -6
- package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
- package/frontend/src/components/JsonHighlight.tsx +21 -9
- package/frontend/src/components/ProjectInfoModal.tsx +128 -0
- package/frontend/src/components/PromptDialog.tsx +1 -4
- package/frontend/src/components/SearchInput.tsx +1 -2
- package/frontend/src/components/Stepper.tsx +53 -0
- package/frontend/src/components/ThemeToggle.tsx +3 -3
- package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
- package/frontend/src/components/datagrid/index.ts +23 -0
- package/frontend/src/components/index.ts +23 -30
- package/frontend/src/components/layout/AppHeader.tsx +131 -91
- package/frontend/src/components/layout/AppSidebar.tsx +80 -170
- package/frontend/src/components/layout/Layout.tsx +12 -23
- package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
- package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
- package/frontend/src/components/layout/index.ts +5 -0
- package/frontend/src/components/radix/Tooltip.tsx +24 -13
- package/frontend/src/components/radix/index.ts +22 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
- package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
- package/frontend/src/features/ai/components/index.ts +6 -0
- package/frontend/src/features/ai/helpers.ts +57 -71
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
- package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
- package/frontend/src/features/ai/pages/AIPage.tsx +166 -0
- package/frontend/src/features/ai/services/ai.service.ts +5 -5
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +54 -30
- package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +50 -14
- package/frontend/src/features/auth/components/index.ts +5 -0
- package/frontend/src/features/auth/helpers.tsx +208 -0
- package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
- package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
- package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
- package/frontend/src/features/auth/index.ts +3 -2
- package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -0
- package/frontend/src/features/auth/pages/ConfigurationPage.tsx +395 -0
- package/frontend/src/features/auth/pages/UsersPage.tsx +257 -0
- package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
- package/frontend/src/features/auth/services/config.service.ts +19 -0
- package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
- package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
- package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
- package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
- package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
- package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
- package/frontend/src/features/dashboard/components/index.ts +4 -0
- package/frontend/src/features/dashboard/pages/DashboardPage.tsx +212 -0
- package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
- package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
- package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
- package/frontend/src/features/dashboard/prompts/index.ts +31 -0
- package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
- package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
- package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
- package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
- package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
- package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
- package/frontend/src/features/database/components/TableForm.tsx +28 -19
- package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
- package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
- package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
- package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
- package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
- package/frontend/src/features/database/components/index.ts +19 -0
- package/frontend/src/features/database/constants.ts +28 -2
- package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
- package/frontend/src/features/database/helpers.ts +2 -2
- package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
- package/frontend/src/features/database/hooks/useRecords.ts +139 -0
- package/frontend/src/features/database/hooks/useTables.ts +135 -0
- package/frontend/src/features/database/index.ts +7 -1
- package/frontend/src/features/database/pages/FunctionsPage.tsx +203 -0
- package/frontend/src/features/database/pages/IndexesPage.tsx +228 -0
- package/frontend/src/features/database/pages/PoliciesPage.tsx +237 -0
- package/frontend/src/features/database/pages/SQLEditorPage.tsx +382 -0
- package/frontend/src/features/database/{page/DatabasePage.tsx → pages/TablesPage.tsx} +168 -209
- package/frontend/src/features/database/pages/TemplatesPage.tsx +39 -0
- package/frontend/src/features/database/pages/TriggersPage.tsx +230 -0
- package/frontend/src/features/database/services/advance.service.ts +40 -0
- package/frontend/src/features/database/services/database.service.ts +33 -194
- package/frontend/src/features/database/services/record.service.ts +219 -0
- package/frontend/src/features/database/services/table.service.ts +58 -0
- package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
- package/frontend/src/features/database/templates/crm-system.ts +528 -0
- package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
- package/frontend/src/features/database/templates/index.ts +34 -0
- package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
- package/frontend/src/features/database/templates/notion-clone.ts +483 -0
- package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
- package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
- package/frontend/src/features/functions/components/index.ts +5 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
- package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
- package/frontend/src/features/functions/pages/FunctionsPage.tsx +148 -0
- package/frontend/src/features/functions/{components/SecretsContent.tsx → pages/SecretsPage.tsx} +19 -21
- package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
- package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
- package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
- package/frontend/src/features/login/pages/CloudLoginPage.tsx +118 -0
- package/frontend/src/features/login/{page → pages}/LoginPage.tsx +16 -23
- package/frontend/src/features/login/services/partnership.service.ts +65 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
- package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
- package/frontend/src/features/logs/components/index.ts +2 -0
- package/frontend/src/features/logs/helpers.ts +24 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
- package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
- package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +128 -0
- package/frontend/src/features/logs/index.ts +8 -2
- package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +91 -38
- package/frontend/src/features/logs/pages/LogsPage.tsx +152 -0
- package/frontend/src/features/logs/pages/MCPLogsPage.tsx +84 -0
- package/frontend/src/features/logs/services/audit.service.ts +63 -0
- package/frontend/src/features/logs/services/log.service.ts +15 -110
- package/frontend/src/features/logs/services/usage.service.ts +31 -0
- package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
- package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
- package/frontend/src/features/onboard/components/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
- package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
- package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
- package/frontend/src/features/onboard/index.ts +13 -3
- package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
- package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
- package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
- package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
- package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
- package/frontend/src/features/realtime/index.ts +11 -0
- package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
- package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
- package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
- package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
- package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
- package/frontend/src/features/storage/components/index.ts +12 -0
- package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +41 -143
- package/frontend/src/features/storage/services/storage.service.ts +22 -1
- package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
- package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
- package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
- package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +97 -0
- package/frontend/src/index.css +1 -0
- package/frontend/src/lib/analytics/posthog.tsx +27 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
- package/frontend/src/lib/contexts/SocketContext.tsx +123 -80
- package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
- package/frontend/src/lib/hooks/useToast.tsx +6 -2
- package/frontend/src/lib/routing/AppRoutes.tsx +99 -0
- package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
- package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
- package/frontend/src/lib/utils/menuItems.ts +207 -0
- package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
- package/frontend/src/lib/utils/utils.ts +32 -1
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/frontend/vite.config.ts +5 -3
- package/functions/deno.json +24 -24
- package/functions/server.ts +315 -290
- package/functions/worker-template.js +15 -4
- package/i18n/README.ar.md +130 -0
- package/i18n/README.de.md +130 -0
- package/i18n/README.es.md +154 -0
- package/i18n/README.fr.md +134 -0
- package/i18n/README.hi.md +129 -0
- package/i18n/README.ja.md +174 -0
- package/i18n/README.ko.md +137 -0
- package/i18n/README.pt-BR.md +131 -0
- package/i18n/README.ru.md +129 -0
- package/i18n/README.zh-CN.md +133 -0
- package/openapi/ai.yaml +715 -688
- package/openapi/auth.yaml +1244 -563
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +223 -223
- package/openapi/metadata.yaml +177 -177
- package/openapi/realtime.yaml +699 -0
- package/openapi/records.yaml +381 -381
- package/openapi/secrets.yaml +370 -370
- package/openapi/storage.yaml +875 -875
- package/openapi/tables.yaml +463 -463
- package/package.json +97 -88
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai-api.schema.ts +34 -58
- package/shared-schemas/src/ai.schema.ts +63 -54
- package/shared-schemas/src/auth-api.schema.ts +352 -193
- package/shared-schemas/src/auth.schema.ts +43 -7
- package/shared-schemas/src/cloud-events.schema.ts +57 -0
- package/shared-schemas/src/database-api.schema.ts +35 -4
- package/shared-schemas/src/database.schema.ts +40 -1
- package/shared-schemas/src/docs.schema.ts +26 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/index.ts +5 -0
- package/shared-schemas/src/logs-api.schema.ts +7 -1
- package/shared-schemas/src/logs.schema.ts +26 -0
- package/shared-schemas/src/metadata.schema.ts +18 -4
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +13 -0
- package/zeabur/template.yml +1032 -0
- package/.github/workflows/deploy-aws.yml +0 -130
- package/backend/src/api/routes/agent.ts +0 -29
- package/backend/src/api/routes/auth.oauth.ts +0 -482
- package/backend/src/api/routes/auth.ts +0 -386
- package/backend/src/api/routes/docs.ts +0 -66
- package/backend/src/api/routes/functions.ts +0 -183
- package/backend/src/api/routes/openapi.ts +0 -82
- package/backend/src/api/routes/usage.ts +0 -96
- package/backend/src/core/ai/client.ts +0 -242
- package/backend/src/core/ai/model.ts +0 -117
- package/backend/src/core/auth/auth.ts +0 -780
- package/backend/src/core/database/manager.ts +0 -178
- package/backend/src/core/database/table.ts +0 -772
- package/backend/src/core/documentation/agent.ts +0 -689
- package/backend/src/core/documentation/openapi.ts +0 -856
- package/backend/src/core/logs/analytics.ts +0 -76
- package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
- package/backend/src/core/socket/socket.ts +0 -388
- package/backend/src/core/storage/storage.ts +0 -923
- package/backend/src/utils/cloud-token.ts +0 -39
- package/backend/src/utils/helpers.ts +0 -49
- package/backend/src/utils/uuid.ts +0 -9
- package/backend/tests/manual/test-better-auth.sh +0 -303
- package/docker-init/db/logs.sql +0 -9
- package/frontend/README.md +0 -112
- package/frontend/src/components/datagrid/index.tsx +0 -20
- package/frontend/src/components/layout/CloudLayout.tsx +0 -95
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
- package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
- package/frontend/src/features/ai/page/AIPage.tsx +0 -178
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
- package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +0 -194
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
- package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
- package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
- package/frontend/src/features/functions/page/FunctionsPage.tsx +0 -28
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
- package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
- package/frontend/src/features/login/page/CloudLoginPage.tsx +0 -93
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
- package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
- package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
- package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
- package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
- package/frontend/src/features/onboard/types.ts +0 -8
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +0 -127
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
- /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
- /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
- /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
|
+
});
|