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

decorators — скилл для Claude Code: что делает и как установить

Скилл обучает агента писать встроенные и кастомные декораторы для Vovk.ts, настраивать авторизацию и передавать контекст между middleware.

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

Что делает decorators

Скилл учит агента работать с декораторами Vovk.ts. Он объясняет модели, как вешать HTTP-методы вроде @get или @post, применять .auto() для генерации путей в kebab-case и группировать эндпоинты через классовый @prefix.

Для кастомной логики используется createDecorator. С его помощью агент создаёт проверки токенов, логирование и замеры времени. Данные между декораторами и процедурами передаются через хранилище req.vovk.meta(). При этом скилл требует указывать явный дженерик для meta(), иначе TypeScript сбросит типизацию к Record<string, any>.

Для сборщика Webpack в Next.js 15 обязательно нужно включить флаг experimentalDecorators в tsconfig.json, хотя Turbopack в Next.js 16 собирает код и без него. Скилл не затрагивает описание самих процедур и генерацию схем OpenAPI — для них у автора предусмотрены отдельные скиллы.

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

  • написать кастомный декоратор авторизации через createDecorator
  • задать префикс для группы эндпоинтов контроллера
  • настроить передачу типизированного контекста пользователя через req.vovk.meta
  • включить CORS и кастомные заголовки для отдельного HTTP-метода

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

  • Сделай декоратор проверки роли admin для метода контроллера через createDecorator
  • Добавь префикс api/v1 ко всем маршрутам UserController
  • Напиши мидлвар авторизации, который сохраняет пользователя в req.vovk.meta

Как установить decorators

1Через Skills CLI (Vercel)

npx skills add finom/vovk --skill decorators -a claude-code

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

~/.claude/skills/decorators/SKILL.md

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

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

Вопросы о decorators

Как установить скилл decorators в Claude Code?

Выполните команду npx skills add finom/vovk --skill decorators -a claude-code. Файл инструкции запишется в ~/.claude/skills/decorators/SKILL.md.

Что делает скилл decorators?

Он объясняет агенту синтаксис HTTP-декораторов Vovk.ts, порядок их выполнения и создание кастомных middleware.

Зачем передавать явный дженерик в req.vovk.meta?

По умолчанию метод использует Record<string, any>. Без явного типа TypeScript потеряет проверку полей между цепочкой декораторов и обработчиком.

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

SKILL.md · MIT
---
name: decorators
description: Vovk.ts decorators — built-in (`@prefix`, `@operation`, `@get/@post/@put/@patch/@del`, `.auto()`) and custom via `createDecorator`. Covers authorization / auth decorators, middleware-style wrapping (pre-handler + post-handler logic), `req.vovk.meta()` for cross-decorator state, stacking order, and the local (`.fn()`) vs HTTP context distinction. Use whenever the user asks to "write a custom decorator", "add auth middleware", "protect an endpoint", "build an authorization decorator", "createDecorator", "stack decorators", "share data between decorator and handler", "prefix all routes", "@auto", "decorator execution order". Does NOT cover procedure authoring → hand off to `procedure` skill. Does NOT cover `@operation` for OpenAPI beyond auth-related fields → hand off to `openapi` skill. Does NOT cover `@operation.x-tool` for tool derivation → hand off to `tools` skill.
---

# Vovk.ts decorators

Decorators wrap static methods on controllers. Three kinds:

- **HTTP decorators** (`@get`, `@post`, `@put`, `@patch`, `@del`) — mark procedure as HTTP route. See `procedure` skill.
- **Metadata decorators** (`@prefix`, `@operation`) — attach info, no behavior change.
- **Custom decorators** (`createDecorator`) — middleware wrapping handler. How auth, logging, feature flags attach.

One syntax: `@decorator`. `vovk init` enables `experimentalDecorators` in `tsconfig.json`; a webpack build (`next build --webpack`, Next.js 15's default) needs it, Turbopack (Next.js 16's default) compiles vovk's decorators either way.

## `@prefix`

Class-level route prefix:

```ts
import { prefix, get, procedure } from 'vovk';

@prefix('users')
export default class UserController {
  @get('{id}')
  static getUser = procedure({ /* ... */ }).handle(/* ... */);
}
// → GET /api/users/{id}
```

Root-level — `@prefix('')` or omit (same effect):

```ts
export default class RootController {
  @get()
  static ping = procedure().handle(() => ({ ok: true }));
}
// → GET /api
```

## `.auto()` — name-derived routes

Use method name instead of path:

```ts
@put.auto()
static doSomething = procedure().handle(/* ... */);
// → PUT /api/<prefix>/do-something
```

Method names → kebab-case. Works with every HTTP decorator (`@get.auto()`, `@post.auto()`, etc.), same options.

## HTTP decorator options

Second arg: response-header + CORS config.

```ts
@put('do-something', {
  headers: { 'x-hello': 'world' },
  cors: true,
})
static doSomething = procedure().handle(/* ... */);
```

- `headers` — merged into response.
- `cors: true` — permissive CORS for that route (`access-control-allow-origin: *`, preflight answered). Needs `OPTIONS` in segment's `initSegment()` exports — else Next.js answers the preflight itself (204 with `Allow`, no `Access-Control-*` headers), and the browser blocks the cross-origin call. Exact headers and CORS for every route → **`base`**.

## Custom decorators — `createDecorator`

Middleware-shaped: receive request + `next`, work before/after, return response. `next()` returns the handler's value (after the `output` check) or throws its error (validation errors included). What the decorator returns is the response: a value is sent as JSON with 200, a `Response` as it is — the way to set a status other than 200 or answer errors in another format (recipes in **`base`**).

```ts
import { createDecorator } from 'vovk';

const timingDecorator = createDecorator(async (req, next) => {
  const start = Date.now();
  const response = await next();
  console.log(`[${req.method}] ${req.url} — ${Date.now() - start}ms`);
  return response;
});
```

Stack it with the HTTP decorator (order below):

```ts
@get('{id}')
@timingDecorator()
static getUser = procedure({ /* ... */ }).handle(/* ... */);
```

### Auth decorator pattern

Gate access, pass user state to handler via `req.vovk.meta()`. **Always pass explicit generic to every `meta()` call** — both set + read — and share single type alias across every site touching auth metadata. `meta()`'s generic defaults to `Record<string, any>` → untyped call silently loses inference, sides drift apart.

```ts
// auth.ts — define once, import everywhere the auth decorator is used
import { createDecorator, HttpException, HttpStatus } from 'vovk';
import { headers } from 'next/headers';

export type AuthMeta = { user: User };

export const authGuard = createDecorator(async (req, next) => {
  const token = (await headers()).get('authorization');
  if (!token) {
    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Missing token');
  }
  const user = await parseToken(token); // your auth logic — returns `User | null`
  if (!user) {
    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Invalid or expired token');
  }
  req.vovk.meta<AuthMeta>({ user });
  return next();
});
```

Reading the token via `next/headers` (not `req.headers`) makes the guard work over HTTP and under `.fn()`. See **Local vs HTTP context** below.

Consume in handler — same `AuthMeta`:

```ts
import { authGuard, type AuthMeta } from './auth';

@get('{id}')
@authGuard()
static getUser = procedure({ /* ... */ }).handle((req) => {
  const { user } = req.vovk.meta<AuthMeta>();
  // user is `User`, present and typed
});
```

**Throw, don't return**: `HttpException` short-circuits chain. Returning response object bypasses handler silently — possible, bug magnet.

### Role / scope check

Decorators with arguments don't wrap `createDecorator` in outer factory — handler signature is `(req, next, ...args)`, so call-site args (`@roleGuard('admin')`) flow through to handler's trailing params. Declare once, reuse same `AuthMeta`:

```ts
import { authGuard, type AuthMeta } from './auth';

const roleGuard = createDecorator(async (req, next, role: 'admin' | 'user') => {
  const { user } = req.vovk.meta<AuthMeta>();
  if (user.role !== role) {
    throw new HttpException(HttpStatus.FORBIDDEN, 'Insufficient role');
  }
  return next();
});

@get('admin-only')
@authGuard()
@roleGuard('admin')
static adminOnly = procedure().handle(/* ... */);
```

`TArgs` inferred from handler signature → `@roleGuard('admin')` type-checked against `'admin' | 'user'` at call site.

`@authGuard()` is **outermost** wrapping decorator (topmost, after `@get`) → runs first, populates `meta<AuthMeta>({ user })`. `@roleGuard('admin')` runs next, reads same alias, decides whether to throw. No need to guard `if (!user)` — auth threw `UNAUTHORIZED` otherwise. See stacking-order section below.

## `req.vovk.meta()` — cross-decorator state

Key/value store on request. Signature: `meta<T>(value?: T | null): T`, `T` defaults to `Record<string, any>` — **always pass generic** on set + read so TypeScript checks shape, doesn't fall through to `any`. For concern used by more than one decorator (auth, tracing, feature flags), hoist type to shared module + import everywhere:

```ts
type TraceMeta = { traceId: string };

req.vovk.meta<AuthMeta>({ user });                   // set, typed against shared alias
const { user } = req.vovk.meta<AuthMeta>();          // read, same alias
req.vovk.meta<TraceMeta>({ traceId: 'abc' });        // merge (keeps existing `user`)
req.vovk.meta(null);                                 // clear all (no generic needed)
```

