convex-actions
Best practices for Convex actions, transactions, and scheduling. Use when writing actions that call external APIs, using ctx.runQuery/ctx.runMutation, scheduling functions with ctx.scheduler, or working with the Convex runtime vs Node.js runtime ("use node").
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o convex-actions.zip https://jpskill.com/download/8727.zip && unzip -o convex-actions.zip && rm convex-actions.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/8727.zip -OutFile "$d\convex-actions.zip"; Expand-Archive "$d\convex-actions.zip" -DestinationPath $d -Force; ri "$d\convex-actions.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
convex-actions.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
convex-actionsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Claude が読む原文 SKILL.md(中身を展開)
この本文は AI(Claude)が読むための原文(英語または中国語)です。日本語訳は順次追加中。
Convex Actions
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 |
Actions with Node.js Runtime
Add "use node"; at the top of files using Node.js APIs:
// convex/email.ts
"use node";
import { action, internalAction } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const sendEmail = action({
args: { to: v.string(), subject: v.string(), body: v.string() },
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey) throw new Error("RESEND_API_KEY not configured");
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ from: "noreply@example.com", ...args }),
});
return { success: response.ok };
},
});
Scheduling Functions
Use ctx.scheduler.runAfter to schedule functions:
export const createTask = mutation({
args: { title: v.string(), userId: v.id("users") },
returns: v.id("tasks"),
handler: async (ctx, args) => {
const taskId = await ctx.db.insert("tasks", {
title: args.title,
userId: args.userId,
status: "pending",
});
// Schedule processing (always use internal functions!)
await ctx.scheduler.runAfter(0, internal.tasks.processTask, { taskId });
return taskId;
},
});
// Internal function for scheduled work
export const processTask = internalMutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.patch("tasks", args.taskId, { status: "processing" });
// ... processing logic
return null;
},
});
Use runAction Only When Changing Runtime
Replace runAction with plain TypeScript functions unless switching runtimes:
// Bad - unnecessary runAction overhead
await ctx.runAction(internal.scrape.scrapePage, { url });
// Good - plain TypeScript function
import * as Scrape from './model/scrape';
await Scrape.scrapePage(ctx, { url });
Avoid Sequential ctx.runMutation / ctx.runQuery
Each call runs in its own transaction. Combine for consistency:
// Bad - inconsistent reads
const team = await ctx.runQuery(internal.teams.getTeam, { teamId });
const owner = await ctx.runQuery(internal.teams.getOwner, { teamId });
// Good - single consistent query
const { team, owner } = await ctx.runQuery(internal.teams.getTeamAndOwner, { teamId });
// Bad - non-atomic loop
for (const user of users) {
await ctx.runMutation(internal.users.insert, user);
}
// Good - atomic batch
await ctx.runMutation(internal.users.insertMany, { users });
Exceptions: Migrations, aggregations, or when side effects occur between calls.
Prefer Helper Functions in Queries/Mutations
Use plain TypeScript instead of ctx.runQuery/ctx.runMutation:
// Good - plain helper
import * as Users from './model/users';
const user = await Users.getCurrentUser(ctx);
// Bad - unnecessary overhead
const user = await ctx.runQuery(api.users.getCurrentUser);
Exception: Partial rollback needs ctx.runMutation:
try {
await ctx.runMutation(internal.orders.process, { orderId });
} catch (e) {
// Rollback process, record failure
await ctx.db.insert("failures", { orderId, error: `${e}` });
}
Await All Promises
Always await async operations:
// Bad - missing await
ctx.scheduler.runAfter(0, internal.tasks.process, { id });
ctx.db.patch("tasks", docId, { status: "done" });
// Good - awaited
await ctx.scheduler.runAfter(0, internal.tasks.process, { id });
await ctx.db.patch("tasks", docId, { status: "done" });
ESLint: Use no-floating-promises rule.
Complete Action Example
// convex/payments.ts
"use node";
import { action, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const processPayment = action({
args: { orderId: v.id("orders"), amount: v.number() },
returns: v.object({ success: v.boolean(), transactionId: v.optional(v.string()) }),
handler: async (ctx, args) => {
// 1. Read data via query
const order = await ctx.runQuery(internal.orders.get, { orderId: args.orderId });
if (!order) throw new Error("Order not found");
// 2. Call external API
const result = await fetch("https://api.stripe.com/v1/charges", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.STRIPE_KEY}` },
body: new URLSearchParams({ amount: String(args.amount * 100), currency: "usd" }),
});
const data = await result.json();
// 3. Update database via mutation
await ctx.runMutation(internal.orders.updateStatus, {
orderId: args.orderId,
status: data.status === "succeeded" ? "paid" : "failed",
transactionId: data.id,
});
return { success: data.status === "succeeded", transactionId: data.id };
},
});