QUESTPIE
Code

Codegen

Codegen scans your convention directories and writes one typed app into .generated/. Run it after adding a file, and the app object, the routes and the types all match what is on disk.

View markdown

You drop a file in collections/. Nothing imports it. It still turns up on app.collections. Codegen is what closes that gap. This page is how it works, and the commands that drive it.

Run it

questpie generate

The command reads questpie.config.ts and scans the directories beside it. Codegen follows a root config that only re-exports a deeper one. The starters do that, so they scan beside src/questpie/server/questpie.config.ts. The #questpie and #questpie/factories imports in your code resolve into .generated/. Before the first run they do not exist. That is why a fresh clone and a CI build both start here.

Write a file:

src/questpie/server/collections/invoices.ts
import { collection } from "#questpie/factories";

export const invoices = collection("invoices").fields(({ f }) => ({
	number: f.text(40).required(),
	total: f.number(),
}));

Generate, then use it:

questpie generate
import { app } from "#questpie";

const { docs } = await app.collections.invoices.find({});

No import list. No manifest. The directory is the registry.

What it reads

Each of these is a directory beside questpie.config.ts. Put a file in one and it is found on the next run.

DirectoryLands onRule
collections/app.collectionsthe file must call collection()
globals/app.globalsthe file must call global()
channels/ctx.channelsthe file must call channel()
routes/, functions/app.routes, an HTTP endpointsubfolders are scanned too
jobs/ctx.queue.<key>one job per file
services/ctx.services.<key>one service per file
emails/ctx.email.sendTemplateone template per file
fields/f.<name> in .fields()the file must call fieldType()
messages/backend translationsone file per locale, en.ts
migrations/, seeds/the lists the CLI runsone per file

A handful of single files are read the same way: modules.ts, env.ts, env.client.ts, fields.ts, config/app.ts and config/auth.ts. Modules add their own, so @questpie/admin also brings views/, blocks/, components/ and config/admin.ts.

What it writes

FileWhat it holds
.generated/index.tsThe shared app instance, env and the public types
.generated/app-factory.tscreateAppForRuntime(), one fresh app per call
.generated/factories.tscollection() and global(), wired to your enabled field types
.generated/entities.gen.tsThe flat category maps, AppCollections and friends
.generated/context.gen.tsThe AppContext shape and the session types
.generated/names.gen.tsThe key registries that make relation targets autocomplete

app-factory.ts is for tests. createAppForRuntime(runtime) builds a new app from the runtime config you pass it. index.ts shares one app, so every import of #questpie gets the same one. Import from #questpie/app-factory.

Import collection and global from #questpie/factories. Import everything else from #questpie. Run codegen after a schema change and commit the result. .generated/ belongs in git. It is build output, but your code imports it, so a teammate, CI and your editor all resolve it with no build step first.

Codegen owns the output directory

Each run that writes deletes .generated/ and recreates it. A file you put there by hand is gone on the next generate. Each file is written to a temp path and renamed, so a killed run never leaves a truncated file behind.

What becomes the key

collection("invoices") is keyed as invoices. The string you pass the factory is the key, whatever the file is called. Hyphens become camelCase. Underscores stay put. So collection("barber_services") in collections/barber-services.ts keys as barber_services.

Files without a factory call are keyed off the file name instead. jobs/send-newsletter.ts is sendNewsletter, default export or named.

Codegen skips index.ts, *.d.ts, test and spec files, and anything starting with an underscore. So a _helpers.ts sitting next to your collections is invisible.

Keep it running

questpie dev

Watch mode. It regenerates when a file is added or removed, and when the config changes. Editing the body of a collection does nothing. The generated file imports your module by path, and that import line does not change when the file's contents do. Both commands take -c for a config path and --verbose. generate also takes --dry-run to print the output instead of writing it.

Scaffold a file

questpie add collection invoice   # writes collections/invoice.ts, then generates
questpie add --list               # every type, and which target provides it

The built-in types are collection, global, channel, job, service, email, route, seed and migration. Modules add more. When two targets declare the same type, questpie add writes a file in both. So questpie add block hero gives you the server definition and the admin renderer at once. Pass --target to pick one. An existing file is skipped with a warning.

Modules bring their own directories

modules.ts lists the packages your app depends on. Codegen reads it before anything else, because a module can carry its own codegen plugin. That plugin is what adds views/, blocks/ and config/admin.ts when you install @questpie/admin.

src/questpie/server/modules.ts
import { adminModule } from "@questpie/admin/modules/admin";
import { openApiModule } from "@questpie/openapi";

const modules = [adminModule, openApiModule] as const;
export default modules;

You do not register the plugin. Installing the module is enough. The plugin key is read at codegen time and dropped before the runtime merge, so it never reaches the app.

Types you import from #questpie

import type { CollectionDoc, CollectionWhere, App, AppConfig } from "#questpie";

type Invoice = CollectionDoc<"invoices">;
type InvoiceFilter = CollectionWhere<"invoices">;
TypeUse it for
CollectionDoc<K>One row of collection K
CollectionWhere<K>A where clause you build before calling find
GlobalDoc<K>One row of global K
AppConfigThe client APIs, as in createClient<AppConfig>()
AppThe typed app instance, typeof app

#questpie also exports createContext(). It builds a typed AppContext for scripts and tests. AppSession and AppSessionUser come from there too.

Generate before you migrate, seed or push

A CLI command resolves .generated/index.ts for the real app instance. When that file is missing it tells you to run questpie generate first. A stale .generated/ is the usual reason a new collection is not on app.collections.

Where each topic lives

TopicPage
Every rule that turns a file into an entityDiscovery
The plugin contract that adds a conventionPlugins
Writing a plugin, start to finishBuilding a plugin
Generating a module inside an npm packagePublishing
questpie.config.ts, config/app.ts, modules.tsConfiguration
env.ts and env.client.tsEnvironment

Next

Modules is the bundle side of this. A module ships collections, routes and jobs, and codegen folds them in before it scans a single directory of yours.

On this page