Преобразование схемы в тип
Узнайте, как определения таблиц становятся типами TypeScript
«Преобразование схемы в тип» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.
От определения таблицы к типу строки
Типобезопасная ORM позволяет один раз описать таблицу базы данных и автоматически получить тип строки на TypeScript. Вам не нужно вручную описывать структуру строки: она вычисляется из определений столбцов.
В этом уроке мы используем pgTable в стиле Drizzle в качестве основного примера.
Определение таблицы
Таблица — это объект, сопоставляющий имена столбцов с построителями столбцов. Каждый построитель кодирует тип SQL и такие модификаторы, как notNull или primaryKey.
import { pgTable, serial, text, integer, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
age: integer("age"),
isAdmin: boolean("is_admin").notNull(),
});Вывод типов столбцов
Каждый построитель столбца содержит фантомный тип, описывающий создаваемое им значение. serial и integer соответствуют типу number, а text — типу string, boolean — типу boolean.
Построитель также отслеживает, допускает ли столбец значение null.
import { InferSelectModel } from "drizzle-orm";
// Row type inferred from the table above
type User = InferSelectModel<typeof users>;
// {
// id: number;
// name: string;
// age: number | null; // not notNull -> nullable
// isAdmin: boolean;
// }notNull управляет допустимостью NULL
Самым важным фактором, определяющим тип строки, является notNull(). Столбец WITHOUT него становится T | null в выведенном типе, поскольку SQL по умолчанию допускает NULL.
const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(), // string
subtitle: text("subtitle"), // string | null
});
type Post = InferSelectModel<typeof posts>;
// { id: number; title: string; subtitle: string | null }Модели для select и insert
Тип строки для чтения (select) отличается от типа для записи (insert). При insert столбцы со значениями по умолчанию или автоинкрементом становятся необязательными.
import { InferInsertModel } from "drizzle-orm";
type NewUser = InferInsertModel<typeof users>;
// {
// id?: number; // serial has a default -> optional
// name: string;
// age?: number | null;
// isAdmin: boolean;
// }Как работает выведение типов
Внутри каждый построитель столбца является обобщённым классом, например PgColumn<{ data: number; notNull: true }>. Отображаемый тип проходит по каждому ключу таблицы и считывает эти флаги.
// Simplified mental model of the inference
type InferRow<T> = {
[K in keyof T]: T[K] extends { _: { data: infer D; notNull: infer N } }
? N extends true ? D : D | null
: never;
};Перечисления и пользовательские типы
Столбцы со строковыми объединениями сопоставляются с объединениями литералов, а не просто с типом string. Поэтому недопустимое значение статуса приводит к ошибке компиляции.
import { pgEnum } from "drizzle-orm/pg-core";
export const roleEnum = pgEnum("role", ["user", "admin", "owner"]);
export const members = pgTable("members", {
id: serial("id").primaryKey(),
role: roleEnum("role").notNull(), // "user" | "admin" | "owner"
});Значения по умолчанию
.default() делает столбец необязательным при insert, но сохраняет его обязательным при select. Система типов кодирует признак «есть значение по умолчанию», чтобы менять обязательность только там, где это уместно.
const events = pgTable("events", {
id: serial("id").primaryKey(),
createdAt: text("created_at").notNull().default("now()"),
});
// Select: createdAt is string
// Insert: createdAt is optional (default fills it)Почему это важно
Поскольку тип строки выводится, изменение схемы автоматически обновляет тип каждого результата запроса. Переименуйте столбец — и компилятор укажет на каждую устаревшую ссылку. Нет второго источника истины, который мог бы разойтись со схемой.
Небольшой пример от начала до конца
Соберём всё вместе: определите сущность один раз, выведите тип и используйте его как контракт для функции репозитория.
type User = InferSelectModel<typeof users>;
async function findUser(id: number): Promise<User | undefined> {
// db.query returns rows already typed as User
const rows = await db.select().from(users).where(eq(users.id, id));
return rows[0];
}Подводный камень: забыть notNull
Если забыть добавить notNull() для фактически обязательного столбца, выведенный тип станет T | null и заставит выполнять ненужные проверки на null по всему коду. Схема должна честно отражать данные.
Быстрая проверка
Проверьте, насколько хорошо Вы поняли сопоставление схемы с типом.
Итоги
Вы узнали, что определение таблицы является единственным источником истины: построители столбцов несут фантомные типы, отображаемые типы проходят по таблице и создают тип строки, notNull() управляет допустимостью NULL, а модели для select и insert различаются обязательностью полей. Измените схему — и каждый тип результата автоматически последует за этим изменением.
Часто задаваемые вопросы
Урок «Преобразование схемы в тип» бесплатный?
Да — полный текст урока «Преобразование схемы в тип» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.
Чему я научусь в уроке «Преобразование схемы в тип»?
Узнайте, как определения таблиц становятся типами TypeScript Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Преобразование схемы в тип»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Преобразование схемы в тип
- Типобезопасное построение запросов
- Вывод структуры результата запроса
- Связи и объединения с типами