[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6X75BW578SW1TVP11RBT6DP":3,"skill-same:01M6X75BW578SW1TVP11RBT6DP":80},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":58,"commitSha":59,"skillDirectory":60,"forksCount":61,"repositoryHtmlUrl":62,"repositoryHomepageUrl":6,"ownerHtmlUrl":63,"ownerBlogUrl":64,"ownerDescription":65,"updatedAt":66,"install":67,"resources":79},"01M6X75BW578SW1TVP11RBT6DP","convex-component-authoring",null,"How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management","Cheggin\u002Frequest-for-startups","Cheggin","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F79483996?v=4","01M6X6MGT5512GJHE8DY5B1ZQJ","Reagan Hsu","01M6X6MGY03X7ZJNVRFM3R1040",1,"GPL-3.0","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fconvex-component-authoring\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"component authoring","component","authoring","convex component authoring","convex","dependency management","how to","isolation","full","self","structure","dependency","how","security-audit","publish","audit","create","packages","components","security","2026-11-10 10:12:30",false,"---\nname: convex-component-authoring\ndisplayName: Convex Component Authoring\ndescription: How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management\nuser-invocable: true\nversion: 1.0.0\nauthor: Convex\ntags: [convex, components, reusable, packages, npm]\ngroup: build\nprerequisites: [convex-best-practices]\nnext: [convex-security-audit]\nworkflows: [full-startup]\n---\n\n# Convex Component Authoring\n\nCreate self-contained, reusable Convex components with proper isolation, exports, and dependency management for sharing across projects.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\n- Component Authoring: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\u002Fauthoring\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### What Are Convex Components?\n\nConvex components are self-contained packages that include:\n- Database tables (isolated from the main app)\n- Functions (queries, mutations, actions)\n- TypeScript types and validators\n- Optional frontend hooks\n\n### Component Structure\n\n```\nmy-convex-component\u002F\n├── package.json\n├── tsconfig.json\n├── README.md\n├── src\u002F\n│   ├── index.ts           # Main exports\n│   ├── component.ts       # Component definition\n│   ├── schema.ts          # Component schema\n│   └── functions\u002F\n│       ├── queries.ts\n│       ├── mutations.ts\n│       └── actions.ts\n└── convex.config.ts       # Component configuration\n```\n\n### Creating a Component\n\n#### 1. Component Configuration\n\n```typescript\n\u002F\u002F convex.config.ts\nimport { defineComponent } from \"convex\u002Fserver\";\n\nexport default defineComponent(\"myComponent\");\n```\n\n#### 2. Component Schema\n\n```typescript\n\u002F\u002F src\u002Fschema.ts\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  \u002F\u002F Tables are isolated to this component\n  items: defineTable({\n    name: v.string(),\n    data: v.any(),\n    createdAt: v.number(),\n  }).index(\"by_name\", [\"name\"]),\n  \n  config: defineTable({\n    key: v.string(),\n    value: v.any(),\n  }).index(\"by_key\", [\"key\"]),\n});\n```\n\n#### 3. Component Definition\n\n```typescript\n\u002F\u002F src\u002Fcomponent.ts\nimport { defineComponent, ComponentDefinition } from \"convex\u002Fserver\";\nimport schema from \".\u002Fschema\";\nimport * as queries from \".\u002Ffunctions\u002Fqueries\";\nimport * as mutations from \".\u002Ffunctions\u002Fmutations\";\n\nconst component = defineComponent(\"myComponent\", {\n  schema,\n  functions: {\n    ...queries,\n    ...mutations,\n  },\n});\n\nexport default component;\n```\n\n#### 4. Component Functions\n\n```typescript\n\u002F\u002F src\u002Ffunctions\u002Fqueries.ts\nimport { query } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const list = query({\n  args: {\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(v.object({\n    _id: v.id(\"items\"),\n    name: v.string(),\n    data: v.any(),\n    createdAt: v.number(),\n  })),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"items\")\n      .order(\"desc\")\n      .take(args.limit ?? 10);\n  },\n});\n\nexport const get = query({\n  args: { name: v.string() },\n  returns: v.union(v.object({\n    _id: v.id(\"items\"),\n    name: v.string(),\n    data: v.any(),\n  }), v.null()),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"items\")\n      .withIndex(\"by_name\", (q) => q.eq(\"name\", args.name))\n      .unique();\n  },\n});\n```\n\n```typescript\n\u002F\u002F src\u002Ffunctions\u002Fmutations.ts\nimport { mutation } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const create = mutation({\n  args: {\n    name: v.string(),\n    data: v.any(),\n  },\n  returns: v.id(\"items\"),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert(\"items\", {\n      name: args.name,\n      data: args.data,\n      createdAt: Date.now(),\n    });\n  },\n});\n\nexport const update = mutation({\n  args: {\n    id: v.id(\"items\"),\n    data: v.any(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.id, { data: args.data });\n    return null;\n  },\n});\n\nexport const remove = mutation({\n  args: { id: v.id(\"items\") },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.id);\n    return null;\n  },\n});\n```\n\n#### 5. Main Exports\n\n```typescript\n\u002F\u002F src\u002Findex.ts\nexport { default as component } from \".\u002Fcomponent\";\nexport * from \".\u002Ffunctions\u002Fqueries\";\nexport * from \".\u002Ffunctions\u002Fmutations\";\n\n\u002F\u002F Export types for consumers\nexport type { Id } from \".\u002F_generated\u002FdataModel\";\n```\n\n### Using a Component\n\n```typescript\n\u002F\u002F In the consuming app's convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport myComponent from \"my-convex-component\";\n\nconst app = defineApp();\n\napp.use(myComponent, { name: \"myComponent\" });\n\nexport default app;\n```\n\n```typescript\n\u002F\u002F In the consuming app's code\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction MyApp() {\n  \u002F\u002F Access component functions through the app's API\n  const items = useQuery(api.myComponent.list, { limit: 10 });\n  const createItem = useMutation(api.myComponent.create);\n  \n  return (\n    \u003Cdiv>\n      {items?.map((item) => (\n        \u003Cdiv key={item._id}>{item.name}\u003C\u002Fdiv>\n      ))}\n      \u003Cbutton onClick={() => createItem({ name: \"New\", data: {} })}>\n        Add Item\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Component Configuration Options\n\n```typescript\n\u002F\u002F convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport myComponent from \"my-convex-component\";\n\nconst app = defineApp();\n\n\u002F\u002F Basic usage\napp.use(myComponent);\n\n\u002F\u002F With custom name\napp.use(myComponent, { name: \"customName\" });\n\n\u002F\u002F Multiple instances\napp.use(myComponent, { name: \"instance1\" });\napp.use(myComponent, { name: \"instance2\" });\n\nexport default app;\n```\n\n### Providing Component Hooks\n\n```typescript\n\u002F\u002F src\u002Fhooks.ts\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { FunctionReference } from \"convex\u002Fserver\";\n\n\u002F\u002F Type-safe hooks for component consumers\nexport function useMyComponent(api: {\n  list: FunctionReference\u003C\"query\">;\n  create: FunctionReference\u003C\"mutation\">;\n}) {\n  const items = useQuery(api.list, {});\n  const createItem = useMutation(api.create);\n  \n  return {\n    items,\n    createItem,\n    isLoading: items === undefined,\n  };\n}\n```\n\n### Publishing a Component\n\n#### package.json\n\n```json\n{\n  \"name\": \"my-convex-component\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A reusable Convex component\",\n  \"main\": \"dist\u002Findex.js\",\n  \"types\": \"dist\u002Findex.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"convex.config.ts\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"peerDependencies\": {\n    \"convex\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"convex\": \"^1.17.0\",\n    \"typescript\": \"^5.0.0\"\n  },\n  \"keywords\": [\n    \"convex\",\n    \"component\"\n  ]\n}\n```\n\n#### tsconfig.json\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"declaration\": true,\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\u002F**\u002F*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\n## Examples\n\n### Rate Limiter Component\n\n```typescript\n\u002F\u002F rate-limiter\u002Fsrc\u002Fschema.ts\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  requests: defineTable({\n    key: v.string(),\n    timestamp: v.number(),\n  })\n    .index(\"by_key\", [\"key\"])\n    .index(\"by_key_and_time\", [\"key\", \"timestamp\"]),\n});\n```\n\n```typescript\n\u002F\u002F rate-limiter\u002Fsrc\u002Ffunctions\u002Fmutations.ts\nimport { mutation } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const checkLimit = mutation({\n  args: {\n    key: v.string(),\n    limit: v.number(),\n    windowMs: v.number(),\n  },\n  returns: v.object({\n    allowed: v.boolean(),\n    remaining: v.number(),\n    resetAt: v.number(),\n  }),\n  handler: async (ctx, args) => {\n    const now = Date.now();\n    const windowStart = now - args.windowMs;\n    \n    \u002F\u002F Clean old entries\n    const oldEntries = await ctx.db\n      .query(\"requests\")\n      .withIndex(\"by_key_and_time\", (q) => \n        q.eq(\"key\", args.key).lt(\"timestamp\", windowStart)\n      )\n      .collect();\n    \n    for (const entry of oldEntries) {\n      await ctx.db.delete(entry._id);\n    }\n    \n    \u002F\u002F Count current window\n    const currentRequests = await ctx.db\n      .query(\"requests\")\n      .withIndex(\"by_key\", (q) => q.eq(\"key\", args.key))\n      .collect();\n    \n    const remaining = Math.max(0, args.limit - currentRequests.length);\n    const allowed = remaining > 0;\n    \n    if (allowed) {\n      await ctx.db.insert(\"requests\", {\n        key: args.key,\n        timestamp: now,\n      });\n    }\n    \n    const oldestRequest = currentRequests[0];\n    const resetAt = oldestRequest \n      ? oldestRequest.timestamp + args.windowMs \n      : now + args.windowMs;\n    \n    return { allowed, remaining: remaining - (allowed ? 1 : 0), resetAt };\n  },\n});\n```\n\n```typescript\n\u002F\u002F Usage in consuming app\nimport { useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction useRateLimitedAction() {\n  const checkLimit = useMutation(api.rateLimiter.checkLimit);\n  \n  return async (action: () => Promise\u003Cvoid>) => {\n    const result = await checkLimit({\n      key: \"user-action\",\n      limit: 10,\n      windowMs: 60000,\n    });\n    \n    if (!result.allowed) {\n      throw new Error(`Rate limited. Try again at ${new Date(result.resetAt)}`);\n    }\n    \n    await action();\n  };\n}\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Keep component tables isolated (don't reference main app tables)\n- Export clear TypeScript types for consumers\n- Document all public functions and their arguments\n- Use semantic versioning for component releases\n- Include comprehensive README with examples\n- Test components in isolation before publishing\n\n## Common Pitfalls\n\n1. **Cross-referencing tables** - Component tables should be self-contained\n2. **Missing type exports** - Export all necessary types\n3. **Hardcoded configuration** - Use component options for customization\n4. **No versioning** - Follow semantic versioning\n5. **Poor documentation** - Document all public APIs\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Components: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\n- Component Authoring: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\u002Fauthoring\n","# Convex Component Authoring\n\nCreate self-contained, reusable Convex components with proper isolation, exports, and dependency management for sharing across projects.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\n- Component Authoring: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\u002Fauthoring\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### What Are Convex Components?\n\nConvex components are self-contained packages that include:\n- Database tables (isolated from the main app)\n- Functions (queries, mutations, actions)\n- TypeScript types and validators\n- Optional frontend hooks\n\n### Component Structure\n\n```\nmy-convex-component\u002F\n├── package.json\n├── tsconfig.json\n├── README.md\n├── src\u002F\n│   ├── index.ts           # Main exports\n│   ├── component.ts       # Component definition\n│   ├── schema.ts          # Component schema\n│   └── functions\u002F\n│       ├── queries.ts\n│       ├── mutations.ts\n│       └── actions.ts\n└── convex.config.ts       # Component configuration\n```\n\n### Creating a Component\n\n#### 1. Component Configuration\n\n```typescript\n\u002F\u002F convex.config.ts\nimport { defineComponent } from \"convex\u002Fserver\";\n\nexport default defineComponent(\"myComponent\");\n```\n\n#### 2. Component Schema\n\n```typescript\n\u002F\u002F src\u002Fschema.ts\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  \u002F\u002F Tables are isolated to this component\n  items: defineTable({\n    name: v.string(),\n    data: v.any(),\n    createdAt: v.number(),\n  }).index(\"by_name\", [\"name\"]),\n  \n  config: defineTable({\n    key: v.string(),\n    value: v.any(),\n  }).index(\"by_key\", [\"key\"]),\n});\n```\n\n#### 3. Component Definition\n\n```typescript\n\u002F\u002F src\u002Fcomponent.ts\nimport { defineComponent, ComponentDefinition } from \"convex\u002Fserver\";\nimport schema from \".\u002Fschema\";\nimport * as queries from \".\u002Ffunctions\u002Fqueries\";\nimport * as mutations from \".\u002Ffunctions\u002Fmutations\";\n\nconst component = defineComponent(\"myComponent\", {\n  schema,\n  functions: {\n    ...queries,\n    ...mutations,\n  },\n});\n\nexport default component;\n```\n\n#### 4. Component Functions\n\n```typescript\n\u002F\u002F src\u002Ffunctions\u002Fqueries.ts\nimport { query } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const list = query({\n  args: {\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(v.object({\n    _id: v.id(\"items\"),\n    name: v.string(),\n    data: v.any(),\n    createdAt: v.number(),\n  })),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"items\")\n      .order(\"desc\")\n      .take(args.limit ?? 10);\n  },\n});\n\nexport const get = query({\n  args: { name: v.string() },\n  returns: v.union(v.object({\n    _id: v.id(\"items\"),\n    name: v.string(),\n    data: v.any(),\n  }), v.null()),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"items\")\n      .withIndex(\"by_name\", (q) => q.eq(\"name\", args.name))\n      .unique();\n  },\n});\n```\n\n```typescript\n\u002F\u002F src\u002Ffunctions\u002Fmutations.ts\nimport { mutation } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const create = mutation({\n  args: {\n    name: v.string(),\n    data: v.any(),\n  },\n  returns: v.id(\"items\"),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert(\"items\", {\n      name: args.name,\n      data: args.data,\n      createdAt: Date.now(),\n    });\n  },\n});\n\nexport const update = mutation({\n  args: {\n    id: v.id(\"items\"),\n    data: v.any(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.id, { data: args.data });\n    return null;\n  },\n});\n\nexport const remove = mutation({\n  args: { id: v.id(\"items\") },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.id);\n    return null;\n  },\n});\n```\n\n#### 5. Main Exports\n\n```typescript\n\u002F\u002F src\u002Findex.ts\nexport { default as component } from \".\u002Fcomponent\";\nexport * from \".\u002Ffunctions\u002Fqueries\";\nexport * from \".\u002Ffunctions\u002Fmutations\";\n\n\u002F\u002F Export types for consumers\nexport type { Id } from \".\u002F_generated\u002FdataModel\";\n```\n\n### Using a Component\n\n```typescript\n\u002F\u002F In the consuming app's convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport myComponent from \"my-convex-component\";\n\nconst app = defineApp();\n\napp.use(myComponent, { name: \"myComponent\" });\n\nexport default app;\n```\n\n```typescript\n\u002F\u002F In the consuming app's code\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction MyApp() {\n  \u002F\u002F Access component functions through the app's API\n  const items = useQuery(api.myComponent.list, { limit: 10 });\n  const createItem = useMutation(api.myComponent.create);\n  \n  return (\n    \u003Cdiv>\n      {items?.map((item) => (\n        \u003Cdiv key={item._id}>{item.name}\u003C\u002Fdiv>\n      ))}\n      \u003Cbutton onClick={() => createItem({ name: \"New\", data: {} })}>\n        Add Item\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Component Configuration Options\n\n```typescript\n\u002F\u002F convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport myComponent from \"my-convex-component\";\n\nconst app = defineApp();\n\n\u002F\u002F Basic usage\napp.use(myComponent);\n\n\u002F\u002F With custom name\napp.use(myComponent, { name: \"customName\" });\n\n\u002F\u002F Multiple instances\napp.use(myComponent, { name: \"instance1\" });\napp.use(myComponent, { name: \"instance2\" });\n\nexport default app;\n```\n\n### Providing Component Hooks\n\n```typescript\n\u002F\u002F src\u002Fhooks.ts\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { FunctionReference } from \"convex\u002Fserver\";\n\n\u002F\u002F Type-safe hooks for component consumers\nexport function useMyComponent(api: {\n  list: FunctionReference\u003C\"query\">;\n  create: FunctionReference\u003C\"mutation\">;\n}) {\n  const items = useQuery(api.list, {});\n  const createItem = useMutation(api.create);\n  \n  return {\n    items,\n    createItem,\n    isLoading: items === undefined,\n  };\n}\n```\n\n### Publishing a Component\n\n#### package.json\n\n```json\n{\n  \"name\": \"my-convex-component\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A reusable Convex component\",\n  \"main\": \"dist\u002Findex.js\",\n  \"types\": \"dist\u002Findex.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"convex.config.ts\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"peerDependencies\": {\n    \"convex\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"convex\": \"^1.17.0\",\n    \"typescript\": \"^5.0.0\"\n  },\n  \"keywords\": [\n    \"convex\",\n    \"component\"\n  ]\n}\n```\n\n#### tsconfig.json\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"declaration\": true,\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\u002F**\u002F*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\n## Examples\n\n### Rate Limiter Component\n\n```typescript\n\u002F\u002F rate-limiter\u002Fsrc\u002Fschema.ts\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  requests: defineTable({\n    key: v.string(),\n    timestamp: v.number(),\n  })\n    .index(\"by_key\", [\"key\"])\n    .index(\"by_key_and_time\", [\"key\", \"timestamp\"]),\n});\n```\n\n```typescript\n\u002F\u002F rate-limiter\u002Fsrc\u002Ffunctions\u002Fmutations.ts\nimport { mutation } from \"..\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const checkLimit = mutation({\n  args: {\n    key: v.string(),\n    limit: v.number(),\n    windowMs: v.number(),\n  },\n  returns: v.object({\n    allowed: v.boolean(),\n    remaining: v.number(),\n    resetAt: v.number(),\n  }),\n  handler: async (ctx, args) => {\n    const now = Date.now();\n    const windowStart = now - args.windowMs;\n    \n    \u002F\u002F Clean old entries\n    const oldEntries = await ctx.db\n      .query(\"requests\")\n      .withIndex(\"by_key_and_time\", (q) => \n        q.eq(\"key\", args.key).lt(\"timestamp\", windowStart)\n      )\n      .collect();\n    \n    for (const entry of oldEntries) {\n      await ctx.db.delete(entry._id);\n    }\n    \n    \u002F\u002F Count current window\n    const currentRequests = await ctx.db\n      .query(\"requests\")\n      .withIndex(\"by_key\", (q) => q.eq(\"key\", args.key))\n      .collect();\n    \n    const remaining = Math.max(0, args.limit - currentRequests.length);\n    const allowed = remaining > 0;\n    \n    if (allowed) {\n      await ctx.db.insert(\"requests\", {\n        key: args.key,\n        timestamp: now,\n      });\n    }\n    \n    const oldestRequest = currentRequests[0];\n    const resetAt = oldestRequest \n      ? oldestRequest.timestamp + args.windowMs \n      : now + args.windowMs;\n    \n    return { allowed, remaining: remaining - (allowed ? 1 : 0), resetAt };\n  },\n});\n```\n\n```typescript\n\u002F\u002F Usage in consuming app\nimport { useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction useRateLimitedAction() {\n  const checkLimit = useMutation(api.rateLimiter.checkLimit);\n  \n  return async (action: () => Promise\u003Cvoid>) => {\n    const result = await checkLimit({\n      key: \"user-action\",\n      limit: 10,\n      windowMs: 60000,\n    });\n    \n    if (!result.allowed) {\n      throw new Error(`Rate limited. Try again at ${new Date(result.resetAt)}`);\n    }\n    \n    await action();\n  };\n}\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Keep component tables isolated (don't reference main app tables)\n- Export clear TypeScript types for consumers\n- Document all public functions and their arguments\n- Use semantic versioning for component releases\n- Include comprehensive README with examples\n- Test components in isolation before publishing\n\n## Common Pitfalls\n\n1. **Cross-referencing tables** - Component tables should be self-contained\n2. **Missing type exports** - Export all necessary types\n3. **Hardcoded configuration** - Use component options for customization\n4. **No versioning** - Follow semantic versioning\n5. **Poor documentation** - Document all public APIs\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Components: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\n- Component Authoring: https:\u002F\u002Fdocs.convex.dev\u002Fcomponents\u002Fauthoring\n",{"name":5,"next":44,"tags":46,"group":49,"author":50,"version":51,"workflows":52,"description":7,"displayName":54,"prerequisites":55,"user-invocable":57},[45],"convex-security-audit",[23,37,47,36,48],"reusable","npm","build","Convex","1.0.0",[53],"full-startup","Convex Component Authoring",[56],"convex-best-practices",true,"main","92dddf7f6e7b55d1424795ce11586b05dba9d8c8","skills\u002Fconvex-component-authoring",3,"https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups","https:\u002F\u002Fgithub.com\u002FCheggin","reaganhsu.com","MTS @datacurve-ai, prev. founding eng @browser-use ","2026-04-24 08:33:47",{"source":8,"skillName":5,"commands":68,"prompt":78,"zipAvailable":57},[69,72,75],{"tool":70,"command":71},"npx","npx skills add Cheggin\u002Frequest-for-startups --skill convex-component-authoring",{"tool":73,"command":74},"bunx","bunx skills add Cheggin\u002Frequest-for-startups --skill convex-component-authoring",{"tool":76,"command":77},"pnpm","pnpm dlx skills add Cheggin\u002Frequest-for-startups --skill convex-component-authoring","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fconvex-component-authoring\u002FSKILL.md",[],[81,105,125,166,181,204],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":39},"01M6X7PHK069RZH25A3RXN5049","writer-memory","Agentic memory system for writers - track characters, relationships, scenes, and themes","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwriter-memory\u002FSKILL.md",[],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104],"memory","writer","writer memory","system","characters","track","for","themes","memory system","scenes","relationships","query","export","status","validate","init","agentic",{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":111,"publishedAt":39},"01M6X7PC3G9228Z323X5YAKTVW","wiki","LLM Wiki — persistent markdown knowledge base that compounds across sessions (Karpathy model)","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwiki\u002FSKILL.md",[],[107,112,113,114,115,116,117,118,119,120,121,122,123,99,124],"wiki lint","knowledge","model","llm wiki","markdown","wiki query","knowledge base","lint","sessions","karpathy","add","across","base",{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":147,"publishedAt":39},"01M6X7P5XFW5C16H3JE4NFQ7MF","website-creation","Build production-quality SaaS websites with opinionated design presets. Use when creating any startup website. The user MUST pick a design style before building. Enforces shadcn\u002Fui, Figma design principles, specific CSS values per style, and anti-AI-writing. Load alongside anti-ai-writing skill. LIGHT MODE ONLY.","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwebsite-creation\u002FSKILL.md",[131,136,139,144],{"categoryNo":132,"categoryName":133,"taxonomyType":134,"taxonomyTypeDesc":135},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":137,"categoryName":138,"taxonomyType":134,"taxonomyTypeDesc":135},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":140,"categoryName":141,"taxonomyType":142,"taxonomyTypeDesc":143},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",{"categoryNo":145,"categoryName":146,"taxonomyType":142,"taxonomyTypeDesc":143},"01JBSEEDCAT000000000000036","内容创作者",[148,149,150,151,152,153,154,27,155,156,157,158,159,160,34,161,162,163,164,165],"website creation","website","creation","when","design style","mode","brand-guidelines","ui","writing","design principles","only","pick","polish","shadcn","figma","css","Anti-AI","design",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":39},"01M6X7P3PCEAKA2SBETXHHTNBA","visual-verdict","Structured visual QA verdict for screenshot-to-reference comparisons","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-verdict\u002FSKILL.md",[],[173,174,175,94,176,177,178,179,180],"visual verdict","verdict","visual","visual qa","structured","reference","screenshot","qa",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":195,"publishedAt":39},"01M6X7NXEWWC4NFT0ZK1RRSGES","visual-qa-pipeline","Screenshot every page with Playwright, feed to visual QA agent for design evaluation, report results to Slack. Use after every build phase to verify UI matches the chosen design preset. Integrates with the Playwright skill and the website-creation preset system.","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-qa-pipeline\u002FSKILL.md",[187,190,193,194],{"categoryNo":188,"categoryName":189,"taxonomyType":134,"taxonomyTypeDesc":135},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":191,"categoryName":192,"taxonomyType":134,"taxonomyTypeDesc":135},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":132,"categoryName":133,"taxonomyType":134,"taxonomyTypeDesc":135},{"categoryNo":140,"categoryName":141,"taxonomyType":142,"taxonomyTypeDesc":143},[175,196,176,180,197,198,127,150,27,199,160,200,155,149,179,94,201,165,202,203],"pipeline","visual qa pipeline","slack","page","Evaluation","review","verify ui","phase",{"skillNo":205,"name":206,"nameZh":6,"description":207,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":208,"categories":209,"keywords":210,"publishedAt":39},"01M6X7NSNW00RQ8J531F52YHV9","verify","Verify that a change really works before you claim completion","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fverify\u002FSKILL.md",[],[206,211,212,213,214,215,196,216,217],"deploy-pipeline","true","you","claim","quality","deploy","change"]