create-aron-app 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/dist/index.js +50 -115
  2. package/package.json +5 -2
  3. package/templates/{_base/.cursor → .cursor}/rules/api_architecture.mdc +27 -33
  4. package/templates/{_base/.cursor → .cursor}/rules/coding_standards.mdc +1 -1
  5. package/templates/{_base/.cursor → .cursor}/rules/convex_rules.mdc +78 -422
  6. package/templates/.cursor/rules/frontend_architecture_core.mdc +495 -0
  7. package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +458 -0
  8. package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +473 -0
  9. package/templates/apps/api/_generated/api.d.ts +57 -0
  10. package/templates/apps/api/_generated/api.js +23 -0
  11. package/templates/apps/api/_generated/dataModel.d.ts +60 -0
  12. package/templates/apps/api/_generated/server.d.ts +143 -0
  13. package/templates/apps/api/_generated/server.js +93 -0
  14. package/templates/apps/api/http.ts +16 -0
  15. package/templates/apps/nextjs/.env.example +10 -0
  16. package/templates/{nextjs → apps/nextjs}/project.json +5 -5
  17. package/templates/{nextjs → apps/nextjs}/src/app/(auth)/not-allowed/page.tsx +1 -0
  18. package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +22 -0
  19. package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +12 -0
  20. package/templates/{nextjs → apps/nextjs}/src/app/(dashboard)/todos/[id]/page.tsx +5 -2
  21. package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +19 -0
  22. package/templates/apps/nextjs/src/middleware.ts +18 -0
  23. package/templates/apps/nextjs/src/surfaces/home/bootstrap.ts +9 -0
  24. package/templates/apps/nextjs/src/surfaces/home/home.tsx +27 -0
  25. package/templates/apps/nextjs/src/surfaces/home/install.tsx +17 -0
  26. package/templates/apps/nextjs/src/surfaces/home/layout.tsx +44 -0
  27. package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +34 -0
  28. package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +23 -0
  29. package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +118 -0
  30. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +19 -0
  31. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +22 -0
  32. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +25 -0
  33. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
  34. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +33 -0
  35. package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +23 -0
  36. package/templates/{nextjs/src/ui/sidebar/nav_link.tsx → apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx} +13 -10
  37. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +28 -0
  38. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +42 -0
  39. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +29 -0
  40. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +61 -0
  41. package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
  42. package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +23 -0
  43. package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +23 -0
  44. package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +44 -0
  45. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +49 -0
  46. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +70 -0
  47. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +56 -0
  48. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +99 -0
  49. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
  50. package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +32 -0
  51. package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/create.tsx +26 -0
  52. package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +22 -0
  53. package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +27 -0
  54. package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +55 -0
  55. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +38 -0
  56. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +49 -0
  57. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +29 -0
  58. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +13 -0
  59. package/templates/apps/nextjs/src/utils/auth.ts +18 -0
  60. package/templates/apps/react-router/.env.example +10 -0
  61. package/templates/apps/react-router/.react-router/types/+future.ts +9 -0
  62. package/templates/apps/react-router/.react-router/types/+routes.ts +71 -0
  63. package/templates/apps/react-router/.react-router/types/+server-build.d.ts +18 -0
  64. package/templates/apps/react-router/.react-router/types/src/+types/root.ts +59 -0
  65. package/templates/apps/react-router/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
  66. package/templates/apps/react-router/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
  67. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
  68. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
  69. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/[id].ts +65 -0
  70. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/index.ts +65 -0
  71. package/templates/{react-router → apps/react-router}/project.json +4 -4
  72. package/templates/apps/react-router/src/app.css +3 -0
  73. package/templates/{react-router → apps/react-router}/src/components/error_boundary.tsx +1 -1
  74. package/templates/{react-router → apps/react-router}/src/providers/api_auth_provider.tsx +2 -0
  75. package/templates/{react-router/src/routes/auth → apps/react-router/src/routes/(auth)}/layout.tsx +1 -1
  76. package/templates/apps/react-router/src/routes/(dashboard)/index.tsx +19 -0
  77. package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +37 -0
  78. package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +19 -0
  79. package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +19 -0
  80. package/templates/apps/react-router/src/routes.ts +12 -0
  81. package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +9 -0
  82. package/templates/apps/react-router/src/surfaces/home/home.tsx +25 -0
  83. package/templates/apps/react-router/src/surfaces/home/install.tsx +17 -0
  84. package/templates/apps/react-router/src/surfaces/home/layout.tsx +35 -0
  85. package/templates/apps/react-router/src/surfaces/home/main/create.tsx +32 -0
  86. package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +23 -0
  87. package/templates/apps/react-router/src/surfaces/sidebar/layout.tsx +110 -0
  88. package/templates/apps/react-router/src/surfaces/sidebar/nav_main/create.tsx +31 -0
  89. package/templates/apps/react-router/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
  90. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
  91. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +31 -0
  92. package/templates/apps/react-router/src/surfaces/sidebar/sidebar.tsx +18 -0
  93. package/templates/apps/react-router/src/surfaces/sidebar/user_menu/create.tsx +26 -0
  94. package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/react-router/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
  95. package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
  96. package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
  97. package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +18 -0
  98. package/templates/apps/react-router/src/surfaces/todos/all_todos/header/create.tsx +21 -0
  99. package/templates/apps/react-router/src/surfaces/todos/all_todos/install.tsx +20 -0
  100. package/templates/apps/react-router/src/surfaces/todos/all_todos/layout.tsx +35 -0
  101. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/create.tsx +47 -0
  102. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/main.tsx +68 -0
  103. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
  104. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
  105. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
  106. package/templates/apps/react-router/src/surfaces/todos/single_todo/bootstrap.ts +36 -0
  107. package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +32 -0
  108. package/templates/apps/react-router/src/surfaces/todos/single_todo/header/header.tsx +25 -0
  109. package/templates/apps/react-router/src/surfaces/todos/single_todo/install.tsx +27 -0
  110. package/templates/apps/react-router/src/surfaces/todos/single_todo/layout.tsx +45 -0
  111. package/templates/apps/react-router/src/surfaces/todos/single_todo/main/create.tsx +35 -0
  112. package/templates/apps/react-router/src/surfaces/todos/single_todo/main/main.tsx +47 -0
  113. package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
  114. package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
  115. package/templates/{react-router → apps/react-router}/vite.config.ts +27 -3
  116. package/templates/{_base/biome.json → biome.json} +7 -0
  117. package/templates/bun.lock +3187 -0
  118. package/templates/{_base/emails → emails}/project.json +1 -1
  119. package/templates/{_base/nx.json → nx.json} +11 -0
  120. package/templates/package.json +92 -0
  121. package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +4 -1
  122. package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
  123. package/templates/_base/.env.convex.example +0 -3
  124. package/templates/_base/_gitignore +0 -58
  125. package/templates/_base/package.json +0 -73
  126. package/templates/nextjs/.env.example +0 -8
  127. package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
  128. package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
  129. package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
  130. package/templates/nextjs/src/middleware.ts +0 -18
  131. package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
  132. package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
  133. package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
  134. package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
  135. package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
  136. package/templates/react-router/.env.example +0 -8
  137. package/templates/react-router/src/app.css +0 -3
  138. package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
  139. package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
  140. package/templates/react-router/src/routes/index.tsx +0 -9
  141. package/templates/react-router/src/routes/layout.tsx +0 -26
  142. package/templates/react-router/src/routes/todos/[id].tsx +0 -22
  143. package/templates/react-router/src/routes/todos/index.tsx +0 -13
  144. package/templates/react-router/src/routes.ts +0 -12
  145. package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
  146. package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
  147. package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
  148. package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
  149. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
  150. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
  151. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
  152. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
  153. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
  154. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
  155. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
  156. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
  157. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
  158. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
  159. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
  160. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
  161. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
  162. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
  163. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
  164. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
  165. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
  166. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
  167. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
  168. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
  169. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
  170. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
  171. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
  172. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
  173. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
  174. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
  175. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
  176. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
  177. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
  178. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
  179. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
  180. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
  181. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
  182. /package/templates/{_base/.cursor → .cursor}/commands/builder.md +0 -0
  183. /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
  184. /package/templates/{_base/.github → .github}/workflows/ci.yml +0 -0
  185. /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
  186. /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
  187. /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
  188. /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
  189. /package/templates/{_base/apps → apps}/api/project.json +0 -0
  190. /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
  191. /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
  192. /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
  193. /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
  194. /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
  195. /package/templates/{_base/apps → apps}/api/types.ts +0 -0
  196. /package/templates/{nextjs → apps/nextjs}/index.d.ts +0 -0
  197. /package/templates/{nextjs → apps/nextjs}/next-env.d.ts +0 -0
  198. /package/templates/{nextjs → apps/nextjs}/next.config.js +0 -0
  199. /package/templates/{nextjs → apps/nextjs}/postcss.config.js +0 -0
  200. /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/layout.tsx +0 -0
  201. /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -0
  202. /package/templates/{nextjs → apps/nextjs}/src/app/app.css +0 -0
  203. /package/templates/{nextjs → apps/nextjs}/src/app/layout.tsx +0 -0
  204. /package/templates/{nextjs → apps/nextjs}/src/providers/convex_provider.tsx +0 -0
  205. /package/templates/{nextjs/src → apps/nextjs/src/utils}/convex.ts +0 -0
  206. /package/templates/{nextjs → apps/nextjs}/src/utils/font.ts +0 -0
  207. /package/templates/{nextjs → apps/nextjs}/tsconfig.json +0 -0
  208. /package/templates/{react-router → apps/react-router}/postcss.config.js +0 -0
  209. /package/templates/{react-router → apps/react-router}/public/favicon.ico +0 -0
  210. /package/templates/{react-router → apps/react-router}/react-router.config.ts +0 -0
  211. /package/templates/{react-router → apps/react-router}/src/root.tsx +0 -0
  212. /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/react-router/src/routes/(auth)/sign-in/index.tsx} +0 -0
  213. /package/templates/{react-router → apps/react-router}/tsconfig.json +0 -0
  214. /package/templates/{_base/convex.json → convex.json} +0 -0
  215. /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
  216. /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
  217. /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
  218. /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
  219. /package/templates/{_base/shared → shared}/assets/src/styles/global.css +0 -0
  220. /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
  221. /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
  222. /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
  223. /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
  224. /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
  225. /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
  226. /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
  227. /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
  228. /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
  229. /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
  230. /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
  231. /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
  232. /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
  233. /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
  234. /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
  235. /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
  236. /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
  237. /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
  238. /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
  239. /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
  240. /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
  241. /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
  242. /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
  243. /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
  244. /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
  245. /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
  246. /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
  247. /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
  248. /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
  249. /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
  250. /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
  251. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +0 -0
  252. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
  253. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
  254. /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
  255. /package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +0 -0
  256. /package/templates/{_base/shared → shared}/ui/tsconfig.json +0 -0
  257. /package/templates/{_base/shared → shared}/utils/src/convex.ts +0 -0
  258. /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
  259. /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
  260. /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
