Skip to content

Suppliers ​

A dependency is usually built once, before its dependent. To build values on demand instead, such as a fresh id for every incoming message, depend on a supplier: a function that builds a new value each time it is called.

Mark the dependency with .supplier(), which changes its type from T to () => T:

ts
import { 
bind
,
createContainer
,
createModule
,
identifier
} from 'haywire';
const
seedId
=
identifier
<number>().
named
('seed');
const
randomId
=
identifier
<string>().
named
('random');
class
MessageHandler
{
constructor(private readonly
nextId
: () => string) {}
handle
(
message
: string) {
return {
id
: this.
nextId
(),
message
};
} } const
container
=
createContainer
(
createModule
(
bind
(
seedId
).
withInstance
(42))
.
addBinding
(
bind
(
randomId
)
.
withDependencies
([
seedId
])
.
withProvider
(
seed
=> (
Math
.
random
() *
seed
).
toString
(36))
) .
addBinding
(
bind
(
MessageHandler
).
withDependencies
([
randomId
.
supplier
()]).
withConstructorProvider
())
);

randomId is bound once, and can be depended on directly or through a supplier. Its own dependencies are resolved each time the supplier is called, so they follow their scopes as usual.

A supplier id is distinct from an id for a function type, so identifier<() => string>() is never confused with randomId.supplier().

Coming from Guice or Dagger?

A Haywire supplier is what those libraries call a Provider<T>. In Haywire, a provider is the function given to withProvider(), which builds a value from its dependencies.

Forms ​

supplier(...)TypeBehaves like
(), true or { sync: true, propagateScope: false }() => TA new container.get() call.
'async' or { sync: false, propagateScope: false }() => Promise<T>A new container.getAsync() call.
{ sync: true, propagateScope: true }() => TBuilds within the current request.
{ sync: false, propagateScope: true }() => Promise<T>Builds within the current request.
falseTNot a supplier. Reverts the modifier.

Any container can have sync suppliers, even one with async bindings, as long as everything that supplier builds is synchronous.

Propagating the request ​

Each call to a supplier is a new request, so request-scoped values aren't shared with its dependent. With propagateScope: true, the supplier builds within the request that built its dependent instead, sharing its request-scoped values.

A binding scoped with isolatedRequestScope opts out: it, and everything that depends on it, is always built in a new request.

Sync suppliers of async values ​

A sync supplier can't wait for a promise, so everything it builds must be synchronous, or already built by an eager scope.

Not checked by types

Types can't tell whether a sync supplier can be built synchronously. container.check() can, so call it in a test. When in doubt, use an async supplier, which is always safe.