Entity - описание таблицы, а не доказательство того, что типы TypeScript и PostgreSQL совпадают. Деньги, идентификаторы и даты задают явно и проверяют на реальном драйвере.
Колонки явно
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('products')
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 200 })
name: string;
@Column({ type: 'numeric', precision: 12, scale: 2 })
price: number;
@Column({ type: 'timestamptz' })
createdAt: Date;
}
precision ограничивает общее число цифр, scale - цифры после запятой. timestamptz хранит момент времени, а не локальную дату без часового пояса.
numeric и bigint приходят строками
PostgreSQL-драйвер возвращает numeric и bigint как строки, чтобы не потерять точность JavaScript-числа. TypeORM не делает их безопасными автоматически.
Для денег можно сохранить копейки в bigint и преобразовывать их в transformer:
const money = {
to: (kopecks: number): number => kopecks,
from: (raw: string): number => Number(raw),
};
Если значение не помещается в безопасный диапазон Number, используют BigInt или decimal-библиотеку. Сначала выбирают доменную модель, затем transformer, а не наоборот.
Проверять на PostgreSQL
Тест на реальной базе должен показать тип и значение после чтения. Сравнение price === 4990 ловит строку "4990.00" сразу, а вот арифметическая ошибка округления видна только на границах.
Не используйте float для денег. Для UUID выбирайте uuid, для JSONB - jsonb, но не складывайте туда поля, по которым нужны индексы и ограничения без продуманной модели.
Коротко
- Тип колонки задают явно и сверяют с доменом.
numericиbigintобычно требуют преобразования из строки.- Деньги хранят в копейках или decimal-типе, не во float.
timestamptzхранит момент времени.- Типы проверяют на PostgreSQL, а не по одному виду entity.
Что почитать дальше
- Relations и lazy loading - где заканчивается одна entity.
- Миграции - как тип колонки меняют без потери данных.