@@ -1,24 +1,10 @@
1
1
  ---
2
- description: Guidelines and best practices for the Convex backend, including schema design, queries, mutations, actions, and real-world examples. Apply to all backend API files.
3
- globs: apps/api/**/*.ts,apps/api/**/*.tsx
2
+ description: Guidelines and best practices for building Convex projects, including database schema design, queries, mutations, and real-world examples
3
+ globs: **/*.ts,**/*.tsx,**/*.js,**/*.jsx
4
4
  ---
5
5
 
6
6
  # Convex guidelines
7
7
  ## Function guidelines
8
- ### New function syntax
9
- - ALWAYS use the new function syntax for Convex functions. For example:
10
- ```typescript
11
- import { query } from "./_generated/server";
12
- import { v } from "convex/values";
13
- export const f = query({
14
- args: {},
15
- returns: v.null(),
16
- handler: async (ctx, args) => {
17
- // Function body
18
- },
19
- });
20
- ```
21
-
22
8
  ### Http endpoint syntax
23
9
  - HTTP endpoints are defined in `convex/http.ts` and require an `httpAction` decorator. For example:
24
10
  ```typescript
@@ -71,20 +57,6 @@ export default defineSchema({
71
57
  )
72
58
  });
73
59
  ```
74
- - Always use the `v.null()` validator when returning a null value. Below is an example query that returns a null value:
75
- ```typescript
76
- import { query } from "./_generated/server";
77
- import { v } from "convex/values";
78
-
79
- export const exampleQuery = query({
80
- args: {},
81
- returns: v.null(),
82
- handler: async (ctx, args) => {
83
- console.log("This query returns a null value");
84
- return null;
85
- },
86
- });
87
- ```
88
60
  - Here are the valid Convex types along with their respective validators:
89
61
  Convex Type | TS/JS type | Example Usage | Validator for argument validation and schemas | Notes |
90
62
  | ----------- | ------------| -----------------------| -----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -103,8 +75,7 @@ Convex Type | TS/JS type | Example Usage | Validator for argument val
103
75
  - Use `internalQuery`, `internalMutation`, and `internalAction` to register internal functions. These functions are private and aren't part of an app's API. They can only be called by other Convex functions. These functions are always imported from `./_generated/server`.
104
76
  - Use `query`, `mutation`, and `action` to register public functions. These functions are part of the public API and are exposed to the public Internet. Do NOT use `query`, `mutation`, or `action` to register sensitive internal functions that should be kept private.
105
77
  - You CANNOT register a function through the `api` or `internal` objects.
106
- - ALWAYS include argument and return validators for all Convex functions. This includes all of `query`, `internalQuery`, `mutation`, `internalMutation`, `action`, and `internalAction`. If a function doesn't return anything, include `returns: v.null()` as its output validator.
107
- - If the JavaScript implementation of a Convex function doesn't have a return value, it implicitly returns `null`.
78
+ - ALWAYS include argument validators for all Convex functions. This includes all of `query`, `internalQuery`, `mutation`, `internalMutation`, `action`, and `internalAction`.
108
79
 
109
80
  ### Function calling
110
81
  - Use `ctx.runQuery` to call a query from a query, mutation, or action.
@@ -117,7 +88,6 @@ Convex Type | TS/JS type | Example Usage | Validator for argument val
117
88
  ```
118
89
  export const f = query({
119
90
  args: { name: v.string() },
120
- returns: v.string(),
121
91
  handler: async (ctx, args) => {
122
92
  return "Hello " + args.name;
123
93
  },
@@ -125,7 +95,6 @@ export const f = query({
125
95
 
126
96
  export const g = query({
127
97
  args: {},
128
- returns: v.null(),
129
98
  handler: async (ctx, args) => {
130
99
  const result: string = await ctx.runQuery(api.example.f, { name: "Bob" });
131
100
  return null;
@@ -134,21 +103,14 @@ export const g = query({
134
103
  ```
135
104
 
136
105
  ### Function references
137
- - Function references are pointers to registered Convex functions.
138
106
  - Use the `api` object defined by the framework in `convex/_generated/api.ts` to call public functions registered with `query`, `mutation`, or `action`.
