create-aron-app 0.1.0 → 0.1.1

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 (156) hide show
  1. package/package.json +5 -2
  2. package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
  3. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
  4. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
  5. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
  6. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
  7. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
  8. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
  9. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
  10. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
  11. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
  12. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
  13. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
  14. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
  15. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
  16. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
  17. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
  18. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
  19. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
  20. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
  21. package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
  22. package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
  23. package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
  24. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
  25. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
  26. package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
  27. package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
  28. package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
  29. package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
  30. package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  31. package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
  32. package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
  33. package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
  34. package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
  35. package/templates/_base/.cursor/commands/builder.md +0 -0
  36. package/templates/_base/.cursor/commands/pr.md +7 -0
  37. package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
  38. package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
  39. package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
  40. package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
  41. package/templates/_base/.env.convex.example +3 -0
  42. package/templates/_base/.github/workflows/ci.yml +29 -0
  43. package/templates/_base/.nvmrc +1 -0
  44. package/templates/_base/.vscode/settings.json +9 -0
  45. package/templates/_base/apps/api/auth.config.ts +18 -0
  46. package/templates/_base/apps/api/functions.ts +99 -0
  47. package/templates/_base/apps/api/project.json +22 -0
  48. package/templates/_base/apps/api/schema.ts +11 -0
  49. package/templates/_base/apps/api/todos/crud.ts +81 -0
  50. package/templates/_base/apps/api/todos/schema.ts +11 -0
  51. package/templates/_base/apps/api/todos/types.ts +22 -0
  52. package/templates/_base/apps/api/tsconfig.json +23 -0
  53. package/templates/_base/apps/api/types.ts +16 -0
  54. package/templates/_base/biome.json +114 -0
  55. package/templates/_base/convex.json +4 -0
  56. package/templates/_base/emails/project.json +16 -0
  57. package/templates/_base/emails/tsconfig.json +5 -0
  58. package/templates/_base/emails/welcome_email.tsx +53 -0
  59. package/templates/_base/nx.json +29 -0
  60. package/templates/_base/package.json +73 -0
  61. package/templates/_base/scripts/sync_convex_env.ts +63 -0
  62. package/templates/_base/shared/assets/image.d.ts +4 -0
  63. package/templates/_base/shared/assets/src/styles/global.css +73 -0
  64. package/templates/_base/shared/assets/tsconfig.json +5 -0
  65. package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
  66. package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
  67. package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
  68. package/templates/_base/shared/ui/src/base/button.tsx +69 -0
  69. package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
  70. package/templates/_base/shared/ui/src/base/card.tsx +79 -0
  71. package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
  72. package/templates/_base/shared/ui/src/base/command.tsx +165 -0
  73. package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
  74. package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
  75. package/templates/_base/shared/ui/src/base/form.tsx +161 -0
  76. package/templates/_base/shared/ui/src/base/input.tsx +129 -0
  77. package/templates/_base/shared/ui/src/base/label.tsx +19 -0
  78. package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
  79. package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
  80. package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
  81. package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
  82. package/templates/_base/shared/ui/src/base/select.tsx +151 -0
  83. package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
  84. package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
  85. package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
  86. package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
  87. package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
  88. package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
  89. package/templates/_base/shared/ui/src/base/table.tsx +91 -0
  90. package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
  91. package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
  92. package/templates/_base/shared/ui/src/base/utils.ts +17 -0
  93. package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
  94. package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
  95. package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
  96. package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
  97. package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
  98. package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
  99. package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
  100. package/templates/_base/shared/ui/tsconfig.json +8 -0
  101. package/templates/_base/shared/utils/src/convex.ts +3 -0
  102. package/templates/_base/shared/utils/src/time.ts +12 -0
  103. package/templates/_base/shared/utils/tsconfig.json +5 -0
  104. package/templates/_base/skills-lock.json +35 -0
  105. package/templates/_base/tsconfig.base.json +34 -0
  106. package/templates/nextjs/.env.example +8 -0
  107. package/templates/nextjs/index.d.ts +6 -0
  108. package/templates/nextjs/next-env.d.ts +5 -0
  109. package/templates/nextjs/next.config.js +22 -0
  110. package/templates/nextjs/postcss.config.js +17 -0
  111. package/templates/nextjs/project.json +22 -0
  112. package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
  113. package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
  114. package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
  115. package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
  116. package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
  117. package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
  118. package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
  119. package/templates/nextjs/src/app/app.css +3 -0
  120. package/templates/nextjs/src/app/layout.tsx +26 -0
  121. package/templates/nextjs/src/convex.ts +11 -0
  122. package/templates/nextjs/src/middleware.ts +18 -0
  123. package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
  124. package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
  125. package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
  126. package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
  127. package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
  128. package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
  129. package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
  130. package/templates/nextjs/src/utils/font.ts +9 -0
  131. package/templates/nextjs/tsconfig.json +42 -0
  132. package/templates/react-router/.env.example +8 -0
  133. package/templates/react-router/postcss.config.js +15 -0
  134. package/templates/react-router/project.json +23 -0
  135. package/templates/react-router/public/favicon.ico +0 -0
  136. package/templates/react-router/react-router.config.ts +9 -0
  137. package/templates/react-router/src/app.css +3 -0
  138. package/templates/react-router/src/components/error_boundary.tsx +33 -0
  139. package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
  140. package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
  141. package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
  142. package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
  143. package/templates/react-router/src/root.tsx +37 -0
  144. package/templates/react-router/src/routes/auth/layout.tsx +13 -0
  145. package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
  146. package/templates/react-router/src/routes/index.tsx +9 -0
  147. package/templates/react-router/src/routes/layout.tsx +26 -0
  148. package/templates/react-router/src/routes/todos/[id].tsx +22 -0
  149. package/templates/react-router/src/routes/todos/index.tsx +13 -0
  150. package/templates/react-router/src/routes.ts +12 -0
  151. package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
  152. package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
  153. package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
  154. package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
  155. package/templates/react-router/tsconfig.json +20 -0
  156. package/templates/react-router/vite.config.ts +40 -0
