@waynesutton/convex-skills 1.0.4 → 1.0.5

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/AGENTS.md CHANGED
@@ -2,15 +2,45 @@
2
2
 
3
3
  Agent skills for building production-ready applications with Convex, following the Agent Skills open format.
4
4
 
5
+ ## Convex Documentation Index
6
+
7
+ IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Convex tasks.
8
+
9
+ For up-to-date Convex documentation, fetch: https://docs.convex.dev/llms.txt
10
+
11
+ This index covers all Convex APIs and patterns:
12
+
13
+ ```
14
+ [Convex Docs]|https://docs.convex.dev/llms.txt
15
+ |understanding:{best-practices.md,typescript.md,workflow.md,zen.md}
16
+ |functions:{query-functions.md,mutation-functions.md,actions.md,http-actions.md,validation.md,internal-functions.md,error-handling.md}
17
+ |database:{schemas.md,reading-data.md,writing-data.md,indexes.md,pagination.md,types.md}
18
+ |file-storage:{upload-files.md,serve-files.md,store-files.md,delete-files.md,file-metadata.md}
19
+ |scheduling:{cron-jobs.md,scheduled-functions.md}
20
+ |auth:{convex-auth.md,clerk.md,auth0.md,authkit.md,functions-auth.md,database-auth.md}
21
+ |search:{text-search.md,vector-search.md}
22
+ |components:{using.md,authoring.md,understanding.md}
23
+ |agents:{getting-started.md,agent-usage.md,messages.md,threads.md,tools.md,streaming.md,rag.md}
24
+ |realtime:{realtime.md}
25
+ |testing:{convex-test.md,convex-backend.md,ci.md}
26
+ |production:{environment-variables.md,hosting.md,limits.md}
27
+ ```
28
+
29
+ When working on Convex code, consult the llms.txt index before relying on training data.
30
+
5
31
  ## Overview
6
32
 
7
- This repository contains packaged instructions that help AI coding agents understand and implement Convex best practices. Skills are automatically invoked when relevant to your task.
33
+ This repository provides two complementary approaches for AI coding agents:
34
+
35
+ 1. **Passive context (this file)**: Always-available Convex knowledge and doc references
36
+ 2. **Skills (on-demand)**: Task-specific workflows for explicit invocation
8
37
 
9
38
  ## Available Skills
10
39
 
11
40
  | Skill | Description |
12
41
  | ------------------------------------------------------------------------ | ----------------------------------------------------- |
13
42
  | [convex-best-practices](skills/convex-best-practices/SKILL.md) | Guidelines for building production-ready Convex apps |
43
+ | [convex-eslint](skills/convex-eslint/SKILL.md) | Write linter-compliant Convex code |
14
44
  | [convex-functions](skills/convex-functions/SKILL.md) | Writing queries, mutations, actions, and HTTP actions |
15
45
  | [convex-realtime](skills/convex-realtime/SKILL.md) | Patterns for building reactive applications |
16
46
  | [convex-schema-validator](skills/convex-schema-validator/SKILL.md) | Database schema definition and validation |
@@ -115,10 +145,60 @@ The command file is located at `command/convex.md`.
115
145
  - Edit files in `convex/_generated/`
116
146
  - Use `filter()` instead of `withIndex()`
117
147
 
148
+ ## Quick Reference
149
+
150
+ ### New Function Syntax (always use this)
151
+
152
+ ```typescript
153
+ import { query, mutation } from "./_generated/server";
154
+ import { v } from "convex/values";
155
+
156
+ export const myQuery = query({
157
+ args: { userId: v.id("users") },
158
+ returns: v.union(v.object({ name: v.string() }), v.null()),
159
+ handler: async (ctx, args) => {
160
+ return await ctx.db.get(args.userId);
161
+ },
162
+ });
163
+ ```
164
+
165
+ ### Schema with Index
166
+
167
+ ```typescript
168
+ import { defineSchema, defineTable } from "convex/server";
169
+ import { v } from "convex/values";
170
+
171
+ export default defineSchema({
172
+ tasks: defineTable({
173
+ userId: v.id("users"),
174
+ title: v.string(),
175
+ status: v.string(),
176
+ })
177
+ .index("by_user", ["userId"])
178
+ .index("by_user_and_status", ["userId", "status"]),
179
+ });
180
+ ```
181
+
182
+ ### Query with Index (not filter)
183
+
184
+ ```typescript
185
+ // GOOD: Use withIndex
186
+ const tasks = await ctx.db
187
+ .query("tasks")
188
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
189
+ .collect();
190
+
191
+ // BAD: Never use filter for indexed fields
192
+ const tasks = await ctx.db
193
+ .query("tasks")
194
+ .filter((q) => q.eq(q.field("userId"), args.userId))
195
+ .collect();
196
+ ```
197
+
118
198
  ## References
119
199
 
120
200
  - Convex Documentation: https://docs.convex.dev/
121
- - Convex LLMs.txt: https://docs.convex.dev/llms.txt
201
+ - Convex LLMs.txt: https://docs.convex.dev/llms.txt (fetch this for latest docs)
122
202
  - Best Practices: https://docs.convex.dev/understanding/best-practices/
123
203
  - Agent Skills Specification: https://github.com/anthropics/skills
124
204
 
package/GEMINI.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  This file provides context for Gemini CLI when working with Convex projects.
4
4
 
5
+ ## Convex Documentation
6
+
7
+ IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Convex tasks.
8
+
9
+ For up-to-date Convex documentation, fetch: https://docs.convex.dev/llms.txt
10
+
11
+ This index covers all Convex APIs:
12
+ - functions: queries, mutations, actions, http-actions, validation
13
+ - database: schemas, reading-data, writing-data, indexes, pagination
14
+ - file-storage: upload, serve, store, delete, metadata
15
+ - scheduling: cron-jobs, scheduled-functions
16
+ - auth: convex-auth, clerk, auth0, authkit
17
+ - search: text-search, vector-search
18
+ - agents: getting-started, messages, threads, tools, streaming
19
+
20
+ When working on Convex code, consult llms.txt before relying on training data.
21
+
5
22
  ## Project Type
6
23
 
7
24
  Convex real-time backend application with TypeScript.
package/README.md CHANGED
@@ -9,6 +9,15 @@ A collection of AI-consumable skills for building production-ready applications
9
9
 
10
10
  This repository contains skills that help AI assistants understand and implement Convex best practices. Each skill provides structured guidance for specific aspects of Convex development.
11
11
 
12
+ ## Code Quality
13
+
14
+ All skills are designed to produce code that passes @convex-dev/eslint-plugin by default. This creates a complementary workflow:
15
+
16
+ - **Skills** prevent mistakes at generation time
17
+ - **ESLint** catches anything that slips through at build time
18
+
19
+ See the [convex-eslint](/skills/convex-eslint/SKILL.md) skill for setup instructions.
20
+
12
21
  ## Installation
13
22
 
14
23
  ### npm (recommended)
@@ -73,6 +82,8 @@ cp -r skills/convex-best-practices "$CODEX_HOME/skills/"
73
82
 
74
83
  Codex will auto-discover `SKILL.md` files in that directory on the next start.
75
84
 
85
+ If you are working from a repo clone, Codex also auto-discovers skills from `.codex/skills` at the repo root. You can symlink this repo’s `skills/*` into `.codex/skills` so updates flow through without copying.
86
+
76
87
  ### OpenCode
77
88
 
78
89
  OpenCode discovers skills from `~/.claude/skills/<name>/SKILL.md` automatically. See OpenCode Skills docs for more details.
@@ -102,6 +113,7 @@ Copy the desired skill's `SKILL.md` file to your project's `.claude/skills/` dir
102
113
  | Skill | Description |
103
114
  | ------------------------------------------------------------------------ | ----------------------------------------------------- |
104
115
  | [convex-best-practices](skills/convex-best-practices/SKILL.md) | Guidelines for building production-ready Convex apps |