139
107
  - Use the `internal` object defined by the framework in `convex/_generated/api.ts` to call internal (or private) functions registered with `internalQuery`, `internalMutation`, or `internalAction`.
140
108
  - Convex uses file-based routing, so a public function defined in `convex/example.ts` named `f` has a function reference of `api.example.f`.
141
109
  - A private function defined in `convex/example.ts` named `g` has a function reference of `internal.example.g`.
142
110
  - Functions can also registered within directories nested within the `convex/` folder. For example, a public function `h` defined in `convex/messages/access.ts` has a function reference of `api.messages.access.h`.
143
111
 
144
- ### Api design
145
- - Convex uses file-based routing, so thoughtfully organize files with public query, mutation, or action functions within the `convex/` directory.
146
- - Use `query`, `mutation`, and `action` to define public functions.
147
- - Use `internalQuery`, `internalMutation`, and `internalAction` to define private, internal functions.
148
-
149
112
  ### Pagination
150
- - Paginated queries are queries that return a list of results in incremental pages.
151
- - You can define pagination using the following syntax:
113
+ - Define pagination using the following syntax:
152
114
 
153
115
  ```ts
154
116
  import { v } from "convex/values";
@@ -159,7 +121,7 @@ export const listWithExtraArg = query({
159
121
  handler: async (ctx, args) => {
160
122
  return await ctx.db
161
123
  .query("messages")
162
- .filter((q) => q.eq(q.field("author"), args.author))
124
+ .withIndex("by_author", (q) => q.eq("author", args.author))
163
125
  .order("desc")
164
126
  .paginate(args.paginationOpts);
165
127
  },
@@ -169,24 +131,57 @@ Note: `paginationOpts` is an object with the following properties:
169
131
  - `numItems`: the maximum number of documents to return (the validator is `v.number()`)
170
132
  - `cursor`: the cursor to use to fetch the next page of documents (the validator is `v.union(v.string(), v.null())`)
171
133
  - A query that ends in `.paginate()` returns an object that has the following properties:
172
- - page (contains an array of documents that you fetches)
173
- - isDone (a boolean that represents whether or not this is the last page of documents)
174
- - continueCursor (a string that represents the cursor to use to fetch the next page of documents)
134
+ - page (contains an array of documents that you fetches)
135
+ - isDone (a boolean that represents whether or not this is the last page of documents)
136
+ - continueCursor (a string that represents the cursor to use to fetch the next page of documents)
175
137
 
176
138
 
177
- ## Validator guidelines
178
- - `v.bigint()` is deprecated for representing signed 64-bit integers. Use `v.int64()` instead.
179
- - Use `v.record()` for defining a record type. `v.map()` and `v.set()` are not supported.
180
-
181
139
  ## Schema guidelines
182
140
  - Always define your schema in `convex/schema.ts`.
183
141
  - Always import the schema definition functions from `convex/server`.
184
142
  - System fields are automatically added to all documents and are prefixed with an underscore. The two system fields that are automatically added to all documents are `_creationTime` which has the validator `v.number()` and `_id` which has the validator `v.id(tableName)`.
185
143
  - Always include all index fields in the index name. For example, if an index is defined as `["field1", "field2"]`, the index name should be "by_field1_and_field2".
186
144
  - Index fields must be queried in the same order they are defined. If you want to be able to query by "field1" then "field2" and by "field2" then "field1", you must create separate indexes.
145
+ - Do not store unbounded lists as an array field inside a document (e.g. `v.array(v.object({...}))`). As the array grows it will hit the 1MB document size limit, and every update rewrites the entire document. Instead, create a separate table for the child items with a foreign key back to the parent.
146
+ - Separate high-churn operational data (e.g. heartbeats, online status, typing indicators) from stable profile data. Storing frequently updated fields on a shared document forces every write to contend with reads of the entire document. Instead, create a dedicated table for the high-churn data with a foreign key back to the parent record.
147
+
148
+ ## Authentication guidelines
149
+ - Convex supports JWT-based authentication through `convex/auth.config.ts`. ALWAYS create this file when using authentication. Without it, `ctx.auth.getUserIdentity()` will always return `null`.
150
+ - Example `convex/auth.config.ts`:
151
+ ```typescript
152
+ export default {
153
+ providers: [
154
+ {
155
+ domain: "https://your-auth-provider.com",
156
+ applicationID: "convex",
157
+ },
158
+ ],
159
+ };
160
+ ```
161
+ The `domain` must be the issuer URL of the JWT provider. Convex fetches `{domain}/.well-known/openid-configuration` to discover the JWKS endpoint. The `applicationID` is checked against the JWT `aud` (audience) claim.
162
+ - Use `ctx.auth.getUserIdentity()` to get the authenticated user's identity in any query, mutation, or action. This returns `null` if the user is not authenticated, or a `UserIdentity` object with fields like `subject`, `issuer`, `name`, `email`, etc. The `subject` field is the unique user identifier.
163
+ - In Convex `UserIdentity`, `tokenIdentifier` is guaranteed and is the canonical stable identifier for the authenticated identity. For any auth-linked database lookup or ownership check, prefer `identity.tokenIdentifier` over `identity.subject`. Do NOT use `identity.subject` alone as a global identity key.
164
+ - NEVER accept a `userId` or any user identifier as a function argument for authorization purposes. Always derive the user identity server-side via `ctx.auth.getUserIdentity()`.
165
+ - When using an external auth provider with Convex on the client, use `ConvexProviderWithAuth` instead of `ConvexProvider`:
166
+ ```tsx
167
+ import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
168
+
169
+ const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
170
+
171
+ function App({ children }: { children: React.ReactNode }) {
172
+ return (
173
+ <ConvexProviderWithAuth client={convex} useAuth={useYourAuthHook}>
174
+ {children}
175
+ </ConvexProviderWithAuth>
176
+ );
177
+ }
178
+ ```
179
+ The `useAuth` prop must return `{ isLoading, isAuthenticated, fetchAccessToken }`. Do NOT use plain `ConvexProvider` when authentication is needed — it will not send tokens with requests.
187
180
 
188
181
  ## Typescript guidelines
189
182
  - You can use the helper typescript type `Id` imported from './_generated/dataModel' to get the type of the id for a given table. For example if there is a table called 'users' you can use `Id<'users'>` to get the type of the id for that table.
183
+ - Use `Doc<"tableName">` from `./_generated/dataModel` to get the full document type for a table.
184
+ - Use `QueryCtx`, `MutationCtx`, `ActionCtx` from `./_generated/server` for typing function contexts. NEVER use `any` for ctx parameters — always use the proper context type.
190
185
  - If you need to define a `Record` make sure that you correctly provide the type of the key and value in the type. For example a validator `v.record(v.id('users'), v.string())` would have the type `Record<Id<'users'>, string>`. Below is an example of using `Record` with an `Id` type in a query:
191
186
  ```ts
