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

convex-component-authoring — скилл для Claude Code: компоненты Convex

Собирает переиспользуемый компонент Convex: своя схема, функции и клиентская обёртка. Пригодится, когда фичу выносят в пакет.

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

Что делает convex-component-authoring

Скилл ведёт агента по структуре компонента Convex: convex.config.ts с defineComponent, отдельная schema.ts, публичные queries и mutations, папка _generated после npx convex codegen. Рядом кладётся типизированная обёртка-класс, которую вызывает родительское приложение.

Главное ограничение задано жёстко: компонент не читает таблицы родителя, ctx.auth и process.env. Всё нужное приложение передаёт аргументами. Обёртка живёт в среде приложения, поэтому именно она достаёт identity и прокидывает строку внутрь. Функции компонента приходят в родителя как internal-ссылки, клиент напрямую их не вызывает — только через ctx.runQuery и ctx.runMutation.

Внутри есть таблица выбора: когда хватит обычного TypeScript-хелпера в convex/lib/, а когда нужен компонент. Если фиче не требуется состояние за границей API, хелпер проще и типизируется легче. Отдельный раздел — регистрация через app.use(counter) и разбор случая, когда функции не появляются в components.<name> у родителя.

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

  • вынести счётчики или лимиты запросов в отдельный пакет
  • сделать компонент для каталога компонентов Convex
  • починить пропавшие функции в components.<name> родительского приложения
  • отдать сторонней интеграции свои таблицы и фоновые задачи
  • переиспользовать один backend-модуль в нескольких приложениях

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

  • Создай компонент Convex для счётчиков с defineComponent и клиентской обёрткой
  • Функции компонента не видны в components.counter — разберись, почему
  • Собери структуру пакета компонента Convex с schema.ts и npm publish

Как установить convex-component-authoring

1Через Skills CLI (Vercel)

npx skills add waynesutton/builder-skills --skill convex-component-authoring -a claude-code

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

~/.claude/skills/convex-component-authoring/SKILL.md

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

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

Вопросы о convex-component-authoring

Как установить convex-component-authoring в Claude?

Выполните npx skills add waynesutton/builder-skills --skill convex-component-authoring -a claude-code. Файл скилла появится по пути ~/.claude/skills/convex-component-authoring/SKILL.md.

Что делает convex-component-authoring?

Проводит агента по созданию компонента Convex: конфиг с defineComponent, собственная схема, публичные функции и типизированная клиентская обёртка для родительского приложения.

Почему компонент не видит данные приложения?

Компонент не имеет доступа к таблицам родителя, ctx.auth и process.env. Всё, что ему нужно, приложение передаёт аргументами, а auth проверяет на своей стороне.

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

SKILL.md · Apache-2.0
---
name: convex-component-authoring
description: Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup. Use when extracting a feature into a package, building something for the Convex component directory, or when a component's functions are not showing up in the parent app.
---

# Convex component authoring

Produces a self contained component: its own `convex.config.ts`, schema, functions, and a typed client wrapper the parent app calls. The one rule: a component cannot read the parent's tables, `ctx.auth`, or `process.env`. Whatever it needs, the app passes in as arguments.

## When to reach for this

- Extracting a feature (counters, rate limits, audit logs, notifications) into a package
- Building something for the Convex component directory
- A component's functions are missing from `components.<name>` in the parent app
- A third party integration should own its own tables and background jobs
- The same backend module is needed in several apps

## Component or helper function

| Need | Use |
| --- | --- |
| Shared logic over the app's own tables | Plain TypeScript helper in `convex/lib/` |
| Its own tables with a schema the app cannot touch | Component |
| Isolated functions that run in their own transaction | Component |
| Reuse across apps with one install line | Component |
| A React hook or client utility only | npm library, no component |

If the feature does not need persistent state behind an API boundary, a helper function is less work and easier to type.

## Folder layout

```
my-component/
  package.json
  src/
    component/
      convex.config.ts     # defineComponent("counter")
      schema.ts            # tables only this component can see
      public.ts            # queries, mutations, actions
      _generated/          # created by npx convex codegen
    client/
      index.ts             # wrapper class the app imports
  example/
    convex/
      convex.config.ts     # app.use(counter) for local testing
```

For a local only component, put the `component/` contents at `convex/components/<name>/` and skip `client/` and `package.json`.

## Define the component

```typescript
// src/component/convex.config.ts
import { defineComponent } from "convex/server";

const component = defineComponent("counter");
export default component;
```

