@btravstack/di
@btravstack/di
Interfaces
Scope
Defined in: port.ts:133
A phantom requirement, not a real service: nothing constructs one or reads it out of a Context. A resourceful provider adds Scope to its Needs, so Module.build refuses a graph that still owns an un-discharged resource and only Module.scoped — which opens one — strips it back out.
Whether Scope can be PROVIDED is a separate hazard, checked at run time by build.ts's plan rather than in the types. A type-level guard keyed on the id was tried and was both bypassable (a widening to AnyPort before the call slips past it) and a false positive on any port-generic helper, since the conditional cannot reduce for an unresolved P.
Extends
PortInstance<"@di/Scope",never>
Properties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "@di/Scope" | PortDeclaration("@di/Scope").[ID] | port.ts:11 |
[SERVICE] | readonly | never | PortDeclaration("@di/Scope").[SERVICE] | port.ts:12 |
Type Aliases
AnyModule
type AnyModule = object;Defined in: module.ts:16
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
exports | readonly | readonly (AnyPort | AnyModule)[] | module.ts:20 |
imports | readonly | readonly AnyModule[] | module.ts:18 |
name | readonly | string | module.ts:17 |
provides | readonly | readonly AnyProvider[] | module.ts:19 |
AnyPort
type AnyPort = object & () => AnyPortInstance;Defined in: port.ts:53
Type Declaration
| Name | Type | Defined in |
|---|---|---|
many? | true | port.ts:55 |
portId | string | port.ts:54 |
AnyProvider
type AnyProvider = object;Defined in: module.ts:14
Structural, listing only the concrete fields: a bound that compared the phantom channels would trip on their mixed variance. Do not replace with Provider<any, any, any>.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
deps | readonly | readonly AnyPort[] | module.ts:14 |
port | readonly | AnyPort | module.ts:14 |
Context
type Context<R> = object;Defined in: context.ts:23
_R is LOAD-BEARING. in R only asserts contravariance, and get cannot carry the check on its own — R appears there solely as the bound of get's own type parameter, which is not independently contravariant. Remove the phantom field, or make it optional, and Context measures as bivariant: Context<Database> flows where Context<Database | Logger> is required, with no signal at the use site.
Type Parameters
| Type Parameter |
|---|
R |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
_R | readonly | (r) => void | context.ts:24 |
get | readonly | <S>(port) => ServiceOf<S> | context.ts:28 |
DependencyGate
type DependencyGate<N> = [N] extends [never] ? unknown : object;Defined in: module.ts:160
The entry points' gate for unmet dependencies, on NeedsGate's mechanism: unknown when nothing is unmet, the one-property object otherwise, so the diagnostic ends on the missing port.
Type Parameters
| Type Parameter |
|---|
N |
Exportable
type Exportable<I, P> =
| AnyPort & () => Available<I, P>
| AnyProvider & object
| I[number];Defined in: module.ts:76
An export entry is legal only if it is an available port, a provider for one (normalised to provider.port by ModuleDeclaration below), or an imported module (whole-module re-export).
The provider arm is for the helpers that mint their own port and hand back no class to name.
Each arm must stay a plain union member, never a generic helper taking AnyPort: instantiating the per-element conditional with the whole union asks whether EVERY port is available, and rejects legal exports.
Type Parameters
| Type Parameter |
|---|
I extends readonly AnyModule[] |
P extends readonly AnyProvider[] |
ManyPortClass()
type ManyPortClass<Id> = PortInstance<Id, readonly Member[]> & object;Defined in: port.ts:30
A set port: several providers may target it, and Context.get yields every contribution rather than one service. Port.many("Id")<Member> fixes the MEMBER shape, but the port's own service — what lands in a Context — is readonly Member[].
many: true is the runtime discriminant build.ts reads off the class; the [MANY] brand is its type-level twin, which is what lets MemberOf recover a member's shape from a concrete set-port class.
Type Parameters
| Type Parameter |
|---|
Id extends string |
type new ManyPortClass<Member>(): PortInstance<Id, readonly Member[]> & object;A set port: several providers may target it, and Context.get yields every contribution rather than one service. Port.many("Id")<Member> fixes the MEMBER shape, but the port's own service — what lands in a Context — is readonly Member[].
many: true is the runtime discriminant build.ts reads off the class; the [MANY] brand is its type-level twin, which is what lets MemberOf recover a member's shape from a concrete set-port class.
Returns
PortInstance<Id, readonly Member[]> & object
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
many | readonly | true | port.ts:33 |
portId | readonly | Id | port.ts:32 |
MemberOf
type MemberOf<T> = T extends object & PortInstance<string, infer S> ? S extends readonly infer M[] ? M : never : T extends () => object & PortInstance<string, infer S> ? S extends readonly infer M[] ? M : never : never;Defined in: port.ts:94
Recovers a set port's member shape — the type a Provider.member factory actually produces — from [MANY], not from ServiceOf<T>'s shape. An earlier version keyed this off "does the service look like an array" (ServiceOf<T> extends readonly (infer M)[] ? M : never), which is unsound: an ordinary port whose declared service happens to be an array — class Tags extends Port("Tags")<readonly string[]> {} — has manyundefined at runtime (build.ts's plan/context.ts's unsafeAddAll both discriminate on that static field, never on shape), so Provider.member(Tags)({ inject: {}, value: "a" }) type-checked under the old definition while landing as a single service at runtime — ctx.get(Tags) would return "a", contradicting its own readonly string[] type. [MANY] is the same brand ManyPortClass's instance type carries and is module-private (declared, not exported, above), so nothing outside this file can forge it onto an ordinary port's instance type; keying MemberOf off its presence makes the type-level check agree with the runtime one.
Type Parameters
| Type Parameter |
|---|
T |
Module
type Module<Exports, E, Needs> = object;Defined in: module.ts:49
The variance rule, shared with Provider: capability channels (_exports) are contravariant, so you may forget what you have; obligation channels (_error, _needs) are covariant, so you may not forget what you owe.
Both directions are load-bearing and were measured. Turning _error or _needs contravariant lets an error channel narrow to one arm and lets Module<X, E, Database> launder past the build gate as Module<X, E, never>.
Type Parameters
| Type Parameter |
|---|
Exports |
E |
Needs |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
_error | readonly | () => E | module.ts:51 |
_exports | readonly | (x) => void | module.ts:50 |
_needs | readonly | () => Needs | module.ts:52 |
exports | readonly | readonly (AnyPort | AnyModule)[] | module.ts:56 |
imports | readonly | readonly AnyModule[] | module.ts:54 |
name | readonly | string | module.ts:53 |
provides | readonly | readonly AnyProvider[] | module.ts:55 |
NeedsGate
type NeedsGate<I, P, N> = [Exclude<NeedOf<P[number]>, Available<I, P>>] extends [InstanceType<N[number]> | Scope] ? unknown : object;Defined in: module.ts:106
The declaration gate. A port this module's own providers read, and that nothing here satisfies, is an error unless it is named in needs — so a provider can never silently receive a service from whoever composed the module. Named, it may: the provider is handed whatever an ancestor supplies. What naming does not do is make the port Available here, so a declared need is still not exportable.
An import's own unmet needs are not this module's to re-declare; Scope is exempt, since nothing can provide it.
The gate is an object with one required property, not StartGate's bare string: only the property makes the diagnostic name the port. Do not "simplify" it back.
Type Parameters
| Type Parameter |
|---|
I extends readonly AnyModule[] |
P extends readonly AnyProvider[] |
N extends readonly AnyPort[] |
PortClass()
type PortClass<Id> = PortInstance<Id, Service>;Defined in: port.ts:15
Type Parameters
| Type Parameter |
|---|
Id extends string |
type new PortClass<Service>(): PortInstance<Id, Service>;Returns
PortInstance<Id, Service>
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
portId | readonly | Id | port.ts:17 |
PortClassOf()
type PortClassOf<Id, Service> = PortInstance<Id, Service>;Defined in: port.ts:64
A concrete port class with Id and Service fixed — the type of provider.port for a helper that mints or targets a port the caller never spells. A class expression's type is anonymous and declaration emit cannot name it across packages; this is the nameable spelling.
Type Parameters
| Type Parameter |
|---|
Id extends string |
Service |
type new PortClassOf(): PortInstance<Id, Service>;A concrete port class with Id and Service fixed — the type of provider.port for a helper that mints or targets a port the caller never spells. A class expression's type is anonymous and declaration emit cannot name it across packages; this is the nameable spelling.
Returns
PortInstance<Id, Service>
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
portId | readonly | Id | port.ts:65 |
PortInstance
type PortInstance<Id, Service> = object;Defined in: port.ts:10
The type that appears in a Needs / Exports union. Identity is the literal Id: two ports declared with different ids have different instance types even when their service shapes are identical.
Extended by
Type Parameters
| Type Parameter |
|---|
Id extends string |
Service |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
[ID] | readonly | Id | port.ts:11 |
[SERVICE] | readonly | Service | port.ts:12 |
Provider
type Provider<P, E, N> = object;Defined in: provider.ts:138
The package's variance rule, stated here and on Module: capability channels (_port) are contravariant, obligation channels (_error, _needs) covariant.
With the obligation channels contravariant an ordinary return-type annotation laundered both — and with them the Scope ScopeOf puts in Needs, routing a resourceful provider to Module.build, which never closes the scope it opens, and silently dropping its release.
Type Parameters
| Type Parameter |
|---|
P |
E |
N |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
_error | readonly | () => E | provider.ts:140 |
_needs | readonly | () => N | provider.ts:141 |
_port | readonly | (p) => void | provider.ts:139 |
construct | readonly | (services) => AsyncResult<unknown, unknown> | provider.ts:147 |
deps | readonly | readonly AnyPort[] | provider.ts:143 |
onStart | readonly | ((service) => void | Promise<void>) | undefined | provider.ts:149 |
onStop | readonly | ((service) => void | Promise<void>) | undefined | provider.ts:150 |
port | readonly | AnyPort | provider.ts:142 |
release | readonly | ((service) => void | Promise<void>) | undefined | provider.ts:148 |
ScopedOptions
type ScopedOptions = object;Defined in: build.ts:230
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
onTeardownError? | readonly | TeardownReporter | - | build.ts:231 |
seed? | readonly | readonly SeedEntry<AnyPort>[] | Values supplied to the scope from OUTSIDE its module tree, keyed by port. The planner treats a seeded port as provided, exactly as it treats the parent's services — a seed is more keys on the same context. | build.ts:237 |
SeedEntry
type SeedEntry<P> = readonly [P, ServiceOf<InstanceType<P>>];Defined in: build.ts:228
Type Parameters
| Type Parameter |
|---|
P extends AnyPort |
ServiceOf
type ServiceOf<T> = T extends PortInstance<string, infer S> ? S : T extends () => PortInstance<string, infer S> ? S : never;Defined in: port.ts:70
Recovers a service shape from either the instance type or the class.
Type Parameters
| Type Parameter |
|---|
T |
Variables
Context
Context: object;Defined in: context.ts:23
Type Declaration
| Name | Type | Defined in |
|---|---|---|
empty() | () => Context<never> | context.ts:63 |
Module
Module: <Name>(name) => <I, P, X, N>(options) => Module<ResolvedExports<X>, ErrOf<P[number]> | ErrOfModule<I[number]>,
| Exclude<NeedOf<P[number]>, Available<I, P>>
| Exclude<NeedsOfModule<I[number]>, Available<I, P>>> & object;Defined in: module.ts:49
Module.build sorts the tree into dependency-ordered levels, checks for wiring bugs before any factory runs, then constructs level by level.
Type Declaration
| Name | Type | Description | Defined in |
|---|---|---|---|
build() | <X, E, N>(module) => AsyncResult<Context<X>, E> | - | module.ts:169 |
forkScope() | <PParent, X, E, N, A, E2, Seeded>(parent, module, use, options?) => AsyncResult<A, E | E2> | A short-lived scope layered over an already-built parent Context, for per-request services that must not outlive the request but do read what the parent constructed. The fork gets a fresh scope, so closing it releases only what the fork acquired and the parent stays up for a sibling. The gate subtracts PParent as well as Scope: a request module may depend on anything the parent already provides. It also subtracts InstanceType<Seeded>, for the same reason: a seed entry supplies a port from outside the tree exactly as the parent does. | module.ts:198 |
scoped() | <X, E, N, A, E2>(module, use, options?) => AsyncResult<A, E | E2> | Module.build's resourceful counterpart: opens a scope, runs the module, hands the built Context to use, and closes the scope — releasing every acquired resource, LIFO — on every path out. The gate excludes Scope alone, since this is the entry point that discharges it; every other unmet requirement still surfaces. | module.ts:180 |
Port
const Port: <Id>(id) => PortClass<Id> & object;Defined in: port.ts:164
Port.many mirrors PortDeclaration exactly, except the returned class also carries a many: true static field — the runtime discriminant build.ts's plan/constructLevel read to decide a port accumulates contributions instead of colliding on the second provider. See ManyPortClass's own doc comment above for why this is a static field (readable at runtime off the class) rather than the [MANY] brand (a type-level-only marker on the never-instantiated instance type).
Type Declaration
| Name | Type | Defined in |
|---|---|---|
many() | <Id>(id) => ManyPortClass<Id> | port.ts:165 |
Provider
Provider: <P, S>(port) => <D, O>(options) => Provider<InstanceType<P>, ErrorOf<O>, InstanceType<D[keyof D]> | ScopeOf<O>> & object & object;Defined in: provider.ts:138
Provider.member's port is bound by the structural AnyPort, not ManyPortClass<string>: a concrete set-port class has a concrete constructor once Member is fixed, so — exactly as with PortClass — it is not assignable to the generic form, and binding it that way would reject every real call site.
Instantiating S as MemberOf<P> is the whole difference: it qualifies an arm against ONE member's shape. The runtime body is the ordinary factory's; context.ts's unsafeAddAll is what turns a member into an array entry.
Type Declaration
| Name | Type | Defined in |
|---|---|---|
member() | <P>(port) => <D, O>(options) => Provider<InstanceType<P>, ErrorOf<O>, InstanceType<D[keyof D]> | ScopeOf<O>> & object | provider.ts:240 |
Functions
overrideProvider()
function overrideProvider<P, E, N>(provider): Provider<P, E, N>;Defined in: provider.ts:206
Type Parameters
| Type Parameter |
|---|
P |
E |
N |
Parameters
| Parameter | Type |
|---|---|
provider | Provider<P, E, N> |
Returns
Provider<P, E, N>