Skip to main content

Type generation

Covara generates TypeScript types from your running API, giving you end-to-end type safety: resource interfaces, input/update types, field-metadata helpers for the query builder, public env types, and a typed client factory.

CLI

npx covara types --url http://localhost:3000 --out src/generated/api-types.ts

Or a script entry point:

// scripts/typegen.ts
import { createTypegenCLI } from "covara/client";
await createTypegenCLI(process.argv.slice(2));
tsx scripts/typegen.ts http://localhost:3000 typescript > src/generated/api-types.ts

Programmatic

import { generateTypes } from "covara/client";
import { writeFileSync } from "fs";

const result = await generateTypes({
serverUrl: "http://localhost:3000",
output: "typescript",
includeClient: true,
includeEnv: true, // default true
envPath: "/api/env",
});
writeFileSync("./src/generated/api-types.ts", result.code);

What's generated

// Resource interfaces
export interface Todo { id: string; title: string; completed: boolean; /* ... */ }

// Input / update types (auto-increment excluded; PKs, nullable, and default fields optional)
export type TodoInput = { title: string; note?: string | null };
export type TodoUpdate = Partial<TodoInput>;

// Field metadata (for type-safe queries)
export type TodoFields = "id" | "title" | "completed";
export type TodoNumericFields = "position";
export type TodoComparableFields = "id" | "title" | "createdAt";

// Path constants
export const ResourcePaths = { todo: "/api/todos", user: "/api/users" } as const;

// Public env type
export type PublicEnv = { PUBLIC_API_URL: string; PUBLIC_VERSION: string };

// Typed client factory
export function createTypedClient(baseClient): TypedCovaraClient;

Date columns are emitted as the branded ISODateString so the compiler nudges you toward toDate(...).

Typed client factory

import { getOrCreateClient } from "covara/client";
import { createTypedClient } from "./generated/api-types";

const client = createTypedClient(getOrCreateClient({ baseUrl: location.origin, credentials: "include" }));

const todos = await client.resources.todos.list(); // Todo[] — no type parameter needed
const result = await client.resources.todos.query().select("id", "title").filter("completed==false").list();
const stats = await client.resources.users.query().groupBy("role").withCount().avg("age").aggregate();

With React hooks, types are inferred from the typed resource:

import { useLiveList } from "covara/client/react";
const { items, mutate } = useLiveList(client.resources.todos, { orderBy: "position" }); // items: Todo[]