Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 60/100
convex-component-authoring
Convexコンポーネントを自己完結型として作成、構成、公開する方法について説明します。適切な分離、エクスポート、依存関係管理を実装することで、再利用可能で保守性の高いコンポーネントを開発できます。このスキルを習得すると、複数のプロジェクト間でコンポーネントを共有し、チーム内での開発効率を向上させることが可能になります。
description の原文を見る
How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management
SKILL.md 本文
Convex コンポーネント オーサリング
プロジェクト間での共有に向けた、適切な分離、エクスポート、および依存関係管理を備えた自己完結型の再利用可能な Convex コンポーネントを作成します。
ドキュメント ソース
実装する前に、仮定せず最新のドキュメントを取得してください:
- プライマリ: https://docs.convex.dev/components
- コンポーネント オーサリング: https://docs.convex.dev/components/authoring
- より広いコンテキスト: https://docs.convex.dev/llms.txt
説明
Convex コンポーネントとは
Convex コンポーネントは、以下を含む自己完結型パッケージです:
- データベース テーブル(メイン アプリから分離)
- 関数(クエリ、ミューテーション、アクション)
- TypeScript 型およびバリデーター
- オプションのフロントエンド フック
コンポーネント構造
my-convex-component/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ ├── index.ts # メイン エクスポート
│ ├── component.ts # コンポーネント定義
│ ├── schema.ts # コンポーネント スキーマ
│ └── functions/
│ ├── queries.ts
│ ├── mutations.ts
│ └── actions.ts
└── convex.config.ts # コンポーネント設定
コンポーネントの作成
1. コンポーネント設定
// convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("myComponent");
2. コンポーネント スキーマ
// src/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
// テーブルはこのコンポーネントに分離されています
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. コンポーネント定義
// 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. コンポーネント関数
// 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();
},
});
// 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. メイン エクスポート
// src/index.ts
export { default as component } from "./component";
export * from "./functions/queries";
export * from "./functions/mutations";
// コンシューマー用に型をエクスポート
export type { Id } from "./_generated/dataModel";
コンポーネントの使用
// コンシューマー アプリの 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;
// コンシューマー アプリのコード内
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
function MyApp() {
// アプリの 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: {} })}>
アイテムを追加
</button>
</div>
);
}
コンポーネント設定オプション
// convex/convex.config.ts
import { defineApp } from "convex/server";
import myComponent from "my-convex-component";
const app = defineApp();
// 基本的な使用法
app.use(myComponent);
// カスタム名を指定
app.use(myComponent, { name: "customName" });
// 複数のインスタンス
app.use(myComponent, { name: "instance1" });
app.use(myComponent, { name: "instance2" });
export default app;
コンポーネント フックの提供
// src/hooks.ts
import { useQuery, useMutation } from "convex/react";
import { FunctionReference } from "convex/server";
// コンポーネント コンシューマー用のタイプセーフなフック
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,
};
}
コンポーネントの公開
package.json
{
"name": "my-convex-component",
"version": "1.0.0",
"description": "再利用可能な Convex コンポーネント",
"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
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"declaration": true,
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
例
レート リミッター コンポーネント
// 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"]),
});
// 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;
// 古いエントリをクリーンアップ
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);
}
// 現在のウィンドウをカウント
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 };
},
});
// コンシューマー アプリでの使用
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(`レート制限されました。${new Date(result.resetAt)} に再試行してください`);
}
await action();
};
}
ベストプラクティス
- 明示的に指示されない限り、
npx convex deployを実行しないでください - 明示的に指示されない限り、git コマンドを実行しないでください
- コンポーネント テーブルを分離した状態に保つ(メイン アプリ テーブルを参照しない)
- コンシューマー向けの明確な TypeScript 型をエクスポート
- すべてのパブリック関数とその引数をドキュメント化
- コンポーネント リリースにはセマンティック バージョニングを使用
- 例を含む包括的な README を含める
- 公開前にコンポーネントを分離して テストする
よくある落とし穴
- テーブル間の相互参照 - コンポーネント テーブルは自己完結型である必要があります
- 型のエクスポート漏れ - すべての必要な型をエクスポート
- ハードコードされた設定 - コンポーネント オプションを使用してカスタマイズ
- バージョニング なし - セマンティック バージョニングに従う
- ドキュメント不足 - すべてのパブリック API をドキュメント化
参照
- Convex ドキュメント: https://docs.convex.dev/
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
- コンポーネント: https://docs.convex.dev/components
- コンポーネント オーサリング: https://docs.convex.dev/components/authoring
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- aurokin
- リポジトリ
- aurokin/agentchat
- ライセンス
- MIT
- 最終更新
- 2026/5/11
Source: https://github.com/aurokin/agentchat / ライセンス: MIT