К основному содержанию
скилл CX Codex

convex-schema-validator — скилл для Claude Code: схема Convex

Проектирует таблицы, валидаторы, индексы и связи в convex/schema.ts. Пригодится при ошибках проверки схемы и развитии модели данных.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add waynesutton/builder-skills --skill convex-schema-validator -a claude-code
Открыть на GitHub

Что делает convex-schema-validator

Скилл готовит convex/schema.ts для таблиц Convex, типов полей, связей и индексов. Он помогает добавить поле к существующим данным, описать документы разных форм через v.union и разобраться с ошибкой при запуске npx convex dev.

Для каждого withIndex нужен соответствующий .index() с именем по полям и тем же порядком полей. Равенства должны идти по префиксу индекса, а сравнение диапазона допускается только для следующего поля. Если запрос использует .filter(), это повод проверить, не нужен ли индекс и withIndex.

Скилл различает отсутствующее поле через v.optional и обязательное поле со значением null. Связи строятся через v.id("table"); ссылка не проверяет существование целевого документа, поэтому это стоит учитывать в логике записи данных.

Какие задачи решает

  • описать таблицу в convex/schema.ts
  • добавить поле к таблице с существующими документами
  • сопоставить withIndex с объявлением .index()
  • заменить .filter() на индексный запрос
  • смоделировать связь через v.id("table")

Примеры запросов агенту

  • Проверь convex/schema.ts и добавь индексы для всех вызовов withIndex.
  • Добавь поле в существующую таблицу Convex так, чтобы проверка старых данных прошла.
  • Переделай запрос с .filter() на withIndex и объяви нужный индекс.

Как установить convex-schema-validator

1Через Skills CLI (Vercel)

npx skills add waynesutton/builder-skills --skill convex-schema-validator -a claude-code

2Вручную: положить файлы

~/.claude/skills/convex-schema-validator/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/convex-schema-validator/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о convex-schema-validator

Как установить convex-schema-validator в Claude Code?

Выполните npx skills add waynesutton/builder-skills --skill convex-schema-validator -a claude-code, затем повторите npx skills add waynesutton/builder-skills --skill convex-schema-validator -a claude-code. Файл скилла расположен в ~/.claude/skills/convex-schema-validator/SKILL.md.

Что делает convex-schema-validator?

Скилл помогает проектировать таблицы Convex, валидаторы, индексы и связи в convex/schema.ts. Он также разбирает ошибки проверки схемы при развёртывании.

Чем v.optional отличается от поля с null?

v.optional(t) разрешает не записывать ключ в документ. v.union(t, v.null()) требует ключ, но позволяет сохранить в нём null.

Исходный текст

SKILL.md · Apache-2.0
---
name: convex-schema-validator
description: Designs convex/schema.ts tables, validators, indexes, and relationships, and keeps the schema honest as data evolves. Use when creating tables, adding fields, choosing index fields, modeling relationships, or when a validator error appears at deploy time.
---

# Convex schema validator

Produces a `convex/schema.ts` that types every document, indexes every query path, and passes validation against the data already in the database. The one rule: every `withIndex` in a function needs a matching `.index()` here, named after its fields, queried in field order.

## When to reach for this

- Creating a new table or adding a field to an existing one
- A query uses `.filter()` and needs an index instead
- Deciding whether to embed an object or link with `v.id`
- Modeling a document that comes in several shapes
- `npx convex dev` fails with a schema validation error

## Schema skeleton

```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()),
  }).index("by_email", ["email"]),

  tasks: defineTable({
    userId: v.id("users"),
    title: v.string(),
    completed: v.boolean(),
    priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
  })
    .index("by_userId", ["userId"])
    .index("by_userId_and_completed", ["userId", "completed"]),
});
```

`defineTable` takes either an object of field validators or a single `v.union` of `v.object` validators (see discriminated unions). Every table gets `_id` and `_creationTime` for free. Do not declare them.

## Validators

| Validator | TypeScript type | Note |
| --- | --- | --- |
| `v.string()` | `string` | UTF-8, under 1 MB |
| `v.number()` | `number` | Float64. Use for timestamps and counts |
| `v.boolean()` | `boolean` | |
| `v.null()` | `null` | `undefined` is not a Convex value. Return `null` instead |
| `v.int64()` | `bigint` | Not `v.bigint()`, which is deprecated |
| `v.bytes()` | `ArrayBuffer` | Under 1 MB |
| `v.id("table")` | `Id<"table">` | Typed reference. Convex does not enforce that the target exists |
| `v.array(t)` | `T[]` | At most 8192 items |
| `v.object({...})` | `{...}` | At most 1024 entries. Keys cannot start with `$` or `_` |
| `v.record(k, t)` | `Record<K, T>` | Dynamic ASCII keys. No `v.map` or `v.set` |
| `v.union(a, b)` | `A \| B` | Use `v.literal` members for enums |
| `v.literal("x")` | `"x"` | |
| `v.optional(t)` | `T \| undefined` | Field may be absent |
| `v.any()` | `any` | Last resort. Loses type safety and validation |

