Codegen
What questpie generate produces — the .generated/ directory, AppConfig, AppContext, and type augmentation.
Running questpie generate scans your file conventions and produces the .generated/ directory. This is the bridge between your definitions and the typed runtime.
What Gets Generated
.generated/
├── index.ts — App instance, type exports, createContext helper
└── module.ts — Merged module with all entitiesGenerated Types
// Auto-generated — do not edit
// Entity type maps
export type AppCollections = {
appointments: typeof appointments;
barbers: typeof barbers;
services: typeof services;
// ...
};
export type AppGlobals = {
siteSettings: typeof siteSettings;
};
export type AppRoutes = {
createBooking: typeof createBooking;
getActiveBarbers: typeof getActiveBarbers;
// ...
};
export type AppJobs = {
sendAppointmentConfirmation: typeof sendAppointmentConfirmation;
notifyBlogSubscribers: typeof notifyBlogSubscribers;
// ...
};
export type AppServices = {
blog: BlogService;
};
export type AppEmails = {
appointmentConfirmation: typeof appointmentConfirmationEmail;
// ...
};
// Flat config type for client SDK
export type AppConfig = {
collections: AppCollections;
globals: AppGlobals;
routes: AppRoutes;
// auth, locales, etc.
};Module Augmentation
Codegen augments AppContext so every handler gets typed DI:
declare global {
namespace Questpie {
interface AppContext {
db: Database;
email: MailerService<AppEmailTemplates>;
queue: QueueClient<AppJobs>;
collections: AppCollections;
globals: AppGlobals;
session: Session | null;
blog: BlogService; // Custom service
// ... all services
}
}
}This is why you can destructure { collections, queue, email, blog } in any handler — codegen generates the type augmentation from your file structure.
App Instance
export const app = createApp(mergedModule, runtimeConfig);
export async function createContext(options?: {
accessMode?: "system" | "user";
locale?: string;
}) {
return app.createContext(options);
}How to Use Generated Types
Client SDK
import type { AppConfig } from "#questpie";
export const client = createClient<AppConfig>({
baseURL: "http://localhost:3000",
basePath: "/api",
});Server-side scripts
import { app, createContext } from "#questpie";
const ctx = await createContext({ accessMode: "system" });
const posts = await app.collections.posts.find({}, ctx);Typed admin hooks
import { createTypedHooks } from "@questpie/admin/client";
import type { AppConfig } from "#questpie";
export const {
useCollectionList,
useCollectionItem,
useCollectionCreate,
useCollectionUpdate,
useCollectionDelete,
useGlobal,
useGlobalUpdate,
} = createTypedHooks<AppConfig>();When to Re-Run Codegen
Re-run questpie generate when you:
- Add, rename, or delete collection/global/route/job files
- Add or remove modules in
modules.ts - Add or remove plugins in
questpie.config.ts - Change file convention structure
You do not need to re-run codegen when you:
- Change field options (label, required, default)
- Modify hook logic
- Update access control rules
- Change runtime config values
The #questpie Alias
The #questpie import in collection/global files resolves to a generated helper that provides typed builders:
import { collection } from "#questpie";
// Provides field builder with autocompletion for relation targets, etc.How Discovery Works
Codegen starts by scanning your project directories for files matching known conventions. Each entity category has a declared directory and file pattern:
| Category | Directory | Pattern | Key Strategy |
|---|---|---|---|
collections | collections/ | *.ts | Filename → camelCase |
globals | globals/ | *.ts | Filename → camelCase |
routes | routes/ | *.ts | Filename → camelCase |
jobs | jobs/ | *.ts | Filename → camelCase |
services | services/ | *.ts | Filename → camelCase |
emails | emails/ | *.ts | Filename → camelCase |
seeds | seeds/ | *.seed.ts | Filename → camelCase |
blocks | blocks/ | *.ts | Filename → camelCase |
Files prefixed with _ (e.g., _helpers.ts) are ignored — use them for shared utilities within a category directory.
Discovery also excludes .d.ts and .d.mts files to avoid picking up stale declaration artifacts.
Plugin System
Codegen is extensible through plugins. Each plugin can contribute category declarations, modify generated output, or add entirely new generated files.
Core Plugins
| Plugin | What It Does |
|---|---|
coreCodegenPlugin | Discovers collections, globals, routes, jobs, services, emails, seeds |
adminPlugin | Discovers admin views, blocks, components, fields, branding |
Category Declarations
Plugins declare categories with a CategoryDeclaration object:
{
name: "collections",
dirs: ["collections"],
prefix: "collection",
registryKey: "collections",
keyFromProperty: undefined, // Uses filename-based keys
extensions: [".ts", ".mts"],
}Key properties:
dirs: Which directories to scanprefix: Used in generated variable namesregistryKey: The key in the module registry (maps toAppCollections, etc.)keyFromProperty: If set, reads the key from a property of the exported value (e.g., views usenameproperty). Otherwise, derives the key from the filename.
Running Codegen
# One-time generation
bunx questpie generate
# Watch mode — re-generates on file changes
bunx questpie devDevelopment Workflow
In development, use questpie dev which watches for file changes and re-runs codegen automatically:
bunx questpie devThis watches for:
- New, renamed, or deleted files in convention directories
- Changes to
modules.tsorquestpie.config.ts - Plugin configuration changes
Field option changes, hook logic changes, and access control rule changes do not require re-running codegen.
Related Pages
- File Convention — What files are discovered
- CLI — CLI commands
- Type Inference — End-to-end type flow
- Plugins — Plugin architecture