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.
- package/dist/index.js +50 -115
- package/package.json +5 -2
- package/templates/{_base/.cursor → .cursor}/rules/api_architecture.mdc +27 -33
- package/templates/{_base/.cursor → .cursor}/rules/coding_standards.mdc +1 -1
- package/templates/{_base/.cursor → .cursor}/rules/convex_rules.mdc +78 -422
- package/templates/.cursor/rules/frontend_architecture_core.mdc +495 -0
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +458 -0
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +473 -0
- package/templates/apps/api/_generated/api.d.ts +57 -0
- package/templates/apps/api/_generated/api.js +23 -0
- package/templates/apps/api/_generated/dataModel.d.ts +60 -0
- package/templates/apps/api/_generated/server.d.ts +143 -0
- package/templates/apps/api/_generated/server.js +93 -0
- package/templates/apps/api/http.ts +16 -0
- package/templates/apps/nextjs/.env.example +10 -0
- package/templates/{nextjs → apps/nextjs}/project.json +5 -5
- package/templates/{nextjs → apps/nextjs}/src/app/(auth)/not-allowed/page.tsx +1 -0
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +22 -0
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +12 -0
- package/templates/{nextjs → apps/nextjs}/src/app/(dashboard)/todos/[id]/page.tsx +5 -2
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +19 -0
- package/templates/apps/nextjs/src/middleware.ts +18 -0
- package/templates/apps/nextjs/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +34 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +118 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +19 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +22 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +25 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +33 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +23 -0
- package/templates/{nextjs/src/ui/sidebar/nav_link.tsx → apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx} +13 -10
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +28 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +42 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +61 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +70 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +56 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +99 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +32 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/create.tsx +26 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +22 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +55 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +38 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +13 -0
- package/templates/apps/nextjs/src/utils/auth.ts +18 -0
- package/templates/apps/react-router/.env.example +10 -0
- package/templates/apps/react-router/.react-router/types/+future.ts +9 -0
- package/templates/apps/react-router/.react-router/types/+routes.ts +71 -0
- package/templates/apps/react-router/.react-router/types/+server-build.d.ts +18 -0
- package/templates/apps/react-router/.react-router/types/src/+types/root.ts +59 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/[id].ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/index.ts +65 -0
- package/templates/{react-router → apps/react-router}/project.json +4 -4
- package/templates/apps/react-router/src/app.css +3 -0
- package/templates/{react-router → apps/react-router}/src/components/error_boundary.tsx +1 -1
- package/templates/{react-router → apps/react-router}/src/providers/api_auth_provider.tsx +2 -0
- package/templates/{react-router/src/routes/auth → apps/react-router/src/routes/(auth)}/layout.tsx +1 -1
- package/templates/apps/react-router/src/routes/(dashboard)/index.tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +37 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +19 -0
- package/templates/apps/react-router/src/routes.ts +12 -0
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/react-router/src/surfaces/home/home.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/react-router/src/surfaces/home/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/home/main/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/react-router/src/surfaces/sidebar/layout.tsx +110 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/create.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/sidebar.tsx +18 -0
- package/templates/apps/react-router/src/surfaces/sidebar/user_menu/create.tsx +26 -0
- package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/react-router/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +18 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/header/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/install.tsx +20 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/create.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/main.tsx +68 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/bootstrap.ts +36 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/header.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/layout.tsx +45 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/create.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/main.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
- package/templates/{react-router → apps/react-router}/vite.config.ts +27 -3
- package/templates/{_base/biome.json → biome.json} +7 -0
- package/templates/bun.lock +3187 -0
- package/templates/{_base/emails → emails}/project.json +1 -1
- package/templates/{_base/nx.json → nx.json} +11 -0
- package/templates/package.json +92 -0
- package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +4 -1
- package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
- package/templates/_base/.env.convex.example +0 -3
- package/templates/_base/_gitignore +0 -58
- package/templates/_base/package.json +0 -73
- package/templates/nextjs/.env.example +0 -8
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
- package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
- package/templates/nextjs/src/middleware.ts +0 -18
- package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
- package/templates/react-router/.env.example +0 -8
- package/templates/react-router/src/app.css +0 -3
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
- package/templates/react-router/src/routes/index.tsx +0 -9
- package/templates/react-router/src/routes/layout.tsx +0 -26
- package/templates/react-router/src/routes/todos/[id].tsx +0 -22
- package/templates/react-router/src/routes/todos/index.tsx +0 -13
- package/templates/react-router/src/routes.ts +0 -12
- package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/builder.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
- /package/templates/{_base/.github → .github}/workflows/ci.yml +0 -0
- /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
- /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
- /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
- /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
- /package/templates/{_base/apps → apps}/api/project.json +0 -0
- /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
- /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
- /package/templates/{_base/apps → apps}/api/types.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/index.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next-env.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/postcss.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/app.css +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/providers/convex_provider.tsx +0 -0
- /package/templates/{nextjs/src → apps/nextjs/src/utils}/convex.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/utils/font.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/tsconfig.json +0 -0
- /package/templates/{react-router → apps/react-router}/postcss.config.js +0 -0
- /package/templates/{react-router → apps/react-router}/public/favicon.ico +0 -0
- /package/templates/{react-router → apps/react-router}/react-router.config.ts +0 -0
- /package/templates/{react-router → apps/react-router}/src/root.tsx +0 -0
- /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/react-router/src/routes/(auth)/sign-in/index.tsx} +0 -0
- /package/templates/{react-router → apps/react-router}/tsconfig.json +0 -0
- /package/templates/{_base/convex.json → convex.json} +0 -0
- /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
- /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
- /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/src/styles/global.css +0 -0
- /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/utils/src/convex.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
- /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Guidelines and best practices for
|
|
3
|
-
globs:
|
|
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
|
|
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
|
-
-
|
|
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
|
-
.
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
```
|