## Optional versus nullable

`v.optional` means the key may be missing from the document. `v.union(t, v.null())` means the key is always present and may hold `null`. They are different at validation time.

```typescript
items: defineTable({
  description: v.optional(v.string()),           // may be absent
  deletedAt: v.union(v.number(), v.null()),       // always present, may be null
  notes: v.optional(v.union(v.string(), v.null())), // either
}),
```

Use `v.optional` for fields added after the table had data. Use `v.union(..., v.null())` when "explicitly cleared" carries meaning.

## Discriminated unions

For a table whose documents come in several shapes, pass a `v.union` of `v.object` validators to `defineTable`. Each member has the same literal key so TypeScript narrows on it.

```typescript
events: defineTable(
  v.union(
    v.object({
      kind: v.literal("signup"),
      userId: v.id("users"),
      email: v.string(),
    }),
    v.object({
      kind: v.literal("purchase"),
      userId: v.id("users"),
      orderId: v.id("orders"),
      amount: v.number(),
    }),
  ),
).index("by_kind", ["kind"]),
```

Put the discriminant (`kind`) first in any index on a union table so queries can scope to one shape. Prefer this over one wide object full of `v.optional` fields.

## Indexes

### Naming and field order

Name the index after its fields in order: `by_field1_and_field2`. Querying must follow the same order: equality on a prefix of the fields, then at most one range on the next field.

```typescript
messages: defineTable({
  channelId: v.id("channels"),
  authorId: v.id("users"),
  sentAt: v.number(),
})
  .index("by_channelId", ["channelId"])
  .index("by_channelId_and_authorId", ["channelId", "authorId"])
  .index("by_channelId_and_sentAt", ["channelId", "sentAt"]),
```

```typescript
// Valid: equality on channelId, range on sentAt
await ctx.db
  .query("messages")
  .withIndex("by_channelId_and_sentAt", (q) =>
    q.eq("channelId", args.channelId).gt("sentAt", args.since),
  )
  .order("desc")
  .take(50);
```

You cannot skip `channelId` and filter on `sentAt` alone with that index. Add `by_sentAt` if that query exists. `_creationTime` is appended to every index automatically, so results within an equal prefix sort by creation time.

Reserved names: `by_id` and `by_creation_time`. Limits: 32 indexes per table, 16 fields per index.

### When to add one

- Any field a function passes to `withIndex`, `.eq`, or a range comparison
- Every foreign key (`userId`, `channelId`, `orgId`) on the child table
- The sort field for a paginated list, prefixed by the scoping field
- Not for fields you only read after fetching the document
- Not for tiny tables where a `.collect()` then in memory filter is fine

If a query uses `.filter()`, that is the signal to add an index and switch to `withIndex`.

## Relationships

Link documents with `v.id("table")` on the child. Do not nest growing arrays of objects inside the parent.

```typescript
// Good: one to many via a foreign key
posts: defineTable({
  authorId: v.id("users"),
  title: v.string(),
}).index("by_authorId", ["authorId"]),

comments: defineTable({
  postId: v.id("posts"),
  authorId: v.id("users"),
  body: v.string(),
}).index("by_postId", ["postId"]),

// Many to many via a join table
postTags: defineTable({
  postId: v.id("posts"),
  tagId: v.id("tags"),
})
  .index("by_postId", ["postId"])
  .index("by_tagId", ["tagId"]),
```

Embed with `v.object` or a small `v.array` only when the data is bounded, always loaded with the parent, and updated together. A user's `settings` object is a good embed. A user's `posts` array is not: it hits the 8192 item cap and every post edit rewrites the user document.

## System fields

`_id: Id<"table">` and `_creationTime: number` (ms since epoch) exist on every document. Include them in return validators when a function returns whole documents:

```typescript
returns: v.array(
  v.object({
    _id: v.id("tasks"),
    _creationTime: v.number(),
    userId: v.id("users"),
    title: v.string(),
    completed: v.boolean(),
  }),
),
```

Do not add your own `createdAt` unless you need a value that differs from insertion time.

## Search and vector indexes

Declared on the table like regular indexes. `filterFields` must be top level fields.

```typescript
articles: defineTable({
  title: v.string(),
  body: v.string(),
  category: v.string(),
  embedding: v.array(v.number()),
})
  .searchIndex("search_body", {
    searchField: "body",
    filterFields: ["category"],
  })
  .vectorIndex("by_embedding", {
    vectorField: "embedding",
    dimensions: 1536,
    filterFields: ["category"],
  }),
```

Query search indexes with `withSearchIndex` in queries. Vector search runs only in actions via `ctx.vectorSearch`.

## Common mistakes