Calls **merge** — consecutive `meta()` accumulate. How decorators pass state to each other + handler. Each `meta()` types only its slice; merged object carries keys from every contributor.

### Client-supplied metadata (`x-meta`)

Clients ship metadata via `x-meta` header. Lives under separate key (`xMetaHeader`) in `meta()` — does **not** overwrite server-set keys. Server-trusted state safe from client spoofing.

## Stacking order

Decorators execute **top-to-bottom** — topmost wrapping decorator (furthest from handler) runs first pre-handler. Opposite of TS's bottom-up *application* order: `@C` applied first → ends up innermost wrapper, runtime runs *last* pre-handler.

```ts
@get('/x')
@decoratorA()   // runs FIRST (outermost wrapper)
@decoratorB()   // runs second
@decoratorC()   // runs LAST pre-handler (innermost, closest to handler)
static handler = procedure().handle(/* ... */);
```

Pre-handler: A → B → C → handler. Post-handler (after `next()` resolves): handler → C → B → A.

Auth: put authentication **at top** (outermost) → populates `meta()` before downstream reads. Authorization / role checks stack just below — see what auth wrote.

### Wrapping decorators vs. schema-only decorators

Not every decorator uses `req`. Placement rule depends on what decorator does:

- **Wrapping decorators** — read `req` / call `next()` (auth, logging, timing, any `createDecorator(handler, …)` with non-null handler). Run on HTTP and `.fn()` calls on either side of `@get`/`@post`/etc.: dispatcher calls latest wrapper, so one applied after HTTP decorator (written above it) still sees `req`. Order stays top-to-bottom. Convention: stack them **below** HTTP decorator.
- **Schema-only decorators** — `@operation`, anything written as `createDecorator(null, initHandler)`. Only mutate handler schema (OpenAPI, tool derivation, etc.), pass through at runtime. Placement doesn't change behavior; convention puts them **on top** (above `@get`) for readability — metadata reads naturally before HTTP verb line.

```ts
@operation({ summary: 'List users' })  // schema-only — on top, reads like a doc comment
@get('/users')                          // HTTP decorator
@authGuard()                            // wrapping — below @get by convention
static listUsers = procedure().handle(/* ... */);
```

## Local vs HTTP context

When procedure called via `.fn()` (SSR, server components, tests), `req.url` is `undefined`. Decorators run in both modes — detect + branch when logic depends on real HTTP:

```ts
const myDecorator = createDecorator((req, next) => {
  if (typeof req.url === 'undefined') {
    // Local context — no network, no real headers
  } else {
    // HTTP request
  }
  return next();
});
```

For headers/cookies, prefer imported `headers()` / `cookies()` from `next/headers` over `req.headers` / `req.cookies`. `next/headers` works over HTTP and under `.fn()` inside a Next.js request — no branch, no reliance on caller synthesizing `req`. Outside a request (unit test, script) it throws "`headers` was called outside a request scope"; mock it there, e.g. `vi.mock('next/headers', () => ({ headers: async () => new Headers({ authorization: 'Bearer test' }) }))`:

```ts
import { createDecorator } from 'vovk';
import { headers } from 'next/headers';

const authGuard = createDecorator(async (req, next) => {
  const token = (await headers()).get('authorization');
  // ...
  return next();
});
```

A `req.headers` / `req.cookies` read works in HTTP mode only — under `.fn()` both are `undefined` (`.fn()` builds only `req.vovk`, and its options take no headers). `next/headers` sidesteps that.

## Flows

### "Protect an endpoint with bearer auth"

1. Define `type AuthMeta = { user: User }` in shared module.
2. Write `authGuard` reading `authorization`, validating, calling `req.vovk.meta<AuthMeta>({ user })`.
3. Stack at top (outermost): `@get('x') @authGuard() static ...`.
4. Read `req.vovk.meta<AuthMeta>()` inside handler — same alias.

### "Only admins can hit this route"

Stack `@authGuard() @roleGuard('admin')` — auth at top (runs first, sets `user`), role check below (runs second, reads `user`).

### "Log every request with timing"

Timing decorator wrapping `next()` — record start, await, record end. Apply to class via shared constant + `@` on every method, or lift to Next.js middleware if every route needs it.

### "Share state between two decorators"

Define shared type (`type SharedMeta = { user: User }`). Decorator 1: `req.vovk.meta<SharedMeta>({ user })`. Decorator 2: `const { user } = req.vovk.meta<SharedMeta>()`. Same alias both sides — `meta()` merges, TypeScript stays in sync.

### "Skip a decorator when called locally"

Branch on `typeof req.url === 'undefined'` inside decorator body, or don't stack the decorator on procedures called via `.fn()`.

