Skip to content

Core concept

Lazy Initialization

Control whether supplied dependency providers run on demand or eagerly.

The lazy option controls when dependency providers supplied in a root dependency map or bound with .provide() are initialized. It does not change the explicit dependency type.

lazy: true (default)

A provider runs on the first read of its key. If the root module never reads that key, the provider does not run. With the default cache: 'run', later reads in the same root call reuse the first value.

import { makeModule, type Live } from '@favy/di';

const LazyModule = makeModule({ lazy: true });

let resourceRuns = 0;
const Resource = LazyModule()('Resource', () => ++resourceRuns);
type ResourceLive = Live<typeof Resource>;

const IgnoreResource = LazyModule<ResourceLive>()(
  'IgnoreResource',
  () => 'unused',
);

console.log(IgnoreResource({ Resource })); // "unused"
console.log(resourceRuns); // 0

const ReadTwice = LazyModule<ResourceLive>()(
  'ReadTwice',
  (deps) => deps.Resource + deps.Resource,
);

console.log(ReadTwice({ Resource })); // 2: 1 + 1 in one run
console.log(resourceRuns); // 1
console.log(ReadTwice({ Resource })); // 4: 2 + 2 in a new run
console.log(resourceRuns); // 2

Access includes parameter destructuring. A callback parameter such as ({ Resource }) reads Resource before its body executes, even if the body never uses the local variable. Object rest/spread enumerates the dependency context and therefore reads every included provider key.

lazy: false

When the root module itself is evaluated, the root call invokes every provider supplied at that root before running its own callback, including providers the callback never reads. With cache: 'module', an already cached root returns before this eager pass. Nested module calls do not eagerly walk forwarded siblings again; only dependencies supplied locally to that invocation are added to its eager pass.

import { makeModule, type Live } from '@favy/di';

const EagerModule = makeModule({ lazy: false });

let resourceRuns = 0;
const Resource = EagerModule()('Resource', () => ++resourceRuns);
type ResourceLive = Live<typeof Resource>;

const IgnoreResource = EagerModule<ResourceLive>()(
  'IgnoreResource',
  () => 'unused',
);

console.log(IgnoreResource({ Resource })); // "unused"
console.log(resourceRuns); // 1

Plain dependency values pass through unchanged. With cache: 'run' or cache: 'module', an eagerly initialized provider is reused when the callback reads it. With cache: 'none', eager initialization runs the provider once and every later read invokes it again.

Lazy versus cache

These options answer different questions:

  • lazy decides whether an unused provider executes.
  • cache decides whether an executed provider’s value is reused.

The standard Module combines lazy: true with cache: 'run': resolve on first access, reuse within that root call, and resolve again in the next root call.