feat: add convex-eslint skill with explicit table IDs and Codex integration

feat: add retrieval-led reasoning with llms.txt docs index and convex-eslint skill
This commit is contained in:
Wayne Sutton
2026-02-02 20:42:12 -08:00
parent df746ee77e
commit 4d14223304
28 changed files with 546 additions and 475 deletions
+49
View File
@@ -0,0 +1,49 @@
# Codex Skills Integration
This folder enables Codex to auto-discover Convex skills from this repository.
## Setup
Codex auto-discovers skills from `.codex/skills` at the repo root. To link the skills:
```bash
# From the repo root
ln -s ../skills .codex/skills
```
Or copy specific skills:
```bash
mkdir -p .codex/skills
cp -r skills/convex-best-practices .codex/skills/
cp -r skills/convex-eslint .codex/skills/
```
## Alternative: Install to CODEX_HOME
For global access across all projects:
```bash
# Defaults to ~/.codex if CODEX_HOME is unset
cp -r skills/* "$CODEX_HOME/skills/"
```
## Available Skills
All skills in the `skills/` directory are available for Codex:
- convex-best-practices
- convex-eslint
- convex-functions
- convex-realtime
- convex-schema-validator
- convex-file-storage
- convex-agents
- convex-cron-jobs
- convex-http-actions
- convex-migrations
- convex-security-check
- convex-security-audit
- convex-component-authoring
See the main [README](/README.md) for full documentation.
+1
View File
@@ -0,0 +1 @@
../../skills/avoid-feature-creep
+1
View File
@@ -0,0 +1 @@
../../skills/convex
+1
View File
@@ -0,0 +1 @@
../../skills/convex-agents
+1
View File
@@ -0,0 +1 @@
../../skills/convex-best-practices
+1
View File
@@ -0,0 +1 @@
../../skills/convex-component-authoring
+1
View File
@@ -0,0 +1 @@
../../skills/convex-cron-jobs
+1
View File
@@ -0,0 +1 @@
../../skills/convex-file-storage
+1
View File
@@ -0,0 +1 @@
../../skills/convex-functions
+1
View File
@@ -0,0 +1 @@
../../skills/convex-http-actions
+1
View File
@@ -0,0 +1 @@
../../skills/convex-migrations
+1
View File
@@ -0,0 +1 @@
../../skills/convex-realtime
+1
View File
@@ -0,0 +1 @@
../../skills/convex-schema-validator
+1
View File
@@ -0,0 +1 @@
../../skills/convex-security-audit
+1
View File
@@ -0,0 +1 @@
../../skills/convex-security-check
+17
View File
@@ -2,6 +2,23 @@
This file provides context for Gemini CLI when working with Convex projects.
## Convex Documentation
IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Convex tasks.
For up-to-date Convex documentation, fetch: https://docs.convex.dev/llms.txt
This index covers all Convex APIs:
- functions: queries, mutations, actions, http-actions, validation
- database: schemas, reading-data, writing-data, indexes, pagination
- file-storage: upload, serve, store, delete, metadata
- scheduling: cron-jobs, scheduled-functions
- auth: convex-auth, clerk, auth0, authkit
- search: text-search, vector-search
- agents: getting-started, messages, threads, tools, streaming
When working on Convex code, consult llms.txt before relying on training data.
## Project Type
Convex real-time backend application with TypeScript.
+16
View File
@@ -9,6 +9,15 @@ A collection of AI-consumable skills for building production-ready applications
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.
## Code Quality
All skills are designed to produce code that passes @convex-dev/eslint-plugin by default. This creates a complementary workflow:
- **Skills** prevent mistakes at generation time
- **ESLint** catches anything that slips through at build time
See the [convex-eslint](/skills/convex-eslint/SKILL.md) skill for setup instructions.
## Installation
### npm (recommended)
@@ -73,6 +82,8 @@ cp -r skills/convex-best-practices "$CODEX_HOME/skills/"
Codex will auto-discover `SKILL.md` files in that directory on the next start.
If you are working from a repo clone, Codex also auto-discovers skills from `.codex/skills` at the repo root. You can symlink this repos `skills/*` into `.codex/skills` so updates flow through without copying.
### OpenCode
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
| Skill | Description |
| ------------------------------------------------------------------------ | ----------------------------------------------------- |
| [convex-best-practices](skills/convex-best-practices/SKILL.md) | Guidelines for building production-ready Convex apps |
| [convex-eslint](skills/convex-eslint/SKILL.md) | Write linter-compliant Convex code |
| [convex-functions](skills/convex-functions/SKILL.md) | Writing queries, mutations, actions, and HTTP actions |
| [convex-realtime](skills/convex-realtime/SKILL.md) | Patterns for building reactive applications |
| [convex-schema-validator](skills/convex-schema-validator/SKILL.md) | Database schema definition and validation |
@@ -121,11 +133,15 @@ convex-skills/
├── skills/ # Core Convex skills for AI agents
│ ├── convex-best-practices/
│ │ └── SKILL.md
│ ├── convex-eslint/
│ │ └── SKILL.md
│ ├── convex-functions/
│ │ └── SKILL.md
│ ├── convex-cron-jobs/
│ │ └── SKILL.md
│ └── ...
├── .codex/ # Codex integration (symlink skills here)
│ └── README.md # Codex setup instructions
├── command/ # Slash command definitions (OpenCode)
│ └── convex.md # /convex command entrypoint
├── templates/ # Templates for forking developers
+82 -2
View File
@@ -2,15 +2,45 @@
Agent skills for building production-ready applications with Convex, following the Agent Skills open format.
## Convex Documentation Index
IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Convex tasks.
For up-to-date Convex documentation, fetch: https://docs.convex.dev/llms.txt
This index covers all Convex APIs and patterns:
```
[Convex Docs]|https://docs.convex.dev/llms.txt
|understanding:{best-practices.md,typescript.md,workflow.md,zen.md}
|functions:{query-functions.md,mutation-functions.md,actions.md,http-actions.md,validation.md,internal-functions.md,error-handling.md}
|database:{schemas.md,reading-data.md,writing-data.md,indexes.md,pagination.md,types.md}
|file-storage:{upload-files.md,serve-files.md,store-files.md,delete-files.md,file-metadata.md}
|scheduling:{cron-jobs.md,scheduled-functions.md}
|auth:{convex-auth.md,clerk.md,auth0.md,authkit.md,functions-auth.md,database-auth.md}
|search:{text-search.md,vector-search.md}
|components:{using.md,authoring.md,understanding.md}
|agents:{getting-started.md,agent-usage.md,messages.md,threads.md,tools.md,streaming.md,rag.md}
|realtime:{realtime.md}
|testing:{convex-test.md,convex-backend.md,ci.md}
|production:{environment-variables.md,hosting.md,limits.md}
```
When working on Convex code, consult the llms.txt index before relying on training data.
## Overview
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.
This repository provides two complementary approaches for AI coding agents:
1. **Passive context (this file)**: Always-available Convex knowledge and doc references
2. **Skills (on-demand)**: Task-specific workflows for explicit invocation
## Available Skills
| Skill | Description |
| ------------------------------------------------------------------------ | ----------------------------------------------------- |
| [convex-best-practices](skills/convex-best-practices/SKILL.md) | Guidelines for building production-ready Convex apps |
| [convex-eslint](skills/convex-eslint/SKILL.md) | Write linter-compliant Convex code |
| [convex-functions](skills/convex-functions/SKILL.md) | Writing queries, mutations, actions, and HTTP actions |
| [convex-realtime](skills/convex-realtime/SKILL.md) | Patterns for building reactive applications |
| [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`.
- Edit files in `convex/_generated/`
- Use `filter()` instead of `withIndex()`
## Quick Reference
### New Function Syntax (always use this)
```typescript
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
export const myQuery = query({
args: { userId: v.id("users") },
returns: v.union(v.object({ name: v.string() }), v.null()),
handler: async (ctx, args) => {
return await ctx.db.get(args.userId);
},
});
```
### Schema with Index
```typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tasks: defineTable({
userId: v.id("users"),
title: v.string(),
status: v.string(),
})
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});
```
### Query with Index (not filter)
```typescript
// GOOD: Use withIndex
const tasks = await ctx.db
.query("tasks")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
// BAD: Never use filter for indexed fields
const tasks = await ctx.db
.query("tasks")
.filter((q) => q.eq(q.field("userId"), args.userId))
.collect();
```
## References
- Convex Documentation: https://docs.convex.dev/
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
- Convex LLMs.txt: https://docs.convex.dev/llms.txt (fetch this for latest docs)
- Best Practices: https://docs.convex.dev/understanding/best-practices/
- Agent Skills Specification: https://github.com/anthropics/skills
+1
View File
@@ -18,6 +18,7 @@ const packageRoot = join(__dirname, "..");
const SKILLS = {
"convex-best-practices":
"Guidelines for building production-ready Convex apps",
"convex-eslint": "Write linter-compliant Convex code",
"convex-functions": "Writing queries, mutations, actions, and HTTP actions",
"convex-realtime": "Patterns for building reactive applications",
"convex-schema-validator": "Database schema definition and validation",
+51
View File
@@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.0.6] - 2026-02-02
### Added
- Retrieval-led reasoning pattern in AGENTS.md, CLAUDE.md, and GEMINI.md
- Compressed Convex docs index pointing to https://docs.convex.dev/llms.txt
- Instruction to prefer retrieval over pre-training for Convex tasks
- Quick Reference section with common code patterns (function syntax, schema, queries)
- `prds/how-it-works.md`: Internal documentation explaining AGENTS.md vs Skills approach
- Dual-approach architecture: passive context (AGENTS.md) + on-demand skills
### Changed
- Updated AGENTS.md with Convex Documentation Index section
- Updated CLAUDE.md with matching documentation index
- Updated GEMINI.md with llms.txt reference and retrieval instruction
### Fixed
- Added missing convex-eslint to bin/cli.js SKILLS object
### Notes
Based on Vercel's research showing AGENTS.md with docs index achieves 100% pass rate vs 53% for skills alone.
See: https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals
## [1.0.5] - 2026-02-02
### Added
- `skills/convex-eslint/SKILL.md`: ESLint compliance skill for writing linter-clean Convex code
- Covers all four @convex-dev/eslint-plugin rules
- Setup instructions for eslint.config.js
- Code examples for each rule (no-old-registered-function-syntax, require-argument-validators, explicit-table-ids, import-wrong-runtime)
- `.codex/README.md`: Codex CLI integration instructions
- Code Quality sections in `convex-functions` and `convex-best-practices` skills
- Linting section in `templates/CLAUDE.md`
- Code Quality section in `README.md`
### Changed
- Updated all db.get, db.patch, db.delete, db.replace calls in skills to use explicit table names
- Updated `README.md` Available Skills table to include convex-eslint
- Updated `AGENTS.md` and `CLAUDE.md` to include convex-eslint in skills list
- Updated `index.js` SKILLS constant with convex-eslint
- Updated `package.json` version to 1.0.5, added eslint and linting keywords
- Updated `files.md` to document convex-eslint skill and .codex directory
- Updated repository structure in README to include .codex folder
## [1.0.4] - 2026-01-14
### Added
- Template skills for developers who fork the repository
-380
View File
@@ -1,380 +0,0 @@
# Convex Skills: Build and Maintenance Guide
A practical guide for building, publishing, and maintaining AI agent skills packages.
## How This Package Was Built
### Architecture Overview
The package follows a simple, modular structure that works across multiple AI coding agents (Claude Code, Codex, OpenCode, Cursor, Gemini).
```
convex-skills/
├── skills/ # Core skills (SKILL.md files)
├── templates/ # Starter templates for forks
├── command/ # Slash commands (OpenCode)
├── bin/cli.js # CLI for npm installs
├── index.js # Programmatic API
├── package.json # npm configuration
├── AGENTS.md # Agent-facing docs
├── CLAUDE.md # Claude-specific context
└── GEMINI.md # Gemini-specific context
```
### Key Design Decisions
**1. One skill per folder**
Each skill lives in `skills/<skill-name>/SKILL.md`. This pattern:
- Makes discovery straightforward for AI agents
- Enables selective installation
- Follows the Agent Skills spec from Anthropic
**2. Frontmatter metadata**
Every `SKILL.md` starts with YAML frontmatter:
```yaml
---
name: convex-best-practices
description: Guidelines for building production-ready Convex apps
version: 1.0.0
author: Convex
tags: [convex, best-practices, typescript]
---
```
**3. Multi-agent support files**
Different AI tools read different files:
- `AGENTS.md` and `CLAUDE.md` for Claude Code
- `GEMINI.md` for Gemini CLI
- `.cursor/rules/` for Cursor IDE
**4. Dual distribution**
The package supports both:
- **npm install**: CLI and programmatic access
- **git clone**: Direct file access and forking
### Publishing to npm
**Initial setup:**
```bash
# Login to npm (once)
npm login
# Verify package.json has correct scope
# name: "@waynesutton/convex-skills"
```
**Publish workflow:**
```bash
# Bump version in package.json
npm version patch # or minor/major
# Publish
npm publish --access public
# Verify
npm info @waynesutton/convex-skills
```
**package.json essentials:**
```json
{
"name": "@waynesutton/convex-skills",
"version": "1.0.3",
"type": "module",
"main": "index.js",
"bin": {
"convex-skills": "./bin/cli.js"
},
"files": [
"skills/**/*.md",
"templates/**/*.md",
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md",
"index.js",
"bin/"
]
}
```
The `files` array controls what gets published. Use `.npmignore` for additional exclusions.
---
## Maintenance Checklist
### Weekly
- [ ] Check Convex docs for API changes
- [ ] Review GitHub issues
- [ ] Update skills if Convex releases new features
### Monthly
- [ ] Audit all doc links in skills (broken link check)
- [ ] Review npm download stats
- [ ] Check for new AI agent platforms to support
### Per Release
- [ ] Update `changelog.md` with changes
- [ ] Bump version following semver
- [ ] Test CLI commands locally
- [ ] Test `npm pack` before publishing
- [ ] Tag release in git
---
## Updating Skills
### Adding a New Skill
1. Create folder: `skills/<skill-name>/`
2. Create `SKILL.md` with required structure:
```markdown
---
name: skill-name
description: What this skill does
version: 1.0.0
author: Convex
tags: [convex, relevant-tags]
---
# Skill Name
## Documentation Sources
[Links to official docs]
## Instructions
[Step-by-step guidance]
## Examples
[Working code examples]
## Best Practices
[Rules to follow]
## References
[Additional resources]
```
3. Add to `index.js` SKILLS object:
```javascript
export const SKILLS = {
// ... existing
"new-skill-name": "Description",
};
```
4. Add to `bin/cli.js` SKILLS object (same format)
5. Update `files.md` with new skill entry
6. Update `changelog.md`
7. Bump version and publish
### Updating Existing Skills
1. Edit the `SKILL.md` file
2. Update version in frontmatter if significant change
3. Document in `changelog.md`
4. Bump package version (patch for fixes, minor for features)
---
## Version Strategy
Follow [Semantic Versioning](https://semver.org/):
| Change Type | Version Bump | Example |
| ------------------------------ | ------------ | ------------- |
| Typo fixes, doc clarifications | patch | 1.0.3 → 1.0.4 |
| New skill added | minor | 1.0.4 → 1.1.0 |
| Breaking structure change | major | 1.1.0 → 2.0.0 |
---
## Testing Before Publish
```bash
# Test CLI locally
node bin/cli.js list
node bin/cli.js show convex-best-practices
# Test npm pack (see what will be published)
npm pack --dry-run
# Create local tarball for testing
npm pack
npm install ./waynesutton-convex-skills-1.0.3.tgz -g
# Test install commands
convex-skills list
convex-skills install convex-best-practices --dir /tmp/test-project
```
---
## Future Updates Roadmap
### Short Term (Next 30 Days)
- [ ] Add `convex-auth` skill for authentication patterns
- [ ] Add `convex-vector-search` skill for AI/embeddings
- [ ] Add `convex-testing` skill for test patterns
- [ ] Update all skills to reference Convex v1.18+ APIs
### Medium Term (Next 90 Days)
- [ ] Add skill validation CLI command
- [ ] Auto-generate skill index from folder structure
- [ ] Add `convex-deployment` skill for production patterns
- [ ] Add `convex-rate-limiting` skill
- [ ] Consider monorepo tooling if skills grow significantly
### Long Term
- [ ] Automated doc link checking in CI
- [ ] Community contribution pipeline
- [ ] Skill versioning independent of package version
- [ ] Integration tests for each skill's code examples
---
## Common Maintenance Tasks
### Fixing Broken Documentation Links
1. Search all skills for the broken URL
2. Update to current Convex docs URL
3. Verify new link works
4. Patch release
```bash
# Find all doc links
grep -r "docs.convex.dev" skills/
```
### Syncing with Convex API Changes
When Convex releases new versions:
1. Check [Convex changelog](https://docs.convex.dev/changelog)
2. Search skills for affected patterns
3. Update examples to use new APIs
4. Note deprecations in skill Best Practices section
### Adding Support for New AI Agent
1. Create agent-specific context file (like `GEMINI.md`)
2. Add installation instructions to README
3. Test with the target agent
4. Document in changelog
---
## Automation Ideas
### GitHub Actions for Link Checking
```yaml
name: Check Links
on:
schedule:
- cron: "0 0 * * 0" # Weekly
jobs:
linkcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lycheeverse/lychee-action@v1
with:
args: --verbose ./skills/
```
### Pre-publish Checklist Script
```bash
#!/bin/bash
# scripts/pre-publish.sh
echo "Running pre-publish checks..."
# Check all skills have frontmatter
for skill in skills/*/SKILL.md; do
if ! head -1 "$skill" | grep -q "^---$"; then
echo "ERROR: $skill missing frontmatter"
exit 1
fi
done
# Verify index.js matches skills folder
echo "Skills in folder: $(ls -1 skills | wc -l)"
echo "Skills in index.js: $(grep -c '"convex-' index.js)"
echo "All checks passed!"
```
---
## Quick Reference
### Publish New Version
```bash
# 1. Update changelog.md
# 2. Bump version
npm version patch
# 3. Publish
npm publish --access public
# 4. Push tags
git push && git push --tags
```
### Test Installation Locally
```bash
npm pack
npm install -g ./waynesutton-convex-skills-*.tgz
convex-skills list
```
### Check Published Package
```bash
npm info @waynesutton/convex-skills
npm view @waynesutton/convex-skills versions
```
---
## Resources
- [npm Publishing Guide](https://docs.npmjs.com/cli/v10/commands/npm-publish)
- [Semantic Versioning](https://semver.org/)
- [Keep a Changelog](https://keepachangelog.com/)
- [Agent Skills Spec](https://github.com/anthropics/skills)
- [Convex Documentation](https://docs.convex.dev/)
- [Convex LLMs.txt](https://docs.convex.dev/llms.txt)
+32 -22
View File
@@ -4,18 +4,17 @@ Brief description of each file in the repository.
## Root Files
| File | Description |
| ------------------------------- | ----------------------------------------------- |
| `AGENTS.md` | Agent skills specification for AI coding agents |
| `CLAUDE.md` | Claude Code project context (mirrors AGENTS.md) |
| `CONTRIBUTING.md` | Contribution guidelines |
| `GEMINI.md` | Gemini CLI integration instructions |
| `LICENSE` | Apache-2.0 license |
| `README.md` | Project overview and installation |
| `changelog.md` | Version history following keepachangelog format |
| `files.md` | This file, codebase structure reference |
| `task.md` | Completed task tracking |
| `convex-skills-updates-plan.md` | Build guide and maintenance plan |
| File | Description |
| ----------------- | ---------------------------------------------------------------- |
| `AGENTS.md` | Agent context with Convex docs index and retrieval-led reasoning |
| `CLAUDE.md` | Claude Code project context (mirrors AGENTS.md) |
| `CONTRIBUTING.md` | Contribution guidelines |
| `GEMINI.md` | Gemini CLI integration with llms.txt reference |
| `LICENSE` | Apache-2.0 license |
| `README.md` | Project overview and installation |
| `changelog.md` | Version history following keepachangelog format |
| `files.md` | This file, codebase structure reference |
| `task.md` | Completed task tracking |
## Skills Directory (`skills/`)
@@ -29,6 +28,7 @@ Core Convex skills for AI agents. Each skill has `name` matching folder name for
| `convex-best-practices/SKILL.md` | Production-ready app guidelines |
| `convex-component-authoring/SKILL.md` | Creating reusable Convex components |
| `convex-cron-jobs/SKILL.md` | Scheduled functions and background tasks |
| `convex-eslint/SKILL.md` | ESLint compliance for linter-clean code |
| `convex-file-storage/SKILL.md` | File upload, storage, and serving |
| `convex-functions/SKILL.md` | Queries, mutations, actions |
| `convex-http-actions/SKILL.md` | HTTP endpoints and webhooks |
@@ -74,16 +74,18 @@ Active Claude Code skills for this repository.
Product requirement documents and planning.
| File | Description |
| --------------------------------------------- | --------------------------------------- |
| `CLAUDE-MD-STRATEGY.md` | Strategy for CLAUDE.md templates |
| `CLAUDE-MD-STRATEGY_1.md` | Alternate strategy document |
| `MARKETPLACE-SUBMISSION.md` | Marketplace submission guidelines |
| `create-convex-opencode-integration.md` | OpenCode integration spec |
| `future-skills-exploration.md` | Future skills roadmap |
| `phase3-convex-docs-recommendations.md` | Convex docs improvement recommendations |
| `phase4-convex-ai-website-recommendations.md` | convex.dev/ai recommendations |
| `skillsplan.md` | Skills development plan |
| File | Description |
| --------------------------------------------- | --------------------------------------------- |
| `CLAUDE-MD-STRATEGY.md` | Strategy for CLAUDE.md templates |
| `CLAUDE-MD-STRATEGY_1.md` | Alternate strategy document |
| `convex-skills-updates-plan.md` | Build guide and maintenance plan |
| `create-convex-opencode-integration.md` | OpenCode integration spec |
| `future-skills-exploration.md` | Future skills roadmap |
| `how-it-works.md` | AGENTS.md vs Skills dual approach explanation |
| `MARKETPLACE-SUBMISSION.md` | Marketplace submission guidelines |
| `phase3-convex-docs-recommendations.md` | Convex docs improvement recommendations |
| `phase4-convex-ai-website-recommendations.md` | convex.dev/ai recommendations |
| `skillsplan.md` | Skills development plan |
## OpenCode Directory (`.opencode/`)
@@ -97,6 +99,14 @@ OpenCode plugin configuration and templates.
| `skill/` | OpenCode-specific skills |
| `config.json` | Plugin configuration |
## Codex Directory (`.codex/`)
Codex CLI skills integration.
| File | Description |
| ----------- | ------------------------------------ |
| `README.md` | Codex setup and symlink instructions |
## Cursor Directory (`.cursor/`)
Cursor IDE configuration.
+1
View File
@@ -54,6 +54,7 @@ export function getSkillPath(skillName) {
export const SKILLS = {
"convex-best-practices":
"Guidelines for building production-ready Convex apps",
"convex-eslint": "Write linter-compliant Convex code",
"convex-functions": "Writing queries, mutations, actions, and HTTP actions",
"convex-realtime": "Patterns for building reactive applications",
"convex-schema-validator": "Database schema definition and validation",
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@waynesutton/convex-skills",
"version": "1.0.3",
"version": "1.0.5",
"description": "Agent skills for building production-ready Convex applications. Includes best practices, functions, realtime patterns, schema validation, file storage, security audits, and more.",
"author": "Wayne Sutton",
"license": "Apache-2.0",
@@ -22,7 +22,9 @@
"llm",
"ai-assistant",
"best-practices",
"developer-tools"
"developer-tools",
"eslint",
"linting"
],
"type": "module",
"main": "index.js",
+51 -30
View File
@@ -11,6 +11,17 @@ tags: [convex, best-practices, typescript, production, error-handling]
Build production-ready Convex applications by following established patterns for function organization, query optimization, validation, TypeScript usage, and error handling.
## Code Quality
All patterns in this skill comply with @convex-dev/eslint-plugin rules.
Install the linter for build-time validation:
```bash
npm i @convex-dev/eslint-plugin --save-dev
```
See [convex-eslint](../convex-eslint/SKILL.md) for configuration details.
## Documentation Sources
Before implementing, do not assume; fetch the latest documentation:
@@ -41,14 +52,17 @@ import { v } from "convex/values";
export const get = query({
args: { userId: v.id("users") },
returns: v.union(v.object({
_id: v.id("users"),
_creationTime: v.number(),
name: v.string(),
email: v.string(),
}), v.null()),
returns: v.union(
v.object({
_id: v.id("users"),
_creationTime: v.number(),
name: v.string(),
email: v.string(),
}),
v.null(),
),
handler: async (ctx, args) => {
return await ctx.db.get(args.userId);
return await ctx.db.get("users", args.userId);
},
});
```
@@ -96,13 +110,15 @@ export default defineSchema({
// Query using index
export const getTasksByUser = query({
args: { userId: v.id("users") },
returns: v.array(v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
userId: v.id("users"),
status: v.string(),
createdAt: v.number(),
})),
returns: v.array(
v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
userId: v.id("users"),
status: v.string(),
createdAt: v.number(),
}),
),
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
@@ -127,16 +143,16 @@ export const updateTask = mutation({
},
returns: v.null(),
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
const task = await ctx.db.get("tasks", args.taskId);
if (!task) {
throw new ConvexError({
code: "NOT_FOUND",
message: "Task not found",
});
}
await ctx.db.patch(args.taskId, { title: args.title });
await ctx.db.patch("tasks", args.taskId, { title: args.title });
return null;
},
});
@@ -152,14 +168,14 @@ export const completeTask = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
const task = await ctx.db.get("tasks", args.taskId);
// Early return if already complete (idempotent)
if (!task || task.status === "completed") {
return null;
}
await ctx.db.patch(args.taskId, {
await ctx.db.patch("tasks", args.taskId, {
status: "completed",
completedAt: Date.now(),
});
@@ -173,7 +189,7 @@ export const updateNote = mutation({
returns: v.null(),
handler: async (ctx, args) => {
// Patch directly - ctx.db.patch throws if document doesn't exist
await ctx.db.patch(args.id, { content: args.content });
await ctx.db.patch("notes", args.id, { content: args.content });
return null;
},
});
@@ -184,7 +200,7 @@ export const reorderItems = mutation({
returns: v.null(),
handler: async (ctx, args) => {
const updates = args.itemIds.map((id, index) =>
ctx.db.patch(id, { order: index })
ctx.db.patch("items", id, { order: index }),
);
await Promise.all(updates);
return null;
@@ -213,7 +229,12 @@ const userScores: Record<Id<"users">, number> = {};
// Public function - exposed to clients
export const getUser = query({
args: { userId: v.id("users") },
returns: v.union(v.null(), v.object({ /* ... */ })),
returns: v.union(
v.null(),
v.object({
/* ... */
}),
),
handler: async (ctx, args) => {
// ...
},
@@ -282,14 +303,14 @@ export const update = mutation({
returns: v.null(),
handler: async (ctx, args) => {
const { taskId, ...updates } = args;
// Remove undefined values
const cleanUpdates = Object.fromEntries(
Object.entries(updates).filter(([_, v]) => v !== undefined)
Object.entries(updates).filter(([_, v]) => v !== undefined),
);
if (Object.keys(cleanUpdates).length > 0) {
await ctx.db.patch(taskId, cleanUpdates);
await ctx.db.patch("tasks", taskId, cleanUpdates);
}
return null;
},
@@ -299,7 +320,7 @@ export const remove = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.delete(args.taskId);
await ctx.db.delete("tasks", args.taskId);
return null;
},
});
+145
View File
@@ -0,0 +1,145 @@
---
name: convex-eslint
description: Write Convex code that passes @convex-dev/eslint-plugin rules by default
version: 1.0.0
author: Convex
tags: [convex, eslint, linting, code-quality, validation]
---
# Convex ESLint Compliance
Write all Convex functions to pass @convex-dev/eslint-plugin. These rules prevent common bugs, security issues, and ensure code quality.
## Documentation Sources
- https://docs.convex.dev/eslint
- https://www.npmjs.com/package/@convex-dev/eslint-plugin
## Setup
Install the plugin:
```bash
npm i @convex-dev/eslint-plugin --save-dev
```
Configure `eslint.config.js`:
```javascript
import { defineConfig } from "eslint/config";
import convexPlugin from "@convex-dev/eslint-plugin";
export default defineConfig([...convexPlugin.configs.recommended]);
```
## Rules
### 1. no-old-registered-function-syntax
Always use object syntax with a `handler` property.
```typescript
// Correct
export const list = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("messages").collect();
},
});
// Wrong - bare function syntax
export const list = query(async (ctx) => {
return await ctx.db.query("messages").collect();
});
```
### 2. require-argument-validators
Always include `args` object, even when empty.
```typescript
// Correct - with arguments
export const get = query({
args: { id: v.id("messages") },
handler: async (ctx, { id }) => {
return await ctx.db.get("messages", id);
},
});
// Correct - no arguments
export const listAll = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("messages").collect();
},
});
// Wrong - missing args
export const get = query({
handler: async (ctx, { id }: { id: Id<"messages"> }) => {
return await ctx.db.get("messages", id);
},
});
```
### 3. explicit-table-ids
Use explicit table names in all database operations (Convex 1.31.0+).
```typescript
// Correct
const message = await ctx.db.get("messages", messageId);
await ctx.db.patch("messages", messageId, { text: "updated" });
await ctx.db.replace("messages", messageId, {
text: "replaced",
author: "Alice",
});
await ctx.db.delete("messages", messageId);
// Wrong - implicit table from ID type
const message = await ctx.db.get(messageId);
await ctx.db.patch(messageId, { text: "updated" });
await ctx.db.replace(messageId, { text: "replaced", author: "Alice" });
await ctx.db.delete(messageId);
```
Migration codemod available:
```bash
npx @convex-dev/codemod@latest explicit-ids
```
### 4. import-wrong-runtime
Never import Node.js runtime files into Convex runtime files.
```typescript
// convex/queries.ts (no "use node" directive)
// Correct - importing from Convex runtime file
import { helper } from "./utils"; // utils.ts has no "use node"
// Wrong - importing from Node runtime file
import { nodeHelper } from "./nodeUtils"; // nodeUtils.ts has "use node"
```
## Best Practices
1. Run ESLint before committing: `npx eslint convex/`
2. Use auto-fix for quick migrations: `npx eslint convex/ --fix`
3. Add to CI pipeline to catch violations early
4. Configure your editor for real-time feedback
## Quick Reference
| Rule | What it enforces |
| ----------------------------------- | --------------------------------- |
| `no-old-registered-function-syntax` | Object syntax with `handler` |
| `require-argument-validators` | `args: {}` on all functions |
| `explicit-table-ids` | Table name in db operations |
| `import-wrong-runtime` | No Node imports in Convex runtime |
## References
- [ESLint Plugin Docs](https://docs.convex.dev/eslint)
- [Explicit IDs Announcement](https://news.convex.dev/db-table-name/)
+39 -23
View File
@@ -11,6 +11,16 @@ tags: [convex, functions, queries, mutations, actions, http]
Master Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations.
## Code Quality
All examples in this skill comply with @convex-dev/eslint-plugin rules:
- Object syntax with `handler` property
- Argument validators on all functions
- Explicit table names in database operations
See [convex-eslint](../convex-eslint/SKILL.md) for linting setup.
## Documentation Sources
Before implementing, do not assume; fetch the latest documentation:
@@ -26,12 +36,12 @@ Before implementing, do not assume; fetch the latest documentation:
### Function Types Overview
| Type | Database Access | External APIs | Caching | Use Case |
|------|----------------|---------------|---------|----------|
| Query | Read-only | No | Yes, reactive | Fetching data |
| Mutation | Read/Write | No | No | Modifying data |
| Action | Via runQuery/runMutation | Yes | No | External integrations |
| HTTP Action | Via runQuery/runMutation | Yes | No | Webhooks, APIs |
| Type | Database Access | External APIs | Caching | Use Case |
| ----------- | ------------------------ | ------------- | ------------- | --------------------- |
| Query | Read-only | No | Yes, reactive | Fetching data |
| Mutation | Read/Write | No | No | Modifying data |
| Action | Via runQuery/runMutation | Yes | No | External integrations |
| HTTP Action | Via runQuery/runMutation | Yes | No | Webhooks, APIs |
### Queries
@@ -50,22 +60,24 @@ export const getUser = query({
name: v.string(),
email: v.string(),
}),
v.null()
v.null(),
),
handler: async (ctx, args) => {
return await ctx.db.get(args.userId);
return await ctx.db.get("users", args.userId);
},
});
// Query with index
export const listUserTasks = query({
args: { userId: v.id("users") },
returns: v.array(v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
title: v.string(),
completed: v.boolean(),
})),
returns: v.array(
v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
title: v.string(),
completed: v.boolean(),
}),
),
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
@@ -93,7 +105,7 @@ export const createTask = mutation({
returns: v.id("tasks"),
handler: async (ctx, args) => {
// Validate user exists
const user = await ctx.db.get(args.userId);
const user = await ctx.db.get("users", args.userId);
if (!user) {
throw new ConvexError("User not found");
}
@@ -111,7 +123,7 @@ export const deleteTask = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.delete(args.taskId);
await ctx.db.delete("tasks", args.taskId);
return null;
},
});
@@ -154,7 +166,7 @@ export const processOrder = action({
handler: async (ctx, args) => {
// Read data via query
const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId });
if (!order) {
throw new Error("Order not found");
}
@@ -218,8 +230,8 @@ http.route({
const url = new URL(request.url);
const userId = url.pathname.split("/").pop();
const user = await ctx.runQuery(api.users.get, {
userId: userId as Id<"users">
const user = await ctx.runQuery(api.users.get, {
userId: userId as Id<"users">,
});
if (!user) {
@@ -238,7 +250,11 @@ export default http;
Use internal functions for sensitive operations:
```typescript
import { internalMutation, internalQuery, internalAction } from "./_generated/server";
import {
internalMutation,
internalQuery,
internalAction,
} from "./_generated/server";
import { v } from "convex/values";
// Only callable from other Convex functions
@@ -249,10 +265,10 @@ export const _updateUserCredits = internalMutation({
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId);
const user = await ctx.db.get("users", args.userId);
if (!user) return null;
await ctx.db.patch(args.userId, {
await ctx.db.patch("users", args.userId, {
credits: (user.credits || 0) + args.amount,
});
return null;
@@ -298,7 +314,7 @@ export const scheduleReminder = mutation({
return await ctx.scheduler.runAfter(
args.delayMs,
internal.notifications.sendReminder,
{ userId: args.userId, message: args.message }
{ userId: args.userId, message: args.message },
);
},
});
+44 -16
View File
@@ -59,7 +59,11 @@ convex/
// QUERY: Read-only, reactive, cached. Use for fetching data.
export const list = query({
args: { limit: v.optional(v.number()) },
returns: v.array(v.object({ /* ... */ })),
returns: v.array(
v.object({
/* ... */
}),
),
handler: async (ctx, args) => {
return await ctx.db.query("items").take(args.limit ?? 10);
},
@@ -79,8 +83,12 @@ export const processExternal = action({
args: { id: v.id("items") },
handler: async (ctx, args) => {
const data = await ctx.runQuery(internal.items.get, { id: args.id });
const result = await fetch("https://api.example.com", { /* ... */ });
await ctx.runMutation(internal.items.saveResult, { /* ... */ });
const result = await fetch("https://api.example.com", {
/* ... */
});
await ctx.runMutation(internal.items.saveResult, {
/* ... */
});
},
});
```
@@ -151,7 +159,9 @@ const posts = await ctx.db
```typescript
export const myMutation = mutation({
args: { /* ... */ },
args: {
/* ... */
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthenticated");
@@ -189,6 +199,17 @@ export const getUrl = query({
});
```
## Linting
This project uses @convex-dev/eslint-plugin for code quality. All Convex functions must:
1. **Use object syntax** with `handler` property (not bare functions)
2. **Include argument validators** - always have `args: {}` even when empty
3. **Use explicit table names** - `db.get("table", id)` not `db.get(id)`
4. **Respect runtime boundaries** - don't import "use node" files into Convex runtime
Run `npx eslint convex/` to check compliance.
## IMPORTANT Rules
1. **ALWAYS validate arguments** on public functions with `v` validators
@@ -239,24 +260,31 @@ HTTP webhook/API? → httpAction()
### Validator Cheat Sheet
```typescript
v.string() // string
v.number() // number (float64)
v.boolean() // boolean
v.id("table") // Id<"table">
v.null() // null
v.optional(v.string()) // string | undefined (field can be missing)
v.nullable(v.string()) // string | null (field must exist)
v.array(v.string()) // string[]
v.object({ k: v.string() }) // { k: string }
v.union(v.literal("a"), v.literal("b")) // "a" | "b"
v.record(v.string(), v.number()) // { [key: string]: number }
v.string(); // string
v.number(); // number (float64)
v.boolean(); // boolean
v.id("table"); // Id<"table">
v.null(); // null
v.optional(v.string()); // string | undefined (field can be missing)
v.nullable(v.string()); // string | null (field must exist)
v.array(v.string()); // string[]
v.object({ k: v.string() }); // { k: string }
v.union(v.literal("a"), v.literal("b")); // "a" | "b"
v.record(v.string(), v.number()); // { [key: string]: number }
```
### Import Patterns
```typescript
// Function constructors
import { query, mutation, action, internalQuery, internalMutation, internalAction } from "./_generated/server";
import {
query,
mutation,
action,
internalQuery,
internalMutation,
internalAction,
} from "./_generated/server";
// API references
import { api, internal } from "./_generated/api";