Переиспользование и модульность

Переиспользование схем в Zod становится центральным механизмом построения поддерживаемых и масштабируемых систем в JavaScript/TypeScript-приложениях, поскольку позволяет вынести повторяющиеся определения типов в единые модули и комбинировать их в различных контекстах без дублирования логики валидации.

Основной принцип заключается в том, что схема не рассматривается как одноразовый объект, а трактуется как строительный блок, который может быть расширен, модифицирован или объединён с другими схемами.


Структурирование начинается с выделения атомарных сущностей предметной области. Каждая такая сущность описывается отдельной схемой, которая не зависит от конкретного сценария использования.

import { z } from "zod";

export const userBaseSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  createdAt: z.date()
});

Такая схема фиксирует инварианты доменной модели и служит источником правды для всех производных структур.


Расширение схем через composition

Механизм расширения позволяет создавать новые структуры на основе базовых без копирования полей. Метод extend используется для добавления новых свойств, сохраняя исходную схему неизменной.

export const userWithProfileSchema = userBaseSchema.extend({
  profile: z.object({
    firstName: z.string(),
    lastName: z.string(),
    avatarUrl: z.string().url().optional()
  })
});

Подобный подход обеспечивает изоляцию изменений: базовая схема остаётся стабильной, а надстройки формируются в зависимости от контекста использования.


Композиция через merge

Когда требуется объединить две независимые схемы, применяется merge. Это особенно полезно при моделировании сущностей, которые развиваются параллельно в разных модулях.

const authSchema = z.object({
  token: z.string(),
  expiresAt: z.number()
});

export const sessionSchema = userBaseSchema.merge(authSchema);

Такой подход позволяет избегать дублирования и поддерживать логическую независимость модулей.


Выборочная модификация структуры

Механизмы pick и omit формируют специализированные представления данных без изменения исходного определения.

export const publicUserSchema = userBaseSchema.pick({
  id: true,
  email: true
});

export const internalUserSchema = userBaseSchema.omit({
  createdAt: true
});

Эта стратегия особенно эффективна при разделении публичных и внутренних API-слоёв.


Частичное переопределение полей

Метод partial преобразует все поля схемы в опциональные, что используется при обработке частичных обновлений объектов.

export const userUpdateSchema = userBaseSchema.partial();

Для более точного контроля возможно комбинирование с pick:

export const emailUpdateSchema = userBaseSchema.pick({
  email: true
}).partial();

Глубокая модификация структур

Для вложенных объектов применяется deepPartial, обеспечивающий рекурсивное преобразование всех уровней структуры.

const settingsSchema = z.object({
  theme: z.object({
    darkMode: z.boolean(),
    fontSize: z.number()
  })
});

const partialSettings = settingsSchema.deepPartial();

Такая техника полезна при конфигурационных системах, где обновление происходит фрагментарно.


Фабрики схем

Переиспользование усиливается через функции-фабрики, которые параметризуют создание схем. Это позволяет вводить вариативность без дублирования логики.

const createPaginatedSchema = (itemSchema) =>
  z.object({
    items: z.array(itemSchema),
    total: z.number(),
    page: z.number()
  });

Использование:

export const userListSchema = createPaginatedSchema(userBaseSchema);

Фабричный подход формирует слой абстракции над повторяющимися паттернами данных.


Рекурсивные структуры и z.lazy

При моделировании древовидных структур используется z.lazy, позволяющий отложить вычисление схемы.

const categorySchema = z.lazy(() =>
  z.object({
    name: z.string(),
    children: z.array(categorySchema).optional()
  })
);

Этот механизм критически важен для графов, меню и иерархий, где структура ссылается сама на себя.


Дисриминированные объединения как модульная база

discriminatedUnion позволяет строить модульные модели с явным разделением вариантов.

const eventSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("click"),
    x: z.number(),
    y: z.number()
  }),
  z.object({
    type: z.literal("scroll"),
    scrollTop: z.number()
  })
]);

Каждый вариант может быть вынесен в отдельный модуль и затем собран в единый контракт.


Организация схем по модулям

При масштабировании системы схемы группируются по доменным контекстам: пользователь, платежи, уведомления, настройки. Каждый модуль содержит:

  • базовые схемы сущностей
  • производные представления (DTO, API модели)
  • схемы запросов и ответов
  • вспомогательные фабрики

Структура становится предсказуемой и изолированной:

/user
  user.base.ts
  user.api.ts
  user.dto.ts
  user.schema.ts

Такой подход снижает связность и упрощает повторное использование.


Композиция через индексы модулей

Централизованный экспорт обеспечивает единый слой доступа к схемам без прямых импортов из внутренних файлов.

export * from "./user.base";
export * from "./user.api";

Это позволяет формировать единые контракты домена и использовать их в разных слоях приложения без нарушения инкапсуляции.


Переиспользование через типовую инференцию

Zod автоматически выводит TypeScript-типы, что позволяет использовать схемы как источник типов без дублирования интерфейсов.

export type User = z.infer<typeof userBaseSchema>;

При композиции схем типы также переиспользуются, сохраняя синхронизацию между runtime-валидацией и статической типизацией.


Комбинирование модульных схем

Сложные структуры формируются через композицию уже существующих модулей:

import { userBaseSchema } from "../user";
import { paymentSchema } from "../payment";

export const userPaymentSchema = z.object({
  user: userBaseSchema,
  payment: paymentSchema
});

Такой подход формирует многоуровневую архитектуру данных без дублирования определения сущностей.


Переиспользование через трансформации

Метод transform позволяет создавать производные схемы, не изменяя базовую структуру.

export const userViewSchema = userBaseSchema.transform((user) => ({
  identifier: user.id,
  contact: user.email
}));

Это создаёт слой адаптации между доменной моделью и внешними представлениями.


Версионирование схем

Модульность упрощает введение версий схем без разрушения существующих контрактов.

export const userSchemaV1 = z.object({
  id: z.string(),
  email: z.string()
});

export const userSchemaV2 = userSchemaV1.extend({
  phone: z.string().optional()
});

Разделение версий в отдельных модулях обеспечивает совместимость и контролируемую эволюцию API.


Архитектурная роль переиспользования

Систематическое применение композиции, фабрик и модульности формирует устойчивую архитектуру, где схемы становятся не вспомогательным слоем, а фундаментом описания данных на всём протяжении приложения.