| Mistake | Why it breaks | Do instead |
| --- | --- | --- |
| `withIndex("by_userId")` with no matching `.index()` | Deploy fails | Declare the index in the schema first |
| Index named `by_user` on `["userId", "status"]` | Hides what it covers, easy to misuse | `by_userId_and_status` |
| Querying `status` on `by_userId_and_status` without `userId` | Index prefix rule | Add `by_status` or include `userId` |
| Adding `newField: v.string()` to a table with rows | Existing documents fail validation | `v.optional`, backfill, then require |
| `posts: v.array(v.object(...))` on `users` | 8192 cap, write conflicts on every edit | Separate `posts` table with `by_authorId` |
| `v.bigint()` | Deprecated | `v.int64()` |
| Declaring `_id` or `_creationTime` in `defineTable` | Rejected | They are automatic |
| `v.any()` to move fast | No validation, no types | Model the shape, or a `v.union` of the real cases |
| `v.union(v.string(), v.null())` for a new field | Old documents lack the key entirely | `v.optional(v.string())` |
| Storing a `Date` | Not a Convex value | `v.number()` ms timestamp |

## Checklist

- [ ] Schema lives in `convex/schema.ts` and exports `defineSchema(...)` as default
- [ ] Every table has explicit field validators, no `v.any()` unless justified
- [ ] Every `withIndex` call in `convex/` has a matching `.index()` with fields in the name
- [ ] Foreign keys are `v.id("table")` with an index on the child table
- [ ] No unbounded arrays of objects embedded in a parent document
- [ ] Fields added to tables with data are `v.optional`
- [ ] Enums and polymorphic shapes use `v.union` of `v.literal` or `v.object` members
- [ ] Return validators include `_id` and `_creationTime` when returning whole documents
- [ ] `npx convex dev` pushes without a schema validation error

## Docs

- https://docs.convex.dev/llms.txt
- https://docs.convex.dev/database/schemas
- https://docs.convex.dev/database/indexes
- https://docs.convex.dev/database/types
- https://docs.convex.dev/database/reading-data

© waynesutton · Apache-2.0 · waynesutton/builder-skills

Похожие скиллы

Все скиллы по теме «Данные и базы» →
convex-migrate convex Скилл проводит миграцию схемы и бэкфилл данных в развёрнутом приложении Convex через @convex-dev/migrations. Пригодится, когда меняешь схему на живом приложении. CX Codex +1 36 100установок convex-explain-app convex Скилл строит понятную карту бэкенда Convex по схеме данных и функциям. Помогает быстро разобраться в незнакомом проекте перед правками или аудитом. CX Codex +1 35 900установок convex Mindrally Задаёт агенту правила работы с Convex: запросы, мутации, actions, индексы и файлы. Пригодится, когда бэкенд на Convex уже выбран. CX Codex +1 651установок database-schema alinaqi Скилл заставляет агента прочитать схему базы и проверить колонки с типами до того, как он напишет запрос или миграцию. CX Codex +1 302установок dms-schema-conversion aws Ведёт миграцию схем между разными движками БД через AWS DMS Schema Conversion: создаёт проект, оценивает совместимость, конвертирует DDL. CX Codex +1 4 700установок planetscale-schema-recommendations-agent-loop planetscale Превращает открытые рекомендации PlanetScale в план, issue, ветку или PR. Продовые изменения сам не применяет. CX Codex +1 1 800установок

Из того же набора14

convex Маршрутизирует задачи по Convex к специализированным скиллам и задаёт базовые правила разработки бэкенда. Пригодится на старте любого проекта с Convex. CX Codex +1 5 700установок convex-best-practices Скилл учит агента писать надёжный бэкенд на Convex без конфликтов записи OCC. Помогает настроить валидаторы, индексы и правила @convex-dev/eslint-plugin. CX Codex +1 3 900установок convex-functions Пишет query, mutation и action для Convex в объектной форме: с валидаторами args и returns, правильным ctx и обработкой ошибок. CX Codex +1 3 200установок convex-realtime Скилл объясняет, как связать фронтенд с подписками Convex: useQuery, оптимистичные обновления, живая пагинация и борьба с лишними ререндерами. CX Codex +1 2 900установок convex-cron-jobs Настраивает периодические задачи в convex/crons.ts и отложенный запуск через ctx.scheduler. Помогает разбить мутации на пачки и отладить фоновые очереди. CX Codex +1 2 800установок convex-security-check Проверяет Convex-бэкенд перед слиянием изменений: публичные функции, авторизацию, владение данными, валидацию и секреты. CX Codex +1 2 700установок convex-http-actions Добавляет HTTP-эндпоинты в convex/http.ts: приём вебхуков с проверкой подписи, REST-маршруты, CORS, стриминг и загрузка файлов. CX Codex +1 2 700установок convex-migrations Проводит изменение схемы Convex через шесть шагов: сделать поле необязательным, залить данные батчами, переключить валидатор, подчистить. Пригодится, когда npx convex dev падает на существующих документах. CX Codex +1 2 600установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой