Моделі
Один файл визначає таблицю, тип рядка та валідатор.
Модель — це один файл. Він визначає таблицю, валідацію, TypeScript-тип рядка і (опційно) REST-ендпоїнти.
// 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'),
});
Типи полів
| Тип | Примітки |
|---|---|
text() | Довільний рядок (обмежуйте через .min/.max/.pattern) |
email() | Рядок із валідацією email-формату |
url() | Рядок із валідацією URL-формату |
phone() | Рядок — вбудованого regex немає; для суворих форматів додайте .pattern(...) |
number(), money() | Числа з min/max (money зберігається як real) |
boolean() | Скаляр |
date(), timestamp() | Приводяться з ISO-рядків; відхиляють некоректні дати |
enumOf('a', 'b') | Літеральний union у TypeScript, enum у БД |
json<T>() | Типізована JSON-колонка |
belongsTo('user'), hasOne('profile'), hasMany('post') | Зв’язки |
password(), secret(), token() | Автоматично виключаються з JSON-відповідей |
file(), image() | Зберігаються як JSON-метадані { url, mimeType, size, name? } |
Модифікатори
Ланцюжок на будь-якому полі:
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) // у байтах також приймається
.required()/.optional()— наявність.unique()/.index()— DB-обмеження / індекси.min(n)/.max(n)— довжина рядка або діапазон числа.default(value)— значення за замовчуванням (спеціальний токен'now'дляdate/timestamp).pattern(regex)— regex-обмеження для рядків.maxSize(n)— максимум байтів дляfile()/image(); приймає'5MB'/'500KB'/ число байтів.onUpdate('now')— наdate/timestamp; наразі виставляє default-значення (те саме що.default('now')). DB-levelON UPDATEне емітиться — автоматичне оновленняupdatedAtпокриваєтьсяtimestamps: true.
Параметри
model('post', { /* fields */ }, {
timestamps: true, // add createdAt + updatedAt columns (default: true)
});
Хуки життєвого циклу
Додано у 1.0
Хуки виконуються навколо однорядкових записів, звідки б ті не приходили — з CRUD-ендпоїнтів чи Ваших власних викликів ctx.db.model(...). Хуки before* можуть повернути замінений payload; хуки after* призначені для побічних ефектів:
// app/models/user.ts
import { model, text, email, password } from '@hopak/core';
export default model('user', {
name: text().required(),
email: email().required().unique(),
password: password().required().min(8),
}, {
hooks: {
async beforeCreate(data) {
return { ...data, password: await Bun.password.hash(String(data.password)) };
},
afterCreate(row) {
console.log(`user #${row.id} signed up`);
},
},
});
Доступні: beforeCreate, afterCreate, beforeUpdate(data, id), afterUpdate, beforeDelete(id), afterDelete(id). Bulk-операції (createMany / updateMany / deleteMany) пропускають хуки — вони транслюються в один SQL-statement.
Хешуєте паролі? Беріть
hashPasswordз@hopak/auth, а неBun.password.hashнапряму. Він пропускає значення, які вже є хешами, тож хук іcredentialsSignupможуть працювати разом без подвійного хешування — подвійний хеш ніколи не збігається під час входу.
Назви таблиць
Додано у 1.0
Фізична таблиця — це назва моделі у множині (post → posts), доступна як post.tableName. Вона потрібна щоразу, коли Ви пишете SQL вручну — див. База даних → Назви таблиць у сирому SQL.