Migración a 2.0
La versión 2.0 mantiene Node 20+ y los decoradores estándar de TypeScript, pero corrige comportamientos observables de 1.x. Esta guía cubre los cambios incompatibles y enlaza las pruebas que definen cada contrato.
@TransitionTo espera el éxito asíncrono
Sección titulada «@TransitionTo espera el éxito asíncrono»En 1.x, @TransitionTo cambiaba el estado justo después de invocar el método, aun cuando éste devolviera una promesa pendiente o finalmente rechazara. En 2.0, un Promise o PromiseLike sólo confirma la transición cuando se resuelve; un rechazo conserva el estado anterior y se propaga sin envolver la razón.
Antes (1.x):
@When('draft')@TransitionTo('review')async submit(): Promise<void> { await saveDraft();}
const result = doc.submit();doc.state; // 'review' antes de que result se resuelvaDespués (2.0):
const result = doc.submit();doc.state; // 'draft' mientras result está pendienteawait result;doc.state; // 'review'Si await result rechaza, doc.state continúa siendo 'draft'. Mantén @When encima de @TransitionTo para que la guarda de estado se ejecute primero. Contrato verificable: tests/state-machine-async.test.ts y tests/state-machine.test.ts.
@Timeout rechaza, pero no cancela
Sección titulada «@Timeout rechaza, pero no cancela»@Timeout(ms) sigue rechazando la promesa devuelta cuando vence el límite y limpia su temporizador al resolver, rechazar o agotar el plazo. No inyecta un AbortSignal ni puede cancelar de forma segura una operación que no coopera: el trabajo original puede seguir ejecutándose y su resultado se descarta.
Antes y después, usa el resultado decorado como límite de espera:
class Api { @Timeout(500) async fetchUser(id: string): Promise<User> { return requestUser(id); }}
await new Api().fetchUser('42'); // rechaza tras 500 ms si no terminaSi necesitas cancelación, haz que tu propia operación acepte y observe una señal; 2.0 no añade argumentos ni una cancelación implícita. Contrato verificable: tests/timeout.test.ts.
@Singleton y @Flyweight aíslan la herencia
Sección titulada «@Singleton y @Flyweight aíslan la herencia»En 1.x, una base decorada podía compartir por accidente su instancia o cache con una derivada no decorada. En 2.0, el cache se particiona por el constructor efectivo (newTarget): base y derivada son distintas incluso con el mismo argumento, y cada clase conserva su propia reutilización.
Antes (1.x):
@Singletonclass Base {}
class Derived extends Base {}
new Base() === new Derived(); // podía ser trueDespués (2.0):
const base = new Base();const derived = new Derived();
base === derived; // falsenew Base() === base; // truenew Derived() === derived; // trueEl mismo aislamiento aplica a @Flyweight: new Base('x') y new Derived('x') no comparten entrada, pero cada clase reutiliza su propia entrada para 'x'.
Hay una limitación de JavaScript para @Singleton: si la derivada no está decorada, su constructor se ejecuta en cada new Derived(...) después de super(). Puede mutar campos propios con argumentos posteriores aunque reutilice la misma instancia derivada. Decora la derivada si necesitas controlar también esa inicialización. Contrato verificable: tests/singleton.test.ts, tests/flyweight.test.ts y tests/characterization/inheritance.test.ts.
Ciclos síncronos del contenedor
Sección titulada «Ciclos síncronos del contenedor»Container ahora detecta un ciclo durante la resolución síncrona y lanza el error público CircularDependencyError. El error expone tokenPath y un mensaje legible, por ejemplo A -> B -> A.
Antes (1.x):
container.resolve(A); // un ciclo A -> B -> A agotaba la pilaDespués (2.0):
try { container.resolve(A);} catch (error) { if (error instanceof CircularDependencyError) { console.log(error.tokenPath); // ['A', 'B', 'A'] }}El contenedor permanece deliberadamente pequeño: providers sólo síncronos (useValue, useClass, useFactory), scopes singleton y transient; no hay providers async, scopes adicionales, contenedores hijo, disposal ni unregister. Contrato verificable: tests/injection.test.ts y tests/characterization/injection-contract.test.ts.
Novedades que no requieren migración
Sección titulada «Novedades que no requieren migración»2.0 añade una API funcional (/operators, /pipe, /run, /policy), observabilidad (/observability), stores async (/store) y contexto de request para Node (/node). Son subpaths nuevos y opt-in: el código que usa solo decoradores no tiene que cambiar nada por ellos. La librería continúa siendo dependency-free en runtime y requiere Node 20+.

