insforge 1.2.10 → 1.4.8

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 (506) hide show
  1. package/.claude-plugin/marketplace.json +20 -20
  2. package/.dockerignore +60 -60
  3. package/.env.example +83 -77
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +65 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -63
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +46 -44
  15. package/CLAUDE_PLUGIN.md +104 -104
  16. package/CODE_OF_CONDUCT.md +128 -128
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -30
  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 -182
  23. package/assets/Dark.svg +23 -23
  24. package/auth/package.json +30 -28
  25. package/auth/src/lib/broadcastService.ts +4 -4
  26. package/auth/src/lib/insforge.ts +8 -0
  27. package/auth/src/main.tsx +2 -4
  28. package/auth/src/pages/SignInPage.tsx +5 -2
  29. package/auth/src/pages/SignUpPage.tsx +5 -2
  30. package/auth/src/pages/VerifyEmailPage.tsx +18 -0
  31. package/auth/tsconfig.json +33 -32
  32. package/auth/tsconfig.node.json +11 -11
  33. package/backend/package.json +82 -75
  34. package/backend/src/api/middlewares/rate-limiters.ts +127 -127
  35. package/backend/src/api/routes/ai/index.routes.ts +475 -468
  36. package/backend/src/api/routes/auth/index.routes.ts +720 -570
  37. package/backend/src/api/routes/auth/oauth.routes.ts +478 -448
  38. package/backend/src/api/routes/database/advance.routes.ts +37 -16
  39. package/backend/src/api/routes/database/index.routes.ts +80 -1
  40. package/backend/src/api/routes/database/records.routes.ts +48 -184
  41. package/backend/src/api/routes/database/rpc.routes.ts +69 -0
  42. package/backend/src/api/routes/database/tables.routes.ts +0 -14
  43. package/backend/src/api/routes/deployments/index.routes.ts +192 -0
  44. package/backend/src/api/routes/docs/index.routes.ts +76 -76
  45. package/backend/src/api/routes/email/index.routes.ts +35 -0
  46. package/backend/src/api/routes/functions/index.routes.ts +21 -15
  47. package/backend/src/api/routes/metadata/index.routes.ts +38 -0
  48. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  49. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  50. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  51. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  52. package/backend/src/api/routes/storage/index.routes.ts +18 -12
  53. package/backend/src/api/routes/usage/index.routes.ts +6 -4
  54. package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
  55. package/backend/src/infra/database/database.manager.ts +14 -11
  56. package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
  57. package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
  58. package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
  59. package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
  60. package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
  61. package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
  62. package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
  63. package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
  64. package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
  65. package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
  66. package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
  67. package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
  68. package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
  69. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
  70. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
  71. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
  72. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
  73. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  74. package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
  75. package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
  76. package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
  77. package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
  78. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  79. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  80. package/backend/src/infra/security/token.manager.ts +216 -125
  81. package/backend/src/infra/socket/socket.manager.ts +198 -64
  82. package/backend/src/providers/ai/openrouter.provider.ts +24 -12
  83. package/backend/src/providers/database/base.provider.ts +39 -0
  84. package/backend/src/providers/database/cloud.provider.ts +159 -0
  85. package/backend/src/providers/deployments/vercel.provider.ts +516 -0
  86. package/backend/src/providers/email/base.provider.ts +4 -7
  87. package/backend/src/providers/email/cloud.provider.ts +84 -0
  88. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  89. package/backend/src/providers/oauth/index.ts +1 -0
  90. package/backend/src/server.ts +329 -284
  91. package/backend/src/services/ai/ai-config.service.ts +6 -6
  92. package/backend/src/services/ai/ai-model.service.ts +60 -60
  93. package/backend/src/services/ai/ai-usage.service.ts +7 -7
  94. package/backend/src/services/ai/chat-completion.service.ts +415 -220
  95. package/backend/src/services/ai/helpers.ts +64 -64
  96. package/backend/src/services/ai/image-generation.service.ts +3 -3
  97. package/backend/src/services/ai/index.ts +13 -13
  98. package/backend/src/services/auth/auth-config.service.ts +4 -4
  99. package/backend/src/services/auth/auth-otp.service.ts +6 -6
  100. package/backend/src/services/auth/auth.service.ts +148 -74
  101. package/backend/src/services/auth/index.ts +4 -4
  102. package/backend/src/services/auth/oauth-config.service.ts +12 -12
  103. package/backend/src/services/database/database-advance.service.ts +19 -55
  104. package/backend/src/services/database/database-table.service.ts +38 -94
  105. package/backend/src/services/database/database.service.ts +127 -0
  106. package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
  107. package/backend/src/services/deployments/deployment.service.ts +693 -0
  108. package/backend/src/services/email/email.service.ts +5 -7
  109. package/backend/src/services/functions/function.service.ts +61 -41
  110. package/backend/src/services/logs/audit.service.ts +10 -10
  111. package/backend/src/services/realtime/index.ts +3 -0
  112. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  113. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  114. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  115. package/backend/src/services/secrets/secret.service.ts +101 -27
  116. package/backend/src/services/storage/storage.service.ts +30 -30
  117. package/backend/src/services/usage/usage.service.ts +6 -6
  118. package/backend/src/types/ai.ts +8 -0
  119. package/backend/src/types/auth.ts +16 -1
  120. package/backend/src/types/database.ts +2 -0
  121. package/backend/src/types/deployments.ts +33 -0
  122. package/backend/src/types/realtime.ts +18 -0
  123. package/backend/src/types/socket.ts +7 -31
  124. package/backend/src/types/storage.ts +1 -1
  125. package/backend/src/types/webhooks.ts +45 -0
  126. package/backend/src/utils/cookies.ts +34 -0
  127. package/backend/src/utils/environment.ts +0 -14
  128. package/backend/src/utils/s3-config-loader.ts +64 -0
  129. package/backend/src/utils/seed.ts +79 -43
  130. package/backend/src/utils/sql-parser.ts +216 -0
  131. package/backend/src/utils/utils.ts +114 -114
  132. package/backend/src/utils/validations.ts +10 -10
  133. package/backend/tests/README.md +133 -133
  134. package/backend/tests/cleanup-all-test-data.sh +230 -230
  135. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  136. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  137. package/backend/tests/local/test-ai-config.sh +129 -129
  138. package/backend/tests/local/test-ai-usage.sh +80 -80
  139. package/backend/tests/local/test-auth-router.sh +143 -143
  140. package/backend/tests/local/test-database-router.sh +222 -222
  141. package/backend/tests/local/test-e2e.sh +240 -240
  142. package/backend/tests/local/test-fk-errors.sh +96 -96
  143. package/backend/tests/local/test-functions.sh +123 -123
  144. package/backend/tests/local/test-id-field.sh +200 -200
  145. package/backend/tests/local/test-logs.sh +132 -132
  146. package/backend/tests/local/test-public-bucket.sh +264 -264
  147. package/backend/tests/local/test-rpc.sh +141 -0
  148. package/backend/tests/local/test-secrets.sh +249 -249
  149. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  150. package/backend/tests/local/test-traditional-rest.sh +208 -208
  151. package/backend/tests/manual/README.md +50 -50
  152. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  153. package/backend/tests/manual/seed-large-table.sql +100 -100
  154. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  155. package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
  156. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  157. package/backend/tests/manual/test-database-advance.sh +296 -296
  158. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  159. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  160. package/backend/tests/manual/test-rawsql-modes.sh +244 -244
  161. package/backend/tests/manual/test-universal-storage.sh +263 -263
  162. package/backend/tests/manual/test-users.sql +17 -17
  163. package/backend/tests/run-all-tests.sh +139 -139
  164. package/backend/tests/setup.ts +0 -0
  165. package/backend/tests/test-config.sh +338 -338
  166. package/backend/tests/unit/analyze-query.test.ts +697 -0
  167. package/backend/tests/unit/database-advance.test.ts +326 -0
  168. package/backend/tests/unit/helpers.test.ts +2 -2
  169. package/backend/tsconfig.json +22 -22
  170. package/claude-plugin/.claude-plugin/plugin.json +24 -24
  171. package/claude-plugin/README.md +133 -133
  172. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +273 -270
  173. package/docker-compose.prod.yml +204 -200
  174. package/docker-compose.yml +232 -228
  175. package/docker-init/db/db-init.sql +97 -97
  176. package/docker-init/db/jwt.sql +5 -5
  177. package/docker-init/db/postgresql.conf +16 -16
  178. package/docker-init/logs/vector.yml +236 -236
  179. package/docs/README.md +44 -44
  180. package/docs/agent-docs/deployment.md +79 -0
  181. package/docs/agent-docs/real-time.md +269 -0
  182. package/docs/changelog.mdx +212 -67
  183. package/docs/core-concepts/ai/architecture.mdx +350 -372
  184. package/docs/core-concepts/ai/sdk.mdx +238 -213
  185. package/docs/core-concepts/authentication/architecture.mdx +276 -278
  186. package/docs/core-concepts/authentication/sdk.mdx +710 -414
  187. package/docs/core-concepts/authentication/ui-components/customization.mdx +733 -529
  188. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +247 -221
  189. package/docs/core-concepts/authentication/ui-components/react-router.mdx +183 -184
  190. package/docs/core-concepts/authentication/ui-components/react.mdx +136 -129
  191. package/docs/core-concepts/database/architecture.mdx +292 -255
  192. package/docs/core-concepts/database/pgvector.mdx +138 -0
  193. package/docs/core-concepts/database/sdk.mdx +382 -382
  194. package/docs/core-concepts/deployments/architecture.mdx +152 -0
  195. package/docs/core-concepts/email/architecture.mdx +103 -0
  196. package/docs/core-concepts/email/sdk.mdx +53 -0
  197. package/docs/core-concepts/functions/architecture.mdx +105 -105
  198. package/docs/core-concepts/functions/sdk.mdx +183 -184
  199. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  200. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  201. package/docs/core-concepts/storage/architecture.mdx +243 -243
  202. package/docs/core-concepts/storage/sdk.mdx +253 -253
  203. package/docs/deployment/README.md +94 -94
  204. package/docs/deployment/deploy-to-aws-ec2.md +564 -564
  205. package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
  206. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
  207. package/docs/deployment/deploy-to-render.md +441 -441
  208. package/docs/deprecated/insforge-auth-api.md +214 -214
  209. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  210. package/docs/deprecated/insforge-db-api.md +358 -358
  211. package/docs/deprecated/insforge-db-sdk.md +139 -139
  212. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  213. package/docs/deprecated/insforge-debug.md +64 -64
  214. package/docs/deprecated/insforge-instructions.md +123 -123
  215. package/docs/deprecated/insforge-project.md +117 -117
  216. package/docs/deprecated/insforge-storage-api.md +278 -278
  217. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  218. package/docs/docs.json +240 -210
  219. package/docs/examples/framework-guides/nextjs.mdx +131 -131
  220. package/docs/examples/framework-guides/nuxt.mdx +165 -165
  221. package/docs/examples/framework-guides/react.mdx +165 -165
  222. package/docs/examples/framework-guides/svelte.mdx +153 -153
  223. package/docs/examples/framework-guides/vue.mdx +159 -159
  224. package/docs/examples/overview.mdx +67 -67
  225. package/docs/favicon.png +0 -0
  226. package/docs/favicon.svg +4 -19
  227. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  228. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  229. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  230. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  231. package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
  232. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  233. package/docs/images/changelog/dec-2025/moreModels.png +0 -0
  234. package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
  235. package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
  236. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  237. package/docs/images/changelog/dec-2025/realtime2.png +0 -0
  238. package/docs/images/icons/ai.svg +4 -4
  239. package/docs/images/logos/nextjs.svg +4 -4
  240. package/docs/images/logos/nuxt.svg +4 -4
  241. package/docs/images/logos/react.svg +5 -5
  242. package/docs/images/logos/svelte.svg +4 -4
  243. package/docs/images/logos/vue.svg +5 -5
  244. package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
  245. package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
  246. package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
  247. package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
  248. package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
  249. package/docs/images/mcp-setup/claude-code-connect.png +0 -0
  250. package/docs/images/mcp-setup/cline-1.png +0 -0
  251. package/docs/images/mcp-setup/cline-2.png +0 -0
  252. package/docs/images/mcp-setup/cline-3.png +0 -0
  253. package/docs/images/mcp-setup/connect-project.png +0 -0
  254. package/docs/images/mcp-setup/copilot-1.png +0 -0
  255. package/docs/images/mcp-setup/copilot-2.png +0 -0
  256. package/docs/images/mcp-setup/copilot-3.png +0 -0
  257. package/docs/images/mcp-setup/mcp-json-1.png +0 -0
  258. package/docs/images/mcp-setup/mcp-json-2.png +0 -0
  259. package/docs/images/mcp-setup/qoder-1.png +0 -0
  260. package/docs/images/mcp-setup/qoder-2.png +0 -0
  261. package/docs/images/mcp-setup/roocode-1.png +0 -0
  262. package/docs/images/mcp-setup/roocode-2.png +0 -0
  263. package/docs/images/mcp-setup/trae-1.png +0 -0
  264. package/docs/images/mcp-setup/trae-2.png +0 -0
  265. package/docs/images/mcp-setup/trae-3.png +0 -0
  266. package/docs/images/mcp-setup/trae-4.png +0 -0
  267. package/docs/images/mcp-setup/trae-5.png +0 -0
  268. package/docs/images/mcp-setup/windsurf-1.png +0 -0
  269. package/docs/images/mcp-setup/windsurf-2.png +0 -0
  270. package/docs/insforge-instructions-sdk.md +93 -88
  271. package/docs/introduction.mdx +46 -45
  272. package/docs/logo/dark.svg +22 -22
  273. package/docs/logo/light.svg +20 -20
  274. package/docs/mcp-setup.mdx +332 -0
  275. package/docs/oauth-server.mdx +563 -0
  276. package/docs/partnership.mdx +720 -646
  277. package/docs/quickstart.mdx +82 -82
  278. package/docs/showcase.mdx +52 -52
  279. package/docs/snippets/sdk-installation.mdx +21 -21
  280. package/docs/snippets/service-icons.mdx +27 -27
  281. package/docs/vscode-extension.mdx +74 -0
  282. package/eslint.config.js +1 -0
  283. package/examples/oauth/frontend-oauth-example.html +250 -250
  284. package/examples/response-examples.md +443 -443
  285. package/frontend/components.json +17 -17
  286. package/frontend/package.json +69 -69
  287. package/frontend/src/App.tsx +8 -3
  288. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  289. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  290. package/frontend/src/assets/icons/checked.svg +3 -3
  291. package/frontend/src/assets/icons/connected.svg +3 -3
  292. package/frontend/src/assets/icons/error.svg +3 -3
  293. package/frontend/src/assets/icons/loader.svg +9 -9
  294. package/frontend/src/assets/icons/pencil.svg +4 -4
  295. package/frontend/src/assets/icons/refresh.svg +4 -4
  296. package/frontend/src/assets/icons/step_active.svg +3 -3
  297. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  298. package/frontend/src/assets/icons/warning.svg +3 -3
  299. package/frontend/src/assets/logos/antigravity.svg +1 -0
  300. package/frontend/src/assets/logos/apple.svg +3 -3
  301. package/frontend/src/assets/logos/claude_code.svg +3 -3
  302. package/frontend/src/assets/logos/cline.svg +6 -6
  303. package/frontend/src/assets/logos/copilot.svg +10 -0
  304. package/frontend/src/assets/logos/cursor.svg +20 -20
  305. package/frontend/src/assets/logos/deepseek.svg +139 -0
  306. package/frontend/src/assets/logos/discord.svg +8 -8
  307. package/frontend/src/assets/logos/facebook.svg +3 -3
  308. package/frontend/src/assets/logos/gemini.svg +19 -19
  309. package/frontend/src/assets/logos/github.svg +5 -5
  310. package/frontend/src/assets/logos/google.svg +13 -13
  311. package/frontend/src/assets/logos/grok.svg +10 -10
  312. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  313. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  314. package/frontend/src/assets/logos/instagram.svg +1 -1
  315. package/frontend/src/assets/logos/kiro.svg +9 -0
  316. package/frontend/src/assets/logos/linkedin.svg +3 -3
  317. package/frontend/src/assets/logos/openai.svg +10 -10
  318. package/frontend/src/assets/logos/qoder.svg +4 -0
  319. package/frontend/src/assets/logos/qwen.svg +15 -0
  320. package/frontend/src/assets/logos/roo_code.svg +9 -9
  321. package/frontend/src/assets/logos/spotify.svg +16 -16
  322. package/frontend/src/assets/logos/tiktok.svg +5 -5
  323. package/frontend/src/assets/logos/trae.svg +3 -3
  324. package/frontend/src/assets/logos/windsurf.svg +10 -10
  325. package/frontend/src/assets/logos/x.svg +3 -3
  326. package/frontend/src/components/CodeBlock.tsx +2 -2
  327. package/frontend/src/components/ConnectCTA.tsx +3 -2
  328. package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
  329. package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
  330. package/frontend/src/components/datagrid/index.ts +1 -1
  331. package/frontend/src/components/index.ts +0 -1
  332. package/frontend/src/components/layout/AppHeader.tsx +13 -37
  333. package/frontend/src/components/layout/AppSidebar.tsx +85 -100
  334. package/frontend/src/components/layout/Layout.tsx +34 -32
  335. package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
  336. package/frontend/src/components/radix/Select.tsx +151 -151
  337. package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
  338. package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
  339. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
  340. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
  341. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
  342. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
  343. package/frontend/src/features/ai/components/index.ts +6 -6
  344. package/frontend/src/features/ai/helpers.ts +147 -141
  345. package/frontend/src/features/ai/{page → pages}/AIPage.tsx +166 -166
  346. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
  347. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
  348. package/frontend/src/features/auth/components/UsersDataGrid.tsx +61 -31
  349. package/frontend/src/features/auth/components/index.ts +5 -5
  350. package/frontend/src/features/auth/helpers.tsx +8 -0
  351. package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +275 -275
  352. package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
  353. package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +1 -1
  354. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
  355. package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
  356. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
  357. package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
  358. package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
  359. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  360. package/frontend/src/features/database/components/TableForm.tsx +0 -4
  361. package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
  362. package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
  363. package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
  364. package/frontend/src/features/database/constants.ts +16 -28
  365. package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
  366. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  367. package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
  368. package/frontend/src/features/database/hooks/useTables.ts +30 -28
  369. package/frontend/src/features/database/index.ts +1 -0
  370. package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -42
  371. package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +34 -51
  372. package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +42 -58
  373. package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +2 -2
  374. package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
  375. package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +34 -51
  376. package/frontend/src/features/database/services/advance.service.ts +1 -41
  377. package/frontend/src/features/database/services/database.service.ts +55 -0
  378. package/frontend/src/features/database/services/record.service.ts +4 -20
  379. package/frontend/src/features/database/services/table.service.ts +1 -10
  380. package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
  381. package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
  382. package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
  383. package/frontend/src/features/database/templates/notion-clone.ts +8 -8
  384. package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
  385. package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
  386. package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
  387. package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
  388. package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
  389. package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
  390. package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
  391. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
  392. package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
  393. package/frontend/src/features/functions/components/index.ts +5 -5
  394. package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
  395. package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
  396. package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
  397. package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +118 -116
  398. package/frontend/src/features/functions/services/function.service.ts +8 -25
  399. package/frontend/src/features/functions/services/secret.service.ts +23 -41
  400. package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +125 -118
  401. package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
  402. package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
  403. package/frontend/src/features/logs/components/index.ts +1 -0
  404. package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
  405. package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +36 -6
  406. package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
  407. package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
  408. package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
  409. package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
  410. package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
  411. package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
  412. package/frontend/src/features/onboard/components/index.ts +9 -4
  413. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
  414. package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
  415. package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
  416. package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
  417. package/frontend/src/features/onboard/index.ts +17 -13
  418. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  419. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  420. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  421. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  422. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  423. package/frontend/src/features/realtime/index.ts +11 -0
  424. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  425. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  426. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  427. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  428. package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
  429. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
  430. package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
  431. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +24 -11
  432. package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +11 -36
  433. package/frontend/src/index.css +249 -249
  434. package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
  435. package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
  436. package/frontend/src/lib/hooks/useMetadata.ts +45 -1
  437. package/frontend/src/lib/hooks/useModal.tsx +2 -0
  438. package/frontend/src/lib/routing/AppRoutes.tsx +103 -84
  439. package/frontend/src/lib/services/metadata.service.ts +20 -3
  440. package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
  441. package/frontend/src/lib/utils/menuItems.ts +223 -183
  442. package/frontend/src/lib/utils/utils.ts +196 -183
  443. package/frontend/tsconfig.json +25 -25
  444. package/frontend/tsconfig.node.json +9 -9
  445. package/functions/deno.json +24 -24
  446. package/functions/server.ts +6 -6
  447. package/functions/worker-template.js +1 -1
  448. package/i18n/README.ar.md +130 -130
  449. package/i18n/README.de.md +130 -130
  450. package/i18n/README.es.md +154 -154
  451. package/i18n/README.fr.md +134 -134
  452. package/i18n/README.hi.md +129 -129
  453. package/i18n/README.ja.md +174 -174
  454. package/i18n/README.ko.md +136 -136
  455. package/i18n/README.pt-BR.md +131 -131
  456. package/i18n/README.ru.md +129 -129
  457. package/i18n/README.zh-CN.md +133 -133
  458. package/openapi/ai.yaml +825 -715
  459. package/openapi/auth.yaml +1324 -1244
  460. package/openapi/email.yaml +158 -0
  461. package/openapi/functions.yaml +475 -475
  462. package/openapi/health.yaml +29 -29
  463. package/openapi/logs.yaml +221 -223
  464. package/openapi/metadata.yaml +175 -177
  465. package/openapi/realtime.yaml +699 -0
  466. package/openapi/records.yaml +381 -381
  467. package/openapi/secrets.yaml +370 -370
  468. package/openapi/storage.yaml +875 -875
  469. package/openapi/tables.yaml +462 -463
  470. package/package.json +97 -97
  471. package/shared-schemas/package.json +31 -31
  472. package/shared-schemas/src/ai-api.schema.ts +251 -143
  473. package/shared-schemas/src/ai.schema.ts +8 -4
  474. package/shared-schemas/src/auth-api.schema.ts +380 -339
  475. package/shared-schemas/src/auth.schema.ts +18 -11
  476. package/shared-schemas/src/cloud-events.schema.ts +26 -0
  477. package/shared-schemas/src/database-api.schema.ts +32 -1
  478. package/shared-schemas/src/database.schema.ts +39 -0
  479. package/shared-schemas/src/deployments-api.schema.ts +55 -0
  480. package/shared-schemas/src/deployments.schema.ts +30 -0
  481. package/shared-schemas/src/docs.schema.ts +32 -0
  482. package/shared-schemas/src/email-api.schema.ts +30 -0
  483. package/shared-schemas/src/functions-api.schema.ts +13 -4
  484. package/shared-schemas/src/functions.schema.ts +1 -1
  485. package/shared-schemas/src/index.ts +22 -14
  486. package/shared-schemas/src/metadata.schema.ts +39 -4
  487. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  488. package/shared-schemas/src/realtime.schema.ts +143 -0
  489. package/shared-schemas/src/secrets-api.schema.ts +44 -0
  490. package/shared-schemas/src/secrets.schema.ts +15 -0
  491. package/shared-schemas/tsconfig.json +21 -21
  492. package/tsconfig.json +7 -7
  493. package/zeabur/README.md +26 -13
  494. package/zeabur/template.yml +1001 -1032
  495. package/.cursor/rules/cursor-rules.mdc +0 -94
  496. package/backend/src/types/profile.ts +0 -55
  497. package/frontend/src/components/ProjectInfoModal.tsx +0 -128
  498. package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
  499. package/test-gemini.sh +0 -35
  500. package/test-usage-admin.sh +0 -57
  501. package/test-usage.sh +0 -50
  502. /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
  503. /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
  504. /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
  505. /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
  506. /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