192
187
  import { query } from "./_generated/server";
@@ -194,11 +189,10 @@ import { Doc, Id } from "./_generated/dataModel";
194
189
 
195
190
  export const exampleQuery = query({
196
191
  args: { userIds: v.array(v.id("users")) },
197
- returns: v.record(v.id("users"), v.string()),
198
192
  handler: async (ctx, args) => {
199
193
  const idToUsername: Record<Id<"users">, string> = {};
200
194
  for (const userId of args.userIds) {
201
- const user = await ctx.db.get(userId);
195
+ const user = await ctx.db.get("users", userId);
202
196
  if (user) {
203
197
  idToUsername[user._id] = user.username;
204
198
  }
@@ -209,10 +203,6 @@ export const exampleQuery = query({
209
203
  });
210
204
  ```
211
205
  - Be strict with types, particularly around id's of documents. For example, if a function takes in an id for a document in the 'users' table, take in `Id<'users'>` rather than `string`.
212
- - Always use `as const` for string literals in discriminated union types.
213
- - When using the `Array` type, make sure to always define your arrays as `const array: Array<T> = [...];`
214
- - When using the `Record` type, make sure to always define your records as `const record: Record<KeyType, ValueType> = {...};`
215
- - Always add `@types/node` to your `package.json` when using any Node.js built-in modules.
216
206
 
217
207
  ## Full text search guidelines
218
208
  - A query for "10 messages in channel '#general' that best match the query 'hello hi' in their body" would look like:
@@ -226,7 +216,10 @@ const messages = await ctx.db
226
216
 
227
217
  ## Query guidelines
228
218
  - Do NOT use `filter` in queries. Instead, define an index in the schema and use `withIndex` instead.
229
- - Convex queries do NOT support `.delete()`. Instead, `.collect()` the results, iterate over them, and call `ctx.db.delete(row._id)` on each result.
219
+ - If the user does not explicitly tell you to return all results from a query you should ALWAYS return a bounded collection instead. So that is instead of using `.collect()` you should use `.take()` or paginate on database queries. This prevents future performance issues when tables grow in an unbounded way.
220
+ - Never use `.collect().length` to count rows. Convex has no built-in count operator, so if you need a count that stays efficient at scale, maintain a denormalized counter in a separate document and update it in your mutations.
221
+ - Convex queries do NOT support `.delete()`. If you need to delete all documents matching a query, use `.take(n)` to read them in batches, iterate over each batch calling `ctx.db.delete(row._id)`, and repeat until no more results are returned.
222
+ - Convex mutations are transactions with limits on the number of documents read and written. If a mutation needs to process more documents than fit in a single transaction (e.g. bulk deletion on a large table), process a batch with `.take(n)` and then call `ctx.scheduler.runAfter(0, api.myModule.myMutation, args)` to schedule itself to continue. This way each invocation stays within transaction limits.
230
223
  - Use `.unique()` to get a single document from a query. This method will throw an error if there are multiple documents that match the query.
231
224
  - When using async iteration, don't use `.collect()` or `.take(n)` on the result of a query. Instead, use the `for await (const row of query)` syntax.
232
225
  ### Ordering
@@ -236,11 +229,13 @@ const messages = await ctx.db
236
229
 
237
230
 
238
231
  ## Mutation guidelines
239
- - Use `ctx.db.replace` to fully replace an existing document. This method will throw an error if the document does not exist.
240
- - Use `ctx.db.patch` to shallow merge updates into an existing document. This method will throw an error if the document does not exist.
232
+ - Use `ctx.db.replace` to fully replace an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.replace('tasks', taskId, { name: 'Buy milk', completed: false })`
233
+ - Use `ctx.db.patch` to shallow merge updates into an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.patch('tasks', taskId, { completed: true })`
241
234
 
242
235
  ## Action guidelines
243
236
  - Always add `"use node";` to the top of files containing actions that use Node.js built-in modules.
237
+ - Never add `"use node";` to a file that also exports queries or mutations. Only actions can run in the Node.js runtime; queries and mutations must stay in the default Convex runtime. If you need Node.js built-ins alongside queries or mutations, put the action in a separate file.
238
+ - `fetch()` is available in the default Convex runtime. You do NOT need `"use node";` just to use `fetch()`.
244
239
  - Never use `ctx.db` inside of an action. Actions don't have access to the database.
245
240
  - Below is an example of the syntax for an action:
246
241
  ```ts
@@ -248,7 +243,6 @@ import { action } from "./_generated/server";
248
243
 
249
244
  export const exampleAction = action({
250
245
  args: {},
251
- returns: v.null(),
252
246
  handler: async (ctx, args) => {
253
247
  console.log("This action does not return anything");
254
248
  return null;
@@ -268,7 +262,6 @@ import { internalAction } from "./_generated/server";
268
262
 
269
263
  const empty = internalAction({
270
264
  args: {},
271
- returns: v.null(),
272
265
  handler: async (ctx, args) => {
273
266
  console.log("empty");
274
267
  },
@@ -285,12 +278,33 @@ export default crons;
285
278
  - If a cron calls an internal function, always import the `internal` object from '_generated/api', even if the internal function is registered in the same file.
286
279
 
287
280
 
281
+ ## Testing guidelines
282
+ - Use `convex-test` with `vitest` and `@edge-runtime/vm` to test Convex functions. Always install the latest versions of these packages. Configure vitest with `environment: "edge-runtime"` in `vitest.config.ts`.
283
+
284
+ Test files go inside the `convex/` directory. You must pass a module map from `import.meta.glob` to `convexTest`:
285
+ ```typescript
286
+ /// <reference types="vite/client" />
287
+ import { convexTest } from "convex-test";
288
+ import { expect, test } from "vitest";
289
+ import { api } from "./_generated/api";
290
+ import schema from "./schema";
291
+
292
+ const modules = import.meta.glob("./**/*.ts");
293
+
294
+ test("some behavior", async () => {
295
+ const t = convexTest(schema, modules);
296
+ await t.mutation(api.messages.send, { body: "Hi!", author: "Sarah" });
297
+ const messages = await t.query(api.messages.list);
298
+ expect(messages).toMatchObject([{ body: "Hi!", author: "Sarah" }]);
299
+ });
300
+ ```
301
+ The `modules` argument is required so convex-test can discover and load function files. The `/// <reference types="vite/client" />` directive is needed for TypeScript to recognize `import.meta.glob`.
302
+
288
303
  ## File storage guidelines
289
- - Convex includes file storage for large files like images, videos, and PDFs.
290
304
  - The `ctx.storage.getUrl()` method returns a signed URL for a given file. It returns `null` if the file doesn't exist.
291
305
  - Do NOT use the deprecated `ctx.storage.getMetadata` call for loading a file's metadata.
292
306
 
293
- Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`.
307
+ Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`.
294
308
  ```
295
309
  import { query } from "./_generated/server";
296
310
  import { Id } from "./_generated/dataModel";
@@ -305,9 +319,8 @@ type FileMetadata = {
305
319
 
306
320
  export const exampleQuery = query({
307
321
  args: { fileId: v.id("_storage") },
308
- returns: v.null(),
309
322
  handler: async (ctx, args) => {
310
- const metadata: FileMetadata | null = await ctx.db.system.get(args.fileId);
323
+ const metadata: FileMetadata | null = await ctx.db.system.get("_storage", args.fileId);
311
324
  console.log(metadata);
312
325
  return null;
313
326
  },
@@ -316,360 +329,3 @@ export const exampleQuery = query({
316
329
  - Convex storage stores items as `Blob` objects. You must convert all items to/from a `Blob` when using Convex storage.
317
330
 
318
331
 
319
- # Examples:
320
- ## Example: chat-app
321
-
322
- ### Task
323
- ```
324
- Create a real-time chat application backend with AI responses. The app should:
325
- - Allow creating users with names
326
- - Support multiple chat channels
327
- - Enable users to send messages to channels
328
- - Automatically generate AI responses to user messages
329
- - Show recent message history
330
-
331
- The backend should provide APIs for:
332
- 1. User management (creation)
333
- 2. Channel management (creation)
334
- 3. Message operations (sending, listing)
335
- 4. AI response generation using OpenAI's GPT-4
336
-
337
- Messages should be stored with their channel, author, and content. The system should maintain message order
338
- and limit history display to the 10 most recent messages per channel.
339
-
340
- ```
341
-
342
- ### Analysis
343
- 1. Task Requirements Summary:
344
- - Build a real-time chat backend with AI integration
345
- - Support user creation
346
- - Enable channel-based conversations
347
- - Store and retrieve messages with proper ordering
348
- - Generate AI responses automatically
349
-
350
- 2. Main Components Needed:
351
- - Database tables: users, channels, messages
352
- - Public APIs for user/channel management
353
- - Message handling functions
354
- - Internal AI response generation system
355
- - Context loading for AI responses
356
-
357
- 3. Public API and Internal Functions Design:
358
- Public Mutations:
359
- - createUser:
360
- - file path: convex/index.ts
361
- - arguments: {name: v.string()}
362
- - returns: v.object({userId: v.id("users")})
363
- - purpose: Create a new user with a given name
364
- - createChannel:
365
- - file path: convex/index.ts
366
- - arguments: {name: v.string()}
367
- - returns: v.object({channelId: v.id("channels")})
368
- - purpose: Create a new channel with a given name
369
- - sendMessage:
370
- - file path: convex/index.ts
371
- - arguments: {channelId: v.id("channels"), authorId: v.id("users"), content: v.string()}
372
- - returns: v.null()
373
- - purpose: Send a message to a channel and schedule a response from the AI
374
-
375
- Public Queries:
376
- - listMessages:
377
- - file path: convex/index.ts
378
- - arguments: {channelId: v.id("channels")}
379
- - returns: v.array(v.object({
380
- _id: v.id("messages"),
381
- _creationTime: v.number(),
382
- channelId: v.id("channels"),
383
- authorId: v.optional(v.id("users")),
384
- content: v.string(),
385
- }))
386
- - purpose: List the 10 most recent messages from a channel in descending creation order
387
-
388
- Internal Functions:
389
- - generateResponse:
390
- - file path: convex/index.ts
391
- - arguments: {channelId: v.id("channels")}
392
- - returns: v.null()
393
- - purpose: Generate a response from the AI for a given channel
394
- - loadContext:
395
- - file path: convex/index.ts
396
- - arguments: {channelId: v.id("channels")}
397
- - returns: v.array(v.object({
398
- _id: v.id("messages"),
399
- _creationTime: v.number(),
400
- channelId: v.id("channels"),
401
- authorId: v.optional(v.id("users")),
402
- content: v.string(),
403
- }))
404
- - writeAgentResponse:
405
- - file path: convex/index.ts
406
- - arguments: {channelId: v.id("channels"), content: v.string()}
407
- - returns: v.null()
408
- - purpose: Write an AI response to a given channel
409
-
410
- 4. Schema Design:
411
- - users
412
- - validator: { name: v.string() }
413
- - indexes: <none>
414
- - channels
415
- - validator: { name: v.string() }
416
- - indexes: <none>
417
- - messages
418
- - validator: { channelId: v.id("channels"), authorId: v.optional(v.id("users")), content: v.string() }
419
- - indexes
420
- - by_channel: ["channelId"]
421
-
422
- 5. Background Processing:
423
- - AI response generation runs asynchronously after each user message
424
- - Uses OpenAI's GPT-4 to generate contextual responses
425
- - Maintains conversation context using recent message history
426
-
427
-
428
- ### Implementation
429
-
430
- #### package.json
431
- ```typescript
432
- {
433
- "name": "chat-app",
434
- "description": "This example shows how to build a chat app without authentication.",
435
- "version": "1.0.0",
436
- "dependencies": {
437
- "convex": "^1.17.4",
438
- "openai": "^4.79.0"
439
- },
440
- "devDependencies": {
441
- "typescript": "^5.7.3"
442
- }
443
- }
444
- ```
445
-
446
- #### tsconfig.json
447
- ```typescript
448
- {
449
- "compilerOptions": {
450
- "target": "ESNext",
451
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
452
- "skipLibCheck": true,
453
- "allowSyntheticDefaultImports": true,
454
- "strict": true,
455
- "forceConsistentCasingInFileNames": true,
456
- "module": "ESNext",
457
- "moduleResolution": "Bundler",
458
- "resolveJsonModule": true,
459
- "isolatedModules": true,
460
- "allowImportingTsExtensions": true,
461
- "noEmit": true,
462
- "jsx": "react-jsx"
463
- },
464
- "exclude": ["convex"],
465
- "include": ["**/src/**/*.tsx", "**/src/**/*.ts", "vite.config.ts"]
466
- }
467
- ```
468
-
469
- #### convex/index.ts
470
- ```typescript
471
- import {
472
- query,
473
- mutation,
474
- internalQuery,
475
- internalMutation,
476
- internalAction,
477
- } from "./_generated/server";
478
- import { v } from "convex/values";
479
- import OpenAI from "openai";
480
- import { internal } from "./_generated/api";
481
-
482
- /**
483
- * Create a user with a given name.
484
- */
485
- export const createUser = mutation({
486
- args: {
487
- name: v.string(),
488
- },
489
- returns: v.id("users"),
490
- handler: async (ctx, args) => {
491
- return await ctx.db.insert("users", { name: args.name });
492
- },
493
- });
494
-
495
- /**
496
- * Create a channel with a given name.
497
- */
498
- export const createChannel = mutation({
499
- args: {
500
- name: v.string(),
501
- },
502
- returns: v.id("channels"),
503
- handler: async (ctx, args) => {
504
- return await ctx.db.insert("channels", { name: args.name });
505
- },
506
- });
507
-
508
- /**
509
- * List the 10 most recent messages from a channel in descending creation order.
510
- */
511
- export const listMessages = query({
512
- args: {
513
- channelId: v.id("channels"),
514
- },
515
- returns: v.array(
516
- v.object({
517
- _id: v.id("messages"),
518
- _creationTime: v.number(),
519
- channelId: v.id("channels"),
520
- authorId: v.optional(v.id("users")),
521
- content: v.string(),
522
- }),
523
- ),
524
- handler: async (ctx, args) => {
525
- const messages = await ctx.db
526
- .query("messages")
527
- .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
528
- .order("desc")
529
- .take(10);
530
- return messages;
531
- },
532
- });
533
-
534
- /**
535
- * Send a message to a channel and schedule a response from the AI.
536
- */
537
- export const sendMessage = mutation({
538
- args: {
539
- channelId: v.id("channels"),
540
- authorId: v.id("users"),
541
- content: v.string(),
542
- },
543
- returns: v.null(),
544
- handler: async (ctx, args) => {
545
- const channel = await ctx.db.get(args.channelId);
546
- if (!channel) {
547
- throw new Error("Channel not found");
548
- }
549
- const user = await ctx.db.get(args.authorId);
550
- if (!user) {
551
- throw new Error("User not found");
552
- }
553
- await ctx.db.insert("messages", {
554
- channelId: args.channelId,
555
- authorId: args.authorId,
556
- content: args.content,
557
- });
558
- await ctx.scheduler.runAfter(0, internal.index.generateResponse, {
559
- channelId: args.channelId,
560
- });
561
- return null;
562
- },
563
- });
564
-
565
- const openai = new OpenAI();
566
-
567
- export const generateResponse = internalAction({
568
- args: {
569
- channelId: v.id("channels"),
570
- },
571
- returns: v.null(),
572
- handler: async (ctx, args) => {
573
- const context = await ctx.runQuery(internal.index.loadContext, {
574
- channelId: args.channelId,
575
- });
576
- const response = await openai.chat.completions.create({
577
- model: "gpt-4o",
578
- messages: context,
579
- });
580
- const content = response.choices[0].message.content;
581
- if (!content) {
582
- throw new Error("No content in response");
583
- }
584
- await ctx.runMutation(internal.index.writeAgentResponse, {
585
- channelId: args.channelId,
586
- content,
587
- });
588
- return null;
589
- },
590
- });
591
-
592
- export const loadContext = internalQuery({
593
- args: {
594
- channelId: v.id("channels"),
595
- },
596
- returns: v.array(
597
- v.object({
598
- role: v.union(v.literal("user"), v.literal("assistant")),
599
- content: v.string(),
600
- }),
601
- ),
602
- handler: async (ctx, args) => {
603
- const channel = await ctx.db.get(args.channelId);
604
- if (!channel) {
605
- throw new Error("Channel not found");
606
- }
607
- const messages = await ctx.db
608
- .query("messages")
609
- .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
610
- .order("desc")
611
- .take(10);
612
-
613
- const result = [];
614
- for (const message of messages) {
615
- if (message.authorId) {
616
- const user = await ctx.db.get(message.authorId);
617
- if (!user) {
618
- throw new Error("User not found");
619
- }
620
- result.push({
621
- role: "user" as const,
622
- content: `${user.name}: ${message.content}`,
623
- });
624
- } else {
625
- result.push({ role: "assistant" as const, content: message.content });
626
- }
627
- }
628
- return result;
629
- },
630
- });
631
-
632
- export const writeAgentResponse = internalMutation({
633
- args: {
634
- channelId: v.id("channels"),
635
- content: v.string(),
636
- },
637
- returns: v.null(),
638
- handler: async (ctx, args) => {
639
- await ctx.db.insert("messages", {
640
- channelId: args.channelId,
641
- content: args.content,
642
- });
643
- return null;
644
- },
645
- });
646
- ```
647
-
648
- #### convex/schema.ts
649
- ```typescript
650
- import { defineSchema, defineTable } from "convex/server";
651
- import { v } from "convex/values";
652
-
653
- export default defineSchema({
654
- channels: defineTable({
655
- name: v.string(),
656
- }),
657
-
658
- users: defineTable({
659
- name: v.string(),
660
- }),
661
-
662
- messages: defineTable({
663
- channelId: v.id("channels"),
664
- authorId: v.optional(v.id("users")),
665
- content: v.string(),
666
- }).index("by_channel", ["channelId"]),
667
- });
668
- ```
669
-
670
- #### src/App.tsx
671
- ```typescript
672
- export default function App() {
673
- return <div>Hello World</div>;
674
- }
675
- ```