TypeScript Type Tree
Lightweight, branded table and action types generated directly from Quickback's compiler model.
Every compile emits quickback.gen.ts: a compact, type-only tree designed for application code. It is generated directly from the same compiler model that drives routes and OpenAPI, so there is no runtime package and no code to initialize.
import type { Api, Qb } from "../quickback.gen";
type Application = Api.applications.Row;
type NewApplication = Api.applications.Insert;
type ApplicationPatch = Api.applications.Update;
type ApplicationId = Api.applications.Id;
type AdvanceInput = Api.applications.actions.advance.Input;
type AdvanceOutput = Api.applications.actions.advance.Output;
type PipelineItem = Api.applications.views.pipeline.Item;
type PipelineResponse = Api.applications.views.pipeline.Response;
type Page = Qb.Page<Application>;
type ApiProblem = Qb.Problem;Feature areas preserve their authored hierarchy:
type Booking = Api.events.travel.bookings.Row;Table types
Each resource table exposes:
| Member | Meaning |
|---|---|
Id | Nominal table-specific primary-key type |
Row | Complete selected wire shape |
Insert | Createable fields, with defaults and nullable fields optional |
Update | Updatable fields, all optional |
ListResponse | Qb.Page<Row> |
columns.<name> | Type of one row column |
views.<name>.Item | A view's projected row |
views.<name>.Response | Page plus typed aggregations where declared |
actions.<name>.Input | The action's Zod input contract |
actions.<name>.Output | The declared Zod output contract, or unknown |
Primary and foreign keys are branded by default. That makes accidentally passing a candidate ID where a job ID is required a TypeScript error, while both values remain ordinary strings or numbers at runtime.
function loadJob(id: Api.jobs.Id) {}
declare const candidateId: Api.candidates.Id;
loadJob(candidateId); // TypeScript errorAt an untyped boundary, validate the value and cast it once:
const jobId = routeParams.id as Api.jobs.Id;Reading collections
List and view responses share one envelope: rows under data, pagination
metadata under pagination, and view naming the projection behind the
response (null for the default collection read).
declare const res: Api.applications.ListResponse; // Qb.Page<Api.applications.Row>
res.data; // Api.applications.Row[]
res.view; // string | null
res.pagination.count; // rows in this page
res.pagination.hasMore;
res.pagination.nextCursor; // pass back as ?starting_after=
res.pagination.total; // number | undefined — only when ?total=truetotal and totalPages are counted only when the request passes
?total=true, so both are optional; every other member is always present. A
view that declares aggregations intersects this shape with a typed
aggregations object, and an aggregate-only view returns { view, aggregations } with no rows or pagination.
Declaring action outputs
Add an optional output Zod schema to make an action end-to-end typed:
export default defineAction({
description: "Advance an application",
input: z.object({
nextStatus: z.enum(["screening", "interview", "offer"]),
}),
output: z.object({
id: z.string(),
status: z.enum(["screening", "interview", "offer"]),
}),
access: { roles: ["recruiter+"] },
async execute({ record, input }) {
return { id: record.id, status: input.nextStatus };
},
});The generated feature helper checks execute against the declared output. The CLI harvests both schemas locally without invoking execute; the resulting contract feeds quickback.gen.ts, OpenAPI, and api-types.gen.ts.
Configuration
The artifact is on by default:
export default defineConfig({
// Default: quickback.gen.ts
types: true,
});Use a custom path, fan out identical copies to colocated apps, disable branding, or turn the artifact off:
export default defineConfig({
types: {
output: [
"quickback.gen.ts",
"apps/web/src/lib/quickback.gen.ts",
],
brandIds: true,
},
});types value | Behavior |
|---|---|
true or omitted | Write quickback.gen.ts |
false | Do not emit the first-party type tree |
"src/lib/quickback.gen.ts" | Write to one custom path |
["quickback.gen.ts", "apps/web/src/quickback.gen.ts"] | Fan out identical copies |
{ output, brandIds: false } | Configure paths and use structural IDs |
api-types.gen.ts remains available through openapi.types for openapi-fetch and third-party OpenAPI tools. The two artifacts are independent: disabling OpenAPI types does not disable the first-party tree.
The compiler also emits reports/types.parity.gen.ts when both artifacts exist. Quickback's output-matrix CI compiles that proof separately to require mutual assignability between direct table/action types and the OpenAPI wire types; it is outside the generated application's src/**/* build.