# План миграции бэкэнда на TypeScript + ESM Статус: черновик плана (реализация ещё не начата). Дата: 2026-06-09. Этот документ описывает полную, поэтапную миграцию `backend/` с CommonJS/JavaScript на TypeScript со строгим режимом и нативные ESM-модули (`import`/`export`). План составлен так, чтобы приложение оставалось рабочим на каждом шаге и соответствовало правилам `CLAUDE.md` (без `any`, без отключения линтера/TS, без приведения типов, импорты через алиас `@`, документация и тесты на каждый модуль, минимальные изменения, без оверинжиниринга). --- ## 0. Принятые решения (2026-06-09) Зафиксированы по итогам обсуждения и проверочных spike: 1. **Дата/время:** `moment` → **`dayjs`** (минимальный diff, близкий API). 2. **sequelize-cli в ESM (spike проведён):** подтверждено — CLI загружает `.cjs`-конфиг и `.sequelizerc` в пакете с `"type": "module"`. Но т.к. выбраны TS-миграции (см. п.4), sequelize-cli из миграционного флоу убирается. 3. **Инструменты:** dev — раннер **`tsx`** (исполняет `.ts`+ESM напрямую, это инструмент, не React-разметка), прод — сборка **`tsc` + `tsc-alias`**. Бандлер не вводим. 4. **Миграции — на TypeScript.** Используем **Umzug 3 + `tsx`** (spike подтвердил выполнение `.ts`-миграций end-to-end). Хранилище — `SequelizeStorage` поверх существующей таблицы `SequelizeMeta`, поэтому история уже применённых миграций сохраняется. Лучшие практики — см. раздел 11. 5. **Версия Node:** **24 (Active LTS)**. Удовлетворяет самым строгим ограничениям зависимостей (`vitest`, `eslint` требуют `>=24`). Node 26 — ещё «Current», не LTS, в прод не берём. 6. **Тесты:** минимальный смоук-набор на этапе миграции (+ unit на чистые модули). 7. **OAuth-стратегии passport:** обновление — **отдельной задачей** после миграции; задача внесена в `docs/full-integration-refactor-plan.md`. ORM остаётся **Sequelize 6.37** (решение зафиксировано отдельно: Prisma — слишком большой рефакторинг, v7 — alpha). --- ## 1. Текущее состояние (факты, на которых строится план) Замеры по `backend/src` (без `node_modules`): - Всего файлов `.js`: **206**, суммарно ~**55 651** строк. - Распределение по слоям: - `db/` — 90 файлов (модели 39, db/api 28, миграции 15, сидеры 5), ~33 400 строк — самый объёмный слой. - `routes/` — 45 файлов, ~12 700 строк. - `services/` — 48 файлов, ~7 600 строк. - `constants/` — 13 файлов, `middlewares/` — 3, `auth/` — 2, `config/` — 1 + `config.js`, `ai/` — 1. - Модульная система: **100% CommonJS**. `require(` используется в 173 файлах, `module.exports` — в 201; ESM-импортов (`import …`) — **0**. - Экспорт-паттерны: `module.exports = class` — 74 файла (services, db/api, middlewares, helpers); `module.exports = function(sequelize, DataTypes)` — 38 (фабрики моделей Sequelize). - ORM: **Sequelize 6.37**, миграции/сидеры через **sequelize-cli 6.6.5** (CommonJS-инструмент, читает `.sequelizerc` → `src/db/db.config.js`). - Express **5.2**, аутентификация на **passport** (JWT + Google + Microsoft), документация через **swagger-jsdoc** (парсит `./src/routes/*.js`). - Dev-запуск: `watcher.js` (chokidar + nodemon), он же триггерит `db:migrate` / `db:seed` при появлении новых файлов. - ESLint flat-config с `sourceType: 'commonjs'` и правилом `import-x/no-unresolved`. - Тестов в бэкэнде сейчас **нет** (0 файлов `*.test.js` / `*.spec.js`); проверка — только `node -c` (синтаксис) согласно `docs/full-integration-refactor-plan.md`. - Алиас `@` в бэкэнде пока **не используется** (хотя `CLAUDE.md` его требует); все импорты относительные. - Окружения: `engines.node >= 18`, Dockerfile на `node:20.15.1-alpine`, локально доступен Node 22. Важный ориентир: **фронтенд уже на TypeScript + ESM + Vite** (strict, `paths: { "@/*": ["./src/*"] }`, `vitest`, `playwright`). Бэкэнд логично привести к тем же конвенциям (strict TS, алиас `@`, vitest). ### ESM-блокеры, выявленные в коде 1. **Динамическая загрузка моделей** в `db/models/index.js`: `fs.readdirSync(__dirname)` + `require(path.join(...))` в цикле. В нативном ESM `require` недоступен; нужен либо явный список `import`, либо динамический `import()`. 2. **`__dirname` / `__filename`** в 7 файлах: `config/load-env.js`, `index.js`, `ai/LocalAIApi.js`, `db/models/index.js`, `services/email/list/{passwordReset,addressVerification,invitation}.js`. В ESM их нет — замена на `fileURLToPath(import.meta.url)`. 3. **sequelize-cli** загружает `.sequelizerc`, `db.config.js`, миграции и сидеры как CommonJS через `require`. При `"type": "module"` файлы `.js` станут ESM и CLI сломается. 4. **swagger-jsdoc** указывает на `./src/routes/*.js` — после сборки путь к исходникам/выходу изменится. 5. **`watcher.js` + nodemon** заточены под запуск `.js`-файла напрямую. 6. В ESM (NodeNext) **относительные импорты требуют явного расширения** (`./foo.js`), а алиасы tsconfig **не резолвятся Node в рантайме** без дополнительного инструмента. --- ## 2. Целевое состояние - Весь рантайм-код бэкэнда — `.ts` со строгим TypeScript (`strict: true`, `noImplicitAny`, `strictNullChecks`), без `any` и без приведения типов. - Нативный ESM: `"type": "module"` в `package.json`, `import`/`export` во всём рантайм-коде. - Импорты через алиас `@` (как на фронтенде), работающие и в dev, и в прод-сборке. - Сборка `tsc` → `dist/`, запуск прод из `dist/`; dev — через `tsx watch` (TS + ESM + алиасы без отдельной сборки). - Миграции и сидеры остаются на sequelize-cli и сохраняют CommonJS (расширение `.cjs`) — как исторические артефакты, переписывать их на TS нецелесообразно. - Введён минимальный каркас типизированных тестов (`vitest`) и `typecheck` как гейт качества. - Обновлены Dockerfile, скрипты `package.json`, ESLint и документация. --- ## 3. Ключевые проектные решения (с обоснованием) ### 3.1. Две фазы вместо «большого взрыва» Задача совмещает две независимые трансформации: смену **языка** (JS → TS) и смену **модульной системы** (CJS → ESM). Делать обе сразу на 206 файлах рискованно. Рекомендуется разделить: - **Фаза A — типизация (остаёмся на CommonJS).** TypeScript компилируется в `module: "CommonJS"`. Включаем `allowJs: true`, чтобы `.ts` и `.js` сосуществовали, и мигрируем файлы снизу вверх по дереву зависимостей. Приложение всё это время запускается как раньше. Риск минимальный. - **Фаза B — перевод на ESM.** Когда весь код уже на TS, механически переводим модульную систему: `"type": "module"`, `module: "NodeNext"`, `require`→`import`, `module.exports`→`export`, `__dirname`→`import.meta.url`, переписываем динамический загрузчик моделей, чиним sequelize-cli и swagger-пути. Такое разделение изолирует «языковые» ошибки от «модульных» и даёт чёткие точки отката. > Альтернатива (не рекомендуется): одновременный переход на `tsx` + `"type": "module"` и > правка всех импортов сразу. Быстрее по числу шагов, но даёт большой нестабильный > diff и трудный откат. Противоречит принципу «минимальные изменения». ### 3.2. Инструмент сборки и запуска - **Сборка:** `tsc` (официальный компилятор) в `dist/`. Просто, предсказуемо, без бандлера — соответствует «без оверинжиниринга». - **Алиасы `@` в выходной сборке:** `tsc` не переписывает `@/*` в относительные пути. Добавляем `tsc-alias` как пост-шаг сборки. (Альтернатива — нативные `imports` в `package.json` с префиксом `#`, но `CLAUDE.md` требует именно `@`.) - **Dev-запуск:** `tsx watch src/index.ts` — исполняет TS+ESM напрямую и резолвит `paths` из tsconfig, заменяя `nodemon`. Авто-миграции/сидинг из `watcher.js` выносим в отдельный `predev`-шаг или оставляем лёгкий watcher только для `db/migrations`+`db/seeders` (см. 4.7). ### 3.3. Миграции — переход на Umzug + TypeScript (решение принято) sequelize-cli — CommonJS-инструмент и не поддерживает `.ts`-миграции. Поскольку выбраны TS-миграции (решение 0.4), **отказываемся от sequelize-cli** в пользу **Umzug 3** — это та же библиотека, что лежит под капотом sequelize-cli, поэтому переход совместим по хранилищу истории. Подход (детали и пример каркаса — в разделе 11): - Собственный лёгкий раннер `db/migrate.ts` на Umzug, запускаемый через `tsx`. - Хранилище истории — `SequelizeStorage` поверх существующей таблицы **`SequelizeMeta`**. Уже применённые миграции остаются записанными, повторно не выполняются. - **Новые** миграции пишутся как `.ts` (ESM, типизированный `QueryInterface`). - **Существующие 15 миграций** не переписываем: оставляем как есть в формате `.cjs` (исторические артефакты), Umzug-glob включает и `.cjs`, и `.ts`, `tsx` грузит оба. Это исключает риск нарушить уже применённую историю БД. - Сидеры — вторым экземпляром Umzug (отдельная meta-таблица) либо оставляем минимальный текущий механизм; решается в разделе 11. > Spike подтвердил: Umzug 3.8.3 + `tsx` выполняют `.ts`-миграции end-to-end. Отдельно > подтверждено, что `.cjs`-артефакты корректно грузятся в ESM-пакете, что нужно для > сосуществования старых `.cjs` и новых `.ts` миграций. Модели переводим на TS/ESM (их импортирует рантайм). Команды миграций моделей не требуют. ### 3.4. Загрузчик моделей и типобезопасный объект `db` Заменяем `fs.readdirSync + require` на **явные `import` всех 39 моделей** в `db/models/index.ts` и сборку строго типизированного объекта `db`. Это убирает динамику (несовместимую с ESM-статикой), даёт автодополнение и исключает `any`. Минус — вербозный index с 39 импортами, но это разовый предсказуемый код. **Динамический `import()` здесь не используем** — только статические. ### 3.4.1. Политика импортов: статика по умолчанию Решение: **избегаем динамических импортов (`import()`)**, кроме случаев, где это действительно оправдано. На текущем коде таких случаев нет: - Единственный реальный динамический `require` — загрузчик моделей (`db/models/index.js:25`) — переводится на статические импорты (3.4). - Найденные `import("…")` — это JSDoc-аннотации типов, а не рантайм; в TS становятся обычными `import type`. - `__dirname`/`__filename` в 7 файлах используются для `fs`-чтения файлов (`.env`, HTML-шаблоны писем), **не для импортов**. Замена на `path.dirname(fileURLToPath(import.meta.url))` сохраняет их статическими — это не динамический импорт. Если в будущем потребуется ленивая загрузка (тяжёлый опциональный модуль, разрыв цикла зависимостей), динамический `import()` допускается **точечно и с обоснованием в коде**, а не как паттерн. Циклы зависимостей предпочтительно разрывать рефакторингом, а не `import()`. ### 3.5. Стратегия типизации моделей Sequelize Модели сейчас — фабрики `sequelize.define('name', {...})`. Чтобы не переписывать 39 моделей в class-based стиль (большой рискованный diff), рекомендуется **остаться на `define()`**, но добавить типобезопасность: - На каждую модель — интерфейс атрибутов (`XAttributes`) и creation-атрибутов (`XCreationAttributes`). - Фабрика возвращает `ModelStatic>`. - `associate` типизируется через общий тип реестра `Db`. Это самый объёмный по трудозатратам пункт миграции (39 моделей + связи), поэтому он вынесен в отдельную подфазу и может идти параллельно остальному после готовности каркаса. ### 3.6. Типизация Express и слоёв - `@types/express`, `Request`/`Response`/`NextFunction`, расширение `Request` полем `currentUser` через declaration merging (`src/types/express.d.ts`). - `helpers.wrapAsync`, `commonErrorHandler`, middlewares — строго типизированные сигнатуры. - db/api и services — публичные методы получают типы входных DTO и возвращаемых моделей. --- ## 4. Поэтапный план работ ### Фаза 0 — Подготовка инструментов (без изменения рантайм-поведения) 1. Установить dev-зависимости: `typescript`, `tsx`, `tsc-alias`, `@types/node`, и типы для библиотек без собственных деклараций: `@types/express`, `@types/cors`, `@types/passport`, `@types/passport-jwt`, `@types/jsonwebtoken`, `@types/nodemailer`, `@types/multer`, `@types/swagger-jsdoc`, `@types/swagger-ui-express`, `@types/bcrypt`, `@types/validator`. (axios, sequelize, pg, `dayjs`, `@json2csv/plainjs`, `umzug` поставляют типы сами; `@types/lodash` не нужен — `lodash` удаляется.) Добавить рантайм-зависимость `umzug` (для TS-миграций, см. раздел 11). 2. Создать `backend/tsconfig.json` (Фаза A — CommonJS): - `strict: true`, `noImplicitAny`, `strictNullChecks`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch` (зеркало фронтенда). - `target: ES2022`, `module: CommonJS`, `moduleResolution: Node`, `esModuleInterop: true`. - `allowJs: true`, `checkJs: false` — для сосуществования `.js` и `.ts`. - `outDir: dist`, `rootDir: src`, `baseUrl: src`, `paths: { "@/*": ["*"] }`. 3. Обновить ESLint: добавить `typescript-eslint`, парсер для `.ts`, сохранить `import-x/no-unresolved` с TS-резолвером. На Фазе A не ломать существующие `.js`-правила. 4. Добавить скрипт `typecheck: tsc --noEmit` и завести его как обязательный гейт. Критерий готовности фазы: `npm run typecheck` проходит на смешанной кодовой базе, приложение запускается как раньше. ### Фаза 0.5 — Аудит и обновление зависимостей (совмещается с Фазой A) Полный разбор — в разделе 10. Кратко по порядку: 1. **Удалить неиспользуемые** драйверы и пакеты: `mysql2`, `tedious`, `sqlite` (диалект везде `postgres`, 0 использований), `lodash` (один импорт `lodash/get`, заменяется на optional chaining). 2. **Заменить deprecated** `json2csv@5.0.7` → `@json2csv/plainjs@7` (26 файлов). 3. **Заменить устаревшую** `moment` → **`dayjs`** (26 файлов). 4. **Консолидировать загрузку файлов**: убрать `formidable` (1 файл), оставить `multer`; убрать `body-parser` → `express.json()` (Express 5). 5. **Убрать `sequelize-cli`** (миграции переходят на Umzug + tsx, см. раздел 11). OAuth-стратегии — отдельной задачей (см. `full-integration-refactor-plan.md`); `sequelize-json-schema` пересмотреть при типизации `routes/openai.ts`. 6. Минорно поднять актуальные пакеты (`@google-cloud/storage` 7.19→7.21 и т.п.). Эти изменения лучше делать вместе с типизацией соответствующих модулей, чтобы новые типы сразу ложились на новые API. Каждое — отдельным PR со смоук-проверкой. ### Фаза A — Перевод на TypeScript (язык), модульная система = CommonJS Мигрируем снизу вверх по зависимостям, по одному связному блоку за раз; после каждого блока — `typecheck` + ручной/смоук-прогон. 1. `constants/` (13 файлов) — листовые, без зависимостей. Перевести в `.ts`, экспортировать типизированные константы (`as const`). 2. `config/load-env.ts`, `config.ts` — типизировать чтение env (хелперы `requiredEnv`/`readBooleanEnv`/`readNumberEnv`/`readListEnv` уже есть, добавить типы и тип `Config`). 3. `helpers.ts`, `db/utils.ts` — общие утилиты. 4. `db/models/*` — типизация моделей (см. 3.5) + типобезопасный `db/models/index.ts` (см. 3.4). Самый крупный блок; можно дробить по доменам. 5. `db/api/*` (28 файлов) — типизировать публичные статические методы, опираясь на типы моделей. 6. `services/*` (48 файлов), включая `services/notifications/errors/*`, `services/email/list/*`. 7. `auth/*` и `middlewares/*` — типизация passport-стратегий, cookies, проверки прав; declaration merging для `Request.currentUser`. 8. `ai/LocalAIApi.ts`. 9. `routes/*` (45 файлов) — типизированные роутеры; swagger-JSDoc-комментарии переносятся как есть (swagger-jsdoc парсит и `.ts`). 10. `index.ts` — точка входа. 11. Существующие миграции/сидеры/`db.config`/`.sequelizerc` — на Фазе A **не трогаем** (остаются `.js`, CommonJS-пакет их корректно исполняет). Перевод на Umzug — в Фазе B. Критерий готовности: 0 `.js` в рантайм-коде (кроме исторических миграций/сидеров), `typecheck` зелёный, приложение работает. ### Фаза B — Перевод на ESM (модульная система) 1. Переключить tsconfig на ESM: `module: NodeNext`, `moduleResolution: NodeNext`. Отключить `allowJs` (язык уже весь TS). 2. Поставить `"type": "module"` в `package.json`. 3. Конвертировать синтаксис во всех `.ts`: `require`→`import`, `module.exports`→`export` / `export default`. (Можно ускорить кодмодом `cjstoesm` — но результат вычитывать вручную; правило: не доверять слепо.) 4. `__dirname`/`__filename` → `path.dirname(fileURLToPath(import.meta.url))` в 7 файлах. 5. Переписать `db/models/index.ts` на статические `import` всех моделей. 6. Поправить относительные импорты под NodeNext (явные расширения в выходе обеспечивает `tsc-alias`; алиас `@` — предпочтительный путь). 7. **Миграции на Umzug + tsx (раздел 11):** убрать `sequelize-cli` и `.sequelizerc`; перевести `db.config.js`→`db/config.ts`; добавить раннер `db/migrate.ts` (Umzug + `SequelizeStorage` на `SequelizeMeta`); существующие 15 миграций оставить как `.cjs`, новые писать на `.ts`. Проверить `migrate up` на чистой БД и на БД с уже применённой историей. 8. **swagger-jsdoc:** сделать путь `apis` env-зависимым — dev: `./src/routes/*.ts`, прод: `./dist/routes/*.js`. 9. **dev-запуск:** заменить `nodemon`/`watcher.js` на `tsx watch src/index.ts` (hot-reload + стриминг логов в реальном времени). Авто-применение миграций — `predev`-шаг (`tsx src/db/migrate.ts up`); если нужно сохранить применение «на лету» при добавлении файла, оставить лёгкий chokidar-вотчер на `db/migrations`/`db/seeders`. Паритет с текущим поведением проверяется в Фазе D, п.4. 10. `db/reset.js` (использует `execSync('sequelize ...')`) — переписать на вызов Umzug-раннера (`migrate down/up` или `sequelize.sync` для dev), перевести в `.ts`/ESM. Критерий готовности: `npm run build` (tsc + tsc-alias) собирает `dist/`, `node dist/index.js` стартует, миграции/сидеры проходят, все маршруты отвечают. ### Фаза C — Скрипты, сборка, контейнеризация 1. `package.json` скрипты: - `dev: tsx watch src/index.ts` - `build: tsc && tsc-alias` - `start: node dist/index.js` (прод; миграции — отдельной командой перед стартом) - `db:migrate: tsx src/db/migrate.ts up`, `db:rollback: tsx src/db/migrate.ts down`, `db:seed: tsx src/db/seed.ts` (раздел 11) - `typecheck`, `lint`, `test`. - Указать `engines.node: ">=24"` (Active LTS, решение 0.5). 2. Обновить корневой `package.json` (`build:production`, `start:production`) под новый build-флоу бэкэнда. 3. **Копирование ассетов в `dist/`.** `tsc` собирает только `.ts`. Не-TS ассеты, которые читаются через `fs` по пути от `import.meta.url`, нужно копировать в `dist/` отдельным build-шагом: HTML-шаблоны писем `src/services/email/htmlTemplates/**` (используются в `services/email/list/*`). Добавить в `build` (например, `cpy`/`copyfiles`/`rsync`) и проверить, что пути от `import.meta.url` резолвятся в `dist/`. 4. Dockerfile: базовый образ **`node:24-alpine`** (текущая Active LTS); добавить шаг `npm run build`, в прод-образ копировать `dist/` (включая скопированные шаблоны) + `node_modules` (multi-stage build). Заменить `yarn install` на `npm ci` (консистентно с корневым `build:production`). 5. `.dockerignore`/`.gitignore`: добавить `dist/`. ### Фаза D — Тесты и верификация 1. Добавить `vitest` (консистентно с фронтендом) и базовый каркас тестов. 2. Покрыть unit-тестами критичные чистые модули: `config` (парсинг env), `helpers`, `db/utils`, auth-хелперы, проверки прав. 3. Смоук-проверка рантайма: старт сервера, `/api-docs`, healthcheck, 1–2 защищённых маршрута. 4. **Проверка паритета dev-workflow (nodemon/watcher).** Убедиться, что после перехода на `tsx watch` опыт разработки не хуже текущего `watcher.js` + nodemon: - **Hot-reload:** правка любого `.ts` в `src/**` вызывает автоматический перезапуск сервера; в консоли видны сообщения о рестарте (аналог `nodemon restarted due changes`). - **Логи в реальном времени:** `console.*`/логи приложения стримятся в stdout сразу, без буферизации; проверить, что вывод не теряется при рестарте (`tsx watch` не глушит stdout дочернего процесса). - **Миграции в dev:** новые миграции применяются ожидаемым образом. Текущий `watcher.js` авто-применял новые файлы в `db/migrations`/`db/seeders` «на лету» через chokidar; план заменяет это на `predev`-шаг `db:migrate`. Подтвердить, что выбранный сценарий (ручной `npm run db:migrate` при добавлении миграции **или** сохранённый отдельный chokidar-вотчер) задокументирован и работает. Если авто-применение «на лету» нужно — оставить лёгкий watcher, запускающий `tsx src/db/migrate.ts up` на событие `add`. - **Завершение процесса:** `Ctrl+C` корректно останавливает `tsx watch` и дочерний сервер (нет «зависших» процессов на порту). 5. Обновить `docs/full-integration-refactor-plan.md` (раздел Backend baseline): добавить `typecheck`, `build`, тесты. 6. Финальный гейт: `typecheck` + `lint` + `vitest` + `build` + ручной прогон миграций на чистой БД + подтверждённый паритет dev-workflow (п.4). --- ## 5. Типизация: где основной объём | Слой | Файлов | Сложность типизации | |---|---|---| | `db/models` | 39 | Высокая — атрибуты, creation-атрибуты, связи | | `db/api` | 28 | Средняя — DTO входа/выхода, опции транзакций | | `routes` | 45 | Средняя — Request/Response, currentUser, обёртки | | `services` | 48 | Средняя — бизнес-DTO, ошибки | | `constants` | 13 | Низкая | | прочее (auth, middlewares, config, ai, helpers) | ~10 | Низкая–средняя | Основной риск и трудозатраты сосредоточены в `db/` (модели + api). Рекомендуется выделить типизацию моделей в отдельный поток работ и при необходимости распараллелить по доменам (люди/учёба/посещаемость/финансы/контент). --- ## 6. Риски и меры - **Переход миграций на Umzug** — главный риск. Меры: использовать `SequelizeStorage` с той же таблицей `SequelizeMeta`, чтобы история уже применённых миграций сохранилась; обязательно прогнать раннер и на чистой БД, и на БД с существующей историей; новые миграции — `.ts`, старые — оставить `.cjs` без переписывания. (Базовый механизм подтверждён spike: Umzug 3.8.3 + tsx выполняют `.ts`-миграции.) - **Разрастание `any` под давлением сроков** — запрещено `CLAUDE.md`. Мера: `typecheck` как блокирующий гейт; временно сложные места изолировать узкими типами, а не `any`. - **Расхождение dev (`tsx`) и прод (`dist`)** — алиасы/расширения резолвятся по-разному. Мера: всегда проверять и `npm run dev`, и `node dist/index.js` в CI. - **Связи Sequelize теряют типы** при `define()`-подходе. Мера: общий тип реестра `Db` + строго типизированный `associate`. - **swagger пути** ломаются после сборки. Мера: env-зависимый `apis`-glob, проверка `/api-docs` в смоуке. - **Большой diff** усложняет ревью. Мера: PR на каждый связный блок (по разделам Фазы A), а не одним коммитом. ## 7. Стратегия отката - Фаза A полностью обратима: TS компилируется в тот же CommonJS; при проблеме можно остановиться на любом блоке — смешанная кодовая база рабочая. - Фаза B — отдельная ветка/серия PR; точка возврата — последний зелёный коммит Фазы A. - Содержимое уже применённых миграций не меняется; Umzug читает ту же `SequelizeMeta`, поэтому история БД не затрагивается. Откат миграционного слоя = вернуть прежний `db/reset.js`/CLI-вызовы. ## 8. Порядок исполнения (сводка) Фаза 0 (инструменты) → Фаза A (TS, снизу вверх: constants → config → helpers/utils → models → db/api → services → auth/middlewares → ai → routes → index) → Фаза B (ESM: tsconfig NodeNext, `type:module`, синтаксис import/export, import.meta.url, загрузчик моделей, Umzug-миграции, swagger, tsx) → Фаза C (скрипты/Docker/Node 24) → Фаза D (тесты/верификация/доки). > Оценка объёма намеренно не дана в часах: основной труд — типизация `db/` (~61 файл) и `routes` > (45 файлов). После готовности каркаса (Фаза 0 + загрузчик моделей) остальное — предсказуемая > механическая работа поблочно. --- ## 9. Открытые вопросы Все вопросы по состоянию на 2026-06-09 закрыты — см. раздел 0 «Принятые решения». Остаётся один пункт к проверке в коде по ходу работ: подтвердить замену `sequelize-json-schema` при типизации `routes/openai.ts` (раздел 10.3). --- ## 10. Аудит зависимостей: обновление и замена Версии проверены по npm-реестру на 2026-06-09 (не из памяти модели). Источник истины — реестр; несколько пакетов в `package.json` (например, `lodash`, `nodemailer`, `cors`, `eslint`) уже соответствуют последним стабильным релизам в текущем реестре, менять их не нужно. ### 10.1. Удалить (не используются в коде) | Пакет | Версия | Причина | |---|---|---| | `mysql2` | 3.22.5 | Диалект БД везде `postgres`, 0 использований | | `tedious` | 19.2.1 | MSSQL-драйвер, 0 использований | | `sqlite` | 5.1.1 | 0 использований | | `lodash` | 4.18.1 | Единственный импорт `lodash/get` в `services/notifications/helpers.js` — заменяется на optional chaining (`?.`) | Эффект: меньше поверхности атаки, легче и быстрее установка/сборка. Оставляем только драйвер `pg` + `pg-hstore`. ### 10.2. Заменить — deprecated / устаревшие | Текущий | Статус | Рекомендуемая замена | Масштаб | |---|---|---|---| | `json2csv` 5.0.7 | **deprecated** («Package no longer supported») | `@json2csv/plainjs` 7.0.6 (официальный преемник, scoped-пакеты) | 26 файлов | | `moment` 2.30.1 | Maintenance mode, проект сам рекомендует альтернативы | **`dayjs` 1.11.21** (решено: минимальный diff, близкий API) | 26 файлов | | `body-parser` (в `index.js`) | Избыточен в Express 5 | Встроенный `express.json()` | 1 файл | | `formidable` 3.5.4 | Дублирует `multer` | Консолидировать на `multer` 2.1.1 (популярнее, активнее), переписать `services/file.js` | 1 файл | | `sequelize-cli` 6.6.5 (devDep) | Не поддерживает `.ts`-миграции | **Umzug 3.8.3 + tsx** (раздел 11) | весь миграционный флоу | Выбор `dayjs` зафиксирован (решение 0.1). ### 10.3. Заменить — опционально (низкая поддержка) | Текущий | Последняя публикация | Вариант | Комментарий | |---|---|---|---| | `passport-google-oauth2` 0.2.0 | 2022 | `passport-google-oauth20` 2.0.0 или `openid-client` 6.8.4 | Низкая активность; `passport-google-oauth20` — почти drop-in, `openid-client` — стратегически современнее, но больше работы | | `passport-microsoft` 2.1.0 | 2024 | оставить либо `openid-client` 6.8.4 | Поддержка приемлема; единый `openid-client` под оба провайдера — отдельная задача | | `sequelize-json-schema` 2.1.1 | 2022 | `zod-to-json-schema` 3.25.2 или ручная схема | Используется в 1 файле (`routes/openai.js`); пересмотреть при типизации этого роута | **Решено (0.7):** OAuth-замена выносится в **отдельную задачу** после основной миграции (внесена в `docs/full-integration-refactor-plan.md`), чтобы не смешивать риски (изменение потока аутентификации ≠ смена языка/модулей). ### 10.4. Обновить версии (минор/патч, без замены) `@google-cloud/storage` 7.19 → 7.21. Остальные ключевые рантайм-пакеты уже на актуальных стабильных: `express` 5.2.1, `helmet` 8.2.0, `axios` 1.17.0, `bcrypt` 6.0.0, `jsonwebtoken` 9.0.3, `pg` 8.21.0, `pg-hstore` 2.3.4, `cors` 2.8.6, `passport` 0.7.0, `passport-jwt` 4.0.1, `csv-parser` 3.2.1, `swagger-jsdoc` 6.3.0, `swagger-ui-express` 5.0.1, `chokidar` 5.0.0, `nodemailer` 8.0.10, `multer` 2.1.1. Перед общим обновлением выполнить `npm outdated` и `npm audit`, фиксировать точные версии в lock-файле. ### 10.5. Sequelize — остаёмся на 6 (важно) `sequelize` стабильный — **6.37.8**; версия 7 существует только как **alpha** (`7.0.0-alpha.9`). Пользователь просил **стабильные** версии, поэтому переход на Sequelize 7 (TS-native) сейчас **не делаем**. ORM как таковой не меняем: переписывание моделей под другой ORM (Drizzle/Prisma) противоречит принципу «минимальные изменения» и выходит за рамки миграции на TS/ESM. Это возможная отдельная инициатива в будущем, но не часть данного плана. ### 10.6. Новые зависимости для TS/ESM (актуальные версии) - Рантайм: `umzug` 3.8.3 (миграции, раздел 11). - Dev: `typescript` 6.0.3, `tsx` 4.22.4, `tsc-alias` 1.8.17, `vitest` 4.1.8, `typescript-eslint` 8.61.0, `@types/node` 25.9.2, `@types/express` 5.0.6, `@types/multer` 2.1.0, и типы для остальных библиотек без собственных деклараций (см. Фаза 0, п. 1). - **Не добавлять:** `@types/lodash` (`lodash` удаляется); `@types/passport-google-oauth20` (OAuth-замена отложена). Целевая среда исполнения — **Node 24 (Active LTS)**: удовлетворяет самым строгим `engines.node` зависимостей (`vitest` и `eslint` требуют `>=24`). ### 10.7. Порядок применения 1. Сначала **удаления** (10.1) — самый безопасный шаг, не меняет поведение. 2. Затем **замены** (10.2) — модуль за модулем, вместе с типизацией соответствующих файлов в Фазе A. 3. **Опциональные** замены (10.3) — после стабилизации основной миграции, отдельными задачами. 4. **Минорные апдейты** (10.4) — единым PR с `npm audit` перед финальной верификацией (Фаза D). --- ## 11. Лучшие практики: TS-миграции на Umzug Umzug — библиотека-движок, на которой построен и сам sequelize-cli, поэтому переход совместим по хранилищу истории. Подход (подтверждён spike: Umzug 3.8.3 + tsx исполняют `.ts`-миграции): 1. **Единое хранилище истории.** Использовать `SequelizeStorage` с той же таблицей `SequelizeMeta`, что вёл sequelize-cli. Уже применённые миграции остаются зарегистрированными и повторно не запускаются — история БД не теряется. 2. **Сосуществование старого и нового.** Glob включает и `.cjs` (15 существующих миграций — не переписываем), и `.ts` (все новые). `tsx` грузит оба формата. 3. **Типизированные миграции.** Каждая новая миграция — `.ts` с типизированным контекстом `QueryInterface`: ```ts // src/db/migrations/2026XXXX-create-foo.ts import type { QueryInterface } from 'sequelize'; import { DataTypes } from 'sequelize'; export async function up({ context: qi }: { context: QueryInterface }) { await qi.createTable('foo', { id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4 }, }); } export async function down({ context: qi }: { context: QueryInterface }) { await qi.dropTable('foo'); } ``` 4. **Раннер с CLI.** Один файл `src/db/migrate.ts` создаёт `Umzug` и пробрасывает CLI через `umzug.runAsCLI()` (даёт команды `up`/`down`/`pending`/`executed`): ```ts // src/db/migrate.ts import { Umzug, SequelizeStorage } from 'umzug'; import { sequelize } from '@/db/models'; // существующий инстанс Sequelize export const migrator = new Umzug({ migrations: { glob: 'src/db/migrations/*.{cjs,ts}' }, context: sequelize.getQueryInterface(), storage: new SequelizeStorage({ sequelize }), // таблица SequelizeMeta logger: console, }); if (import.meta.url === `file://${process.argv[1]}`) { migrator.runAsCLI(); } ``` 5. **Скрипты:** `db:migrate` = `tsx src/db/migrate.ts up`, `db:rollback` = `... down`, `db:status` = `... pending`. В Dockerfile/прод миграции запускать перед стартом (`tsx` ставить в рантайм-зависимости либо запускать из собранного `dist`). 6. **Сидеры.** Второй экземпляр Umzug со своей meta-таблицей (`SequelizeMeta_seeders`) и аналогичным раннером `src/db/seed.ts`; существующие 5 сидеров перенести как `.cjs` (не переписывать) или по необходимости — на `.ts`. 7. **Замена `watcher.js`.** Авто-миграции в dev — через `predev`-шаг (`tsx src/db/migrate.ts up`), а не chokidar-вотчер; перезапуск сервера — `tsx watch`. 8. **Проверка перед мерджем.** Прогнать раннер дважды: на чистой БД (все миграции применяются по порядку) и на БД с уже заполненной `SequelizeMeta` (применяются только новые).