```typescript
// src/component/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  counters: defineTable({
    name: v.string(),
    value: v.number(),
  }).index("by_name", ["name"]),
});
```

Functions import `query` and `mutation` from the component's own `_generated/server`, never from the app's. Every public function needs `args` and `returns` validators; without them the parent sees `any`.

```typescript
// src/component/public.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";

export const add = mutation({
  args: { name: v.string(), amount: v.number() },
  returns: v.number(),
  handler: async (ctx, args) => {
    const existing = await ctx.db
      .query("counters")
      .withIndex("by_name", (q) => q.eq("name", args.name))
      .unique();
    if (!existing) {
      await ctx.db.insert("counters", { name: args.name, value: args.amount });
      return args.amount;
    }
    const value = existing.value + args.amount;
    await ctx.db.patch(existing._id, { value });
    return value;
  },
});

export const get = query({
  args: { name: v.string() },
  returns: v.number(),
  handler: async (ctx, args) => {
    const counter = await ctx.db
      .query("counters")
      .withIndex("by_name", (q) => q.eq("name", args.name))
      .unique();
    return counter?.value ?? 0;
  },
});
```

## Register it in the parent

```typescript
// convex/convex.config.ts (parent app)
import { defineApp } from "convex/server";
import counter from "@acme/counter/convex.config.js";
// local component: import counter from "./components/counter/convex.config.js";

const app = defineApp();
app.use(counter);
// A second instance under a different name:
// app.use(counter, { name: "pageViews" });
export default app;
```

Run `npx convex dev`. It generates `components.counter` in the app's `convex/_generated/api` and the component's own `_generated/` folder. The reference path mirrors the file: a function in `public.ts` is `components.counter.public.add`.

Component functions arrive in the parent as internal references. Call them with `ctx.runQuery` and `ctx.runMutation`; clients cannot hit them directly.

## Client wrapper class

The wrapper runs in the app's environment, so it can read `ctx.auth` and `process.env` and pass what the component needs. It takes the component reference first and options second.

```typescript
// src/client/index.ts
import type {
  GenericDataModel,
  GenericMutationCtx,
  GenericQueryCtx,
} from "convex/server";
import type { ComponentApi } from "../component/_generated/component.js";

// Pick only the capabilities each method needs so any ctx with runQuery works
type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runMutation">;

export class Counter {
  constructor(
    public component: ComponentApi,
    private options: { prefix?: string } = {},
  ) {}

  private key(name: string): string {
    return this.options.prefix ? `${this.options.prefix}:${name}` : name;
  }

  async add(ctx: MutationCtx, name: string, amount = 1): Promise<number> {
    return await ctx.runMutation(this.component.public.add, {
      name: this.key(name),
      amount,
    });
  }

  async get(ctx: QueryCtx, name: string): Promise<number> {
    return await ctx.runQuery(this.component.public.get, { name: this.key(name) });
  }
}
```

```typescript
// convex/counter.ts (parent app)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { Counter } from "@acme/counter";

const counter = new Counter(components.counter, { prefix: "app" });

// The app owns auth. The component only sees the string it is handed.
export const incrementMine = mutation({
  args: {},
  returns: v.number(),
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");
    return await counter.add(ctx, identity.subject);
  },
});

export const getMine = query({
  args: {},
  returns: v.number(),
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");
    return await counter.get(ctx, identity.subject);
  },
});
```

These app side functions are what React calls through `api.counter.incrementMine`. The app decides on auth and rate limits; the component stays generic.

## Boundary rules

- **No parent tables.** `v.id("users")` inside a component refers to a component table named `users`, not the app's. Accept parent ids as `v.string()`.
- **Ids become strings.** Every `Id<"counters">` in the component is `string` in `ComponentApi`. Return them as strings on purpose.
- **No `ctx.auth`.** Resolve the user in the app and pass `userId`.
- **No app `process.env`.** Declare typed env in `defineComponent("name", { env: { API_KEY: v.string() } })` and let the app supply it with `app.use(c, { env: { API_KEY: ... } })`, or pass the value as a function argument.
- **`.paginate()` does not work across the boundary.** Use `paginator` from `convex-helpers` inside the component.
- **HTTP routes** in a component's `http.ts` are mounted by the app with `httpPrefix`. Component HTTP actions have no `ctx.auth`.
- **Callbacks into the app** travel as function handles: `createFunctionHandle(internal.x.y)` in the app, stored as `v.string()`, cast to `FunctionHandle<"mutation">` in the component.