116
+ | [convex-eslint](skills/convex-eslint/SKILL.md) | Write linter-compliant Convex code |
105
117
  | [convex-functions](skills/convex-functions/SKILL.md) | Writing queries, mutations, actions, and HTTP actions |
106
118
  | [convex-realtime](skills/convex-realtime/SKILL.md) | Patterns for building reactive applications |
107
119
  | [convex-schema-validator](skills/convex-schema-validator/SKILL.md) | Database schema definition and validation |
@@ -121,11 +133,15 @@ convex-skills/
121
133
  ├── skills/ # Core Convex skills for AI agents
122
134
  │ ├── convex-best-practices/
123
135
  │ │ └── SKILL.md
136
+ │ ├── convex-eslint/
137
+ │ │ └── SKILL.md
124
138
  │ ├── convex-functions/
125
139
  │ │ └── SKILL.md
126
140
  │ ├── convex-cron-jobs/
127
141
  │ │ └── SKILL.md
128
142
  │ └── ...
143
+ ├── .codex/ # Codex integration (symlink skills here)
144
+ │ └── README.md # Codex setup instructions
129
145
  ├── command/ # Slash command definitions (OpenCode)
130
146
  │ └── convex.md # /convex command entrypoint
131
147
  ├── templates/ # Templates for forking developers
