Skip to main content

convex-schema-validator

Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes

Ir para a instalação

Informações da origem

Repositório
waynesutton/convexskills
Última atividade na origem
3 de fevereiro de 2026 às 04:58
Idioma detectado do SKILL.md
inglês
Estrelas
404
Forks
32

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
4 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
convex-schema-validator
displayName
Convex Schema Validator
description
Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes
version
1.0.0
author
Convex
tags
["convex","schema","validation","typescript","indexes","migrations"]
# 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 ```typescript // 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<K, V>` | Dynamic keys | ### Index Configuration ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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: ```typescript 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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">;
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub