Metadata is the source of truth consumed by validation, relation resolution, serialization, audit, schema checks and type inference.
export const users = defineTable({
name: 'users',
collectionName: 'app_users',
columns: [/* ... */],
relations: [],
tableValidators: [],
indexes: [],
audit: { enabled: true, excludedFields: ['password'] }
})
const users: TableMetadata = {...} when you need inference. That
annotation widens "users", field names and column types to generic strings.interface TableMetadata {
name: string
collectionName: string
columns: ColumnMetadata[]
relations: RelationMetadata[]
tableValidators: TableValidatorMetadata[]
indexes?: IndexMetadata[]
audit?: AuditMetadata
}
Logical names are used by the engine; physical names are used by the adapter.
| Metadata type | Runtime and inferred type |
|---|---|
uuid, string, text, date | string |
integer | number |
bigint | bigint |
boolean | boolean |
timestamp | Date |
decimal | string | number |
json | unknown |
A nullable column becomes T | null. hidden: true removes the field from the
inferred result. A field using a dynamic visibility resolver becomes optional.
type User = InferTableEntity<typeof users>
{
name: 'createdAt',
columnName: 'created_at',
type: 'timestamp',
nullable: false,
primaryKey: false,
unique: false,
default: () => new Date(),
validators: []
}
Defaults are applied for missing creation fields before before-create hooks. Validators may be synchronous or asynchronous.
indexes: [
{
name: 'applications_tenant_status_idx',
fields: ['tenantId', 'status'],
unique: false
}
]
Index fields use logical column names and preserve order. Registry validation rejects empty indexes, duplicate names and unknown fields.
const registry = createMetadataRegistry(users, posts, applications)
registry.lock()
The registry carries its table map into QueryEngine. TypeScript then restricts
table names and infers return types from the selected table.
The registry validates:
audit.excludedFields.lock() aggregates validation errors and makes registration immutable.