Skip to content

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.

ts
// 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",
});
ts
// startup
import { atscriptModels } from "./atscript.models";
import { syncSchema } from "@atscript/db/sync";

await syncSchema(db, atscriptModels); // instead of a hand-maintained import array

Generated exports ​

The manifest lists each model exactly once and derives the aggregates:

ts
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 .as build — 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:

ts
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():

ts
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 manifest path resolves relative to the config's rootDir, not the package root — with rootDir: "src", "atscript.models.ts" emits src/atscript.models.ts (a src/ prefix would emit src/src/…).
  • DON'T worry about narrowed special-purpose builds (like the asc db sync temp compile) — manifest generation only runs for full dts/default-format builds, so they never shrink a committed manifest.

See also ​

Released under the MIT License.