Migrating to 2.0
2.0 keeps Node 20+ and standard TypeScript decorators, but fixes observable behaviors from 1.x. This guide covers the breaking changes and links to the tests that pin down each contract.
@TransitionTo waits for async success
Section titled “@TransitionTo waits for async success”In 1.x, @TransitionTo changed state right after invoking the method, even when it returned a pending promise or eventually rejected. In 2.0, a Promise or PromiseLike only confirms the transition once it resolves; a rejection keeps the previous state and propagates without wrapping the reason.
Before (1.x):
@When('draft')@TransitionTo('review')async submit(): Promise<void> { await saveDraft();}
const result = doc.submit();doc.state; // 'review' before result resolvesAfter (2.0):
const result = doc.submit();doc.state; // 'draft' while result is pendingawait result;doc.state; // 'review'If await result rejects, doc.state stays 'draft'. Keep @When above @TransitionTo so the state guard runs first. Verifiable contract: tests/state-machine-async.test.ts and tests/state-machine.test.ts.
@Timeout rejects, but does not cancel
Section titled “@Timeout rejects, but does not cancel”@Timeout(ms) still rejects the returned promise when the limit is exceeded and clears its timer on resolve, reject, or timeout. It does not inject an AbortSignal and cannot safely cancel an operation that doesn’t cooperate: the original work may keep running and its result is simply discarded.
Before and after, use the decorated result as your wait limit:
class Api { @Timeout(500) async fetchUser(id: string): Promise<User> { return requestUser(id); }}
await new Api().fetchUser('42'); // rejects after 500ms if it hasn't finishedIf you need cancellation, make your own operation accept and observe a signal; 2.0 does not add arguments or implicit cancellation. Verifiable contract: tests/timeout.test.ts.
@Singleton and @Flyweight isolate inheritance
Section titled “@Singleton and @Flyweight isolate inheritance”In 1.x, a decorated base class could accidentally share its instance or cache with an undecorated subclass. In 2.0, the cache is partitioned by the effective constructor (newTarget): base and derived are distinct even with the same argument, and each class keeps its own reuse.
Before (1.x):
@Singletonclass Base {}
class Derived extends Base {}
new Base() === new Derived(); // could be trueAfter (2.0):
const base = new Base();const derived = new Derived();
base === derived; // falsenew Base() === base; // truenew Derived() === derived; // trueThe same isolation applies to @Flyweight: new Base('x') and new Derived('x') don’t share an entry, but each class reuses its own entry for 'x'.
There’s a JavaScript limitation for @Singleton: if the subclass isn’t decorated, its constructor still runs on every new Derived(...) after super(). It can mutate its own fields with later arguments even while reusing the same derived instance. Decorate the subclass too if you need to control that initialization. Verifiable contract: tests/singleton.test.ts, tests/flyweight.test.ts, and tests/characterization/inheritance.test.ts.
Synchronous container cycles
Section titled “Synchronous container cycles”Container now detects a cycle during synchronous resolution and throws the public CircularDependencyError. The error exposes tokenPath and a readable message, e.g. A -> B -> A.
Before (1.x):
container.resolve(A); // an A -> B -> A cycle exhausted the stackAfter (2.0):
try { container.resolve(A);} catch (error) { if (error instanceof CircularDependencyError) { console.log(error.tokenPath); // ['A', 'B', 'A'] }}The container remains deliberately small: synchronous-only providers (useValue, useClass, useFactory), singleton and transient scopes; there are no async providers, additional scopes, child containers, disposal, or unregister. Verifiable contract: tests/injection.test.ts and tests/characterization/injection-contract.test.ts.
New features that need no migration
Section titled “New features that need no migration”2.0 adds a functional API (/operators, /pipe, /run, /policy), observability (/observability), async stores (/store), and Node request context (/node). They are new, opt-in subpaths: code that only uses decorators does not need to change for them. The library stays dependency-free at runtime and requires Node 20+.

