Model Manifest
Adding a model to your project requires remembering to add it to the sync list — a forgotten entry means silently missing tables and indexes. The generated model manifest removes that failure mode: dbPlugin({ manifest }) makes every full build emit an inventory module of all exported @db.table / @db.view entities, so startup code consumes a list that can never be out of date.
// atscript.config.mts
import { defineConfig } from "@atscript/core";
import ts from "@atscript/typescript";
import { dbPlugin } from "@atscript/db/plugin";
export default defineConfig({
rootDir: "src",
// Path is rootDir-relative → emits src/atscript.models.ts
plugins: [ts(), dbPlugin({ manifest: "atscript.models.ts" })],
format: "dts",
});// startup
import { atscriptModels } from "./atscript.models";
import { syncSchema } from "@atscript/db/sync";
await syncSchema(db, atscriptModels); // instead of a hand-maintained import arrayGenerated exports
The manifest lists each model exactly once and derives the aggregates:
export const dbTables = [User, Post] as const;
export const dbViews = [ActiveUsers] as const;
/** Every @db.table / @db.view model in this project. */
export const atscriptModels = [...dbTables, ...dbViews] as const;
/** Models grouped by @db.space (absent annotation → "default"). */
export const modelsBySpace = {
default: atscriptModels,
} as const;Selection rule: every export whose own declaration carries @db.table or @db.view — the same rule the asc db sync CLI uses for discovery. A @db.alias, a plain export type Admin = User and an interface that merely references a table are not entities. At runtime a @db.alias and an interface referencing a table carry no db.table metadata (since 0.1.141), and a plain alias is the table itself (since @atscript/typescript 0.2); isDbEntityType is the runtime form of the rule. Colliding export names across .as files are alias-deduplicated in the imports (import { User as User_1 } from …).
The manifest is an inventory, not an action
Nothing syncs automatically — you pass the arrays to syncSchema yourself, so full control stays at the call site:
- Exclude:
syncSchema(db, atscriptModels.filter(m => m !== LegacyTable))— an intentional exclusion is a visible, greppable filter, while doing nothing safely defaults to "synced". - Extend: models from external npm packages are outside your project's
.asbuild — append them:syncSchema(db, [...atscriptModels, AsPresetEntry]).
A partial inventory no longer drops what it forgot (since 0.1.128): if a table left out of the list is still referenced by a @db.rel.FK or a managed view of a model that is in the list, sync refuses the whole run before any DDL (Cannot drop "<table>": it is still referenced by …) instead of dropping the referenced table. The manifest remains the way to never get there.
Multi-database apps: @db.space
@db.space "analytics" on a model (interface-level, sibling of @db.schema) assigns it to a named space. Absent → "default". The manifest groups by it, so a mixed Mongo + Postgres startup is mechanical:
await syncSchema(mongo, modelsBySpace.default);
await syncSchema(pg, modelsBySpace.analytics);The same annotation drives token-based controller binding in @atscript/moost-db: @TableController(Model) resolves the named space from the ambient registry automatically. Moving a model between databases is a one-annotation change.
Guarding exposure completeness: assertExposed
The manifest guards sync completeness; assertExposed (from @atscript/moost-db) guards exposure completeness with the same input. After app.init():
const missing = assertExposed(app, atscriptModels); // default: audits models that declare @db.http.path in the schema
// Prefix-bound repos (@TableController(Model, 'db/x') everywhere):
const missing = assertExposed(app, atscriptModels, {
all: true, // every passed model must have a bound controller
exclude: [EmbeddingCache], // internal-on-purpose collections, greppable
});
if (missing.length && process.env.CI) throw new Error("unexposed models");Lazy-factory bindings can't name their model and will false-positive under all: true — list them in exclude. A @db.alias type in the list is skipped (since 0.1.141) — it is a join scope, not a model a controller serves.
DOs and DON'Ts
- DO commit the generated file — it is imported by your startup code and regenerates on every full build (
npx asc -f dts). - DON'T hand-edit it; edits are overwritten on the next build.
- DO remember the
manifestpath resolves relative to the config'srootDir, not the package root — withrootDir: "src","atscript.models.ts"emitssrc/atscript.models.ts(asrc/prefix would emitsrc/src/…). - DON'T worry about narrowed special-purpose builds (like the
asc db synctemp compile) — manifest generation only runs for fulldts/default-format builds, so they never shrink a committed manifest.
See also
- Programmatic API —
syncSchema,planSchema, sync options - CLI —
asc db syncand its discovery rules - Annotations Reference —
@db.table,@db.view,@db.space