Skip to content
Agentmart
Submit a tool

base

Vovk.ts essentials — ALWAYS load first for any Vovk.ts task, including building a back end or API in a project that has vovk.config.* or .vovk-schema/.

Skill by finom

IndexedMIT★ 54
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/finom-base

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Vovk.ts essentials — ALWAYS load first for any Vovk.ts task, including building a back end or API in a project that has vovk.config.* or .vovk-schema/. Holds what a typical build needs, checked against vovk 4's code - project layout, a complete endpoint, how requests, validation and errors behave, status codes, a custom error format, CORS, and how to get the schema, the typed client and the OpenAPI document. The other vovk:* skills go deeper on one topic each; load them only for that topic.

Use cases: documents, backend, productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/base

What's inside

Skill name
base
Description
Vovk.ts essentials — ALWAYS load first for any Vovk.ts task, including building a back end or API in a project that has vovk.config.* or .vovk-schema/. Holds what a typical build needs, checked against vovk 4's code - project layout, a complete endpoint, how requests, validation and errors behave, status codes, a custom error format, CORS, and how to get the schema, the typed client and the OpenAPI document. The other vovk:* skills go deeper on one topic each; load them only for that topic.
License
MIT
Instructions
186 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Vovk.ts essentials

What a typical API build needs. Every fact here matches vovk 4.1's code: don't read `node_modules/vovk*` to check it (version, exports, CLI), and don't `WebFetch` vovk.dev. If something is missing, say so. The installed version is the `vovk` entry in `package.json` (`require('vovk/package.json')` throws: the file isn't exported); the `vovk` bin is in `node_modules/.bin`.

Deeper topics, one skill each: `procedure` (`.fn()`, content types, uploads, downloads, server actions), `decorators` (auth, `meta`, stacking), `segment` (several or static segments), `rpc` (client options, fetcher), `openapi` (`@operation`, Scalar), `jsonlines` (streaming), `tools` (AI tools, MCP), `config`, `init`, `bundle`, `mixins`, `multitenant`, `python`, `rust`.

## What `vovk` exports

- Values: `procedure`, `initSegment`, `get`, `post`, `put`, `patch`, `del`, `head`, `options`, `prefix`, `operation`, `createDecorator`, `HttpException`, `HttpStatus`, `HttpMethod`, `JSONLinesResponder`, `progressive`, `toDownloadResponse`, `controllersToStaticParams`, `multitenant`, `deriveTools`, `ToModelOutput`, `cloneControllerMetadata`.
- Types: `VovkRequest`, `VovkBody`, `VovkQuery`, `VovkParams`, `VovkInput`, `VovkOutput`, `VovkIteration`, `VovkReturnType`, `VovkYieldType`, `VovkConfig`, `VovkSchema`, and a few more for tools and streams.
- Other entry points: `vovk/fetcher` (`fetcher`, `createFetcher`), `vovk/create-rpc`, `vovk/create-validate-on-client`.

## Rules

- Node.js 24.15+ (or Bun 1.4+, Deno 2.9+), Next.js 15+ App Router, TypeScript 5.5+.
- Commit `.vovk-schema/`; the generated client dir (`src/client`) is gitignored and rebuilt by `prebuild`.

## Layout

- **Segment**: `src/app/api/[[...vovk]]/route.ts` serves `/api/*`. Create it once with `npx vovk new segment`.
- **Module**: `src/modules/<name>/<name>-controller.ts` and `<name>-service.ts`. `npx vovk new controller service <name> --empty` writes both and adds the controller to `route.ts`; writing them yourself and adding the controller to `controllers` works the same. `<name>` is camelCase (`userProfile`).
- **Controller**: static procedures with HTTP decorators. A procedure reads the validated input and calls the service.
- **Service**: business logic and data, plain typed arguments, never `req`.

```ts
// src/app/api/[[...vovk]]/route.ts
import { initSegment } from 'vovk';
import ArticleController from '../../../modules/article/article-controller';

const controllers = { ArticleRPC: ArticleController }; // the key is the client module name
export type Controllers = typeof controllers;

export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = initSegment({ controllers });
```

## An endpoint

