These types describe the serialized, JSON-able form of a Damat schema. They are
produced by the fluent builders in @damatjs/orm-model and consumed by the
registry (@damatjs/orm-core), the migration/DDL engine, and
@damatjs/schema-codegen. Editing
anything here ripples through every one of those packages.
All types in this group are exported from src/model/index.ts and re-exported by
the package root.
ColumnType and ColumnSchema (column.ts)
ColumnType is a string-literal union of ~80 PostgreSQL type names, grouped by
family (numeric, monetary, character, binary, date/time, boolean, enum,
geometric, network, bit string, text-search, UUID, XML, JSON, range, multirange,
object-identifier/system). The literals are the exact SQL names —
"character varying", "timestamp without time zone", "double precision",
"bit varying", etc.
export interface ColumnSchema {
name: string;
type: ColumnType;
primaryKey?: boolean;
length?: number; // varchar/char length or numeric precision
dimensions?: number; // native VECTOR/HALFVEC dimensionality
scale?: number; // numeric digits after the decimal point
nullable: boolean; // required — always set by builders
default?: any; // default value *expression* (already SQL-quoted)
unique?: boolean;
enum?: string; // named enum type reference (matches EnumSchema.name)
array?: boolean; // array column (e.g. text[])
fieldName?: string; // DB column name when it differs from the property name
autoincrement?: boolean; // serial/bigserial/smallserial
}
Notes / gotchas:
lengthis reserved for varchar/char length and numeric precision. Native pgvector columns store their dimensionality indimensions;scaleis numeric-only.defaultis an expression string (e.g."now()","generate_id('usr')","'active'"). String literal defaults arrive already single-quoted from the builder — do not re-quote when generating DDL.enumholds the name of anEnumSchema, not the values. The values live on theEnumSchemadeclared at module level.
TableSchema (table.ts)
export interface TableSchema {
name: string;
columns: ColumnSchema[];
indexes?: IndexSchema[];
foreignKeys?: ForeignKeySchema[];
constraints?: ConstraintSchema[];
relations?: RelationSchema[]; // ORM metadata only — no DB artifact
}
relations is pure ORM metadata (the relationship graph) and produces no DDL.
toModuleSchema() in @damatjs/orm-model strips it out into the module-level
relationships array, which is why ModuleSchema.tables uses
Omit<TableSchema, "relations">.
ModuleSchema (module.ts)
export interface ModuleSchema {
schema?: string; // PG schema name, default "public"
moduleName: string;
tables: Omit<TableSchema, "relations">[];
enums?: EnumSchema[];
relationships?: RelationSchema[]; // all relations hoisted from every table
extensions?: string[]; // required PostgreSQL extensions (for example vector)
}
This is the top-level snapshot for a module (a group of related tables). It is
what gets serialized to module.snap.json, fed to the migration diff engine, and
passed to schema generation.
EnumSchema (enum.ts)
export interface EnumSchema {
schema?: string;
name: string; // CREATE TYPE <name> AS ENUM (...)
values: string[];
}
ForeignKey* (foreignKey.ts)
export type ForeignKeyAction =
"CASCADE" | "SET NULL" | "SET DEFAULT" | "RESTRICT" | "NO ACTION";
export type ForeignKeySchemaMatch = "SIMPLE" | "FULL";
export type ForeignKeyType = { name: string; type: ColumnType };
export interface ForeignKeySchema {
name: string;
columns: ForeignKeyType[]; // FK columns on THIS table (composite-aware)
referencedTable: string;
referencedColumns: string[];
onDelete?: ForeignKeyAction;
onUpdate?: ForeignKeyAction;
deferrable?: boolean;
initiallyDeferred?: boolean;
match?: ForeignKeySchemaMatch;
unique?: boolean; // 1:1 marker
nullable?: boolean;
indexed?: boolean;
}
Key point: columns is ForeignKeyType[] (each carries its own SQL type), not a
bare string[]. This lets the DDL generator emit the FK column with the correct
type, and supports composite keys by construction (everything is an array).
Constraints (constrain.ts)
export type ConstraintType = "unique" | "primary_key" | "check" | "exclude";
export interface Constraint {
name?: string;
type: ConstraintType;
where?: string; // partial constraint condition
deferrable?: boolean;
initiallyDeferred?: boolean;
}
The four concrete shapes (UniqueConstraint, PrimaryKeyConstraint,
CheckConstraint, ExcludeConstraint) narrow type and add their own payload
(columns, condition, or expressions + indexType). ConstraintSchema is
their discriminated union. Narrow on .type before accessing kind-specific
fields.
Indexes (indexType.ts)
export type IndexType =
| "btree" | "hash" | "gin" | "gist" | "brin" | "hnsw" | "ivfflat";
export type VectorOperatorClass =
| "vector_l2_ops" | "vector_cosine_ops" | "vector_ip_ops" | "vector_l1_ops"
| "halfvec_l2_ops" | "halfvec_cosine_ops" | "halfvec_ip_ops" | "halfvec_l1_ops";
export type IndexColumn =
| { name: string; expression?: never; operatorClass?: VectorOperatorClass; order?: "ASC" | "DESC" }
| { name?: never; expression: string; operatorClass?: VectorOperatorClass; order?: "ASC" | "DESC" };
export interface IndexSchema {
name?: string;
columns: (string | IndexColumn)[]; // strings are normalised to IndexColumn
unique?: boolean;
type?: IndexType;
where?: string; // partial index
concurrently?: boolean; // CREATE INDEX CONCURRENTLY
with?: Record<string, string | number | boolean>; // storage parameters
}
columns accepts bare strings or IndexColumn objects; the model layer's
cleanupIndexSchema normalises strings to { name }.
Relations (relation.ts)
This file holds both the snapshot relation type and the builder option payloads.
export type RelationType = "belongsTo" | "hasMany" | "hasOne";
// Builder option payloads (inputs to the @damatjs/orm-model fluent API):
export interface RelationOptions {
mappedBy?: string;
}
export interface LinkConfig {
name?: string;
foreignKey?: string | string[] | ForeignKeyType | ForeignKeyType[];
reference?: string | string[];
}
export interface ConstraintOptions {
name?: string;
onDelete?: ForeignKeyAction;
onUpdate?: ForeignKeyAction;
deferrable?: boolean;
initiallyDeferred?: boolean;
match?: ForeignKeySchemaMatch;
}
// Snapshot relation record (module-level metadata):
export interface RelationSchema {
fromTable: string; // source table
from: string; // source property name
to: string; // target table
type: RelationType;
mappedBy?: string[]; // inverse property name(s) on the other side
linkedBy?: string[]; // FK column name(s) — populated on belongsTo only
rule?: {
onDelete?: ForeignKeyAction;
onUpdate?: ForeignKeyAction;
deferrable?: boolean;
initiallyDeferred?: boolean;
match?: ForeignKeySchemaMatch;
};
}
RelationSchema is not the FK constraint record — that is ForeignKeySchema.
RelationSchema is the graph view: who points at whom, by which property, via
which FK column. linkedBy only appears on belongsTo entries (the side that
physically owns the FK). The TODO comment in source flags that this record is
slightly repetitive — leave the shape stable; downstream validators
(checkBelongsToSchema / checkInverseSchema in orm-model) match on type,
fromTable, and to.
Editing checklist
- Add a
ColumnTypeliteral → add apgTypeToTsBasecase in orm-model. - Change
ColumnSchema/TableSchema/ModuleSchema→ rebuild orm-model (toSchema/toTableSchema/toModuleSchema) and the migration DDL generator. - Change
RelationSchema→ update orm-model'stoRelationSchema()emitters and the schema-level validators. - Regenerate downstream snapshots if a shape changes (orm-model has
bun run snapshot).