{"id":"convex-component-authoring","name":"convex-component-authoring","summary":"適切な分離、エクスポート、依存関係管理を用いて、自己完結型のConvexコンポーネントを作成、構造化、公開する方法","body":"# 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://docs.convex.dev/components\n- Component Authoring: https://docs.convex.dev/components/authoring\n- For broader context: https://docs.convex.dev/llms.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/\n├── package.json\n├── tsconfig.json\n├── README.md\n├── src/\n│   ├── index.ts           # Main exports\n│   ├── component.ts       # Component definition\n│   ├── schema.ts          # Component schema\n│   └── functions/\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// convex.config.ts\nimport { defineComponent } from \"convex/server\";\n\nexport default defineComponent(\"myComponent\");\n```\n\n#### 2. Component Schema\n\n```typescript\n// src/schema.ts\nimport { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSchema({\n  // 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// src/component.ts\nimport { defineComponent, ComponentDefinition } from \"convex/server\";\nimport schema from \"./schema\";\nimport * as queries from \"./functions/queries\";\nimport * as mutations from \"./functions/mutations\";\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// src/functions/queries.ts\nimport { query } from \"../_generated/server\";\nimport { v } from \"convex/values\";\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// src/functions/mutations.ts\nimport { mutation } from \"../_generated/server\";\nimport { v } from \"convex/values\";\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// src/index.ts\nexport { default as component } from \"./component\";\nexport * from \"./functions/queries\";\nexport * from \"./functions/mutations\";\n\n// Export types for consumers\nexport type { Id } from \"./_generated/dataModel\";\n```\n\n### Using a Component\n\n```typescript\n// In the consuming app's convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\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// In the consuming app's code\nimport { useQuery, useMutation } from \"convex/react\";\nimport { api } from \"../convex/_generated/api\";\n\nfunction MyApp() {\n  // 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    <div>\n      {items?.map((item) => (\n        <div key={item._id}>{item.name}</div>\n      ))}\n      <button onClick={() => createItem({ name: \"New\", data: {} })}>\n        Add Item\n      </button>\n    </div>\n  );\n}\n```\n\n### Component Configuration Options\n\n```typescript\n// convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\nimport myComponent from \"my-convex-component\";\n\nconst app = defineApp();\n\n// Basic usage\napp.use(myComponent);\n\n// With custom name\napp.use(myComponent, { name: \"customName\" });\n\n// 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// src/hooks.ts\nimport { useQuery, useMutation } from \"convex/react\";\nimport { FunctionReference } from \"convex/server\";\n\n// Type-safe hooks for component consumers\nexport function useMyComponent(api: {\n  list: FunctionReference<\"query\">;\n  create: FunctionReference<\"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/index.js\",\n  \"types\": \"dist/index.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/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\n## Examples\n\n### Rate Limiter Component\n\n```typescript\n// rate-limiter/src/schema.ts\nimport { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\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// rate-limiter/src/functions/mutations.ts\nimport { mutation } from \"../_generated/server\";\nimport { v } from \"convex/values\";\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    // 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    // 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// Usage in consuming app\nimport { useMutation } from \"convex/react\";\nimport { api } from \"../convex/_generated/api\";\n\nfunction useRateLimitedAction() {\n  const checkLimit = useMutation(api.rateLimiter.checkLimit);\n  \n  return async (action: () => Promise<void>) => {\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://docs.convex.dev/\n- Convex LLMs.txt: https://docs.convex.dev/llms.txt\n- Components: https://docs.convex.dev/components\n- Component Authoring: https://docs.convex.dev/components/authoring","author":"@waynesutton","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/waynesutton/convexskills/tree/main/skills/convex-component-authoring","license":"Apache-2.0","category":"document","lang":"en","tokens":2577,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"agents/openai.yaml","size":91,"sha256":"bb57e6929f0916464111ae4a5e0a2ec5d301653b5a3b818a81dc2c0a7deb21c2"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["docs.convex.dev"]}}