```ts
// src/modules/article/article-controller.ts
import { get, post, prefix, procedure, operation, HttpException, HttpStatus } from 'vovk';
import { NextResponse } from 'next/server';
import { z } from 'zod';
import ArticleService from './article-service';

const Article = z.object({ slug: z.string(), title: z.string() });

@prefix('articles')
export default class ArticleController {
  @operation({ summary: 'List articles' })
  @get()                                    // GET /api/articles?limit=20
  static list = procedure({
    query: z.object({ limit: z.coerce.number().int().max(100).default(20) }),
    output: z.object({ articles: z.array(Article) }),
  }).handle(async ({ vovk }) => ArticleService.list(vovk.query()));

  @operation({ summary: 'Get an article' })
  @get('{slug}')                            // GET /api/articles/{slug}
  static get = procedure({
    params: z.object({ slug: z.string() }),
    output: Article,
  }).handle(async (_req, { slug }) => {
    const article = ArticleService.find(slug);
    if (!article) throw new HttpException(HttpStatus.NOT_FOUND, 'Article not found');
    return article;
  });

  @operation({ summary: 'Create an article' })
  @post()                                   // POST /api/articles, 201
  static create = procedure({
    body: z.object({ title: z.string().min(1) }),
  }).handle(async ({ vovk }) => NextResponse.json(ArticleService.create(await vovk.body()), { status: 201 }));
}
```

URL = `/api/<segment>/<@prefix>/<path>`, empty parts dropped. `{name}` in a path is a param. `@put`, `@patch`, `@del`, `@head`, `@options` work the same.

A path equal to a route with no params gets that route, whatever the order (`users/me` over `users/{id}`). If two routes with params match the same path, the request answers 500 `Conflicting routes found: …`.

`output` is optional. It checks the returned value and becomes the response schema in the OpenAPI document. Without it the client takes the handler's return type, including the body type of `NextResponse.json(…)`.

## Requests

- `await req.vovk.body()`, `req.vovk.query()`, `req.vovk.params()` return the validated input; the handler's second argument is the params. These also work under `.fn()`, where `req.json()` and `req.nextUrl` don't.
- Query values arrive as strings: use `z.coerce.number()`, `z.stringbool()` (not `z.coerce.boolean()`, which makes `"false"` true) or `z.enum`. Repeated keys (`tag=a&tag=b`) and `tag[]=a` give arrays, `filter[status]=open` gives objects, and a lone value for an array field becomes a one-item array.
- Headers and cookies: `headers()` / `cookies()` from `next/headers`, or `req.headers` over HTTP only.
- Validation takes any Standard Schema library that also gives JSON Schema: Zod 4 and ArkType as they are, Valibot wrapped in `toStandardJsonSchema()`.

## Responses

- A returned value is sent as JSON with status 200. `undefined` and `null` send `null`.
- A returned `Response` is sent as it is: `NextResponse.json(data, { status: 201 })` (from `next/server`, so the client keeps the body type), `new Response(null, { status: 204 })`, or with headers.
- With an `output` schema, return the data instead: the output check runs on the returned value, so a `Response` fails it with a 500. Set the status in a decorator, which gets the data after the check:

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

export const status = createDecorator(async (_req, next, code: number) => {
  const data = await next();
  return code === 204 ? new Response(null, { status: 204 }) : Response.json(data, { status: code });
});

// @post() @status(201) static create = procedure({ body, output }).handle(...)
```

- HEAD gets the GET route's status and headers, without a body.

## Errors

`throw new HttpException(HttpStatus.NOT_FOUND, 'Article not found', cause?)` answers with that status and this body (`cause` only when set):

```json
{ "statusCode": 404, "message": "Article not found", "isError": true, "cause": {} }
```

The generated client throws it again as `HttpException` with the same `statusCode`, `message` and `cause`.

`HttpStatus` names: `OK`, `CREATED`, `ACCEPTED`, `NO_CONTENT`, `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`, `CONFLICT`, `GONE`, `PAYLOAD_TOO_LARGE`, `UNSUPPORTED_MEDIA_TYPE`, `UNPROCESSABLE_ENTITY`, `TOO_MANY_REQUESTS`, `INTERNAL_SERVER_ERROR`, `NOT_IMPLEMENTED`, `SERVICE_UNAVAILABLE`, and the other standard codes.

What vovk answers by itself, in the same body:

From finom/vovk, MIT.

More from finom/vovk

Signals

Stars
54
Forks
0
Open issues
0
Last commit (this folder)
Oct 20, 2026 (8 days ago)
Last release
Oct 22, 2026 (6 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
Website
vovk.dev
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from finom

Cite this page

base skill. Agent Mart. https://agentmart.dev/skills/finom-base (last updated 2026-10-24)