Почему AppError не присваивается к AppError после добавления одной зависимости?
Приложение перестало компилироваться сразу после установки новой зависимости. Обе копии пакета @acme/errors одной и той же версии, исходник класса не менялся.
Ограничения: npm ls @acme/errors печатает пакет дважды — один раз поднятым в корень, один раз вложенным под @acme/client. Не ослабляйте код через as any.
// node_modules/@acme/errors/index.d.ts (на диске лежат две копии)
export declare class AppError extends Error {
private code: string;
constructor(code: string);
}
// apps/web/src/handler.ts
import { AppError } from '@acme/errors'; // резолвится в поднятую корневую копию
import { report } from '@acme/client'; // report(e: AppError) — вложенная копия
report(new AppError('E_TIMEOUT'));
// error TS2345: Argument of type 'AppError' is not assignable to parameter of type 'AppError'.
// Types have separate declarations of a private property 'code'.
Определите причину.
На диске две копии @acme/errors, поэтому компилятор строит два несвязанных типа AppError. Структурное сопоставление их не спасёт: член private делает класс номинативным, а приватные члены из разных объявлений не совпадают никогда. Дедуплицируйте пакет.
- ✗Считать, что структурная типизация делает два одинаковых класса взаимозаменяемыми, забыв про
private - ✗Винить расхождение версий, когда обе копии на деле одной версии
- ✗Глушить ошибку через
as anyвместо дедупликации зависимости
- →Какая команда покажет, что пакет отрезолвился в дереве зависимостей дважды?
- →Почему поле
privateзаставляет структурный класс вести себя номинативно?
Ошибка выглядит абсурдно — тип не присваивается сам себе, — но имена лгут. На диске два @acme/errors, и компилятор построил из них два независимых символа AppError.
Обычно структурная типизация такое сглаживает: два одинаковых по форме типа совместимы. Но private code делает класс номинативным. Приватный член совместим только сам с собой — с тем же объявлением из того же файла. Две копии файла — два разных приватных члена — несовместимые типы.
Лечится дедупликацией, а не типами. Заставьте резолвиться одну копию:
{
"overrides": { "@acme/errors": "2.4.1" }
}
Затем npm ls @acme/errors должен показать пакет ровно один раз. В воркспейсе то же самое даёт подъём зависимости в корень или переезд @acme/errors в peerDependencies у @acme/client, чтобы он не тянул собственную вложенную копию.