Publishing to npm (exports map, build order, README, versioning) is covered in [references/publishing.md](references/publishing.md); open it when the component will be installed from a package rather than a local folder.

## Common mistakes

| Mistake | Why it breaks | Do instead |
| --- | --- | --- |
| Functions missing from `components.<name>` | `app.use(...)` not added, or `npx convex dev` has not run since | Register in `convex.config.ts`, run dev, check the reference path matches the file name |
| Importing `mutation` from the app's `_generated/server` | The function registers on the app, not the component | Import from the component's own `./_generated/server.js` |
| Calling `api.counter.add` from React | Component functions are internal references in the parent | Write an app mutation that calls `ctx.runMutation(components.counter.public.add)` |
| `v.id("users")` in component args | Table numbers differ per component, validation fails | `v.string()` at the boundary |
| `ctx.auth.getUserIdentity()` inside the component | Always returns no user | Authenticate in the app, pass `userId` |
| `process.env.MY_KEY` inside the component | Undefined at runtime | Declare env in `defineComponent` or pass as an argument |
| Public function without `returns` | Parent sees `any`, loses type safety | Add validators to every public function |
| Reading component env at module scope | Undefined during deploy analysis | Read `env.X` inside the handler |

## Checklist

- [ ] Decided a component is needed rather than a helper function
- [ ] `convex.config.ts` calls `defineComponent("<name>")` and exports it
- [ ] Schema lives in the component; no `v.id()` for parent tables anywhere
- [ ] Functions import from the component's own `_generated/server`
- [ ] Every public function has `args` and `returns` validators
- [ ] Parent registers with `app.use(...)` and `npx convex dev` runs clean
- [ ] Client wrapper takes `ComponentApi` first, options second, uses `Pick` ctx types
- [ ] App side wrapper functions handle auth and pass ids as strings
- [ ] No `ctx.auth`, `process.env`, or `.paginate()` inside the component
- [ ] Tested through an example app that installs the component

## Docs

- https://docs.convex.dev/llms.txt
- https://docs.convex.dev/components/authoring
- https://docs.convex.dev/components/using
- https://www.convex.dev/components

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

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

Всё по теме «Бэкенд и API» →
convex-create-component convex Скилл помогает создавать переиспользуемые Convex-компоненты с изолированными таблицами и чётким API. Пригодится, когда нужно выделить backend-логику в отдельный модуль или собрать интеграцию, которая владеет своими данными. CX Codex +1 130 900установок convex-quickstart convex Разворачивает минимальный шаблон Next.js + Convex из идеи в одно предложение и доводит его до запущенного локального приложения. CX Codex +1 131 100установок convex Mindrally Задаёт агенту правила работы с Convex: запросы, мутации, actions, индексы и файлы. Пригодится, когда бэкенд на Convex уже выбран. CX Codex +1 651установок tigris-object-operations tigrisdata Скилл для загрузки, скачивания, удаления и листинга объектов в Tigris Storage прямо из агента, плюс presigned URL. CX Codex +1 3 200установок vtex-io-auth-tokens-and-context vtex Помогает решить, какой токен VTEX IO должен стоять за запросом из бэкенд-приложения: приложение, покупатель или админ. CX Codex +1 1 100установок extension-to-functions-codebase Google LLC Переносит Firebase Extension в локальную кодовую базу Cloud Functions или в отдельный npm-пакет. Помогает перевести триггеры с V1 на V2 и настроить декларативный доступ к IAM и API. CX Codex +1 56 400установок

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

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установок convex-file-storage Настраивает хранение файлов в Convex: загрузку через временные URL, сохранение blob в actions и раздачу через getUrl. Пригодится при работе с документами и пользовательскими картинками. CX Codex +1 2 600установок convex-agents Скилл учит ИИ строить агентов на компоненте @convex-dev/agent. Пригодится для чатов с историей тредов, RAG и вызова мутаций Convex как инструментов. CX Codex +1 1 500установок convex-security-audit Скилл проводит полный разбор безопасности приложения на Convex и выдаёт письменный отчёт по найденным проблемам. Пригодится перед запуском или после инцидента. CX Codex +1 1 500установок avoid-feature-creep Удерживает правки агента строго в границах задачи. Выносит попутный рефакторинг и лишние фичи в отдельный список предложений вместо diff. CX Codex +1 1 300установок

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

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