Docs / Models
Models
One file defines the table, the row type, the validator.
A model is one file. It defines the table, the validation, the TypeScript row type, and (optionally) the REST endpoints.
// app/models/post.ts
import { model, text, boolean, belongsTo } from '@hopak/core';
export default model('post', {
title: text().required().min(3).max(200),
content: text().required(),
published: boolean().default(false),
author: belongsTo('user'),
});
Field types
| Type | Notes |
|---|---|
text() | Free-form string (use .min/.max/.pattern to constrain) |
email() | String with email-format validation |
url() | String with URL-format validation |
phone() | String — no built-in regex; add .pattern(...) for strict formats |
number(), money() | Numbers with min/max (money stored as real) |
boolean() | Scalar |
date(), timestamp() | Coerced from ISO strings; rejects invalid dates |
enumOf('a', 'b') | TypeScript literal union, DB enum |
json<T>() | Typed JSON column |
belongsTo('user'), hasOne('profile'), hasMany('post') | Relations |
password(), secret(), token() | Auto-excluded from JSON responses |
file(), image() | Stored as JSON metadata { url, mimeType, size, name? } |
Modifiers
Chain on any field:
text().required().min(3).max(200).unique().index()
number().required().min(0).max(100).default(0)
text().pattern(/^[a-z]+$/)
date().default('now')
file().maxSize('5MB')
image().maxSize(2_097_152) // bytes also accepted
.required()/.optional()— presence.unique()/.index()— DB constraints / indexes.min(n)/.max(n)— string length or number range.default(value)— default value (special token'now'fordate/timestamp).pattern(regex)— regex constraint on strings.maxSize(n)— max bytes onfile()/image(); accepts'5MB'/'500KB'/ raw bytes.onUpdate('now')— ondate/timestamp; currently a seed-on-create helper (same as.default('now')). DB-levelON UPDATEis not emitted yet — rely ontimestamps: truefor automaticupdatedAtrefresh.
Options
model('post', { /* fields */ }, {
timestamps: true, // add createdAt + updatedAt columns (default: true)
});