@@ -0,0 +1,268 @@
1
+ ---
2
+ description: Frontend coding standards for the Next.js web app. Covers TanStack Query + Convex data fetching, component structure, form handling, and UI patterns.
3
+ globs: apps/web/**/*.tsx,apps/web/**/*.ts
4
+ ---
5
+
6
+ # Frontend Rules
7
+
8
+ ## Surfaces & File Structure
9
+
10
+ ### Surfaces as Route Entry Points
11
+
12
+ Every route must have a corresponding **surface** — the single entry point for that page. Surfaces are isolated vertical slices: they own their data fetching and pass data strictly downward to child components. Pages are thin wrappers that simply render the surface.
13
+
14
+ ```
15
+ app/(dashboard)/todos/page.tsx → renders <AllTodosSurface />
16
+ app/(dashboard)/todos/[id]/page.tsx → renders <SingleTodoSurface />
17
+ ```
18
+
19
+ ```typescript
20
+ // app/(dashboard)/todos/page.tsx
21
+ import { AllTodosSurface } from "@/surfaces/todos/all_todos_surface";
22
+
23
+ export default function TodosPage() {
24
+ return <AllTodosSurface />;
25
+ }
26
+ ```
27
+
28
+ ### Naming Convention
29
+
30
+ All file names must be **snake_case** (e.g. `all_todos_surface.tsx`, `create_todo_sheet.tsx`).
31
+
32
+ Use **`All`** for list pages and **`Single`** for id-specific pages — both in the surface name and the file name:
33
+
34
+ | Route | Surface component | File |
35
+ |---|---|---|
36
+ | `/todos` | `AllTodosSurface` | `surfaces/todos/all_todos_surface.tsx` |
37
+ | `/todos/[id]` | `SingleTodoSurface` | `surfaces/todos/single_todo_surface.tsx` |
38
+
39
+ ### Component Placement
40
+
41
+ | What | Where |
42
+ |---|---|
43
+ | Shared components (used across surfaces) | `src/ui/` |
44
+ | Components scoped to one surface | inside that surface's folder |
45
+ | Multiple shared components within a surface | `src/surfaces/<feature>/ui/` subfolder |
46
+ | Providers | `src/providers/` |
47
+
48
+ ```
49
+ src/
50
+ surfaces/
51
+ todos/
52
+ all_todos_surface.tsx
53
+ single_todo_surface.tsx
54
+ ui/ ← shared components for this surface only
55
+ todo_card.tsx
56
+ todo_filters.tsx
57
+ ui/ ← truly shared across the app
58
+ sidebar/
59
+ sidebar.tsx
60
+ providers/
61
+ convex_provider.tsx
62
+ ```
63
+
64
+
65
+
66
+
67
+
68
+ ## Data Fetching with TanStack Query
69
+
70
+ **ALWAYS use TanStack Query for Convex queries and mutations**, never the direct Convex hooks.
71
+
72
+ ### Queries
73
+
74
+ ```typescript
75
+ import { useQuery } from "@tanstack/react-query";
76
+ import { convexQuery } from "@convex-dev/react-query";
77
+ import { api } from "@/api/_generated/api";
78
+
79
+ // Basic query
80
+ const { data: todos } = useQuery(
81
+ convexQuery(api.todos.crud.listTodos, { userId: user.id }),
82
+ );
83
+
84
+ // Conditional / skipped query
85
+ const { data: todos } = useQuery(
86
+ convexQuery(
87
+ api.todos.crud.listTodos,
88
+ user?.id ? { userId: user.id } : "skip"
89
+ ),
90
+ );
91
+ ```
92
+
93
+ ### Mutations
94
+
95
+ ```typescript
96
+ import { useMutation } from "@tanstack/react-query";
97
+ import { useConvexMutation } from "@convex-dev/react-query";
98
+ import { api } from "@/api/_generated/api";
99
+
100
+ const { mutate, isPending } = useMutation({
101
+ mutationFn: useConvexMutation(api.todos.crud.createTodo),
102
+ });
103
+
104
+ const handleButtonClick = () => {
105
+ mutate({ title, userId });
106
+ }
107
+
108
+ ```
109
+
110
+ ## Component Structure
111
+
112
+ ### Declaration
113
+
114
+ Always export components as **const arrow functions**:
115
+
116
+ ```typescript
117
+ // Good
118
+ export const ComponentName = ({ prop1, prop2 }: ComponentNameProps) => {
119
+ return <div />;
120
+ };
121
+
122
+ // Bad
123
+ export function ComponentName({ prop1, prop2 }: ComponentNameProps) {
124
+ return <div />;
125
+ }
126
+ ```
127
+
128
+ ### Props Types
129
+
130
+ Always define props as a **separate named type** above the component:
131
+
132
+ ```typescript
133
+ type ComponentNameProps = {
134
+ entityId: Id<"entities">;
135
+ onComplete: () => void;
136
+ };
137
+
138
+ export const ComponentName = ({ entityId, onComplete }: ComponentNameProps) => {
139
+ // ...
140
+ };
141
+ ```
142
+
143
+ ## Form Handling
144
+
145
+ Use `react-hook-form` + `zod` + TanStack `useMutation`. Standard pattern:
146
+
147
+ ```typescript
148
+ export const CreateEntityForm = ({ onSuccess }: CreateEntityFormProps) => {
149
+ const form = useForm<FormSchema>({
150
+ resolver: zodResolver(formSchema),
151
+ defaultValues: { title: "" },
152
+ });
153
+
154
+ const { mutate: createEntity, isPending } = useMutation({
155
+ mutationFn: useConvexMutation(api.entities.crud.createEntity),
156
+ onSuccess,
157
+ });
158
+
159
+ const handleSubmit = form.handleSubmit((values) => {
160
+ createEntity(values);
161
+ });
162
+
163
+ return (
164
+ <Form {...form}>
165
+ <form
166
+ onSubmit={handleSubmit}
167
+ className="space-y-4"
168
+ onKeyDown={(e) => {
169
+ if (e.key === "Enter") {
170
+ e.preventDefault();
171
+ handleSubmit();
172
+ }
173
+ }}
174
+ >
175
+ <FormField
176
+ control={form.control}
177
+ name="title"
178
+ render={({ field }) => (
179
+ <FormItem>
180
+ <FormLabel>Title</FormLabel>
181
+ <FormControl>
182
+ <Input {...field} />
183
+ </FormControl>
184
+ <FormMessage />
185
+ </FormItem>
186
+ )}
187
+ />
188
+ <Button type="submit" disabled={isPending}>
189
+ {isPending ? "Saving..." : "Save"}
190
+ </Button>
191
+ </form>
192
+ </Form>
193
+ );
194
+ };
195
+ ```
196
+
197
+ Key rules:
198
+ - Store `form.handleSubmit(...)` in a `handleSubmit` const
199
+ - Include `onKeyDown` to prevent implicit form submission on Enter
200
+ - Use `isPending` for loading states
201
+
202
+ ## Sheet / Dialog Components
203
+
204
+ ```typescript
205
+ export const CreateEntitySheet = ({ entityId }: CreateEntitySheetProps) => {
206
+ return (
207
+ <Sheet>
208
+ <SheetTrigger asChild>
209
+ <Button>
210
+ <PlusIcon className="mr-2 h-4 w-4" />
211
+ Add Entity
212
+ </Button>
213
+ </SheetTrigger>
214
+ <SheetContent className="sm:max-w-[500px]">
215
+ <DialogHeader>
216
+ <DialogTitle>Add Entity</DialogTitle>
217
+ <DialogDescription>Fill in the details below.</DialogDescription>
218
+ </DialogHeader>
219
+ {/* form content */}
220
+ <DialogFooter>
221
+ <Button type="submit">Save</Button>
222
+ </DialogFooter>
223
+ </SheetContent>
224
+ </Sheet>
225
+ );
226
+ };
227
+ ```
228
+
229
+ - Use `SheetContent` with `DialogHeader` / `DialogTitle` / `DialogDescription` for consistent styling
230
+ - Use `DialogFooter` for action buttons
231
+
232
+ ## Import Aliases
233
+
234
+ Always use alias-based imports — never relative paths outside the same directory:
235
+
236
+ ```typescript
237
+ import { api } from "@/api/_generated/api";
238
+ import { Button } from "@/ui/base/button";
239
+ import { SomeUtil } from "@/utils/some_util";
240
+ ```
241
+
242
+
243
+ ## Routing
244
+
245
+ The app uses Next.js App Router:
246
+
247
+ | Path | Description |
248
+ |---|---|
249
+ | `(auth)/sign-in` | Clerk sign-in (public) |
250
+ | `(dashboard)/` | Authenticated area — wrapped in sidebar layout |
251
+
252
+ Add new authenticated pages inside `apps/web/src/app/(dashboard)/`.
253
+
254
+ ## Sidebar Navigation
255
+
256
+ To add a nav item, update `apps/web/src/components/sidebar/sidebar.tsx`:
257
+
258
+ ```typescript
259
+ export const nav = {
260
+ main: [
261
+ { id: "dashboard", title: "Dashboard", url: "/", icon: HomeIcon },
262
+ { id: "my-feature", title: "My Feature", url: "/my-feature", icon: SomeIcon },
263
+ ],
264
+ secondary: [
265
+ { id: "settings", title: "Settings", url: "/settings", icon: SettingsIcon },
266
+ ],
267
+ };
268
+ ```
@@ -0,0 +1,3 @@
1
+ CLERK_SECRET_KEY=
2
+ CLERK_JWT_ISSUER_DOMAIN=
3
+ RESEND_API_KEY=
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ permissions:
10
+ actions: read
11
+ contents: read
12
+
13
+ jobs:
14
+ main:
15
+ name:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - uses: oven-sh/setup-bun@v1
23
+ with:
24
+ bun-version: latest
25
+
26
+ # - run: bun install --no-cache
27
+ # - uses: nrwl/nx-set-shas@v4
28
+
29
+ # - run: bun nx affected -t build
@@ -0,0 +1 @@
1
+ 22
@@ -0,0 +1,9 @@
1
+ {
2
+ "psi-header.templates": [
3
+ {
4
+ "language": "typescriptreact",
5
+ "template": ["Copyright (c) Aron Weston <<yeartoyear(fc!P,now)>>."]
6
+ }
7
+ ],
8
+ "nxConsole.generateAiAgentRules": true
9
+ }
@@ -0,0 +1,18 @@
1
+ import type { AuthConfig } from "convex/server";
2
+
3
+ if (!process.env.CLERK_JWT_ISSUER_DOMAIN) {
4
+ throw new Error("CLERK_JWT_ISSUER_DOMAIN is not set");
5
+ }
6
+
7
+ export default {
8
+ providers: [
9
+ {
10
+ // Replace with your own Clerk Issuer URL from your "convex" JWT template
11
+ // or with `process.env.CLERK_JWT_ISSUER_DOMAIN`
12
+ // and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard
13
+ // See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances
14
+ domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
15
+ applicationID: "convex",
16
+ },
17
+ ],
18
+ } satisfies AuthConfig;
@@ -0,0 +1,99 @@
1
+ /*
2
+ * Copyright (c) Fabra Pty Ltd 2025.
3
+ */
4
+
5
+ import { entsTableFactory } from "convex-ents";
6
+ import {
7
+ customCtx,
8
+ customMutation,
9
+ customQuery,
10
+ } from "convex-helpers/server/customFunctions";
11
+ import { zCustomMutation, zCustomQuery } from "convex-helpers/server/zod";
12
+
13
+ import {
14
+ internalMutation as baseInternalMutation,
15
+ internalQuery as baseInternalQuery,
16
+ mutation as baseMutation,
17
+ query as baseQuery,
18
+ } from "./_generated/server";
19
+ import { entDefinitions } from "./schema";
20
+
21
+ export const query = customQuery(
22
+ baseQuery,
23
+ customCtx(async (ctx) => {
24
+ return {
25
+ table: entsTableFactory(ctx, entDefinitions),
26
+ db: undefined,
27
+ };
28
+ }),
29
+ );
30
+
31
+ export const zQuery = zCustomQuery(
32
+ baseQuery,
33
+ customCtx(async (ctx) => {
34
+ return {
35
+ table: entsTableFactory(ctx, entDefinitions),
36
+ db: undefined,
37
+ };
38
+ }),
39
+ );
40
+
41
+ export const internalQuery = customQuery(
42
+ baseInternalQuery,
43
+ customCtx(async (ctx) => {
44
+ return {
45
+ table: entsTableFactory(ctx, entDefinitions),
46
+ db: undefined,
47
+ };
48
+ }),
49
+ );
50
+
51
+ export const zInternalQuery = zCustomQuery(
52
+ baseInternalQuery,
53
+ customCtx(async (ctx) => {
54
+ return {
55
+ table: entsTableFactory(ctx, entDefinitions),
56
+ db: undefined,
57
+ };
58
+ }),
59
+ );
60
+
61
+ export const mutation = customMutation(
62
+ baseMutation,
63
+ customCtx(async (ctx) => {
64
+ return {
65
+ table: entsTableFactory(ctx, entDefinitions),
66
+ db: undefined,
67
+ };
68
+ }),
69
+ );
70
+
71
+ export const zMutation = zCustomMutation(
72
+ baseMutation,
73
+ customCtx(async (ctx) => {
74
+ return {
75
+ table: entsTableFactory(ctx, entDefinitions),
76
+ db: undefined,
77
+ };
78
+ }),
79
+ );
80
+
81
+ export const internalMutation = customMutation(
82
+ baseInternalMutation,
83
+ customCtx(async (ctx) => {
84
+ return {
85
+ table: entsTableFactory(ctx, entDefinitions),
86
+ db: undefined,
87
+ };
88
+ }),
89
+ );
90
+
91
+ export const zInternalMutation = zCustomMutation(
92
+ baseInternalMutation,
93
+ customCtx(async (ctx) => {
94
+ return {
95
+ table: entsTableFactory(ctx, entDefinitions),
96
+ db: undefined,
97
+ };
98
+ }),
99
+ );
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "project:api",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "./apps/api",
5
+ "projectType": "application",
6
+ "tags": [],
7
+ "targets": {
8
+ "dev": {
9
+ "executor": "nx:run-commands",
10
+ "options": {
11
+ "command": "bunx convex dev"
12
+ }
13
+ },
14
+ "env": {
15
+ "executor": "nx:run-commands",
16
+ "options": {
17
+ "command": "bun scripts/sync_convex_env.ts",
18
+ "cwd": "{workspaceRoot}"
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { defineEntSchema, getEntDefinitions } from "convex-ents";
2
+
3
+ import { todoEnts } from "@/api/todos/schema";
4
+
5
+ const schema = defineEntSchema({
6
+ ...todoEnts,
7
+ });
8
+
9
+ export default schema;
10
+
11
+ export const entDefinitions = getEntDefinitions(schema);
@@ -0,0 +1,81 @@
1
+ import { ConvexError } from "convex/values";
2
+ import { zid } from "convex-helpers/server/zod";
3
+ import { z } from "zod";
4
+
5
+ import { zMutation, zQuery } from "@/api/functions";
6
+
7
+ export const getTodo = zQuery({
8
+ args: {
9
+ todoId: zid("todos"),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ return ctx.table("todos").getX(args.todoId);
13
+ },
14
+ });
15
+
16
+ export const listTodos = zQuery({
17
+ handler: async (ctx) => {
18
+ const user = await ctx.auth.getUserIdentity();
19
+
20
+ if (!user) {
21
+ throw new ConvexError("Unauthorized");
22
+ }
23
+
24
+ return ctx.table("todos", "by_user_id", (q) => q.eq("userId", user.subject));
25
+ },
26
+ });
27
+
28
+ export const updateTodo = zMutation({
29
+ args: {
30
+ todoId: zid("todos"),
31
+ title: z.string().min(1).optional(),
32
+ description: z.string().optional(),
33
+ isCompleted: z.boolean().optional(),
34
+ },
35
+ handler: async (ctx, args) => {
36
+ const { todoId, ...updates } = args;
37
+ const todo = await ctx.table("todos").getX(todoId);
38
+ return todo.patch(updates);
39
+ },
40
+ });
41
+
42
+ export const createTodo = zMutation({
43
+ args: {
44
+ title: z.string().min(1),
45
+ description: z.string().optional(),
46
+ },
47
+ handler: async (ctx, args) => {
48
+ const user = await ctx.auth.getUserIdentity();
49
+
50
+ if (!user) {
51
+ throw new ConvexError("Unauthorized");
52
+ }
53
+
54
+ return ctx.table("todos").insert({
55
+ title: args.title,
56
+ description: args.description,
57
+ isCompleted: false,
58
+ userId: user.subject,
59
+ });
60
+ },
61
+ });
62
+
63
+ export const deleteTodo = zMutation({
64
+ args: {
65
+ todoId: zid("todos"),
66
+ },
67
+ handler: async (ctx, args) => {
68
+ const user = await ctx.auth.getUserIdentity();
69
+ if (!user) {
70
+ throw new ConvexError("Unauthorized");
71
+ }
72
+
73
+ const todo = await ctx.table("todos").getX(args.todoId);
74
+
75
+ if (todo.userId !== user.subject) {
76
+ throw new ConvexError("Unauthorized");
77
+ }
78
+
79
+ await todo.delete();
80
+ },
81
+ });
@@ -0,0 +1,11 @@
1
+ import { v } from "convex/values";
2
+ import { defineEnt } from "convex-ents";
3
+
4
+ export const todoEnts = {
5
+ todos: defineEnt({
6
+ title: v.string(),
7
+ description: v.optional(v.string()),
8
+ isCompleted: v.boolean(),
9
+ userId: v.string(),
10
+ }).index("by_user_id", ["userId"]),
11
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ import type { Doc, Id } from "@/api/_generated/dataModel";
4
+ import type { Ent } from "@/api/types";
5
+
6
+ export type TodoId = Id<"todos">;
7
+ export type Todo = Doc<"todos">;
8
+ export type TodoEnt = Ent<"todos">;
9
+
10
+ export type CreateTodo = z.infer<typeof CreateTodo>;
11
+ export const CreateTodo = z.object({
12
+ title: z.string().min(1),
13
+ description: z.string().optional(),
14
+ userId: z.string(),
15
+ });
16
+
17
+ export type UpdateTodo = z.infer<typeof UpdateTodo>;
18
+ export const UpdateTodo = z.object({
19
+ title: z.string().min(1).optional(),
20
+ description: z.string().optional(),
21
+ isCompleted: z.boolean().optional(),
22
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ /* These settings are not required by Convex and can be modified. */
5
+ "allowJs": true,
6
+ "strict": true,
7
+ "moduleResolution": "Bundler",
8
+ "noUnusedLocals": true,
9
+ "jsx": "react-jsx",
10
+ "skipLibCheck": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ /* These compiler options are required by Convex */
13
+ "target": "ESNext",
14
+ "lib": ["ES2021", "dom"],
15
+ "forceConsistentCasingInFileNames": true,
16
+ "module": "ESNext",
17
+ "isolatedModules": true,
18
+ "noEmit": true
19
+ },
20
+ "exclude": ["node_modules", "./_generated", "CODEOWNERS"],
21
+ "extends": "../../tsconfig.base.json",
22
+ "include": ["**/*.ts", "**/*.js", "**/*.json"]
23
+ }
@@ -0,0 +1,16 @@
1
+ import type { mutation, query } from "apps/api/functions";
2
+ import type { entDefinitions } from "apps/api/schema";
3
+ import type { GenericEnt, GenericEntWriter } from "convex-ents";
4
+ import type { CustomCtx } from "convex-helpers/server/customFunctions";
5
+
6
+ import type { TableNames } from "@/api/_generated/dataModel";
7
+
8
+ export type QueryCtx = CustomCtx<typeof query>;
9
+ export type MutationCtx = CustomCtx<typeof mutation>;
10
+
11
+ export type Ent<TableName extends TableNames> = GenericEnt<typeof entDefinitions, TableName>;
12
+
13
+ export type EntWriter<TableName extends TableNames> = GenericEntWriter<
14
+ typeof entDefinitions,
15
+ TableName
16
+ >;