Skip to content

@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

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"@di/Scope"PortDeclaration("@di/Scope").[ID]port.ts:11
[SERVICE]readonlyneverPortDeclaration("@di/Scope").[SERVICE]port.ts:12

Type Aliases

AnyModule

ts
type AnyModule = object;

Defined in: module.ts:16

Properties

PropertyModifierTypeDefined in
exportsreadonlyreadonly (AnyPort | AnyModule)[]module.ts:20
importsreadonlyreadonly AnyModule[]module.ts:18
namereadonlystringmodule.ts:17
providesreadonlyreadonly AnyProvider[]module.ts:19

AnyPort

ts
type AnyPort = object & () => AnyPortInstance;

Defined in: port.ts:53

Type Declaration

NameTypeDefined in
many?trueport.ts:55
portIdstringport.ts:54

AnyProvider

ts
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

PropertyModifierTypeDefined in
depsreadonlyreadonly AnyPort[]module.ts:14
portreadonlyAnyPortmodule.ts:14

Context

ts
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

PropertyModifierTypeDefined in
_Rreadonly(r) => voidcontext.ts:24
getreadonly<S>(port) => ServiceOf<S>context.ts:28

DependencyGate

ts
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

ts
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()

ts
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
ts
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

PropertyModifierTypeDefined in
manyreadonlytrueport.ts:33
portIdreadonlyIdport.ts:32

MemberOf

ts
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

ts
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

PropertyModifierTypeDefined in
_errorreadonly() => Emodule.ts:51
_exportsreadonly(x) => voidmodule.ts:50
_needsreadonly() => Needsmodule.ts:52
exportsreadonlyreadonly (AnyPort | AnyModule)[]module.ts:56
importsreadonlyreadonly AnyModule[]module.ts:54
namereadonlystringmodule.ts:53
providesreadonlyreadonly AnyProvider[]module.ts:55

NeedsGate

ts
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()

ts
type PortClass<Id> = PortInstance<Id, Service>;

Defined in: port.ts:15

Type Parameters

Type Parameter
Id extends string
ts
type new PortClass<Service>(): PortInstance<Id, Service>;

Returns

PortInstance<Id, Service>

Properties

PropertyModifierTypeDefined in
portIdreadonlyIdport.ts:17

PortClassOf()

ts
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
ts
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

PropertyModifierTypeDefined in
portIdreadonlyIdport.ts:65

PortInstance

ts
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

PropertyModifierTypeDefined in
[ID]readonlyIdport.ts:11
[SERVICE]readonlyServiceport.ts:12

Provider

ts
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

PropertyModifierTypeDefined in
_errorreadonly() => Eprovider.ts:140
_needsreadonly() => Nprovider.ts:141
_portreadonly(p) => voidprovider.ts:139
constructreadonly(services) => AsyncResult<unknown, unknown>provider.ts:147
depsreadonlyreadonly AnyPort[]provider.ts:143
onStartreadonly((service) => void | Promise<void>) | undefinedprovider.ts:149
onStopreadonly((service) => void | Promise<void>) | undefinedprovider.ts:150
portreadonlyAnyPortprovider.ts:142
releasereadonly((service) => void | Promise<void>) | undefinedprovider.ts:148

ScopedOptions

ts
type ScopedOptions = object;

Defined in: build.ts:230

Properties

PropertyModifierTypeDescriptionDefined in
onTeardownError?readonlyTeardownReporter-build.ts:231
seed?readonlyreadonly 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

ts
type SeedEntry<P> = readonly [P, ServiceOf<InstanceType<P>>];

Defined in: build.ts:228

Type Parameters

Type Parameter
P extends AnyPort

ServiceOf

ts
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

ts
Context: object;

Defined in: context.ts:23

Type Declaration

NameTypeDefined in
empty()() => Context<never>context.ts:63

Module

ts
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

NameTypeDescriptionDefined 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

ts
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

NameTypeDefined in
many()<Id>(id) => ManyPortClass<Id>port.ts:165

Provider

ts
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

NameTypeDefined in
member()<P>(port) => <D, O>(options) => Provider<InstanceType<P>, ErrorOf<O>, InstanceType<D[keyof D]> | ScopeOf<O>> & objectprovider.ts:240

Functions

overrideProvider()

ts
function overrideProvider<P, E, N>(provider): Provider<P, E, N>;

Defined in: provider.ts:206

Type Parameters

Type Parameter
P
E
N

Parameters

ParameterType
providerProvider<P, E, N>

Returns

Provider<P, E, N>

Released under the MIT License.