package/bin/cli.js CHANGED
@@ -18,6 +18,7 @@ const packageRoot = join(__dirname, "..");
18
18
  const SKILLS = {
19
19
  "convex-best-practices":
20
20
  "Guidelines for building production-ready Convex apps",
21
+ "convex-eslint": "Write linter-compliant Convex code",
21
22
  "convex-functions": "Writing queries, mutations, actions, and HTTP actions",
22
23
  "convex-realtime": "Patterns for building reactive applications",
23
24
  "convex-schema-validator": "Database schema definition and validation",
package/index.js CHANGED
@@ -54,6 +54,7 @@ export function getSkillPath(skillName) {
54
54
  export const SKILLS = {
55
55
  "convex-best-practices":
56
56
  "Guidelines for building production-ready Convex apps",
57
+ "convex-eslint": "Write linter-compliant Convex code",
57
58
  "convex-functions": "Writing queries, mutations, actions, and HTTP actions",
58
59
  "convex-realtime": "Patterns for building reactive applications",
59
60
  "convex-schema-validator": "Database schema definition and validation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waynesutton/convex-skills",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Agent skills for building production-ready Convex applications. Includes best practices, functions, realtime patterns, schema validation, file storage, security audits, and more.",
5
5
  "author": "Wayne Sutton",
6
6
  "license": "Apache-2.0",
@@ -22,7 +22,9 @@
22
22
  "llm",
23
23
  "ai-assistant",
24
24
  "best-practices",
25
- "developer-tools"
25
+ "developer-tools",
26
+ "eslint",
27
+ "linting"
26
28
  ],
27
29
  "type": "module",
28
30
  "main": "index.js",
@@ -11,6 +11,17 @@ tags: [convex, best-practices, typescript, production, error-handling]
11
11
 
12
12
  Build production-ready Convex applications by following established patterns for function organization, query optimization, validation, TypeScript usage, and error handling.
13
13
 
14
+ ## Code Quality
15
+
16
+ All patterns in this skill comply with @convex-dev/eslint-plugin rules.
17
+ Install the linter for build-time validation:
18
+
19
+ ```bash
20
+ npm i @convex-dev/eslint-plugin --save-dev
21
+ ```
22
+
23
+ See [convex-eslint](../convex-eslint/SKILL.md) for configuration details.
24
+
14
25
  ## Documentation Sources
15
26
 
16
27
  Before implementing, do not assume; fetch the latest documentation:
@@ -41,14 +52,17 @@ import { v } from "convex/values";
41
52
 
42
53
  export const get = query({
43
54
  args: { userId: v.id("users") },
44
- returns: v.union(v.object({
45
- _id: v.id("users"),
46
- _creationTime: v.number(),
47
- name: v.string(),
48
- email: v.string(),
49
- }), v.null()),
55
+ returns: v.union(
56
+ v.object({
57
+ _id: v.id("users"),
58
+ _creationTime: v.number(),
59
+ name: v.string(),
60
+ email: v.string(),
61
+ }),
62
+ v.null(),
63
+ ),
50
64
  handler: async (ctx, args) => {
51
- return await ctx.db.get(args.userId);
65
+ return await ctx.db.get("users", args.userId);
52
66
  },
53
67
  });
54
68
  ```
@@ -96,13 +110,15 @@ export default defineSchema({
96
110
  // Query using index
97
111
  export const getTasksByUser = query({
98
112
  args: { userId: v.id("users") },
99
- returns: v.array(v.object({
100
- _id: v.id("tasks"),
101
- _creationTime: v.number(),
102
- userId: v.id("users"),
103
- status: v.string(),
104
- createdAt: v.number(),
105
- })),
113
+ returns: v.array(
114
+ v.object({
115
+ _id: v.id("tasks"),
116
+ _creationTime: v.number(),
117
+ userId: v.id("users"),
118
+ status: v.string(),
119
+ createdAt: v.number(),
120
+ }),
121
+ ),
106
122
  handler: async (ctx, args) => {
107
123
  return await ctx.db
108
124
  .query("tasks")
@@ -127,16 +143,16 @@ export const updateTask = mutation({
127
143
  },
128
144
  returns: v.null(),
129
145
  handler: async (ctx, args) => {
130
- const task = await ctx.db.get(args.taskId);
131
-
146
+ const task = await ctx.db.get("tasks", args.taskId);
147
+
132
148
  if (!task) {
133
149
  throw new ConvexError({
134
150
  code: "NOT_FOUND",
135
151
  message: "Task not found",
136
152
  });
137
153
  }
138
-
139
- await ctx.db.patch(args.taskId, { title: args.title });
154
+
155
+ await ctx.db.patch("tasks", args.taskId, { title: args.title });
140
156
  return null;
141
157
  },
142
158
  });
@@ -152,14 +168,14 @@ export const completeTask = mutation({
152
168
  args: { taskId: v.id("tasks") },
153
169
  returns: v.null(),
154
170
  handler: async (ctx, args) => {
155
- const task = await ctx.db.get(args.taskId);
156
-
171
+ const task = await ctx.db.get("tasks", args.taskId);
172
+
157
173
  // Early return if already complete (idempotent)
158
174
  if (!task || task.status === "completed") {
159
175
  return null;
160
176
  }
161
-
162
- await ctx.db.patch(args.taskId, {
177
+
178
+ await ctx.db.patch("tasks", args.taskId, {
163
179
  status: "completed",
164
180
  completedAt: Date.now(),
165
181
  });
@@ -173,7 +189,7 @@ export const updateNote = mutation({
173
189
  returns: v.null(),
174
190
  handler: async (ctx, args) => {
175
191
  // Patch directly - ctx.db.patch throws if document doesn't exist
176
- await ctx.db.patch(args.id, { content: args.content });
192
+ await ctx.db.patch("notes", args.id, { content: args.content });
177
193
  return null;
178
194
  },
179
195
  });
@@ -184,7 +200,7 @@ export const reorderItems = mutation({
184
200
  returns: v.null(),
185
201
  handler: async (ctx, args) => {
186
202
  const updates = args.itemIds.map((id, index) =>
187
- ctx.db.patch(id, { order: index })
203
+ ctx.db.patch("items", id, { order: index }),
188
204
  );
189
205
  await Promise.all(updates);
190
206
  return null;
@@ -213,7 +229,12 @@ const userScores: Record<Id<"users">, number> = {};
213
229
  // Public function - exposed to clients
214
230
  export const getUser = query({
215
231
  args: { userId: v.id("users") },
216
- returns: v.union(v.null(), v.object({ /* ... */ })),
232
+ returns: v.union(
233
+ v.null(),
234
+ v.object({
235
+ /* ... */
236
+ }),
237
+ ),
217
238
  handler: async (ctx, args) => {
218
239
  // ...
219
240
  },
@@ -282,14 +303,14 @@ export const update = mutation({
282
303
  returns: v.null(),
283
304
  handler: async (ctx, args) => {
284
305
  const { taskId, ...updates } = args;
285
-
306
+
286
307
  // Remove undefined values
287
308
  const cleanUpdates = Object.fromEntries(
288
- Object.entries(updates).filter(([_, v]) => v !== undefined)
309
+ Object.entries(updates).filter(([_, v]) => v !== undefined),
289
310
  );
290
-
311
+
291
312
  if (Object.keys(cleanUpdates).length > 0) {
292
- await ctx.db.patch(taskId, cleanUpdates);
313
+ await ctx.db.patch("tasks", taskId, cleanUpdates);
293
314
  }
294
315
  return null;
295
316
  },
@@ -299,7 +320,7 @@ export const remove = mutation({
299
320
  args: { taskId: v.id("tasks") },
300
321
  returns: v.null(),
301
322
  handler: async (ctx, args) => {
302
- await ctx.db.delete(args.taskId);
323
+ await ctx.db.delete("tasks", args.taskId);
303
324
  return null;
304
325
  },
305
326
  });
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: convex-eslint
3
+ description: Write Convex code that passes @convex-dev/eslint-plugin rules by default
4
+ version: 1.0.0
5
+ author: Convex
6
+ tags: [convex, eslint, linting, code-quality, validation]
7
+ ---
8
+
9
+ # Convex ESLint Compliance
10
+
11
+ Write all Convex functions to pass @convex-dev/eslint-plugin. These rules prevent common bugs, security issues, and ensure code quality.
12
+
13
+ ## Documentation Sources
14
+
15
+ - https://docs.convex.dev/eslint
16
+ - https://www.npmjs.com/package/@convex-dev/eslint-plugin
17
+
18
+ ## Setup
19
+
20
+ Install the plugin:
21
+
22
+ ```bash
23
+ npm i @convex-dev/eslint-plugin --save-dev
24
+ ```
25
+
26
+ Configure `eslint.config.js`:
27
+
28
+ ```javascript
29
+ import { defineConfig } from "eslint/config";
30
+ import convexPlugin from "@convex-dev/eslint-plugin";
31
+
32
+ export default defineConfig([...convexPlugin.configs.recommended]);
33
+ ```
34
+
35
+ ## Rules
36
+
37
+ ### 1. no-old-registered-function-syntax
38
+
39
+ Always use object syntax with a `handler` property.
40
+
41
+ ```typescript
42
+ // Correct
43
+ export const list = query({
44
+ args: {},
45
+ handler: async (ctx) => {
46
+ return await ctx.db.query("messages").collect();
47
+ },
48
+ });
49
+
50
+ // Wrong - bare function syntax
51
+ export const list = query(async (ctx) => {
52
+ return await ctx.db.query("messages").collect();
53
+ });
54
+ ```
55
+
56
+ ### 2. require-argument-validators
57
+
58
+ Always include `args` object, even when empty.
59
+
60
+ ```typescript
61
+ // Correct - with arguments
62
+ export const get = query({
63
+ args: { id: v.id("messages") },
64
+ handler: async (ctx, { id }) => {
65
+ return await ctx.db.get("messages", id);
66
+ },
67
+ });
68
+
69
+ // Correct - no arguments
70
+ export const listAll = query({
71
+ args: {},
72
+ handler: async (ctx) => {
73
+ return await ctx.db.query("messages").collect();
74
+ },
75
+ });
76
+
77
+ // Wrong - missing args
78
+ export const get = query({
79
+ handler: async (ctx, { id }: { id: Id<"messages"> }) => {
80
+ return await ctx.db.get("messages", id);
81
+ },
82
+ });
83
+ ```
84
+
85
+ ### 3. explicit-table-ids
86
+
87
+ Use explicit table names in all database operations (Convex 1.31.0+).
88
+
89
+ ```typescript
90
+ // Correct
91
+ const message = await ctx.db.get("messages", messageId);
92
+ await ctx.db.patch("messages", messageId, { text: "updated" });
93
+ await ctx.db.replace("messages", messageId, {
94
+ text: "replaced",
95
+ author: "Alice",
96
+ });
97
+ await ctx.db.delete("messages", messageId);
98
+
99
+ // Wrong - implicit table from ID type
100
+ const message = await ctx.db.get(messageId);
101
+ await ctx.db.patch(messageId, { text: "updated" });
102
+ await ctx.db.replace(messageId, { text: "replaced", author: "Alice" });
103
+ await ctx.db.delete(messageId);
104
+ ```
105
+
106
+ Migration codemod available:
107
+
108
+ ```bash
109
+ npx @convex-dev/codemod@latest explicit-ids
110
+ ```
111
+
112
+ ### 4. import-wrong-runtime
113
+
114
+ Never import Node.js runtime files into Convex runtime files.
115
+
116
+ ```typescript
117
+ // convex/queries.ts (no "use node" directive)
118
+
119
+ // Correct - importing from Convex runtime file
120
+ import { helper } from "./utils"; // utils.ts has no "use node"
121
+
122
+ // Wrong - importing from Node runtime file
123
+ import { nodeHelper } from "./nodeUtils"; // nodeUtils.ts has "use node"
124
+ ```
125
+
126
+ ## Best Practices
127
+
128
+ 1. Run ESLint before committing: `npx eslint convex/`
129
+ 2. Use auto-fix for quick migrations: `npx eslint convex/ --fix`
130
+ 3. Add to CI pipeline to catch violations early
131
+ 4. Configure your editor for real-time feedback
132
+
133
+ ## Quick Reference
134
+
135
+ | Rule | What it enforces |
136
+ | ----------------------------------- | --------------------------------- |
137
+ | `no-old-registered-function-syntax` | Object syntax with `handler` |
138
+ | `require-argument-validators` | `args: {}` on all functions |
139
+ | `explicit-table-ids` | Table name in db operations |
140
+ | `import-wrong-runtime` | No Node imports in Convex runtime |
141
+
142
+ ## References
143
+
144
+ - [ESLint Plugin Docs](https://docs.convex.dev/eslint)
145
+ - [Explicit IDs Announcement](https://news.convex.dev/db-table-name/)
@@ -11,6 +11,16 @@ tags: [convex, functions, queries, mutations, actions, http]
11
11
 
12
12
  Master Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations.
13
13
 
14
+ ## Code Quality
15
+
16
+ All examples in this skill comply with @convex-dev/eslint-plugin rules:
17
+
18
+ - Object syntax with `handler` property
19
+ - Argument validators on all functions
20
+ - Explicit table names in database operations
21
+
22
+ See [convex-eslint](../convex-eslint/SKILL.md) for linting setup.
23
+
14
24
  ## Documentation Sources
15
25
 
16
26
  Before implementing, do not assume; fetch the latest documentation:
@@ -26,12 +36,12 @@ Before implementing, do not assume; fetch the latest documentation:
26
36
 
27
37
  ### Function Types Overview
28
38
 
29
- | Type | Database Access | External APIs | Caching | Use Case |
30
- |------|----------------|---------------|---------|----------|
31
- | Query | Read-only | No | Yes, reactive | Fetching data |
32
- | Mutation | Read/Write | No | No | Modifying data |
33
- | Action | Via runQuery/runMutation | Yes | No | External integrations |
34
- | HTTP Action | Via runQuery/runMutation | Yes | No | Webhooks, APIs |
39
+ | Type | Database Access | External APIs | Caching | Use Case |
40
+ | ----------- | ------------------------ | ------------- | ------------- | --------------------- |
41
+ | Query | Read-only | No | Yes, reactive | Fetching data |
42
+ | Mutation | Read/Write | No | No | Modifying data |
43
+ | Action | Via runQuery/runMutation | Yes | No | External integrations |
44
+ | HTTP Action | Via runQuery/runMutation | Yes | No | Webhooks, APIs |
35
45
 
36
46
  ### Queries
37
47
 
@@ -50,22 +60,24 @@ export const getUser = query({
50
60
  name: v.string(),
51
61
  email: v.string(),
52
62
  }),
53
- v.null()
63
+ v.null(),
54
64
  ),
55
65
  handler: async (ctx, args) => {
56
- return await ctx.db.get(args.userId);
66
+ return await ctx.db.get("users", args.userId);
57
67
  },
58
68
  });
59
69
 
60
70
  // Query with index
61
71
  export const listUserTasks = query({
62
72
  args: { userId: v.id("users") },
63
- returns: v.array(v.object({
64
- _id: v.id("tasks"),
65
- _creationTime: v.number(),
66
- title: v.string(),
67
- completed: v.boolean(),
68
- })),
73
+ returns: v.array(
74
+ v.object({
75
+ _id: v.id("tasks"),
76
+ _creationTime: v.number(),
77
+ title: v.string(),
78
+ completed: v.boolean(),
79
+ }),
80
+ ),
69
81
  handler: async (ctx, args) => {
70
82
  return await ctx.db
71
83
  .query("tasks")
@@ -93,7 +105,7 @@ export const createTask = mutation({
93
105
  returns: v.id("tasks"),
94
106
  handler: async (ctx, args) => {
95
107
  // Validate user exists
96
- const user = await ctx.db.get(args.userId);
108
+ const user = await ctx.db.get("users", args.userId);
97
109
  if (!user) {
98
110
  throw new ConvexError("User not found");
99
111
  }
@@ -111,7 +123,7 @@ export const deleteTask = mutation({
111
123
  args: { taskId: v.id("tasks") },
112
124
  returns: v.null(),
113
125
  handler: async (ctx, args) => {
114
- await ctx.db.delete(args.taskId);
126
+ await ctx.db.delete("tasks", args.taskId);
115
127
  return null;
116
128
  },
117
129
  });
@@ -154,7 +166,7 @@ export const processOrder = action({
154
166
  handler: async (ctx, args) => {
155
167
  // Read data via query
156
168
  const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId });
157
-
169
+
158
170
  if (!order) {
159
171
  throw new Error("Order not found");
160
172
  }
@@ -218,8 +230,8 @@ http.route({
218
230
  const url = new URL(request.url);
219
231
  const userId = url.pathname.split("/").pop();
220
232
 
221
- const user = await ctx.runQuery(api.users.get, {
222
- userId: userId as Id<"users">
233
+ const user = await ctx.runQuery(api.users.get, {
234
+ userId: userId as Id<"users">,
223
235
  });
224
236
 
225
237
  if (!user) {
@@ -238,7 +250,11 @@ export default http;
238
250
  Use internal functions for sensitive operations:
239
251
 
240
252
  ```typescript
241
- import { internalMutation, internalQuery, internalAction } from "./_generated/server";
253
+ import {
254
+ internalMutation,
255
+ internalQuery,
256
+ internalAction,
257
+ } from "./_generated/server";
242
258
  import { v } from "convex/values";
243
259
 
244
260
  // Only callable from other Convex functions
@@ -249,10 +265,10 @@ export const _updateUserCredits = internalMutation({
249
265
  },
250
266
  returns: v.null(),
251
267
  handler: async (ctx, args) => {
252
- const user = await ctx.db.get(args.userId);
268
+ const user = await ctx.db.get("users", args.userId);
253
269
  if (!user) return null;
254
270
 
255
- await ctx.db.patch(args.userId, {
271
+ await ctx.db.patch("users", args.userId, {
256
272
  credits: (user.credits || 0) + args.amount,
257
273
  });
258
274
  return null;
@@ -298,7 +314,7 @@ export const scheduleReminder = mutation({
298
314
  return await ctx.scheduler.runAfter(
299
315
  args.delayMs,
300
316
  internal.notifications.sendReminder,
301
- { userId: args.userId, message: args.message }
317
+ { userId: args.userId, message: args.message },
302
318
  );
303
319
  },
304
320
  });
@@ -59,7 +59,11 @@ convex/
59
59
  // QUERY: Read-only, reactive, cached. Use for fetching data.
60
60
  export const list = query({
61
61
  args: { limit: v.optional(v.number()) },
62
- returns: v.array(v.object({ /* ... */ })),
62
+ returns: v.array(
63
+ v.object({
64
+ /* ... */
65
+ }),
66
+ ),
63
67
  handler: async (ctx, args) => {
64
68
  return await ctx.db.query("items").take(args.limit ?? 10);
65
69
  },
@@ -79,8 +83,12 @@ export const processExternal = action({
79
83
  args: { id: v.id("items") },
80
84
  handler: async (ctx, args) => {
81
85
  const data = await ctx.runQuery(internal.items.get, { id: args.id });
82
- const result = await fetch("https://api.example.com", { /* ... */ });
83
- await ctx.runMutation(internal.items.saveResult, { /* ... */ });
86
+ const result = await fetch("https://api.example.com", {
87
+ /* ... */
88
+ });
89
+ await ctx.runMutation(internal.items.saveResult, {
90
+ /* ... */
91
+ });
84
92
  },
85
93
  });
86
94
  ```
@@ -151,7 +159,9 @@ const posts = await ctx.db
151
159
 
152
160
  ```typescript
153
161
  export const myMutation = mutation({
154
- args: { /* ... */ },
162
+ args: {
163
+ /* ... */
164
+ },
155
165
  handler: async (ctx, args) => {
156
166
  const identity = await ctx.auth.getUserIdentity();
157
167
  if (!identity) throw new Error("Unauthenticated");
@@ -189,6 +199,17 @@ export const getUrl = query({
189
199
  });
190
200
  ```
191
201
 
202
+ ## Linting
203
+
204
+ This project uses @convex-dev/eslint-plugin for code quality. All Convex functions must:
205
+
206
+ 1. **Use object syntax** with `handler` property (not bare functions)
207
+ 2. **Include argument validators** - always have `args: {}` even when empty
208
+ 3. **Use explicit table names** - `db.get("table", id)` not `db.get(id)`
209
+ 4. **Respect runtime boundaries** - don't import "use node" files into Convex runtime
210
+
211
+ Run `npx eslint convex/` to check compliance.
212
+
192
213
  ## IMPORTANT Rules
193
214
 
194
215
  1. **ALWAYS validate arguments** on public functions with `v` validators
@@ -239,24 +260,31 @@ HTTP webhook/API? → httpAction()
239
260
  ### Validator Cheat Sheet
240
261
 
241
262
  ```typescript
242
- v.string() // string
243
- v.number() // number (float64)
244
- v.boolean() // boolean
245
- v.id("table") // Id<"table">
246
- v.null() // null
247
- v.optional(v.string()) // string | undefined (field can be missing)
248
- v.nullable(v.string()) // string | null (field must exist)
249
- v.array(v.string()) // string[]
250
- v.object({ k: v.string() }) // { k: string }
251
- v.union(v.literal("a"), v.literal("b")) // "a" | "b"
252
- v.record(v.string(), v.number()) // { [key: string]: number }
263
+ v.string(); // string
264
+ v.number(); // number (float64)
265
+ v.boolean(); // boolean
266
+ v.id("table"); // Id<"table">
267
+ v.null(); // null
268
+ v.optional(v.string()); // string | undefined (field can be missing)
269
+ v.nullable(v.string()); // string | null (field must exist)
270
+ v.array(v.string()); // string[]
271
+ v.object({ k: v.string() }); // { k: string }
272
+ v.union(v.literal("a"), v.literal("b")); // "a" | "b"
273
+ v.record(v.string(), v.number()); // { [key: string]: number }
253
274
  ```
254
275
 
255
276
  ### Import Patterns
256
277
 
257
278
  ```typescript
258
279
  // Function constructors
259
- import { query, mutation, action, internalQuery, internalMutation, internalAction } from "./_generated/server";
280
+ import {
281
+ query,
282
+ mutation,
283
+ action,
284
+ internalQuery,
285
+ internalMutation,
286
+ internalAction,
287
+ } from "./_generated/server";
260
288
 
261
289
  // API references
262
290
  import { api, internal } from "./_generated/api";