ワンクリックで
source-command-convex-schema-builder
Design Convex database schemas with proper validation, indexes, and relationship patterns
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Design Convex database schemas with proper validation, indexes, and relationship patterns
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows.
Guide to using Convex components for feature encapsulation. Learn about sibling components, creating your own, and when to use components vs monolithic code.
Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.
Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.
Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.
| name | source-command-convex-schema-builder |
| description | Design Convex database schemas with proper validation, indexes, and relationship patterns |
Use this skill when the user asks to run the migrated source command convex-schema-builder.
Design schemas for the Convex document-relational database. Schemas are defined in convex/schema.ts.
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tableName: defineTable({
field: v.string(),
}).index("by_field", ["field"]),
});
// Primitives
v.string(); // string
v.number(); // number (use for timestamps, floats)
v.boolean(); // boolean
v.int64(); // 64-bit integer (NOT v.bigint())
v.null(); // null
v.bytes(); // ArrayBuffer
// Complex
v.id("tableName"); // Document ID reference
v.array(v.string()); // Array of strings
v.object({ key: v.string() }); // Typed object
v.optional(v.string()); // Optional field
v.union(v.literal("a"), v.literal("b")); // Enum / union type
v.record(v.string(), v.number()); // Dynamic keys (map)
v.any(); // Any type (avoid if possible)
From convex-helpers/validators:
import { nullable, literals, partial } from "convex-helpers/validators";
nullable(v.string()); // v.union(v.string(), v.null())
literals("a", "b", "c"); // v.union(v.literal("a"), ...)
partial(myObjectValidator); // All fields become optional
Store the foreign key on the "many" side with an index:
// One user has many tasks
users: defineTable({
name: v.string(),
email: v.string(),
}),
tasks: defineTable({
userId: v.id("users"), // Foreign key
title: v.string(),
completed: v.boolean(),
})
.index("by_userId", ["userId"]),
users: defineTable({
name: v.string(),
}),
teams: defineTable({
name: v.string(),
}),
// Junction table
teamMembers: defineTable({
userId: v.id("users"),
teamId: v.id("teams"),
role: v.union(v.literal("member"), v.literal("admin")),
joinedAt: v.number(),
})
.index("by_userId", ["userId"])
.index("by_teamId", ["teamId"])
.index("by_teamId_and_userId", ["teamId", "userId"]),
categories: defineTable({
name: v.string(),
parentId: v.optional(v.id("categories")), // Null for root
depth: v.number(),
})
.index("by_parentId", ["parentId"]),
Rules:
by_fieldA_and_fieldBby_userId_and_status covers queries on just userId)Common patterns:
tasks: defineTable({
userId: v.id("users"),
status: v.union(v.literal("active"), v.literal("completed")),
priority: v.number(),
createdAt: v.number(),
})
// Query tasks by user
.index("by_userId", ["userId"])
// Query tasks by user AND status (also covers by_userId queries)
.index("by_userId_and_status", ["userId", "status"])
// Query tasks by status across all users
.index("by_status", ["status"]),
Using indexes in queries:
// Single field
await ctx.db
.query("tasks")
.withIndex("by_userId", (q) => q.eq("userId", userId))
.collect();
// Compound index
await ctx.db
.query("tasks")
.withIndex("by_userId_and_status", (q) =>
q.eq("userId", userId).eq("status", "active"),
)
.collect();
// Range query on last index field
await ctx.db
.query("tasks")
.withIndex("by_userId_and_status", (q) =>
q.eq("userId", userId).gte("status", "a"),
)
.collect();
v.number() with Date.now(), not date stringsv.union(v.literal(...)) patternv.optional() for fields that may not existBad - deeply nested:
users: defineTable({
posts: v.array(
v.object({
comments: v.array(
v.object({
text: v.string(),
replies: v.array(v.object({ text: v.string() })),
}),
),
}),
),
});
Good - flat with relationships:
users: defineTable({ name: v.string() }),
posts: defineTable({ userId: v.id("users"), text: v.string() })
.index("by_userId", ["userId"]),
comments: defineTable({ postId: v.id("posts"), userId: v.id("users"), text: v.string() })
.index("by_postId", ["postId"]),
convex/schema.tsby_fieldA_and_fieldBv.int64() not v.bigint()npx convex codegen after changes