@lunora/codegen
Emits _generated/{api,server,dataModel}.ts from your schema.ts.
@lunora/codegen reads your backend and writes the types that make it
type-safe end to end. It parses lunora/schema.ts plus every function file
under lunora/, then writes lunora/_generated/ so the rest of your app gets
typed access to its own queries, mutations, actions, and tables.
You rarely call this package directly. The @lunora/vite
plugin runs it on every schema change, and the lunora codegen
command runs it once on demand. Both share this same core, so the output is
identical regardless of how it was produced.
What it generates
The core outputs, always written:
api.ts— the typedapi.<file>.<function>(andinternal.*) function references your client and server code call through.server.ts— thequery/mutation/action/internalQuery/internalMutation/internalAction/definePolicyprocedure builders, bound to your schema.dataModel.ts— theDoc<"messages">/Id<"users">types derived from your tables.
It also emits app.ts, functions.ts, shard.ts, and Drizzle schemas, plus —
only when you use the matching feature — crons.ts, containers.ts,
workflows.ts, vectors.ts, seed.ts, and the OpenAPI / OpenRPC spec files.
When it runs
// vite.config.ts — codegen runs on startup and on every schema change
import { lunora } from "@lunora/vite";
export default defineConfig({ plugins: [lunora()] });lunora codegen # one-shot
lunora codegen --api-spec both # also emit OpenAPI + OpenRPCUnder lunora dev, codegen runs in watch mode: save a function or change the
schema and the regenerated types are in the module graph before you switch
windows.
Don't edit _generated/
These files are derived from your schema and overwritten on every run, so any
hand edit is lost the next time codegen runs. Change schema.ts or your
function files instead, then let codegen catch up.
Commit lunora/_generated/. The output is deterministic, and CI re-runs codegen
and diffs the result (lunora verify / lunora prepare) to catch drift between
your schema and the committed types.
The .js extension exception
The emitted modules are consumed under NodeNext, where relative imports need a
file extension. So codegen deliberately writes .js into the imports it
generates:
import { api } from "./_generated/api.js";This is the one place in a Lunora project where hand-written .js extensions
are correct. Your own lunora/ source still omits extensions like the rest of
the codebase.
Calling it programmatically
If you need codegen in your own tooling, runCodegen takes a single options
object and returns the result, including any static schema advisories found this
run:
import { formatAdvisories, runCodegen } from "@lunora/codegen";
const result = runCodegen({ projectRoot: process.cwd() });
console.log(`wrote _generated/ -> ${result.outputDirectory}`);
if (result.advisories.length > 0) {
console.warn(formatAdvisories(result.advisories));
}Common options:
projectRoot(required) — the directory containing thelunora/folder.dryRun— run discovery and emit (so parse errors still surface) without writing files.outputDirectoryis still the path that would have been written.lint— run the static schema advisor (defaulttrue); whenfalse,result.advisoriesis empty.apiSpec—"openapi"(default),"openrpc","both", or"none".lunoraDirectory— override thelunorasubdirectory name.
See also
- @lunora/vite — runs codegen on schema changes during dev
- @lunora/cli — the
lunora codegencommand - @lunora/values — the
v.*validators the parser understands