agentleFS
Sign inSign up

chanfana

cloudflare/chanfana/AGENTS.md

Chanfana is an OpenAPI 3/3.1 schema generator and request validator for Hono and itty-router on Cloudflare Workers. It uses Zod v4 for schemas and provides auto CRUD endpoints with D1 support. Tests run inside @cloudflare/vitest-pool-workers with a real D1 binding. Config: tests/vitest.config.mts. Formatter/Linter: Biome (biome.json). Key settings: - 2-space indent, 120 char line width, double quotes, always semicolons, trailing commas on multiline - noExplicitAny is OFF — any is used deliberately throughout TypeScript (tsconfig.json): strict: true, verbatimModuleSyntax: true (requires import…

AGENTS.md766 starsChanged 7 months ago
# AGENTS.md — Chanfana

Chanfana is an OpenAPI 3/3.1 schema generator and request validator for Hono and itty-router on Cloudflare Workers. It uses Zod v4 for schemas and provides auto CRUD endpoints with D1 support.

## Commands

```bash
npm test                              # Run all tests (tsc + vitest)
npm test -- coverage-gaps.test.ts     # Run a single test file by name
npm test -- --coverage                # Run with Istanbul coverage report
npm run build                         # Build CJS + ESM to dist/
npm run lint                          # Lint via Biome (auto-fixes on failure)
```

Tests run inside `@cloudflare/vitest-pool-workers` with a real D1 binding. Config: `tests/vitest.config.mts`.

## Project Layout

```
src/
  adapters/        # Router adapters: hono.ts, ittyRouter.ts
  endpoints/       # Auto CRUD: create, read, update, delete, list
    d1/            # D1-specific implementations + SQL utilities (base.ts)
  zod/             # OpenAPI registry merger
  openapi.ts       # Core handler: route registration, schema generation, doc UI
  route.ts         # Base OpenAPIRoute class (validation, lifecycle, error handling)
  parameters.ts    # Query/path param coercion (string → number/boolean/BigInt/Date)
  exceptions.ts    # ApiException hierarchy (12 exception classes, codes 7000–7012)
  types.ts         # Shared TypeScript types
  index.ts         # Barrel re-exports (export * from every module)
tests/integration/ # All tests (no unit test directory)
docs/              # VitePress documentation
skills/            # AI coding skills (write-endpoints)
```

## Code Style

**Formatter/Linter**: Biome (`biome.json`). Key settings:
- 2-space indent, 120 char line width, double quotes, always semicolons, trailing commas on multiline
- `noExplicitAny` is OFF — `any` is used deliberately throughout

**TypeScript** (`tsconfig.json`): `strict: true`, `verbatimModuleSyntax: true` (requires `import type` for type-only imports), target ES2022, bundler module resolution.

**Imports**: Biome auto-organizes. External packages first (alphabetical), then relative imports. No blank lines between groups. Use `import type` for type-only:
```typescript
import { z } from "zod";
import type { AnyZodObject, RouteParameter } from "./types";
// or mixed:
import { MetaGenerator, type MetaInput, type O } from "./types";
```

**Naming**:
- Classes: `PascalCase` — suffixed `Endpoint` or `Exception` (`CreateEndpoint`, `NotFoundException`)
- Functions/methods/variables: `camelCase` (`getValidatedData`, `coerceInputs`)
- Module-level constants: `SCREAMING_SNAKE_CASE` (`HIJACKED_METHODS`)
- User-facing config keys: `snake_case` (`docs_url`, `openapi_url`, `default_message`)
- Booleans: `is`/`has` prefix (`isVisible`, `isRoute`, `includesPath`)
- Unused params: `_` prefix (`_args`, `_e`, `_oldObj`)

**Types**: Return types are generally inferred. Parameters are always explicitly typed. Class properties always have explicit types or initializers. `@ts-expect-error` is used when needed (e.g., `_meta` in endpoint subclasses).

**Error handling**: Exception class hierarchy rooted at `ApiException extends Error`. Each has `buildResponse()` and `static schema()`. Pattern in `execute()`:
```typescript
try {
  resp = await this.handle(...args);
} catch (e) {
  if (this.params?.raiseOnError) throw e;
  const errorResponse = formatChanfanaError(e);
  if (errorResponse) return errorResponse;
  throw e; // unknown error: re-throw
}
```

**Async**: Always `async/await`, never `.then()` chains. Use `for...of` for iteration, never `for...in`.

**Exports**: All named, no default exports. Barrel file `src/index.ts` uses `export *`.

## Zod v4 Rules (Critical)

All code must use Zod v4 syntax. Common mistakes:
```typescript
// WRONG (v3)          → CORRECT (v4)
z.string().email()     → z.email()
z.string().uuid()      → z.uuid()
z.string().datetime()  → z.iso.datetime()
z.string().date()      → z.iso.date()
z.string().url()       → z.url()
z.nativeEnum(X)        → z.enum([...])
obj.strict()           → z.strictObject({...})
{ message: "..." }     → { error: "..." }
z.ZodTypeAny           → z.ZodType
```

## Testing Patterns

Tests live in `tests/integration/`. Framework: Vitest with `describe`/`it` (not `test`).

**Two request-building approaches**:
1. `buildRequest({ method, path })` — plain object for itty-router (add `json: () => ({...})` for body)
2. `new Request(url, { method, body, headers })` — for Hono or when body/headers needed

**Test endpoint classes** are defined inline at the top of test files, before `describe` blocks. Named descriptively with `Endpoint` suffix (`FalsyDefaultsEndpoint`, `ThrowNotFoundEndpoint`).

**D1 tests** use `import { env } from "cloudflare:test"` and pass `env` as second arg to `router.fetch()`. Setup with raw SQL in `beforeEach`.

**Standard assertions**:
```typescript
expect(request.status).toBe(200);
expect(resp.success).toBe(true);
expect(resp.result).toEqual({ ... });
expect(resp.errors[0].code).toBe(7001);
```

## Architecture Quick Reference

**OpenAPIRoute lifecycle**: `execute()` → reset caches → `handle()` → catch errors → auto-JSON-wrap response

**Auto endpoints** (`CreateEndpoint`, `ReadEndpoint`, etc.) require a `_meta` property with `model.schema` (Zod), `model.primaryKeys`, and `model.tableName` (for D1). Optional `tags` for OpenAPI grouping. Support `before()`/`after()` hooks.

**Router adapters** (`fromHono`, `fromIttyRouter`) return a Proxy that intercepts route registration to capture OpenAPI metadata, then delegates to the underlying router.

**D1 endpoints** extend the base CRUD classes with SQL generation. Use parameterized queries exclusively. `d1/base.ts` provides `validateSqlIdentifier()`, `buildSafeFilters()`, `buildPrimaryKeyFilters()`, `handleDbError()`.

## Changesets

This project uses `@changesets/cli`. Add a changeset for user-facing changes:
```bash
npx changeset        # Interactive prompt
```
For internal-only changes (tests, CI), use an empty changeset (frontmatter only, no package entry).

## Key Resources

- Docs: https://chanfana.pages.dev
- Source: https://github.com/cloudflare/chanfana
- Detailed coding skill: `skills/write-endpoints/SKILL.md`
- Zod v4: https://zod.dev/v4

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.