convex-schema-validator

安装量: 1.6K
排名: #966

安装

npx skills add https://github.com/waynesutton/convexskills --skill convex schema validator

Convex Schema Validator

Define and validate database schemas in Convex with proper typing, index configuration, optional fields, unions, and strategies for schema migrations.

Documentation Sources

Before implementing, do not assume; fetch the latest documentation:

Primary: https://docs.convex.dev/database/schemas Indexes: https://docs.convex.dev/database/indexes Data Types: https://docs.convex.dev/database/types For broader context: https://docs.convex.dev/llms.txt Instructions Basic Schema Definition // convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values";

export default defineSchema({ users: defineTable({ name: v.string(), email: v.string(), avatarUrl: v.optional(v.string()), createdAt: v.number(), }),

tasks: defineTable({ title: v.string(), description: v.optional(v.string()), completed: v.boolean(), userId: v.id("users"), priority: v.union( v.literal("low"), v.literal("medium"), v.literal("high") ), }), });

Validator Types Validator TypeScript Type Example v.string() string "hello" v.number() number 42, 3.14 v.boolean() boolean true, false v.null() null null v.int64() bigint 9007199254740993n v.bytes() ArrayBuffer Binary data v.id("table") Id<"table"> Document reference v.array(v) T[] [1, 2, 3] v.object({}) { ... } { name: "..." } v.optional(v) T | undefined Optional field v.union(...) T1 | T2 Multiple types v.literal(x) "x" Exact value v.any() any Any value v.record(k, v) Record Dynamic keys Index Configuration export default defineSchema({ messages: defineTable({ channelId: v.id("channels"), authorId: v.id("users"), content: v.string(), sentAt: v.number(), }) // Single field index .index("by_channel", ["channelId"]) // Compound index .index("by_channel_and_author", ["channelId", "authorId"]) // Index for sorting .index("by_channel_and_time", ["channelId", "sentAt"]),

// Full-text search index articles: defineTable({ title: v.string(), body: v.string(), category: v.string(), }) .searchIndex("search_content", { searchField: "body", filterFields: ["category"], }), });

Complex Types export default defineSchema({ // Nested objects profiles: defineTable({ userId: v.id("users"), settings: v.object({ theme: v.union(v.literal("light"), v.literal("dark")), notifications: v.object({ email: v.boolean(), push: v.boolean(), }), }), }),

// Arrays of objects orders: defineTable({ customerId: v.id("users"), items: v.array(v.object({ productId: v.id("products"), quantity: v.number(), price: v.number(), })), status: v.union( v.literal("pending"), v.literal("processing"), v.literal("shipped"), v.literal("delivered") ), }),

// Record type for dynamic keys analytics: defineTable({ date: v.string(), metrics: v.record(v.string(), v.number()), }), });

Discriminated Unions export default defineSchema({ events: defineTable( v.union( v.object({ type: v.literal("user_signup"), userId: v.id("users"), email: v.string(), }), v.object({ type: v.literal("purchase"), userId: v.id("users"), orderId: v.id("orders"), amount: v.number(), }), v.object({ type: v.literal("page_view"), sessionId: v.string(), path: v.string(), }) ) ).index("by_type", ["type"]), });

Optional vs Nullable Fields export default defineSchema({ items: defineTable({ // Optional: field may not exist description: v.optional(v.string()),

// Nullable: field exists but can be null
deletedAt: v.union(v.number(), v.null()),

// Optional and nullable
notes: v.optional(v.union(v.string(), v.null())),

}), });

Index Naming Convention

Always include all indexed fields in the index name:

export default defineSchema({ posts: defineTable({ authorId: v.id("users"), categoryId: v.id("categories"), publishedAt: v.number(), status: v.string(), }) // Good: descriptive names .index("by_author", ["authorId"]) .index("by_author_and_category", ["authorId", "categoryId"]) .index("by_category_and_status", ["categoryId", "status"]) .index("by_status_and_published", ["status", "publishedAt"]), });

Schema Migration Strategies Adding New Fields // Before users: defineTable({ name: v.string(), email: v.string(), })

// After - add as optional first users: defineTable({ name: v.string(), email: v.string(), avatarUrl: v.optional(v.string()), // New optional field })

Backfilling Data // convex/migrations.ts import { internalMutation } from "./_generated/server"; import { v } from "convex/values";

export const backfillAvatars = internalMutation({ args: {}, returns: v.number(), handler: async (ctx) => { const users = await ctx.db .query("users") .filter((q) => q.eq(q.field("avatarUrl"), undefined)) .take(100);

for (const user of users) {
  await ctx.db.patch(user._id, {
    avatarUrl: `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`,
  });
}

return users.length;

}, });

Making Optional Fields Required // Step 1: Backfill all null values // Step 2: Update schema to required users: defineTable({ name: v.string(), email: v.string(), avatarUrl: v.string(), // Now required after backfill })

Examples Complete E-commerce Schema // convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values";

export default defineSchema({ users: defineTable({ email: v.string(), name: v.string(), role: v.union(v.literal("customer"), v.literal("admin")), createdAt: v.number(), }) .index("by_email", ["email"]) .index("by_role", ["role"]),

products: defineTable({ name: v.string(), description: v.string(), price: v.number(), category: v.string(), inventory: v.number(), isActive: v.boolean(), }) .index("by_category", ["category"]) .index("by_active_and_category", ["isActive", "category"]) .searchIndex("search_products", { searchField: "name", filterFields: ["category", "isActive"], }),

orders: defineTable({ userId: v.id("users"), items: v.array(v.object({ productId: v.id("products"), quantity: v.number(), priceAtPurchase: v.number(), })), total: v.number(), status: v.union( v.literal("pending"), v.literal("paid"), v.literal("shipped"), v.literal("delivered"), v.literal("cancelled") ), shippingAddress: v.object({ street: v.string(), city: v.string(), state: v.string(), zip: v.string(), country: v.string(), }), createdAt: v.number(), updatedAt: v.number(), }) .index("by_user", ["userId"]) .index("by_user_and_status", ["userId", "status"]) .index("by_status", ["status"]),

reviews: defineTable({ productId: v.id("products"), userId: v.id("users"), rating: v.number(), comment: v.optional(v.string()), createdAt: v.number(), }) .index("by_product", ["productId"]) .index("by_user", ["userId"]), });

Using Schema Types in Functions // convex/products.ts import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import { Doc, Id } from "./_generated/dataModel";

// Use Doc type for full documents type Product = Doc<"products">;

// Use Id type for references type ProductId = Id<"products">;

export const get = query({ args: { productId: v.id("products") }, returns: v.union( v.object({ _id: v.id("products"), _creationTime: v.number(), name: v.string(), description: v.string(), price: v.number(), category: v.string(), inventory: v.number(), isActive: v.boolean(), }), v.null() ), handler: async (ctx, args): Promise => { return await ctx.db.get(args.productId); }, });

Best Practices Never run npx convex deploy unless explicitly instructed Never run any git commands unless explicitly instructed Always define explicit schemas rather than relying on inference Use descriptive index names that include all indexed fields Start with optional fields when adding new columns Use discriminated unions for polymorphic data Validate data at the schema level, not just in functions Plan index strategy based on query patterns Common Pitfalls Missing indexes for queries - Every withIndex needs a corresponding schema index Wrong index field order - Fields must be queried in order defined Using v.any() excessively - Lose type safety benefits Not making new fields optional - Breaks existing data Forgetting system fields - _id and _creationTime are automatic References Convex Documentation: https://docs.convex.dev/ Convex LLMs.txt: https://docs.convex.dev/llms.txt Schemas: https://docs.convex.dev/database/schemas Indexes: https://docs.convex.dev/database/indexes Data Types: https://docs.convex.dev/database/types

返回排行榜