**Safety caveat — only skip auth/authorization when caller already did equivalent check.** Server Component with authenticated session calling `.fn()` purely to fetch already-authorized data is legitimate skip. `.fn()` against unauthenticated context (cron jobs, background work, untrusted callers) still needs to authenticate — keep guard + ensure it can read headers (via `next/headers`, which needs a Next.js request: a cron route handler has one, a standalone script doesn't), or do equivalent explicit check before calling `.fn()`.

If you skip guard for one callsite, audit every other `.fn()` callsite for same procedure. Easy to add second call later that assumes "this procedure is protected" + quietly isn't. When in doubt, keep the guard: inside a Next.js request it reads the same headers over HTTP and under `.fn()`.

## Gotchas

- **Stacking order top-down.** Topmost wrapping decorator runs first pre-handler; innermost (closest to handler) runs last. Auth at top; authorization / role checks below; logging/tracing above everything to see whole timing.
- **`meta()` merges, doesn't replace.** Pass `null` to clear. Multiple decorators setting different keys all land in final object.
- **Client `x-meta` sandboxed.** Under `xMetaHeader` in `meta()` — server-trusted state stays safe. Don't collapse together.
- **Throw to short-circuit.** `throw new HttpException(...)` is standard control-flow for auth failures. Returning response works but unusual + easy to misread.
- **Local context has no `req.url`.** If decorator dereferences HTTP-specific fields, guard. `next/headers` works in both, inside a Next.js request; in a unit test, mock it.
- **Decorators on `.fn()` still run.** If you don't want auth in SSR, either don't stack decorator on that procedure or branch inside body. Controller-only procedures (no HTTP decorator) can still have custom decorators — run on every `.fn()` call.
- **Vitest drops decorators** unless `useDefineForClassFields` is on → `procedure` skill, Testing.
- **`.auto()` names flow from method names.** Renaming method renames route — breaking change for consumers. Prefer explicit paths on public APIs.

© finom · MIT · finom/vovk

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

Все скиллы по теме «Бэкенд и API» →
mcp-builder официальный anthropics Скилл проводит через создание MCP-сервера на TypeScript или Python: от изучения протокола до тестов и оценки качества. 130 949установок claude-api официальный anthropics Справочник по Anthropic SDK и API Claude для генерации кода без устаревших параметров и выдуманных методов. 74 198установок lark-im larksuite Скилл подключает Lark IM к агенту: он отправляет сообщения, ищет историю, управляет чатами и файлами. Подойдёт для задач вокруг командных коммуникаций. CX Codex +1 472 900установок lark-event larksuite Слушает события Lark/Feishu и отдаёт их потоком NDJSON. Подходит для ботов, обработчиков сообщений и долгих подписчиков. CX Codex +1 466 800установок caveman-discover JuliusBrussee Находит точки вызова LLM в репозитории и размечает их воркфлоу для Caveman Cloud, чтобы расходы на токены не сливались в одну общую кучу. CX Codex +1 104 400установок migration JuliusBrussee Скилл задаёт правила безопасных миграций схем, данных и API с обязательным откатом. Пригодится при изменениях, где нельзя терять данные или ломать совместимость. CX Codex +1 103 900установок

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

openapi Скилл объясняет, как Vovk.ts собирает спецификацию OpenAPI из процедур и схем валидации. Пригодится, чтобы описать API и показать его документацию через Scalar. CX Codex +1 ★ 57звёзд репо multitenant Скилл для Vovk.ts: разводит несколько тенантов по поддоменам в одном Next.js-приложении — прокси, сегменты, страницы. CX Codex +1 ★ 57звёзд репо mixins Скилл учит агента подключать схемы OpenAPI 3.x в Vovk.ts как типизированные RPC-клиенты и выгружать их методы в MCP-инструменты. CX Codex +1 ★ 57звёзд репо jsonlines Скилл помогает настроить потоковую передачу JSON Lines в Vovk.ts: сервер отправляет данные по мере готовности, а клиент читает их как асинхронный поток. CX Codex +1 ★ 57звёзд репо init Разворачивает бэкенд в Next.js через Vovk.ts: проверяет версии, запускает vovk-cli и доводит проект до рабочего состояния. CX Codex +1 ★ 57звёзд репо config Скилл объясняет настройки Vovk.ts и tsconfig.json. Пригодится, когда нужно изменить путь генерации, параметры клиента или настройки TypeScript. CX Codex +1 ★ 57звёзд репо bundle Упаковывает сгенерированный TypeScript-клиент Vovk.ts в npm-пакет без зависимостей: готовые JS, типы, package.json и README. Нужен, когда клиент пора выкладывать в реестр. CX Codex +1 ★ 57звёзд репо base Даёт агенту правила и шаблоны для разработки бэкенда на Vovk.ts 4 в Next.js 15+. Пригодится при настройке эндпоинтов, валидации схем и сегментов API. CX Codex +1 ★ 57звёзд репо

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

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