На этой странице

@zirion/ioc · Гайд по миграции

Миграция с v2 на v3

Обновление @zirion/ioc с v2 до v3: время жизни providers, типизация TypeScript, фабрики и lifecycle hooks.

Обновлено · ·

Что меняется в первую очередь

Регистрация по-прежнему использует add(), inject и valueFactory; асинхронные get() и getOrFail() по-прежнему возвращают промисы. Главное несовместимое изменение: время жизни по умолчанию. Provider без явного scope становится transient в v3 вместо singleton в v2.

Сначала сохраните поведение приложения. Внедряйте transient и синхронное разрешение после того, как обновление заработает.

Область v2 v3
Scope без указания SINGLETON TRANSIENT
Разрешение Асинхронные get / getOrFail Асинхронный API и getSync / getOrFailSync
Типы фабричных значений Ограниченный вывод Вывод зарегистрированных selectors и результатов фабрик
Повторы в inject Схлопываются Порядок и повторения сохраняются
Асинхронный onInitialized() Промис не ожидается Промис ожидается
Falsy-результаты singleton Могли создаваться повторно Кешируются, включая null и undefined
Конкурентное кешируемое разрешение Могло создать несколько значений Разделяет выполняющуюся singleton/request-работу
Циклический граф inject Нет ошибки цикла CIRCULAR_DEPENDENCY
Финализация классов Могла затронуть request-providers Создаются только singleton-классы

Обновите пакет и среду выполнения

Используйте Node.js 20 или новее в разработке, CI и production. Установите новую мажорную версию своим пакетным менеджером:

npm install @zirion/ioc@^3.0.0
# или: yarn add @zirion/ioc@^3.0.0
# или: pnpm add @zirion/ioc@^3.0.0

Запускайте проверку типов и тесты приложения после каждого шага. Пока не убирайте await из существующих вызовов разрешения.

Сохраните общие экземпляры

Проверьте каждую регистрацию без scope. Пулы БД, кеши, общее состояние, загрузчики настроек и сервисы со startup hooks часто требуют явного singleton.

До обновления, в v2:

import { Container } from '@zirion/ioc';

class Store {
  users: string[] = [];
}

const container = new Container().add(Store);
const first = await container.getOrFail(Store);
const second = await container.getOrFail(Store);
console.log(first === second); // true в v2

После обновления, с сохранением поведения в v3:

import { Container, InjectScope } from '@zirion/ioc';

class Store {
  users: string[] = [];
}

const container = new Container()
  .add(Store, { scope: InjectScope.SINGLETON });

const first = await container.getOrFail(Store);
const second = await container.getOrFail(Store);
console.log(first === second); // true в v3

Без явного scope каждое разрешение создаёт отдельный Store; изменения одного экземпляра не видны в другом. Фабрика подключения к БД также выполняется при каждом transient-разрешении; для общего пула выберите singleton.

Сохраните явные регистрации REQUEST. Создавайте один context на запрос и передавайте этот объект во все разрешения запроса. Не переносите данные запроса в singleton. Transient может передать context request-зависимостям, но размещение под singleton не делает request-зависимость безопасной.

Сохраняйте тип, возвращённый add()

V3 знает зарегистрированные selectors и выводит результаты фабрик. Такой прежний паттерн меняет контейнер во время выполнения, но оставляет переменную с типом пустого контейнера:

const container = new Container();
container.add('port', { valueFactory: () => 3000 });
// container.getOrFail('port'); // Ошибка TypeScript в v3

Сохраняйте возвращённый тип:

const container = new Container()
  .add('port', { valueFactory: () => 3000 });

const port = await container.getOrFail('port'); // number

При модульной регистрации возвращайте цепочку из каждого composition helper и сохраняйте результат. Не задавайте результату голый тип Container: так теряется информация о зарегистрированных selectors. Если другой функции нужен тип готового контейнера, используйте ReturnType<typeof createContainer>.

Типы не доказывают соответствие inject конструктору и наличие всех зависимостей. Сохраните интеграционные тесты, разрешающие главные сервисы.

Проверьте инициализацию и финализацию

Асинхронный onInitialized() теперь завершается до получения экземпляра. Ошибка hook отклоняет разрешение; следующий вызов может повторить создание. Если раньше hook запускал работу без ожидания, разрешение теперь может стать дольше или выявить ошибку запуска. Обязательную работу оставьте инициализацией; независимые фоновые задачи запускайте явно в коде приложения.

build() / finalize() создаёт только singleton-классы с onFinalized(). Явно задайте SINGLETON классам, которым нужен startup hook. У объектов-селекторов с onFinalized() hook по-прежнему вызывается. Request и transient-классы при сборке не создаются.

build() не разрешает все зарегистрированные графы и не закрывает ресурсы. Серверы, подключения и клиенты по-прежнему закрывайте при завершении приложения.

Проверьте фабрики и массивы inject

  • Независимые асинхронные зависимости выполняются параллельно. Если фабрике B нужен результат A, зарегистрируйте A в inject фабрики B; порядок регистраций не задаёт порядок инициализации.
  • Массив inject копируется при add(). Подготовьте его до регистрации: последующие изменения больше не меняют граф.
  • Повторы остаются отдельными аргументами конструктора. Повторный singleton передаёт тот же объект дважды, transient создаёт два значения. Проверьте код, рассчитывавший на схлопывание повторов в v2.
  • Обычная функция, возвращённая фабрикой, остаётся значением; возвращённый класс инстанцируется. При важности этого различия явно возвращайте нужный объект.
  • Кешируемые фабрики сохраняют все успешные значения, включая false, 0, '', null, undefined и NaN. getOrFail() проверяет регистрацию, а не отсутствие null.
  • Конкурентные singleton и request-разрешения разделяют одну выполняющуюся операцию. После ошибки следующий вызов повторит создание. Инициализация должна допускать повторную попытку.
  • Циклический граф inject теперь бросает CIRCULAR_DEPENDENCY. Выделите общую логику или разделите настройку и выполнение вместо скрытых обращений к контейнеру из фабрики.

Внедряйте синхронные вызовы осознанно

Новые синхронные методы необязательны. Переписывать существующие асинхронные вызовы не нужно.

const container = new Container()
  .add('port', { valueFactory: () => 3000 });

const port = container.getOrFailSync('port');
console.log(port); // 3000, не Promise

Синхронное разрешение подходит только для графа, где все фабрики, зависимости и hooks инициализации синхронны. Иначе возникает ASYNC_RESOLUTION_REQUIRED. Вызов может запустить асинхронную фабрику до обнаружения промиса, поэтому это не безобидная проверка возможностей. Сохраните await для графов с возможной асинхронной инициализацией.

Проверьте поведение после обновления

  1. Проверьте идентичность singleton и общее состояние между отдельными разрешениями.
  2. Убедитесь, что один context переиспользует request-значения, а разные context изолированы.
  3. Проверьте свежие значения transient, для которых нужно независимое создание, включая повторные внедрения.
  4. Разрешите кешируемые providers конкурентно и проверьте один вызов фабрики.
  5. Проверьте ошибки фабрик и асинхронных hooks, затем успешную повторную попытку.
  6. Проверьте startup hooks явных singleton-классов и закрытие ресурсов при завершении.
  7. Запустите TypeScript-проверку и smoke test своего входа ESM или CommonJS.

Дальше: руководство v3 и запускаемые примеры. Руководство v2 доступно для сравнения.

Наверх