Add 260+ Claude Code skills from skills.sh
Complete collection of AI agent skills including: - Frontend Development (Vue, React, Next.js, Three.js) - Backend Development (NestJS, FastAPI, Node.js) - Mobile Development (React Native, Expo) - Testing (E2E, frontend, webapp) - DevOps (GitHub Actions, CI/CD) - Marketing (SEO, copywriting, analytics) - Security (binary analysis, vulnerability scanning) - And many more... Synchronized from: https://skills.sh/ Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
456
convex-component-authoring/skill.md
Normal file
456
convex-component-authoring/skill.md
Normal file
@@ -0,0 +1,456 @@
|
||||
---
|
||||
name: Convex Component Authoring
|
||||
description: How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management
|
||||
version: 1.0.0
|
||||
author: Convex
|
||||
tags: [convex, components, reusable, packages, npm]
|
||||
---
|
||||
|
||||
# Convex Component Authoring
|
||||
|
||||
Create self-contained, reusable Convex components with proper isolation, exports, and dependency management for sharing across projects.
|
||||
|
||||
## Documentation Sources
|
||||
|
||||
Before implementing, do not assume; fetch the latest documentation:
|
||||
|
||||
- Primary: https://docs.convex.dev/components
|
||||
- Component Authoring: https://docs.convex.dev/components/authoring
|
||||
- For broader context: https://docs.convex.dev/llms.txt
|
||||
|
||||
## Instructions
|
||||
|
||||
### What Are Convex Components?
|
||||
|
||||
Convex components are self-contained packages that include:
|
||||
- Database tables (isolated from the main app)
|
||||
- Functions (queries, mutations, actions)
|
||||
- TypeScript types and validators
|
||||
- Optional frontend hooks
|
||||
|
||||
### Component Structure
|
||||
|
||||
```
|
||||
my-convex-component/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── README.md
|
||||
├── src/
|
||||
│ ├── index.ts # Main exports
|
||||
│ ├── component.ts # Component definition
|
||||
│ ├── schema.ts # Component schema
|
||||
│ └── functions/
|
||||
│ ├── queries.ts
|
||||
│ ├── mutations.ts
|
||||
│ └── actions.ts
|
||||
└── convex.config.ts # Component configuration
|
||||
```
|
||||
|
||||
### Creating a Component
|
||||
|
||||
#### 1. Component Configuration
|
||||
|
||||
```typescript
|
||||
// convex.config.ts
|
||||
import { defineComponent } from "convex/server";
|
||||
|
||||
export default defineComponent("myComponent");
|
||||
```
|
||||
|
||||
#### 2. Component Schema
|
||||
|
||||
```typescript
|
||||
// src/schema.ts
|
||||
import { defineSchema, defineTable } from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export default defineSchema({
|
||||
// Tables are isolated to this component
|
||||
items: defineTable({
|
||||
name: v.string(),
|
||||
data: v.any(),
|
||||
createdAt: v.number(),
|
||||
}).index("by_name", ["name"]),
|
||||
|
||||
config: defineTable({
|
||||
key: v.string(),
|
||||
value: v.any(),
|
||||
}).index("by_key", ["key"]),
|
||||
});
|
||||
```
|
||||
|
||||
#### 3. Component Definition
|
||||
|
||||
```typescript
|
||||
// src/component.ts
|
||||
import { defineComponent, ComponentDefinition } from "convex/server";
|
||||
import schema from "./schema";
|
||||
import * as queries from "./functions/queries";
|
||||
import * as mutations from "./functions/mutations";
|
||||
|
||||
const component = defineComponent("myComponent", {
|
||||
schema,
|
||||
functions: {
|
||||
...queries,
|
||||
...mutations,
|
||||
},
|
||||
});
|
||||
|
||||
export default component;
|
||||
```
|
||||
|
||||
#### 4. Component Functions
|
||||
|
||||
```typescript
|
||||
// src/functions/queries.ts
|
||||
import { query } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export const list = query({
|
||||
args: {
|
||||
limit: v.optional(v.number()),
|
||||
},
|
||||
returns: v.array(v.object({
|
||||
_id: v.id("items"),
|
||||
name: v.string(),
|
||||
data: v.any(),
|
||||
createdAt: v.number(),
|
||||
})),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("items")
|
||||
.order("desc")
|
||||
.take(args.limit ?? 10);
|
||||
},
|
||||
});
|
||||
|
||||
export const get = query({
|
||||
args: { name: v.string() },
|
||||
returns: v.union(v.object({
|
||||
_id: v.id("items"),
|
||||
name: v.string(),
|
||||
data: v.any(),
|
||||
}), v.null()),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("items")
|
||||
.withIndex("by_name", (q) => q.eq("name", args.name))
|
||||
.unique();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/functions/mutations.ts
|
||||
import { mutation } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export const create = mutation({
|
||||
args: {
|
||||
name: v.string(),
|
||||
data: v.any(),
|
||||
},
|
||||
returns: v.id("items"),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db.insert("items", {
|
||||
name: args.name,
|
||||
data: args.data,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const update = mutation({
|
||||
args: {
|
||||
id: v.id("items"),
|
||||
data: v.any(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.id, { data: args.data });
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
export const remove = mutation({
|
||||
args: { id: v.id("items") },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.delete(args.id);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### 5. Main Exports
|
||||
|
||||
```typescript
|
||||
// src/index.ts
|
||||
export { default as component } from "./component";
|
||||
export * from "./functions/queries";
|
||||
export * from "./functions/mutations";
|
||||
|
||||
// Export types for consumers
|
||||
export type { Id } from "./_generated/dataModel";
|
||||
```
|
||||
|
||||
### Using a Component
|
||||
|
||||
```typescript
|
||||
// In the consuming app's convex/convex.config.ts
|
||||
import { defineApp } from "convex/server";
|
||||
import myComponent from "my-convex-component";
|
||||
|
||||
const app = defineApp();
|
||||
|
||||
app.use(myComponent, { name: "myComponent" });
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// In the consuming app's code
|
||||
import { useQuery, useMutation } from "convex/react";
|
||||
import { api } from "../convex/_generated/api";
|
||||
|
||||
function MyApp() {
|
||||
// Access component functions through the app's API
|
||||
const items = useQuery(api.myComponent.list, { limit: 10 });
|
||||
const createItem = useMutation(api.myComponent.create);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{items?.map((item) => (
|
||||
<div key={item._id}>{item.name}</div>
|
||||
))}
|
||||
<button onClick={() => createItem({ name: "New", data: {} })}>
|
||||
Add Item
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Component Configuration Options
|
||||
|
||||
```typescript
|
||||
// convex/convex.config.ts
|
||||
import { defineApp } from "convex/server";
|
||||
import myComponent from "my-convex-component";
|
||||
|
||||
const app = defineApp();
|
||||
|
||||
// Basic usage
|
||||
app.use(myComponent);
|
||||
|
||||
// With custom name
|
||||
app.use(myComponent, { name: "customName" });
|
||||
|
||||
// Multiple instances
|
||||
app.use(myComponent, { name: "instance1" });
|
||||
app.use(myComponent, { name: "instance2" });
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Providing Component Hooks
|
||||
|
||||
```typescript
|
||||
// src/hooks.ts
|
||||
import { useQuery, useMutation } from "convex/react";
|
||||
import { FunctionReference } from "convex/server";
|
||||
|
||||
// Type-safe hooks for component consumers
|
||||
export function useMyComponent(api: {
|
||||
list: FunctionReference<"query">;
|
||||
create: FunctionReference<"mutation">;
|
||||
}) {
|
||||
const items = useQuery(api.list, {});
|
||||
const createItem = useMutation(api.create);
|
||||
|
||||
return {
|
||||
items,
|
||||
createItem,
|
||||
isLoading: items === undefined,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Publishing a Component
|
||||
|
||||
#### package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-convex-component",
|
||||
"version": "1.0.0",
|
||||
"description": "A reusable Convex component",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"convex.config.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"convex": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"convex": "^1.17.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"convex",
|
||||
"component"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Rate Limiter Component
|
||||
|
||||
```typescript
|
||||
// rate-limiter/src/schema.ts
|
||||
import { defineSchema, defineTable } from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export default defineSchema({
|
||||
requests: defineTable({
|
||||
key: v.string(),
|
||||
timestamp: v.number(),
|
||||
})
|
||||
.index("by_key", ["key"])
|
||||
.index("by_key_and_time", ["key", "timestamp"]),
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// rate-limiter/src/functions/mutations.ts
|
||||
import { mutation } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export const checkLimit = mutation({
|
||||
args: {
|
||||
key: v.string(),
|
||||
limit: v.number(),
|
||||
windowMs: v.number(),
|
||||
},
|
||||
returns: v.object({
|
||||
allowed: v.boolean(),
|
||||
remaining: v.number(),
|
||||
resetAt: v.number(),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
const now = Date.now();
|
||||
const windowStart = now - args.windowMs;
|
||||
|
||||
// Clean old entries
|
||||
const oldEntries = await ctx.db
|
||||
.query("requests")
|
||||
.withIndex("by_key_and_time", (q) =>
|
||||
q.eq("key", args.key).lt("timestamp", windowStart)
|
||||
)
|
||||
.collect();
|
||||
|
||||
for (const entry of oldEntries) {
|
||||
await ctx.db.delete(entry._id);
|
||||
}
|
||||
|
||||
// Count current window
|
||||
const currentRequests = await ctx.db
|
||||
.query("requests")
|
||||
.withIndex("by_key", (q) => q.eq("key", args.key))
|
||||
.collect();
|
||||
|
||||
const remaining = Math.max(0, args.limit - currentRequests.length);
|
||||
const allowed = remaining > 0;
|
||||
|
||||
if (allowed) {
|
||||
await ctx.db.insert("requests", {
|
||||
key: args.key,
|
||||
timestamp: now,
|
||||
});
|
||||
}
|
||||
|
||||
const oldestRequest = currentRequests[0];
|
||||
const resetAt = oldestRequest
|
||||
? oldestRequest.timestamp + args.windowMs
|
||||
: now + args.windowMs;
|
||||
|
||||
return { allowed, remaining: remaining - (allowed ? 1 : 0), resetAt };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Usage in consuming app
|
||||
import { useMutation } from "convex/react";
|
||||
import { api } from "../convex/_generated/api";
|
||||
|
||||
function useRateLimitedAction() {
|
||||
const checkLimit = useMutation(api.rateLimiter.checkLimit);
|
||||
|
||||
return async (action: () => Promise<void>) => {
|
||||
const result = await checkLimit({
|
||||
key: "user-action",
|
||||
limit: 10,
|
||||
windowMs: 60000,
|
||||
});
|
||||
|
||||
if (!result.allowed) {
|
||||
throw new Error(`Rate limited. Try again at ${new Date(result.resetAt)}`);
|
||||
}
|
||||
|
||||
await action();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Never run `npx convex deploy` unless explicitly instructed
|
||||
- Never run any git commands unless explicitly instructed
|
||||
- Keep component tables isolated (don't reference main app tables)
|
||||
- Export clear TypeScript types for consumers
|
||||
- Document all public functions and their arguments
|
||||
- Use semantic versioning for component releases
|
||||
- Include comprehensive README with examples
|
||||
- Test components in isolation before publishing
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Cross-referencing tables** - Component tables should be self-contained
|
||||
2. **Missing type exports** - Export all necessary types
|
||||
3. **Hardcoded configuration** - Use component options for customization
|
||||
4. **No versioning** - Follow semantic versioning
|
||||
5. **Poor documentation** - Document all public APIs
|
||||
|
||||
## References
|
||||
|
||||
- Convex Documentation: https://docs.convex.dev/
|
||||
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
|
||||
- Components: https://docs.convex.dev/components
|
||||
- Component Authoring: https://docs.convex.dev/components/authoring
|
||||
Reference in New Issue
Block a user