@@ -1,1032 +1,1001 @@
1
- # yaml-language-server: $schema=https://schema.zeabur.app/template.json
2
- apiVersion: zeabur.com/v1
3
- kind: Template
4
- metadata:
5
- name: InsForge
6
- spec:
7
- description: InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously.
8
- coverImage: https://cdn.zeabur.com/insforge.png
9
- icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4
10
- variables:
11
- - key: PUBLIC_DOMAIN
12
- type: DOMAIN
13
- name: InsForge Domain
14
- description: The domain for accessing your InsForge application.
15
- - key: ADMIN_EMAIL
16
- type: STRING
17
- name: Admin Email
18
- description: Email address for the admin user.
19
- - key: ADMIN_PASSWORD
20
- type: STRING
21
- name: Admin Password
22
- description: Password for admin. Must be at least 8 characters.
23
- - key: OPENROUTER_API_KEY
24
- type: STRING
25
- name: OpenRouter API Key
26
- description: API key for OpenRouter LLM services (optional).
27
- tags:
28
- - Development
29
- - Database
30
- - API
31
- - Platform
32
- readme: |-
33
- # InsForge
34
-
35
- InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously.
36
-
37
- ## Features
38
-
39
- - **Authentication System** - User management and secure authentication
40
- - **Database Storage** - Flexible PostgreSQL database with automatic schema management
41
- - **File Management** - Secure file upload and storage capabilities
42
- - **PostgREST API** - Automatic REST API generation from database schema
43
- - **Serverless Functions** - Edge functions for custom business logic
44
- - **AI Agent Integration** - Connect AI agents like Claude or GPT to manage your backend
45
-
46
- ## Quick Start
47
-
48
- This template provides **one-click deployment** of the complete InsForge platform:
49
-
50
- 1. **Deploy** - Click deploy and bind a domain
51
- 2. **Login** - Use your admin credentials to access the dashboard
52
- 3. **Connect AI Agent** - Link your AI agent (Claude, GPT, etc.) through the dashboard
53
- 4. **Build Apps** - Use natural language prompts to create applications:
54
- - "Build a todo app with user authentication"
55
- - "Create an Instagram clone with image upload"
56
- - "Build a blog with comments and likes"
57
-
58
- ## Use Cases
59
-
60
- - **AI-Generated Frontends** - Rapidly create backends for AI-generated frontend projects
61
- - **Agent-Driven Development** - Let AI agents manage your entire backend infrastructure
62
- - **Rapid Prototyping** - Build full-stack applications using natural language
63
-
64
- ## Community
65
-
66
- - [Discord](https://discord.gg/MPxwj5xVvW) - Join our community
67
- - [GitHub](https://github.com/InsForge/InsForge) - Contribute to the project
68
- - Email: info@insforge.dev
69
-
70
- services:
71
- - name: postgres
72
- icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg
73
- template: PREBUILT
74
- spec:
75
- source:
76
- image: postgres:15.13
77
- command:
78
- - docker-entrypoint.sh
79
- - -c
80
- - config_file=/etc/postgresql/postgresql.conf
81
- ports:
82
- - id: database
83
- port: 5432
84
- type: TCP
85
- volumes:
86
- - id: data
87
- dir: /var/lib/postgresql/data
88
- instructions:
89
- - title: Connection String
90
- content: postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge
91
- - title: PostgreSQL Connect Command
92
- content: psql "postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge"
93
- - title: PostgreSQL username
94
- content: postgres
95
- - title: PostgresSQL password
96
- content: ${POSTGRES_PASSWORD}
97
- - title: PostgresSQL database
98
- content: insforge
99
- - title: PostgreSQL host
100
- content: ${PORT_FORWARDED_HOSTNAME}
101
- - title: PostgreSQL port
102
- content: ${DATABASE_PORT_FORWARDED_PORT}
103
- env:
104
- PGDATA:
105
- default: /var/lib/postgresql/data/pgdata
106
- POSTGRES_CONNECTION_STRING:
107
- default: postgres://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/insforge
108
- expose: true
109
- POSTGRES_DATABASE:
110
- default: insforge
111
- expose: true
112
- POSTGRES_DB:
113
- default: insforge
114
- POSTGRES_HOST:
115
- default: ${CONTAINER_HOSTNAME}
116
- expose: true
117
- POSTGRES_PASSWORD:
118
- default: ${PASSWORD}
119
- expose: true
120
- readonly: true
121
- POSTGRES_PORT:
122
- default: ${DATABASE_PORT}
123
- expose: true
124
- POSTGRES_URI:
125
- default: ${POSTGRES_CONNECTION_STRING}
126
- expose: true
127
- POSTGRES_USER:
128
- default: postgres
129
- POSTGRES_USERNAME:
130
- default: postgres
131
- expose: true
132
- JWT_SECRET:
133
- default: ${PASSWORD}
134
- expose: true
135
- readonly: true
136
- JWT_EXP:
137
- default: "3600"
138
- configs:
139
- - path: /etc/postgresql/postgresql.conf
140
- template: |
141
- # PostgreSQL configuration for InsForge
142
- # Enable logical replication for Logflare
143
-
144
- # Listen on all interfaces to allow container connections
145
- listen_addresses = '*'
146
-
147
- # Set WAL level to logical for Logflare replication
148
- wal_level = logical
149
-
150
- # Set max replication slots (needed for logical replication)
151
- max_replication_slots = 10
152
-
153
- # Set max WAL senders
154
- max_wal_senders = 10
155
-
156
- # Shared preload libraries (if needed)
157
- # shared_preload_libraries = 'pg_stat_statements'
158
- - path: /docker-entrypoint-initdb.d/01-init.sql
159
- template: |
160
- -- init.sql
161
- -- Create role for anonymous user
162
- CREATE ROLE anon NOLOGIN;
163
-
164
- -- Create role for authenticator
165
- CREATE ROLE authenticated NOLOGIN;
166
-
167
- -- Create project admin role for admin users
168
- CREATE ROLE project_admin NOLOGIN;
169
-
170
- GRANT USAGE ON SCHEMA public TO anon;
171
- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO anon;
172
- GRANT USAGE ON SCHEMA public TO authenticated;
173
- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;
174
- GRANT USAGE ON SCHEMA public TO project_admin;
175
- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_admin;
176
-
177
- -- Grant permissions to roles
178
- -- NOTICE: The anon role is intended for unauthenticated users, so it should only have read access.
179
- GRANT SELECT ON ALL TABLES IN SCHEMA public TO anon;
180
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
181
- GRANT SELECT ON TABLES TO anon;
182
-
183
- GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
184
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
185
- GRANT SELECT ON TABLES TO authenticated;
186
-
187
- GRANT INSERT ON ALL TABLES IN SCHEMA public TO authenticated;
188
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
189
- GRANT INSERT ON TABLES TO authenticated;
190
-
191
- GRANT UPDATE ON ALL TABLES IN SCHEMA public TO authenticated;
192
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
193
- GRANT UPDATE ON TABLES TO authenticated;
194
-
195
- GRANT DELETE ON ALL TABLES IN SCHEMA public TO authenticated;
196
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
197
- GRANT DELETE ON TABLES TO authenticated;
198
-
199
- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO project_admin;
200
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
201
- GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO project_admin;
202
-
203
- -- Create function to automatically create RLS policies for new tables
204
- CREATE OR REPLACE FUNCTION public.create_default_policies()
205
- RETURNS event_trigger AS $$
206
- DECLARE
207
- obj record;
208
- table_schema text;
209
- table_name text;
210
- has_rls boolean;
211
- BEGIN
212
- FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
213
- LOOP
214
- -- Extract schema and table name from object_identity
215
- -- Handle quoted identifiers by removing quotes
216
- SELECT INTO table_schema, table_name
217
- split_part(obj.object_identity, '.', 1),
218
- trim(both '"' from split_part(obj.object_identity, '.', 2));
219
- -- Check if RLS is enabled on the table
220
- SELECT INTO has_rls
221
- rowsecurity
222
- FROM pg_tables
223
- WHERE schemaname = table_schema
224
- AND tablename = table_name;
225
- -- Only create policies if RLS is enabled
226
- IF has_rls THEN
227
- -- Create policies for each role
228
- -- anon: read-only access
229
- EXECUTE format('CREATE POLICY "anon_policy" ON %s FOR SELECT TO anon USING (true)', obj.object_identity);
230
- -- authenticated: full access
231
- EXECUTE format('CREATE POLICY "authenticated_policy" ON %s FOR ALL TO authenticated USING (true) WITH CHECK (true)', obj.object_identity);
232
- -- project_admin: full access
233
- EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
234
- END IF;
235
- END LOOP;
236
- END;
237
- $$ LANGUAGE plpgsql;
238
-
239
- -- Create event trigger to run the function when new tables are created
240
- CREATE EVENT TRIGGER create_policies_on_table_create
241
- ON ddl_command_end
242
- WHEN TAG IN ('CREATE TABLE')
243
- EXECUTE FUNCTION public.create_default_policies();
244
-
245
- -- Create function to handle RLS enablement
246
- CREATE OR REPLACE FUNCTION public.create_policies_after_rls()
247
- RETURNS event_trigger AS $$
248
- DECLARE
249
- obj record;
250
- table_schema text;
251
- table_name text;
252
- BEGIN
253
- FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'ALTER TABLE'
254
- LOOP
255
- -- Extract schema and table name
256
- -- Handle quoted identifiers by removing quotes
257
- SELECT INTO table_schema, table_name
258
- split_part(obj.object_identity, '.', 1),
259
- trim(both '"' from split_part(obj.object_identity, '.', 2));
260
- -- Check if table has RLS enabled and no policies yet
261
- IF EXISTS (
262
- SELECT 1 FROM pg_tables
263
- WHERE schemaname = table_schema
264
- AND tablename = table_name
265
- AND rowsecurity = true
266
- ) AND NOT EXISTS (
267
- SELECT 1 FROM pg_policies
268
- WHERE schemaname = table_schema
269
- AND tablename = table_name
270
- ) THEN
271
- -- Create default policies
272
- EXECUTE format('CREATE POLICY "anon_policy" ON %s FOR SELECT TO anon USING (true)', obj.object_identity);
273
- EXECUTE format('CREATE POLICY "authenticated_policy" ON %s FOR ALL TO authenticated USING (true) WITH CHECK (true)', obj.object_identity);
274
- EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
275
- END IF;
276
- END LOOP;
277
- END;
278
- $$ LANGUAGE plpgsql;
279
-
280
- -- Create event trigger for ALTER TABLE commands
281
- CREATE EVENT TRIGGER create_policies_on_rls_enable
282
- ON ddl_command_end
283
- WHEN TAG IN ('ALTER TABLE')
284
- EXECUTE FUNCTION public.create_policies_after_rls();
285
- - path: /docker-entrypoint-initdb.d/02-jwt.sql
286
- template: |
287
- \set jwt_secret `echo "$JWT_SECRET"`
288
- \set jwt_exp `echo "$JWT_EXP"`
289
-
290
- ALTER DATABASE insforge SET "app.settings.jwt_secret" TO :'jwt_secret';
291
- ALTER DATABASE insforge SET "app.settings.jwt_exp" TO :'jwt_exp';
292
- healthCheck:
293
- type: TCP
294
- port: database
295
-
296
- - name: postgrest
297
- icon: https://avatars.githubusercontent.com/u/15115011?s=96&v=4
298
- dependencies:
299
- - postgres
300
- template: PREBUILT
301
- spec:
302
- source:
303
- image: postgrest/postgrest:v12.2.12
304
- ports:
305
- - id: api
306
- port: 3000
307
- type: HTTP
308
- env:
309
- PGHOST:
310
- default: postgres
311
- PGPORT:
312
- default: "5432"
313
- PGDATABASE:
314
- default: insforge
315
- PGUSER:
316
- default: postgres
317
- PGPASSWORD:
318
- default: ${POSTGRES_PASSWORD}
319
- expose: true
320
- PGRST_OPENAPI_SERVER_PROXY_URI:
321
- default: http://localhost:3000
322
- PGRST_DB_SCHEMA:
323
- default: public
324
- PGRST_DB_ANON_ROLE:
325
- default: anon
326
- PGRST_JWT_SECRET:
327
- default: ${JWT_SECRET}
328
- expose: true
329
- readonly: true
330
- PGRST_DB_CHANNEL_ENABLED:
331
- default: "true"
332
- PGRST_DB_CHANNEL:
333
- default: pgrst
334
- healthCheck:
335
- type: HTTP
336
- port: api
337
- http:
338
- path: /
339
-
340
- - name: deno
341
- icon: https://avatars.githubusercontent.com/u/42048915?s=96&v=4
342
- dependencies:
343
- - postgres
344
- - postgrest
345
- template: PREBUILT
346
- spec:
347
- source:
348
- image: denoland/deno:alpine-2.0.6
349
- command:
350
- - sh
351
- - -c
352
- - |
353
- cd /app &&
354
- echo 'Downloading Deno dependencies...' &&
355
- deno cache functions/server.ts &&
356
- echo 'Starting Deno server on port 7133...' &&
357
- deno run --allow-net --allow-env --allow-read=./functions/worker-template.js --watch functions/server.ts
358
- ports:
359
- - id: runtime
360
- port: 7133
361
- type: HTTP
362
- volumes:
363
- - id: cache
364
- dir: /deno-dir
365
- env:
366
- PORT:
367
- default: "7133"
368
- DENO_ENV:
369
- default: development
370
- DENO_DIR:
371
- default: /deno-dir
372
- POSTGRES_HOST:
373
- default: postgres
374
- POSTGRES_PORT:
375
- default: "5432"
376
- POSTGRES_DB:
377
- default: insforge
378
- POSTGRES_USER:
379
- default: postgres
380
- POSTGRES_PASSWORD:
381
- default: ${PGPASSWORD}
382
- POSTGREST_BASE_URL:
383
- default: http://postgrest:3000
384
- WORKER_TIMEOUT_MS:
385
- default: "30000"
386
- ENCRYPTION_KEY:
387
- default: ${PASSWORD}
388
- JWT_SECRET:
389
- default: ${PGRST_JWT_SECRET}
390
- configs:
391
- - path: /app/functions/server.ts
392
- template: |
393
- import { Client } from 'https://deno.land/x/postgres@v0.17.0/mod.ts';
394
- import { join, dirname, fromFileUrl } from 'https://deno.land/std@0.224.0/path/mod.ts';
395
-
396
- /* eslint-disable no-console */
397
- const port = parseInt(Deno.env.get('PORT') ?? '7133');
398
-
399
- console.log(`Deno serverless runtime running on port ${port}`);
400
-
401
- // Configuration
402
- const WORKER_TIMEOUT_MS = parseInt(Deno.env.get('WORKER_TIMEOUT_MS') ?? '30000');
403
-
404
- // Worker template code - loaded on first use
405
- let workerTemplateCode: string | null = null;
406
-
407
- async function getWorkerTemplateCode(): Promise<string> {
408
- if (!workerTemplateCode) {
409
- const currentDir = dirname(fromFileUrl(import.meta.url));
410
- workerTemplateCode = await Deno.readTextFile(join(currentDir, 'worker-template.js'));
411
- }
412
- return workerTemplateCode;
413
- }
414
-
415
- // Decrypt function for Deno (compatible with Node.js encryption)
416
- async function decryptSecret(ciphertext: string, key: string): Promise<string> {
417
- try {
418
- const parts = ciphertext.split(':');
419
- if (parts.length !== 3) {
420
- throw new Error('Invalid ciphertext format');
421
- }
422
-
423
- // Get the encryption key by hashing the JWT secret
424
- const keyData = new TextEncoder().encode(key);
425
- const hashBuffer = await crypto.subtle.digest('SHA-256', keyData);
426
- const cryptoKey = await crypto.subtle.importKey(
427
- 'raw',
428
- hashBuffer,
429
- { name: 'AES-GCM' },
430
- false,
431
- ['decrypt']
432
- );
433
-
434
- // Extract IV, auth tag, and encrypted data
435
- const iv = Uint8Array.from(parts[0].match(/.{2}/g)!.map(byte => parseInt(byte, 16)));
436
- const authTag = Uint8Array.from(parts[1].match(/.{2}/g)!.map(byte => parseInt(byte, 16)));
437
- const encrypted = Uint8Array.from(parts[2].match(/.{2}/g)!.map(byte => parseInt(byte, 16)));
438
-
439
- // Combine encrypted data and auth tag (GCM expects them together)
440
- const cipherData = new Uint8Array(encrypted.length + authTag.length);
441
- cipherData.set(encrypted);
442
- cipherData.set(authTag, encrypted.length);
443
-
444
- // Decrypt
445
- const decryptedBuffer = await crypto.subtle.decrypt(
446
- { name: 'AES-GCM', iv },
447
- cryptoKey,
448
- cipherData
449
- );
450
-
451
- return new TextDecoder().decode(decryptedBuffer);
452
- } catch (error) {
453
- console.error('Failed to decrypt secret:', error);
454
- throw error;
455
- }
456
- }
457
-
458
- // Database connection
459
- const dbConfig = {
460
- user: Deno.env.get('POSTGRES_USER') || 'postgres',
461
- password: Deno.env.get('POSTGRES_PASSWORD') || 'postgres',
462
- database: Deno.env.get('POSTGRES_DB') || 'insforge',
463
- hostname: Deno.env.get('POSTGRES_HOST') || 'postgres',
464
- port: parseInt(Deno.env.get('POSTGRES_PORT') || '5432', 10),
465
- };
466
-
467
- // Get function code from database
468
- async function getFunctionCode(slug: string): Promise<string | null> {
469
- const client = new Client(dbConfig);
470
-
471
- try {
472
- await client.connect();
473
-
474
- const result = await client.queryObject<{ code: string }>`
475
- SELECT code FROM _functions
476
- WHERE slug = ${slug} AND status = 'active'
477
- `;
478
-
479
- if (!result.rows.length) {
480
- return null;
481
- }
482
-
483
- return result.rows[0].code;
484
- } catch (error) {
485
- console.error(`Error fetching function ${slug}:`, error);
486
- return null;
487
- } finally {
488
- await client.end();
489
- }
490
- }
491
-
492
- // Get all secrets from main secrets table and decrypt them
493
- async function getFunctionSecrets(): Promise<Record<string, string>> {
494
- const client = new Client(dbConfig);
495
-
496
- try {
497
- await client.connect();
498
-
499
- // Get the encryption key from environment
500
- const encryptionKey = Deno.env.get('ENCRYPTION_KEY') || Deno.env.get('JWT_SECRET');
501
- if (!encryptionKey) {
502
- console.error('No encryption key available for decrypting secrets');
503
- return {};
504
- }
505
-
506
- // Fetch all active secrets from _secrets table
507
- const result = await client.queryObject<{
508
- key: string;
509
- value_ciphertext: string;
510
- }>`
511
- SELECT key, value_ciphertext
512
- FROM _secrets
513
- WHERE is_active = true
514
- AND (expires_at IS NULL OR expires_at > NOW())
515
- `;
516
-
517
- const secrets: Record<string, string> = {};
518
-
519
- // Decrypt each secret
520
- for (const row of result.rows) {
521
- try {
522
- secrets[row.key] = await decryptSecret(row.value_ciphertext, encryptionKey);
523
- } catch (error) {
524
- console.error(`Failed to decrypt secret ${row.key}:`, error);
525
- // Skip this secret if decryption fails
526
- }
527
- }
528
-
529
- return secrets;
530
- } catch (error) {
531
- console.error('Error fetching secrets:', error);
532
- return {};
533
- } finally {
534
- await client.end();
535
- }
536
- }
537
-
538
- // Execute function in isolated worker
539
- async function executeInWorker(code: string, request: Request): Promise<Response> {
540
- // Get worker template
541
- const template = await getWorkerTemplateCode();
542
-
543
- // Fetch all function secrets
544
- const secrets = await getFunctionSecrets();
545
-
546
- // Create blob for worker
547
- const workerBlob = new Blob([template], { type: 'application/javascript' });
548
- const workerUrl = URL.createObjectURL(workerBlob);
549
-
550
- return new Promise(async (resolve) => {
551
- const worker = new Worker(workerUrl, { type: 'module' });
552
-
553
- // Set timeout for worker execution
554
- const timeout = setTimeout(() => {
555
- worker.terminate();
556
- URL.revokeObjectURL(workerUrl);
557
- resolve(
558
- new Response(JSON.stringify({ error: 'Function timeout' }), {
559
- status: 504,
560
- headers: { 'Content-Type': 'application/json' },
561
- })
562
- );
563
- }, WORKER_TIMEOUT_MS);
564
-
565
- // Handle worker response
566
- worker.onmessage = (e) => {
567
- clearTimeout(timeout);
568
- worker.terminate();
569
- URL.revokeObjectURL(workerUrl);
570
-
571
- if (e.data.success) {
572
- const { response } = e.data;
573
- // The worker now properly sends null for bodyless responses
574
- resolve(
575
- new Response(response.body, {
576
- status: response.status,
577
- statusText: response.statusText,
578
- headers: response.headers,
579
- })
580
- );
581
- } else {
582
- resolve(
583
- new Response(JSON.stringify({ error: e.data.error }), {
584
- status: e.data.status || 500,
585
- headers: { 'Content-Type': 'application/json' },
586
- })
587
- );
588
- }
589
- };
590
-
591
- // Handle worker errors
592
- worker.onerror = (error) => {
593
- clearTimeout(timeout);
594
- worker.terminate();
595
- URL.revokeObjectURL(workerUrl);
596
- console.error('Worker error:', error);
597
- resolve(
598
- new Response(JSON.stringify({ error: 'Worker execution error' }), {
599
- status: 500,
600
- headers: { 'Content-Type': 'application/json' },
601
- })
602
- );
603
- };
604
-
605
- // Prepare request data
606
- const body = request.body ? await request.text() : null;
607
- const requestData = {
608
- url: request.url,
609
- method: request.method,
610
- headers: Object.fromEntries(request.headers),
611
- body,
612
- };
613
-
614
- // Send message with code, request data, and secrets
615
- worker.postMessage({ code, requestData, secrets });
616
- });
617
- }
618
-
619
- Deno.serve({ port }, async (req: Request) => {
620
- const url = new URL(req.url);
621
- const pathname = url.pathname;
622
-
623
- // Health check
624
- if (pathname === '/health') {
625
- return new Response(
626
- JSON.stringify({
627
- status: 'ok',
628
- runtime: 'deno',
629
- version: Deno.version.deno,
630
- typescript: Deno.version.typescript,
631
- v8: Deno.version.v8,
632
- }),
633
- {
634
- headers: { 'Content-Type': 'application/json' },
635
- }
636
- );
637
- }
638
-
639
- // Function execution - match ONLY exact slug, no subpaths
640
- const slugMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)$/);
641
- if (slugMatch) {
642
- const slug = slugMatch[1];
643
- const startTime = Date.now();
644
-
645
- // Get function code from database
646
- const code = await getFunctionCode(slug);
647
-
648
- if (!code) {
649
- return new Response(JSON.stringify({ error: 'Function not found or not active' }), {
650
- status: 404,
651
- headers: { 'Content-Type': 'application/json' },
652
- });
653
- }
654
-
655
- // Execute in worker with original request
656
- try {
657
- const response = await executeInWorker(code, req);
658
- const duration = Date.now() - startTime;
659
-
660
- // Log completed invocations only
661
- console.log(
662
- JSON.stringify({
663
- timestamp: new Date().toISOString(),
664
- level: 'info',
665
- slug,
666
- method: req.method,
667
- status: response.status,
668
- duration: `${duration}ms`,
669
- })
670
- );
671
-
672
- return response;
673
- } catch (error) {
674
- const duration = Date.now() - startTime;
675
- console.error(
676
- JSON.stringify({
677
- timestamp: new Date().toISOString(),
678
- level: 'error',
679
- slug,
680
- error: error instanceof Error ? error.message : String(error),
681
- duration: `${duration}ms`,
682
- })
683
- );
684
- return new Response(JSON.stringify({ error: 'Function execution failed' }), {
685
- status: 500,
686
- headers: { 'Content-Type': 'application/json' },
687
- });
688
- }
689
- }
690
-
691
- // Runtime info
692
- if (pathname === '/info') {
693
- return new Response(
694
- JSON.stringify({
695
- runtime: 'deno',
696
- version: Deno.version,
697
- env: Deno.env.get('DENO_ENV') || 'production',
698
- database: {
699
- host: dbConfig.hostname,
700
- database: dbConfig.database,
701
- },
702
- }),
703
- {
704
- headers: { 'Content-Type': 'application/json' },
705
- }
706
- );
707
- }
708
-
709
- // 404
710
- return new Response('Not Found', { status: 404 });
711
- });
712
- - path: /app/functions/deno.json
713
- template: |
714
- {
715
- "compilerOptions": {
716
- "lib": ["deno.window", "deno.worker"],
717
- "strict": true
718
- },
719
- "lint": {
720
- "files": {
721
- "include": ["./**/*.ts", "./**/*.js"]
722
- },
723
- "rules": {
724
- "tags": ["recommended"]
725
- }
726
- },
727
- "fmt": {
728
- "files": {
729
- "include": ["./**/*.ts", "./**/*.js"]
730
- },
731
- "options": {
732
- "useTabs": false,
733
- "lineWidth": 100,
734
- "indentWidth": 2,
735
- "singleQuote": true
736
- }
737
- }
738
- }
739
- - path: /app/functions/worker-template.js
740
- template: |
741
- /**
742
- * Worker Template for Serverless Functions
743
- *
744
- * This code runs inside a Web Worker environment created by Deno.
745
- * Each worker is created fresh for a single request, executes once, and terminates.
746
- */
747
- /* eslint-env worker */
748
- /* global self, Request, Deno */
749
-
750
- // Import SDK at worker level - this will be available to all functions
751
- import { createClient } from 'npm:@insforge/sdk';
752
- // Import base64 utilities for encoding/decoding
753
- import { encodeBase64, decodeBase64 } from 'https://deno.land/std@0.224.0/encoding/base64.ts';
754
-
755
- // Handle the single message with code, request data, and secrets
756
- self.onmessage = async (e) => {
757
- const { code, requestData, secrets = {} } = e.data;
758
-
759
- try {
760
- /**
761
- * MOCK DENO OBJECT EXPLANATION:
762
- *
763
- * Why we need a mock Deno object:
764
- * - Edge functions run in isolated Web Workers (sandboxed environments)
765
- * - Web Workers don't have access to the real Deno global object for security
766
- * - We need to provide Deno.env functionality so functions can access secrets
767
- *
768
- * How it works:
769
- * 1. The main server (server.ts) fetches all active secrets from the _secrets table
770
- * 2. Only active (is_active=true) and non-expired secrets are included
771
- * 3. Secrets are decrypted and passed to this worker via the 'secrets' object
772
- * 4. We create a mock Deno object that provides Deno.env.get()
773
- * 5. When user code calls Deno.env.get('MY_SECRET'), it reads from our secrets object
774
- *
775
- * This allows edge functions to use familiar Deno.env syntax while maintaining security
776
- * Secrets are managed via the /api/secrets endpoint
777
- */
778
- const mockDeno = {
779
- // Mock the Deno.env API - only get() is needed for reading secrets
780
- env: {
781
- get: (key) => secrets[key] || undefined,
782
- },
783
- };
784
-
785
- /**
786
- * FUNCTION WRAPPING EXPLANATION:
787
- *
788
- * Here we create a wrapper function that will execute the user's code.
789
- * The user's function expects to have access to:
790
- * - module.exports (to export their function)
791
- * - createClient (the Insforge SDK)
792
- * - Deno (for Deno.env.get() etc.)
793
- * - encodeBase64, decodeBase64 (base64 encoding utilities)
794
- *
795
- * We inject our mockDeno as the 'Deno' parameter, so when the user's code
796
- * calls Deno.env.get('MY_SECRET'), it's actually calling mockDeno.env.get('MY_SECRET')
797
- */
798
- const wrapper = new Function(
799
- 'exports',
800
- 'module',
801
- 'createClient',
802
- 'Deno',
803
- 'encodeBase64',
804
- 'decodeBase64',
805
- code
806
- );
807
- const exports = {};
808
- const module = { exports };
809
-
810
- // Execute the wrapper, passing mockDeno as the Deno global and utility functions
811
- // This makes Deno.env.get(), encodeBase64(), and decodeBase64() available inside the user's function
812
- wrapper(exports, module, createClient, mockDeno, encodeBase64, decodeBase64);
813
-
814
- // Get the exported function
815
- const functionHandler = module.exports || exports.default || exports;
816
-
817
- if (typeof functionHandler !== 'function') {
818
- throw new Error(
819
- 'No function exported. Expected: module.exports = async function(req) { ... }'
820
- );
821
- }
822
-
823
- // Create Request object from data
824
- const request = new Request(requestData.url, {
825
- method: requestData.method,
826
- headers: requestData.headers,
827
- body: requestData.body,
828
- });
829
-
830
- // Execute the function
831
- const response = await functionHandler(request);
832
-
833
- // Serialize and send response
834
- // Properly handle responses with no body
835
- let body = null;
836
-
837
- // Only read body if response has content
838
- // Status codes 204, 205, and 304 should not have a body
839
- if (![204, 205, 304].includes(response.status)) {
840
- body = await response.text();
841
- }
842
-
843
- const responseData = {
844
- status: response.status,
845
- statusText: response.statusText,
846
- headers: Object.fromEntries(response.headers),
847
- body: body,
848
- };
849
-
850
- self.postMessage({ success: true, response: responseData });
851
- } catch (error) {
852
- // Check if the error is actually a Response object (thrown by the function)
853
- if (error instanceof Response) {
854
- // Handle error responses the same way
855
- let body = null;
856
-
857
- if (![204, 205, 304].includes(error.status)) {
858
- body = await error.text();
859
- }
860
-
861
- const responseData = {
862
- status: error.status,
863
- statusText: error.statusText,
864
- headers: Object.fromEntries(error.headers),
865
- body: body,
866
- };
867
- self.postMessage({ success: true, response: responseData });
868
- } else {
869
- // For actual errors, include status if available
870
- self.postMessage({
871
- success: false,
872
- error: error.message || 'Unknown error',
873
- status: error.status || 500,
874
- });
875
- }
876
- }
877
- };
878
- healthCheck:
879
- type: HTTP
880
- port: runtime
881
- http:
882
- path: /health
883
-
884
- - name: insforge
885
- icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4
886
- dependencies:
887
- - postgres
888
- - postgrest
889
- - deno
890
- template: PREBUILT
891
- spec:
892
- source:
893
- image: ghcr.io/insforge/insforge-oss:v1.2.6
894
- ports:
895
- - id: web
896
- port: 7130
897
- type: HTTP
898
- - id: dev
899
- port: 7131
900
- type: HTTP
901
- env:
902
- PORT:
903
- default: "7130"
904
- PROJECT_ROOT:
905
- default: /app
906
- API_BASE_URL:
907
- default: ${ZEABUR_WEB_URL}
908
- VITE_API_BASE_URL:
909
- default: ${ZEABUR_WEB_URL}
910
- JWT_SECRET:
911
- default: ${PGRST_JWT_SECRET}
912
- ADMIN_EMAIL:
913
- default: ${ADMIN_EMAIL}
914
- ADMIN_PASSWORD:
915
- default: ${ADMIN_PASSWORD}
916
- # PostgreSQL connection
917
- POSTGRES_HOST:
918
- default: postgres
919
- POSTGRES_PORT:
920
- default: "5432"
921
- POSTGRES_DB:
922
- default: insforge
923
- POSTGRES_USER:
924
- default: postgres
925
- POSTGRESDB_PASSWORD:
926
- default: ${PGPASSWORD}
927
- DATABASE_URL:
928
- default: postgres://postgres:${PGPASSWORD}@postgres:5432/insforge
929
- POSTGREST_BASE_URL:
930
- default: http://postgrest:3000
931
- # Deno Runtime URL for serverless functions
932
- DENO_RUNTIME_URL:
933
- default: http://deno:7133
934
- # OAuth Configuration (optional)
935
- GOOGLE_CLIENT_ID:
936
- default: ""
937
- GOOGLE_CLIENT_SECRET:
938
- default: ""
939
- GOOGLE_REDIRECT_URI:
940
- default: ${ZEABUR_WEB_URL}/api/auth/v1/callback
941
- GITHUB_CLIENT_ID:
942
- default: ""
943
- GITHUB_CLIENT_SECRET:
944
- default: ""
945
- GITHUB_REDIRECT_URI:
946
- default: ${ZEABUR_WEB_URL}/api/auth/v1/callback
947
- # Multi-tenant Cloud Configuration
948
- DEPLOYMENT_ID:
949
- default: ${ZEABUR_SERVICE_ID}
950
- PROJECT_ID:
951
- default: ${ZEABUR_PROJECT_ID}
952
- APP_KEY:
953
- default: ""
954
- ACCESS_API_KEY:
955
- default: ""
956
- OPENROUTER_API_KEY:
957
- default: ${OPENROUTER_API_KEY}
958
- healthCheck:
959
- type: HTTP
960
- port: web
961
- http:
962
- path: /
963
- domainKey: PUBLIC_DOMAIN
964
-
965
- localization:
966
- zh-CN:
967
- description: InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。
968
- readme: |
969
- # InsForge
970
-
971
- InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。
972
-
973
- ## 功能特性
974
-
975
- - **身份验证系统** - 用户管理和安全认证
976
- - **数据库存储** - 灵活的 PostgreSQL 数据库,支持自动模式管理
977
- - **文件管理** - 安全的文件上传和存储功能
978
- - **PostgREST API** - 从数据库模式自动生成 REST API
979
- - **无服务器函数** - 用于自定义业务逻辑的边缘函数
980
- - **AI 智能体集成** - 连接 Claude 或 GPT 等 AI 智能体来管理后端
981
-
982
- ## 快速开始
983
-
984
- 此模板提供 InsForge 平台的**一键部署**:
985
-
986
- 1. **部署** - 点击部署并绑定域名
987
- 2. **登录** - 使用管理员凭据访问控制台
988
- 3. **连接 AI 智能体** - 通过控制台连接您的 AI 智能体(Claude、GPT 等)
989
- 4. **构建应用** - 使用自然语言提示创建应用程序:
990
- - "构建一个带用户认证的待办事项应用"
991
- - "创建一个带图片上传的 Instagram 克隆"
992
- - "构建一个带评论和点赞的博客"
993
-
994
- ## 使用场景
995
-
996
- - **AI 生成的前端** - 为 AI 生成的前端项目快速创建后端
997
- - **智能体驱动开发** - 让 AI 智能体管理您的整个后端基础设施
998
- - **快速原型设计** - 使用自然语言构建全栈应用程序
999
-
1000
- zh-TW:
1001
- description: InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。
1002
- readme: |
1003
- # InsForge
1004
-
1005
- InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。
1006
-
1007
- ## 功能特色
1008
-
1009
- - **身份驗證系統** - 使用者管理和安全認證
1010
- - **資料庫儲存** - 靈活的 PostgreSQL 資料庫,支援自動模式管理
1011
- - **檔案管理** - 安全的檔案上傳和儲存功能
1012
- - **PostgREST API** - 從資料庫模式自動產生 REST API
1013
- - **無伺服器函數** - 用於自訂業務邏輯的邊緣函數
1014
- - **AI 智慧體整合** - 連接 Claude 或 GPT 等 AI 智慧體來管理後端
1015
-
1016
- ## 快速開始
1017
-
1018
- 此模版提供 InsForge 平台的**一鍵部署**:
1019
-
1020
- 1. **部署** - 點擊部署並綁定網域
1021
- 2. **登入** - 使用管理員憑證存取控制台
1022
- 3. **連接 AI 智慧體** - 透過控制台連接您的 AI 智慧體(Claude、GPT 等)
1023
- 4. **建構應用** - 使用自然語言提示建立應用程式:
1024
- - "建構一個帶使用者認證的待辦事項應用"
1025
- - "建立一個帶圖片上傳的 Instagram 複製品"
1026
- - "建構一個帶評論和按讚的部落格"
1027
-
1028
- ## 使用情境
1029
-
1030
- - **AI 產生的前端** - 為 AI 產生的前端專案快速建立後端
1031
- - **智慧體驅動開發** - 讓 AI 智慧體管理您的整個後端基礎設施
1032
- - **快速原型設計** - 使用自然語言建構全端應用程式
1
+ # yaml-language-server: $schema=https://schema.zeabur.app/template.json
2
+ apiVersion: zeabur.com/v1
3
+ kind: Template
4
+ metadata:
5
+ name: InsForge
6
+ spec:
7
+ description: InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously.
8
+ coverImage: https://cdn.zeabur.com/insforge.png
9
+ icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4
10
+ variables:
11
+ - key: PUBLIC_DOMAIN
12
+ type: DOMAIN
13
+ name: InsForge Domain
14
+ description: The domain for accessing your InsForge application.
15
+ - key: ADMIN_EMAIL
16
+ type: STRING
17
+ name: Admin Email
18
+ description: Email address for the admin user.
19
+ - key: ADMIN_PASSWORD
20
+ type: STRING
21
+ name: Admin Password
22
+ description: Password for admin. Must be at least 8 characters.
23
+ - key: OPENROUTER_API_KEY
24
+ type: STRING
25
+ name: OpenRouter API Key
26
+ description: API key for OpenRouter LLM services (optional).
27
+ tags:
28
+ - Development
29
+ - Database
30
+ - API
31
+ - Platform
32
+ readme: |-
33
+ # InsForge
34
+
35
+ InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously.
36
+
37
+ ## Features
38
+
39
+ - **Authentication System** - User management and secure authentication
40
+ - **Database Storage** - Flexible PostgreSQL database with automatic schema management
41
+ - **File Management** - Secure file upload and storage capabilities
42
+ - **PostgREST API** - Automatic REST API generation from database schema
43
+ - **Serverless Functions** - Edge functions for custom business logic
44
+ - **AI Agent Integration** - Connect AI agents like Claude or GPT to manage your backend
45
+
46
+ ## Quick Start
47
+
48
+ This template provides **one-click deployment** of the complete InsForge platform:
49
+
50
+ 1. **Deploy** - Click deploy and bind a domain
51
+ 2. **Login** - Use your admin credentials to access the dashboard
52
+ 3. **Connect AI Agent** - Link your AI agent (Claude, GPT, etc.) through the dashboard
53
+ 4. **Build Apps** - Use natural language prompts to create applications:
54
+ - "Build a todo app with user authentication"
55
+ - "Create an Instagram clone with image upload"
56
+ - "Build a blog with comments and likes"
57
+
58
+ ## Use Cases
59
+
60
+ - **AI-Generated Frontends** - Rapidly create backends for AI-generated frontend projects
61
+ - **Agent-Driven Development** - Let AI agents manage your entire backend infrastructure
62
+ - **Rapid Prototyping** - Build full-stack applications using natural language
63
+
64
+ ## Community
65
+
66
+ - [Discord](https://discord.gg/MPxwj5xVvW) - Join our community
67
+ - [GitHub](https://github.com/InsForge/InsForge) - Contribute to the project
68
+ - Email: info@insforge.dev
69
+
70
+ services:
71
+ - name: postgres
72
+ icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg
73
+ template: PREBUILT
74
+ spec:
75
+ source:
76
+ image: postgres:15.13
77
+ command:
78
+ - docker-entrypoint.sh
79
+ - -c
80
+ - config_file=/etc/postgresql/postgresql.conf
81
+ ports:
82
+ - id: database
83
+ port: 5432
84
+ type: TCP
85
+ volumes:
86
+ - id: data
87
+ dir: /var/lib/postgresql/data
88
+ instructions:
89
+ - title: Connection String
90
+ content: postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge
91
+ - title: PostgreSQL Connect Command
92
+ content: psql "postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge"
93
+ - title: PostgreSQL username
94
+ content: postgres
95
+ - title: PostgresSQL password
96
+ content: ${POSTGRES_PASSWORD}
97
+ - title: PostgresSQL database
98
+ content: insforge
99
+ - title: PostgreSQL host
100
+ content: ${PORT_FORWARDED_HOSTNAME}
101
+ - title: PostgreSQL port
102
+ content: ${DATABASE_PORT_FORWARDED_PORT}
103
+ env:
104
+ PGDATA:
105
+ default: /var/lib/postgresql/data/pgdata
106
+ POSTGRES_CONNECTION_STRING:
107
+ default: postgres://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/insforge
108
+ expose: true
109
+ POSTGRES_DATABASE:
110
+ default: insforge
111
+ expose: true
112
+ POSTGRES_DB:
113
+ default: insforge
114
+ POSTGRES_HOST:
115
+ default: ${CONTAINER_HOSTNAME}
116
+ expose: true
117
+ POSTGRES_PASSWORD:
118
+ default: ${PASSWORD}
119
+ expose: true
120
+ readonly: true
121
+ POSTGRES_PORT:
122
+ default: ${DATABASE_PORT}
123
+ expose: true
124
+ POSTGRES_URI:
125
+ default: ${POSTGRES_CONNECTION_STRING}
126
+ expose: true
127
+ POSTGRES_USER:
128
+ default: postgres
129
+ POSTGRES_USERNAME:
130
+ default: postgres
131
+ expose: true
132
+ JWT_SECRET:
133
+ default: ${PASSWORD}
134
+ expose: true
135
+ readonly: true
136
+ JWT_EXP:
137
+ default: "3600"
138
+ configs:
139
+ - path: /etc/postgresql/postgresql.conf
140
+ template: |
141
+ # PostgreSQL configuration for InsForge
142
+ # Enable logical replication for Logflare
143
+
144
+ # Listen on all interfaces to allow container connections
145
+ listen_addresses = '*'
146
+
147
+ # Set WAL level to logical for Logflare replication
148
+ wal_level = logical
149
+
150
+ # Set max replication slots (needed for logical replication)
151
+ max_replication_slots = 10
152
+
153
+ # Set max WAL senders
154
+ max_wal_senders = 10
155
+
156
+ # Shared preload libraries (if needed)
157
+ # shared_preload_libraries = 'pg_stat_statements'
158
+ - path: /docker-entrypoint-initdb.d/01-init.sql
159
+ template: |
160
+ -- init.sql
161
+ -- Create role for anonymous user
162
+ CREATE ROLE anon NOLOGIN;
163
+
164
+ -- Create role for authenticator
165
+ CREATE ROLE authenticated NOLOGIN;
166
+
167
+ -- Create project admin role for admin users
168
+ CREATE ROLE project_admin NOLOGIN;
169
+
170
+ GRANT USAGE ON SCHEMA public TO anon;
171
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO anon;
172
+ GRANT USAGE ON SCHEMA public TO authenticated;
173
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;
174
+ GRANT USAGE ON SCHEMA public TO project_admin;
175
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_admin;
176
+
177
+ -- Grant permissions to roles
178
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO anon, authenticated, project_admin;
179
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated, project_admin;
180
+
181
+ -- Create function to automatically create RLS policies for new tables
182
+ CREATE OR REPLACE FUNCTION public.create_default_policies()
183
+ RETURNS event_trigger AS $$
184
+ DECLARE
185
+ obj record;
186
+ table_schema text;
187
+ table_name text;
188
+ has_rls boolean;
189
+ BEGIN
190
+ FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
191
+ LOOP
192
+ -- Extract schema and table name from object_identity
193
+ -- Handle quoted identifiers by removing quotes
194
+ SELECT INTO table_schema, table_name
195
+ split_part(obj.object_identity, '.', 1),
196
+ trim(both '"' from split_part(obj.object_identity, '.', 2));
197
+ -- Check if RLS is enabled on the table
198
+ SELECT INTO has_rls
199
+ rowsecurity
200
+ FROM pg_tables
201
+ WHERE schemaname = table_schema
202
+ AND tablename = table_name;
203
+ -- Only create policies if RLS is enabled
204
+ IF has_rls THEN
205
+ -- Create policy for project_admin role only
206
+ -- Users must define their own policies for anon and authenticated roles
207
+ EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
208
+ END IF;
209
+ END LOOP;
210
+ END;
211
+ $$ LANGUAGE plpgsql;
212
+
213
+ -- Create event trigger to run the function when new tables are created
214
+ CREATE EVENT TRIGGER create_policies_on_table_create
215
+ ON ddl_command_end
216
+ WHEN TAG IN ('CREATE TABLE')
217
+ EXECUTE FUNCTION public.create_default_policies();
218
+
219
+ -- Create function to handle RLS enablement
220
+ CREATE OR REPLACE FUNCTION public.create_policies_after_rls()
221
+ RETURNS event_trigger AS $$
222
+ DECLARE
223
+ obj record;
224
+ table_schema text;
225
+ table_name text;
226
+ BEGIN
227
+ FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'ALTER TABLE'
228
+ LOOP
229
+ -- Extract schema and table name
230
+ -- Handle quoted identifiers by removing quotes
231
+ SELECT INTO table_schema, table_name
232
+ split_part(obj.object_identity, '.', 1),
233
+ trim(both '"' from split_part(obj.object_identity, '.', 2));
234
+ -- Check if table has RLS enabled and no policies yet
235
+ IF EXISTS (
236
+ SELECT 1 FROM pg_tables
237
+ WHERE schemaname = table_schema
238
+ AND tablename = table_name
239
+ AND rowsecurity = true
240
+ ) AND NOT EXISTS (
241
+ SELECT 1 FROM pg_policies
242
+ WHERE schemaname = table_schema
243
+ AND tablename = table_name
244
+ ) THEN
245
+ -- Create policy for project_admin role only
246
+ -- Users must define their own policies for anon and authenticated roles
247
+ EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
248
+ END IF;
249
+ END LOOP;
250
+ END;
251
+ $$ LANGUAGE plpgsql;
252
+
253
+ -- Create event trigger for ALTER TABLE commands
254
+ CREATE EVENT TRIGGER create_policies_on_rls_enable
255
+ ON ddl_command_end
256
+ WHEN TAG IN ('ALTER TABLE')
257
+ EXECUTE FUNCTION public.create_policies_after_rls();
258
+ - path: /docker-entrypoint-initdb.d/02-jwt.sql
259
+ template: |
260
+ \set jwt_secret `echo "$JWT_SECRET"`
261
+ \set jwt_exp `echo "$JWT_EXP"`
262
+
263
+ ALTER DATABASE insforge SET "app.settings.jwt_secret" TO :'jwt_secret';
264
+ ALTER DATABASE insforge SET "app.settings.jwt_exp" TO :'jwt_exp';
265
+ healthCheck:
266
+ type: TCP
267
+ port: database
268
+
269
+ - name: postgrest
270
+ icon: https://avatars.githubusercontent.com/u/15115011?s=96&v=4
271
+ dependencies:
272
+ - postgres
273
+ template: PREBUILT
274
+ spec:
275
+ source:
276
+ image: postgrest/postgrest:v12.2.12
277
+ ports:
278
+ - id: api
279
+ port: 3000
280
+ type: HTTP
281
+ env:
282
+ PGHOST:
283
+ default: postgres
284
+ PGPORT:
285
+ default: "5432"
286
+ PGDATABASE:
287
+ default: insforge
288
+ PGUSER:
289
+ default: postgres
290
+ PGPASSWORD:
291
+ default: ${POSTGRES_PASSWORD}
292
+ expose: true
293
+ PGRST_OPENAPI_SERVER_PROXY_URI:
294
+ default: http://localhost:3000
295
+ PGRST_DB_SCHEMA:
296
+ default: public
297
+ PGRST_DB_ANON_ROLE:
298
+ default: anon
299
+ PGRST_JWT_SECRET:
300
+ default: ${JWT_SECRET}
301
+ expose: true
302
+ readonly: true
303
+ PGRST_DB_CHANNEL_ENABLED:
304
+ default: "true"
305
+ PGRST_DB_CHANNEL:
306
+ default: pgrst
307
+ healthCheck:
308
+ type: HTTP
309
+ port: api
310
+ http:
311
+ path: /
312
+
313
+ - name: deno
314
+ icon: https://avatars.githubusercontent.com/u/42048915?s=96&v=4
315
+ dependencies:
316
+ - postgres
317
+ - postgrest
318
+ template: PREBUILT
319
+ spec:
320
+ source:
321
+ image: denoland/deno:alpine-2.0.6
322
+ command:
323
+ - sh
324
+ - -c
325
+ - |
326
+ cd /app &&
327
+ echo 'Downloading Deno dependencies...' &&
328
+ deno cache functions/server.ts &&
329
+ echo 'Starting Deno server on port 7133...' &&
330
+ deno run --allow-net --allow-env --allow-read=./functions/worker-template.js --watch functions/server.ts
331
+ ports:
332
+ - id: runtime
333
+ port: 7133
334
+ type: HTTP
335
+ volumes:
336
+ - id: cache
337
+ dir: /deno-dir
338
+ env:
339
+ PORT:
340
+ default: "7133"
341
+ DENO_ENV:
342
+ default: development
343
+ DENO_DIR:
344
+ default: /deno-dir
345
+ POSTGRES_HOST:
346
+ default: postgres
347
+ POSTGRES_PORT:
348
+ default: "5432"
349
+ POSTGRES_DB:
350
+ default: insforge
351
+ POSTGRES_USER:
352
+ default: postgres
353
+ POSTGRES_PASSWORD:
354
+ default: ${PGPASSWORD}
355
+ POSTGREST_BASE_URL:
356
+ default: http://postgrest:3000
357
+ WORKER_TIMEOUT_MS:
358
+ default: "60000"
359
+ ENCRYPTION_KEY:
360
+ default: ${PASSWORD}
361
+ JWT_SECRET:
362
+ default: ${PGRST_JWT_SECRET}
363
+ configs:
364
+ - path: /app/functions/server.ts
365
+ template: |
366
+ import { Client } from 'https://deno.land/x/postgres@v0.17.0/mod.ts';
367
+ import { join, dirname, fromFileUrl } from 'https://deno.land/std@0.224.0/path/mod.ts';
368
+
369
+ /* eslint-disable no-console */
370
+ const port = parseInt(Deno.env.get('PORT') ?? '7133');
371
+
372
+ console.log(`Deno serverless runtime running on port ${port}`);
373
+
374
+ // Configuration
375
+ const WORKER_TIMEOUT_MS = parseInt(Deno.env.get('WORKER_TIMEOUT_MS') ?? '60000');
376
+
377
+ // Worker template code - loaded on first use
378
+ let workerTemplateCode: string | null = null;
379
+
380
+ async function getWorkerTemplateCode(): Promise<string> {
381
+ if (!workerTemplateCode) {
382
+ const currentDir = dirname(fromFileUrl(import.meta.url));
383
+ workerTemplateCode = await Deno.readTextFile(join(currentDir, 'worker-template.js'));
384
+ }
385
+ return workerTemplateCode;
386
+ }
387
+
388
+ // Decrypt function for Deno (compatible with Node.js encryption)
389
+ async function decryptSecret(ciphertext: string, key: string): Promise<string> {
390
+ try {
391
+ const parts = ciphertext.split(':');
392
+ if (parts.length !== 3) {
393
+ throw new Error('Invalid ciphertext format');
394
+ }
395
+
396
+ // Get the encryption key by hashing the JWT secret
397
+ const keyData = new TextEncoder().encode(key);
398
+ const hashBuffer = await crypto.subtle.digest('SHA-256', keyData);
399
+ const cryptoKey = await crypto.subtle.importKey('raw', hashBuffer, { name: 'AES-GCM' }, false, [
400
+ 'decrypt',
401
+ ]);
402
+
403
+ // Extract IV, auth tag, and encrypted data
404
+ const iv = Uint8Array.from(parts[0].match(/.{2}/g)!.map((byte) => parseInt(byte, 16)));
405
+ const authTag = Uint8Array.from(parts[1].match(/.{2}/g)!.map((byte) => parseInt(byte, 16)));
406
+ const encrypted = Uint8Array.from(parts[2].match(/.{2}/g)!.map((byte) => parseInt(byte, 16)));
407
+
408
+ // Combine encrypted data and auth tag (GCM expects them together)
409
+ const cipherData = new Uint8Array(encrypted.length + authTag.length);
410
+ cipherData.set(encrypted);
411
+ cipherData.set(authTag, encrypted.length);
412
+
413
+ // Decrypt
414
+ const decryptedBuffer = await crypto.subtle.decrypt(
415
+ { name: 'AES-GCM', iv },
416
+ cryptoKey,
417
+ cipherData
418
+ );
419
+
420
+ return new TextDecoder().decode(decryptedBuffer);
421
+ } catch (error) {
422
+ console.error('Failed to decrypt secret:', error);
423
+ throw error;
424
+ }
425
+ }
426
+
427
+ // Database connection
428
+ const dbConfig = {
429
+ user: Deno.env.get('POSTGRES_USER') || 'postgres',
430
+ password: Deno.env.get('POSTGRES_PASSWORD') || 'postgres',
431
+ database: Deno.env.get('POSTGRES_DB') || 'insforge',
432
+ hostname: Deno.env.get('POSTGRES_HOST') || 'postgres',
433
+ port: parseInt(Deno.env.get('POSTGRES_PORT') || '5432', 10),
434
+ };
435
+
436
+ // Get function code from database
437
+ async function getFunctionCode(slug: string): Promise<string | null> {
438
+ const client = new Client(dbConfig);
439
+
440
+ try {
441
+ await client.connect();
442
+
443
+ const result = await client.queryObject<{ code: string }>`
444
+ SELECT code FROM functions.definitions
445
+ WHERE slug = ${slug} AND status = 'active'
446
+ `;
447
+
448
+ if (!result.rows.length) {
449
+ return null;
450
+ }
451
+
452
+ return result.rows[0].code;
453
+ } catch (error) {
454
+ console.error(`Error fetching function ${slug}:`, error);
455
+ return null;
456
+ } finally {
457
+ await client.end();
458
+ }
459
+ }
460
+
461
+ // Get all secrets from main secrets table and decrypt them
462
+ async function getFunctionSecrets(): Promise<Record<string, string>> {
463
+ const client = new Client(dbConfig);
464
+
465
+ try {
466
+ await client.connect();
467
+
468
+ // Get the encryption key from environment
469
+ const encryptionKey = Deno.env.get('ENCRYPTION_KEY') || Deno.env.get('JWT_SECRET');
470
+ if (!encryptionKey) {
471
+ console.error('No encryption key available for decrypting secrets');
472
+ return {};
473
+ }
474
+
475
+ // Fetch all active secrets from system.secrets table
476
+ const result = await client.queryObject<{
477
+ key: string;
478
+ value_ciphertext: string;
479
+ }>`
480
+ SELECT key, value_ciphertext
481
+ FROM system.secrets
482
+ WHERE is_active = true
483
+ AND (expires_at IS NULL OR expires_at > NOW())
484
+ `;
485
+
486
+ const secrets: Record<string, string> = {};
487
+
488
+ // Decrypt each secret
489
+ for (const row of result.rows) {
490
+ try {
491
+ secrets[row.key] = await decryptSecret(row.value_ciphertext, encryptionKey);
492
+ } catch (error) {
493
+ console.error(`Failed to decrypt secret ${row.key}:`, error);
494
+ // Skip this secret if decryption fails
495
+ }
496
+ }
497
+
498
+ return secrets;
499
+ } catch (error) {
500
+ console.error('Error fetching secrets:', error);
501
+ return {};
502
+ } finally {
503
+ await client.end();
504
+ }
505
+ }
506
+
507
+ // Execute function in isolated worker
508
+ async function executeInWorker(code: string, request: Request): Promise<Response> {
509
+ // Get worker template
510
+ const template = await getWorkerTemplateCode();
511
+
512
+ // Fetch all function secrets
513
+ const secrets = await getFunctionSecrets();
514
+
515
+ // Create blob for worker
516
+ const workerBlob = new Blob([template], { type: 'application/javascript' });
517
+ const workerUrl = URL.createObjectURL(workerBlob);
518
+
519
+ return new Promise(async (resolve) => {
520
+ const worker = new Worker(workerUrl, { type: 'module' });
521
+
522
+ // Set timeout for worker execution
523
+ const timeout = setTimeout(() => {
524
+ worker.terminate();
525
+ URL.revokeObjectURL(workerUrl);
526
+ resolve(
527
+ new Response(JSON.stringify({ error: 'Function timeout' }), {
528
+ status: 504,
529
+ headers: { 'Content-Type': 'application/json' },
530
+ })
531
+ );
532
+ }, WORKER_TIMEOUT_MS);
533
+
534
+ // Handle worker response
535
+ worker.onmessage = (e) => {
536
+ clearTimeout(timeout);
537
+ worker.terminate();
538
+ URL.revokeObjectURL(workerUrl);
539
+
540
+ if (e.data.success) {
541
+ const { response } = e.data;
542
+ // The worker now properly sends null for bodyless responses
543
+ resolve(
544
+ new Response(response.body, {
545
+ status: response.status,
546
+ statusText: response.statusText,
547
+ headers: response.headers,
548
+ })
549
+ );
550
+ } else {
551
+ resolve(
552
+ new Response(JSON.stringify({ error: e.data.error }), {
553
+ status: e.data.status || 500,
554
+ headers: { 'Content-Type': 'application/json' },
555
+ })
556
+ );
557
+ }
558
+ };
559
+
560
+ // Handle worker errors
561
+ worker.onerror = (error) => {
562
+ clearTimeout(timeout);
563
+ worker.terminate();
564
+ URL.revokeObjectURL(workerUrl);
565
+ console.error('Worker error:', error);
566
+ resolve(
567
+ new Response(JSON.stringify({ error: 'Worker execution error' }), {
568
+ status: 500,
569
+ headers: { 'Content-Type': 'application/json' },
570
+ })
571
+ );
572
+ };
573
+
574
+ // Prepare request data
575
+ const body = request.body ? await request.text() : null;
576
+ const requestData = {
577
+ url: request.url,
578
+ method: request.method,
579
+ headers: Object.fromEntries(request.headers),
580
+ body,
581
+ };
582
+
583
+ // Send message with code, request data, and secrets
584
+ worker.postMessage({ code, requestData, secrets });
585
+ });
586
+ }
587
+
588
+ Deno.serve({ port }, async (req: Request) => {
589
+ const url = new URL(req.url);
590
+ const pathname = url.pathname;
591
+
592
+ // Health check
593
+ if (pathname === '/health') {
594
+ return new Response(
595
+ JSON.stringify({
596
+ status: 'ok',
597
+ runtime: 'deno',
598
+ version: Deno.version.deno,
599
+ typescript: Deno.version.typescript,
600
+ v8: Deno.version.v8,
601
+ }),
602
+ {
603
+ headers: { 'Content-Type': 'application/json' },
604
+ }
605
+ );
606
+ }
607
+
608
+ // Function execution - match ONLY exact slug, no subpaths
609
+ const slugMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)$/);
610
+ if (slugMatch) {
611
+ const slug = slugMatch[1];
612
+ const startTime = Date.now();
613
+
614
+ // Get function code from database
615
+ const code = await getFunctionCode(slug);
616
+
617
+ if (!code) {
618
+ return new Response(JSON.stringify({ error: 'Function not found or not active' }), {
619
+ status: 404,
620
+ headers: { 'Content-Type': 'application/json' },
621
+ });
622
+ }
623
+
624
+ // Execute in worker with original request
625
+ try {
626
+ const response = await executeInWorker(code, req);
627
+ const duration = Date.now() - startTime;
628
+
629
+ // Log completed invocations only
630
+ console.log(
631
+ JSON.stringify({
632
+ timestamp: new Date().toISOString(),
633
+ level: 'info',
634
+ slug,
635
+ method: req.method,
636
+ status: response.status,
637
+ duration: `${duration}ms`,
638
+ })
639
+ );
640
+
641
+ return response;
642
+ } catch (error) {
643
+ const duration = Date.now() - startTime;
644
+ console.error(
645
+ JSON.stringify({
646
+ timestamp: new Date().toISOString(),
647
+ level: 'error',
648
+ slug,
649
+ error: error instanceof Error ? error.message : String(error),
650
+ duration: `${duration}ms`,
651
+ })
652
+ );
653
+ return new Response(JSON.stringify({ error: 'Function execution failed' }), {
654
+ status: 500,
655
+ headers: { 'Content-Type': 'application/json' },
656
+ });
657
+ }
658
+ }
659
+
660
+ // Runtime info
661
+ if (pathname === '/info') {
662
+ return new Response(
663
+ JSON.stringify({
664
+ runtime: 'deno',
665
+ version: Deno.version,
666
+ env: Deno.env.get('DENO_ENV') || 'production',
667
+ database: {
668
+ host: dbConfig.hostname,
669
+ database: dbConfig.database,
670
+ },
671
+ }),
672
+ {
673
+ headers: { 'Content-Type': 'application/json' },
674
+ }
675
+ );
676
+ }
677
+
678
+ // 404
679
+ return new Response('Not Found', { status: 404 });
680
+ });
681
+ - path: /app/functions/deno.json
682
+ template: |
683
+ {
684
+ "compilerOptions": {
685
+ "lib": ["deno.window", "deno.worker"],
686
+ "strict": true
687
+ },
688
+ "lint": {
689
+ "files": {
690
+ "include": ["./**/*.ts", "./**/*.js"]
691
+ },
692
+ "rules": {
693
+ "tags": ["recommended"]
694
+ }
695
+ },
696
+ "fmt": {
697
+ "files": {
698
+ "include": ["./**/*.ts", "./**/*.js"]
699
+ },
700
+ "options": {
701
+ "useTabs": false,
702
+ "lineWidth": 100,
703
+ "indentWidth": 2,
704
+ "singleQuote": true
705
+ }
706
+ }
707
+ }
708
+ - path: /app/functions/worker-template.js
709
+ template: |
710
+ /**
711
+ * Worker Template for Serverless Functions
712
+ *
713
+ * This code runs inside a Web Worker environment created by Deno.
714
+ * Each worker is created fresh for a single request, executes once, and terminates.
715
+ */
716
+ /* eslint-env worker */
717
+ /* global self, Request, Deno */
718
+
719
+ // Import SDK at worker level - this will be available to all functions
720
+ import { createClient } from 'npm:@insforge/sdk';
721
+ // Import base64 utilities for encoding/decoding
722
+ import { encodeBase64, decodeBase64 } from 'https://deno.land/std@0.224.0/encoding/base64.ts';
723
+
724
+ // Handle the single message with code, request data, and secrets
725
+ self.onmessage = async (e) => {
726
+ const { code, requestData, secrets = {} } = e.data;
727
+
728
+ try {
729
+ /**
730
+ * MOCK DENO OBJECT EXPLANATION:
731
+ *
732
+ * Why we need a mock Deno object:
733
+ * - Edge functions run in isolated Web Workers (sandboxed environments)
734
+ * - Web Workers don't have access to the real Deno global object for security
735
+ * - We need to provide Deno.env functionality so functions can access secrets
736
+ *
737
+ * How it works:
738
+ * 1. The main server (server.ts) fetches all active secrets from the system.secrets table
739
+ * 2. Only active (is_active=true) and non-expired secrets are included
740
+ * 3. Secrets are decrypted and passed to this worker via the 'secrets' object
741
+ * 4. We create a mock Deno object that provides Deno.env.get()
742
+ * 5. When user code calls Deno.env.get('MY_SECRET'), it reads from our secrets object
743
+ *
744
+ * This allows edge functions to use familiar Deno.env syntax while maintaining security
745
+ * Secrets are managed via the /api/secrets endpoint
746
+ */
747
+ const mockDeno = {
748
+ // Mock the Deno.env API - only get() is needed for reading secrets
749
+ env: {
750
+ get: (key) => secrets[key] || undefined,
751
+ },
752
+ };
753
+
754
+ /**
755
+ * FUNCTION WRAPPING EXPLANATION:
756
+ *
757
+ * Here we create a wrapper function that will execute the user's code.
758
+ * The user's function expects to have access to:
759
+ * - module.exports (to export their function)
760
+ * - createClient (the Insforge SDK)
761
+ * - Deno (for Deno.env.get() etc.)
762
+ * - encodeBase64, decodeBase64 (base64 encoding utilities)
763
+ *
764
+ * We inject our mockDeno as the 'Deno' parameter, so when the user's code
765
+ * calls Deno.env.get('MY_SECRET'), it's actually calling mockDeno.env.get('MY_SECRET')
766
+ */
767
+ const wrapper = new Function(
768
+ 'exports',
769
+ 'module',
770
+ 'createClient',
771
+ 'Deno',
772
+ 'encodeBase64',
773
+ 'decodeBase64',
774
+ code
775
+ );
776
+ const exports = {};
777
+ const module = { exports };
778
+
779
+ // Execute the wrapper, passing mockDeno as the Deno global and utility functions
780
+ // This makes Deno.env.get(), encodeBase64(), and decodeBase64() available inside the user's function
781
+ wrapper(exports, module, createClient, mockDeno, encodeBase64, decodeBase64);
782
+
783
+ // Get the exported function
784
+ const functionHandler = module.exports || exports.default || exports;
785
+
786
+ if (typeof functionHandler !== 'function') {
787
+ throw new Error(
788
+ 'No function exported. Expected: module.exports = async function(req) { ... }'
789
+ );
790
+ }
791
+
792
+ // Create Request object from data
793
+ const request = new Request(requestData.url, {
794
+ method: requestData.method,
795
+ headers: requestData.headers,
796
+ body: requestData.body,
797
+ });
798
+
799
+ // Execute the function
800
+ const response = await functionHandler(request);
801
+
802
+ // Serialize and send response
803
+ // Properly handle responses with no body
804
+ let body = null;
805
+
806
+ // Only read body if response has content
807
+ // Status codes 204, 205, and 304 should not have a body
808
+ if (![204, 205, 304].includes(response.status)) {
809
+ body = await response.text();
810
+ }
811
+
812
+ const responseData = {
813
+ status: response.status,
814
+ statusText: response.statusText,
815
+ headers: Object.fromEntries(response.headers),
816
+ body: body,
817
+ };
818
+
819
+ self.postMessage({ success: true, response: responseData });
820
+ } catch (error) {
821
+ // Check if the error is actually a Response object (thrown by the function)
822
+ if (error instanceof Response) {
823
+ // Handle error responses the same way
824
+ let body = null;
825
+
826
+ if (![204, 205, 304].includes(error.status)) {
827
+ body = await error.text();
828
+ }
829
+
830
+ const responseData = {
831
+ status: error.status,
832
+ statusText: error.statusText,
833
+ headers: Object.fromEntries(error.headers),
834
+ body: body,
835
+ };
836
+ self.postMessage({ success: true, response: responseData });
837
+ } else {
838
+ // For actual errors, include status if available
839
+ self.postMessage({
840
+ success: false,
841
+ error: error.message || 'Unknown error',
842
+ status: error.status || 500,
843
+ });
844
+ }
845
+ }
846
+ };
847
+ healthCheck:
848
+ type: HTTP
849
+ port: runtime
850
+ http:
851
+ path: /health
852
+
853
+ - name: insforge
854
+ icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4
855
+ dependencies:
856
+ - postgres
857
+ - postgrest
858
+ - deno
859
+ template: PREBUILT
860
+ spec:
861
+ source:
862
+ image: ghcr.io/insforge/insforge-oss:v1.4.3
863
+ ports:
864
+ - id: web
865
+ port: 7130
866
+ type: HTTP
867
+ - id: dev
868
+ port: 7131
869
+ type: HTTP
870
+ env:
871
+ PORT:
872
+ default: "7130"
873
+ PROJECT_ROOT:
874
+ default: /app
875
+ API_BASE_URL:
876
+ default: ${ZEABUR_WEB_URL}
877
+ VITE_API_BASE_URL:
878
+ default: ${ZEABUR_WEB_URL}
879
+ JWT_SECRET:
880
+ default: ${PGRST_JWT_SECRET}
881
+ ADMIN_EMAIL:
882
+ default: ${ADMIN_EMAIL}
883
+ ADMIN_PASSWORD:
884
+ default: ${ADMIN_PASSWORD}
885
+ # PostgreSQL connection
886
+ POSTGRES_HOST:
887
+ default: postgres
888
+ POSTGRES_PORT:
889
+ default: "5432"
890
+ POSTGRES_DB:
891
+ default: insforge
892
+ POSTGRES_USER:
893
+ default: postgres
894
+ POSTGRESDB_PASSWORD:
895
+ default: ${PGPASSWORD}
896
+ DATABASE_URL:
897
+ default: postgres://postgres:${PGPASSWORD}@postgres:5432/insforge
898
+ POSTGREST_BASE_URL:
899
+ default: http://postgrest:3000
900
+ # Deno Runtime URL for serverless functions
901
+ DENO_RUNTIME_URL:
902
+ default: http://deno:7133
903
+ # OAuth Configuration (optional)
904
+ GOOGLE_CLIENT_ID:
905
+ default: ""
906
+ GOOGLE_CLIENT_SECRET:
907
+ default: ""
908
+ GOOGLE_REDIRECT_URI:
909
+ default: ${ZEABUR_WEB_URL}/api/auth/v1/callback
910
+ GITHUB_CLIENT_ID:
911
+ default: ""
912
+ GITHUB_CLIENT_SECRET:
913
+ default: ""
914
+ GITHUB_REDIRECT_URI:
915
+ default: ${ZEABUR_WEB_URL}/api/auth/v1/callback
916
+ # Multi-tenant Cloud Configuration
917
+ DEPLOYMENT_ID:
918
+ default: ${ZEABUR_SERVICE_ID}
919
+ PROJECT_ID:
920
+ default: ${ZEABUR_PROJECT_ID}
921
+ APP_KEY:
922
+ default: ""
923
+ ACCESS_API_KEY:
924
+ default: ""
925
+ OPENROUTER_API_KEY:
926
+ default: ${OPENROUTER_API_KEY}
927
+ healthCheck:
928
+ type: HTTP
929
+ port: web
930
+ http:
931
+ path: /api/health
932
+ domainKey: PUBLIC_DOMAIN
933
+
934
+ localization:
935
+ zh-CN:
936
+ description: InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。
937
+ readme: |
938
+ # InsForge
939
+
940
+ InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。
941
+
942
+ ## 功能特性
943
+
944
+ - **身份验证系统** - 用户管理和安全认证
945
+ - **数据库存储** - 灵活的 PostgreSQL 数据库,支持自动模式管理
946
+ - **文件管理** - 安全的文件上传和存储功能
947
+ - **PostgREST API** - 从数据库模式自动生成 REST API
948
+ - **无服务器函数** - 用于自定义业务逻辑的边缘函数
949
+ - **AI 智能体集成** - 连接 Claude 或 GPT 等 AI 智能体来管理后端
950
+
951
+ ## 快速开始
952
+
953
+ 此模板提供 InsForge 平台的**一键部署**:
954
+
955
+ 1. **部署** - 点击部署并绑定域名
956
+ 2. **登录** - 使用管理员凭据访问控制台
957
+ 3. **连接 AI 智能体** - 通过控制台连接您的 AI 智能体(Claude、GPT 等)
958
+ 4. **构建应用** - 使用自然语言提示创建应用程序:
959
+ - "构建一个带用户认证的待办事项应用"
960
+ - "创建一个带图片上传的 Instagram 克隆"
961
+ - "构建一个带评论和点赞的博客"
962
+
963
+ ## 使用场景
964
+
965
+ - **AI 生成的前端** - 为 AI 生成的前端项目快速创建后端
966
+ - **智能体驱动开发** - 让 AI 智能体管理您的整个后端基础设施
967
+ - **快速原型设计** - 使用自然语言构建全栈应用程序
968
+
969
+ zh-TW:
970
+ description: InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。
971
+ readme: |
972
+ # InsForge
973
+
974
+ InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。
975
+
976
+ ## 功能特色
977
+
978
+ - **身份驗證系統** - 使用者管理和安全認證
979
+ - **資料庫儲存** - 靈活的 PostgreSQL 資料庫,支援自動模式管理
980
+ - **檔案管理** - 安全的檔案上傳和儲存功能
981
+ - **PostgREST API** - 從資料庫模式自動產生 REST API
982
+ - **無伺服器函數** - 用於自訂業務邏輯的邊緣函數
983
+ - **AI 智慧體整合** - 連接 Claude 或 GPT 等 AI 智慧體來管理後端
984
+
985
+ ## 快速開始
986
+
987
+ 此模版提供 InsForge 平台的**一鍵部署**:
988
+
989
+ 1. **部署** - 點擊部署並綁定網域
990
+ 2. **登入** - 使用管理員憑證存取控制台
991
+ 3. **連接 AI 智慧體** - 透過控制台連接您的 AI 智慧體(Claude、GPT 等)
992
+ 4. **建構應用** - 使用自然語言提示建立應用程式:
993
+ - "建構一個帶使用者認證的待辦事項應用"
994
+ - "建立一個帶圖片上傳的 Instagram 複製品"
995
+ - "建構一個帶評論和按讚的部落格"
996
+
997
+ ## 使用情境
998
+
999
+ - **AI 產生的前端** - 為 AI 產生的前端專案快速建立後端
1000
+ - **智慧體驅動開發** - 讓 AI 智慧體管理您的整個後端基礎設施
1001
+ - **快速原型設計** - 使用自然語言建構全端應用程式