//#region src/domain/errors.d.ts /** * Base class for all container errors. * Every error includes a human-readable `hint` and structured `details` * so that AI tools and developers can auto-correct issues. * * @example * ```typescript * try { container.userService; } * catch (e) { * if (e instanceof ContainerError) { * console.log(e.hint); // actionable fix * console.log(e.details); // structured context * } * } * ``` */ declare abstract class ContainerError extends Error { abstract readonly hint: string; abstract readonly details: Record; constructor(message: string); } /** * Thrown when a non-function value is passed in `scope()` or `extend()` deps. * (`.add()` accepts non-function values as eager instances — see {@link ContainerBuilder.add}.) * * @example * ```typescript * app.scope({ apiKey: 'sk-123' }); * // ContainerConfigError: 'apiKey' must be a factory function, got string. * // hint: "Wrap it: apiKey: () => 'sk-123'" * ``` */ declare class ContainerConfigError extends ContainerError { readonly hint: string; readonly details: { key: string; actualType: string; }; constructor(key: string, actualType: string); } /** * Thrown when a key is registered more than once on the same builder. * Use `.extend()` or `.scope()` for intentional overrides at runtime. * * @example * ```typescript * container().add('db', () => new DB()).add('db', () => new DB()); * // DuplicateKeyError: 'db' is already registered in this container. * // hint: "Use .extend({ db: ... }) to override at runtime, or .scope({ db: ... }) for request-level overrides." * ``` */ declare class DuplicateKeyError extends ContainerError { readonly hint: string; readonly details: { key: string; }; constructor(key: string); } /** * Thrown when a reserved container method name is used as a dependency key. * * @example * ```typescript * container().add('inspect', () => 'foo'); * // ReservedKeyError: 'inspect' is a reserved container method. * // hint: "Rename this dependency, e.g. 'inspectService' or 'dataInspector'." * ``` */ declare class ReservedKeyError extends ContainerError { readonly hint: string; readonly details: { key: string; reserved: string[]; }; constructor(key: string, reserved: readonly string[]); } /** * Thrown when a dependency cannot be found during resolution. * Includes fuzzy suggestion if a similar key exists. * * @example * ```typescript * container.userService; * // ProviderNotFoundError: Cannot resolve 'userService': dependency 'userRepo' not found. * // hint: "Did you mean 'userRepository'?" * ``` */ declare class ProviderNotFoundError extends ContainerError { readonly hint: string; readonly details: { key: string; chain: string[]; registered: string[]; suggestion: string | undefined; }; constructor(key: string, chain: string[], registered: string[], suggestion?: string); } /** * Thrown when a circular dependency is detected. * * @example * ```typescript * // CircularDependencyError: Circular dependency detected while resolving 'authService'. * // Cycle: authService -> userService -> authService * ``` */ declare class CircularDependencyError extends ContainerError { readonly hint: string; readonly details: { key: string; chain: string[]; cycle: string; }; constructor(key: string, chain: string[]); } /** * Thrown when a factory function returns `undefined`. * * @example * ```typescript * container.db; * // UndefinedReturnError: Factory 'db' returned undefined. * // hint: "Did you forget a return statement?" * ``` */ declare class UndefinedReturnError extends ContainerError { readonly hint: string; readonly details: { key: string; chain: string[]; }; constructor(key: string, chain: string[]); } /** * Thrown when a factory function throws an error during resolution. * Wraps the original error with resolution context. * * @example * ```typescript * container.db; * // FactoryError: Factory 'db' threw an error: "Connection refused" * ``` */ declare class FactoryError extends ContainerError { readonly hint: string; readonly details: { key: string; chain: string[]; originalError: string; }; readonly originalError: unknown; constructor(key: string, chain: string[], originalError: unknown); } /** * Warning emitted when a singleton depends on a transient dependency. * The transient value gets frozen inside the singleton — almost always a bug. * * @example * ```typescript * // ScopeMismatchWarning: Singleton 'userService' depends on transient 'requestId'. * ``` */ declare class ScopeMismatchWarning implements ContainerWarning { readonly type: "scope_mismatch"; readonly message: string; readonly hint: string; readonly details: { singleton: string; transient: string; }; constructor(singletonKey: string, transientKey: string); } /** * Warning emitted when an async `onInit()` rejects during lazy resolution. * The error is collected (not thrown) because lazy access is fire-and-forget. * Use `preload()` to await and surface async init errors. * * @example * ```typescript * // health().warnings may include: * // { type: 'async_init_error', message: "onInit() for 'db' rejected: connection refused" } * ``` */ declare class AsyncInitErrorWarning implements ContainerWarning { readonly type: "async_init_error"; readonly message: string; readonly hint: string; readonly details: { key: string; error: string; }; constructor(key: string, error: unknown); } //#endregion //#region src/domain/types/public.d.ts /** * Public types — everything users see and consume from `import 'inwire'`. * No internal collaborator interfaces here (those live in `./internal.ts`). */ /** * A factory function that receives the container and returns an instance. * * @example * ```typescript * const factory: Factory = (c) => new MyService(c.db); * ``` */ type Factory = (container: unknown) => T; /** * Utility: `T` with keys of `U` overridden by `U` — avoids the `A & A → never` * collapse when classes have private members and the same key is declared on * both sides (e.g. global `AppDeps` + module `.add()`). */ type Override = Omit & U; /** * Reserved method names on the container that cannot be used as dependency keys. */ declare const RESERVED_KEYS: readonly ["scope", "extend", "module", "preload", "reset", "inspect", "describe", "health", "dispose", "toString", "toJSON", "size"]; type ReservedKey = (typeof RESERVED_KEYS)[number]; /** * String key constrained to `TContract`'s keys. Equivalent to `string & keyof TContract`, * just named for readability in builder signatures. */ type BuilderKey = string & keyof TContract; /** * Rejects {@link ReservedKey} values at the type level (collapses to `never`). * Used to gate `.add()` / `.addTransient()` against container method names. */ type NonReservedKey = K & (K extends ReservedKey ? never : K); /** * Argument type for `.add()` — accepts either a lazy factory or a non-function * eager instance. The `V extends Function ? never : V` clause excludes functions * from the instance variant (functions are always treated as factories). */ type FactoryOrInstance = ((c: TBuilt) => V) | (V & (V extends Function ? never : V)); /** * Resulting `TBuilt` after `.add(key, value)` — same as `Override>`, * just named for readability. */ type AddBuilt = Override>; /** * Global, augmentable interface describing the application's dependency shape. * * Empty by default. Each module file augments it with the bindings IT provides, * enabling **cross-module forward references** in factories — `c.X` resolves * even when `X` is added by another module loaded later. * * @example Augment from a module file: * ```typescript * declare module 'inwire' { * interface AppDeps { * IUserRepository: IUserRepository; * SignInUseCase: SignInUseCase; * } * } * ``` * * When `defineModule()` is called without an explicit `` generic, the * builder's `c` parameter is typed as `AppDeps` — the union of every module's * augmentations. TypeScript merges these declarations across files. */ interface AppDeps {} /** * Options for creating a scoped container. */ interface ScopeOptions { /** Optional name for the scope, useful for debugging and introspection. */ name?: string; } /** * Full container type exposed to the user. * Combines resolved dependencies with container methods. */ type Container = Record> = T & IContainer; /** * Container methods interface. Defines the API available on every container. */ interface IContainer = Record> { /** * Creates a child container with additional dependencies. * Child inherits all parent singletons and can add/override deps. */ scope unknown>>(extra: E, options?: ScopeOptions): Container }> & { [K in keyof E]: ReturnType }>; /** * Returns a new container with additional dependencies. * Existing singletons are shared. The original container is not modified. */ extend unknown>>(extra: E): Container }> & { [K in keyof E]: ReturnType }>; /** * Applies a module post-build using the builder pattern. * Semantically equivalent to `extend()` but uses `IContainerBuilder` for * incremental type accumulation of `c` in factories. */ module>(fn: (builder: IContainerBuilder, T>) => IContainerBuilder, T & TNew>): Container; /** Pre-resolves dependencies (warm-up). No args = preload everything. */ preload(...keys: (keyof T)[]): Promise; /** Returns the full dependency graph as a serializable JSON object. */ inspect(): ContainerGraph; /** Returns detailed information about a specific provider. */ describe(key: keyof T | string): ProviderInfo; /** Returns container health status and warnings. */ health(): ContainerHealth; /** Invalidates cached singletons, forcing re-creation on next access. */ reset(...keys: (keyof T)[]): void; /** LIFO `onDestroy()` on all resolved instances. */ dispose(): Promise; /** ES2023 explicit resource management hook — alias of {@link IContainer.dispose}. */ [Symbol.asyncDispose](): Promise; /** Returns a plain object of all currently resolved (cached) deps — does NOT trigger lazy resolution. Used by JSON.stringify. */ toJSON(): Record; /** Count of registered providers (factories), regardless of resolution state. */ readonly size: number; /** Iterate over `[key, resolvedValue]` pairs for all registered keys. Triggers lazy resolution. */ [Symbol.iterator](): IterableIterator<[string, unknown]>; } /** * Domain-level contract of the fluent builder. * * The concrete `ContainerBuilder` class in `application/` implements this * structurally — keeping the dependency rule one-way (`domain ← application`). * Consumers writing builder callbacks (e.g. inside `.module()` or `defineModule()`) * receive a value of this interface; they should never need the concrete class. */ interface IContainerBuilder = Record, TBuilt extends Record = {}> { /** Registers a dependency — factory (lazy) or instance (eager). */ add, V extends TContract[K]>(key: NonReservedKey, factoryOrInstance: FactoryOrInstance): IContainerBuilder>; /** Registers a transient dependency (new instance on every access). */ addTransient, V extends TContract[K]>(key: NonReservedKey, factory: (c: TBuilt) => V): IContainerBuilder>; /** Applies a module — a function that chains `.add()` calls on this builder. */ addModule, TNew extends Record>(module: (builder: IContainerBuilder) => IContainerBuilder): IContainerBuilder>; /** Merges a standalone builder's factories into this one. */ merge>(other: IContainerBuilder, TOther>): IContainerBuilder>; /** Builds and returns the final container. */ build(): Container; /** * Returns the accumulated factories as a plain record. * @internal Used by `module()` on the container and `merge()` on builders. */ _toRecord(): Record; } /** * Full dependency graph of the container. */ interface ContainerGraph { name?: string; providers: Record; } /** * Detailed information about a single provider/dependency. */ interface ProviderInfo { key: string; resolved: boolean; deps: string[]; scope: 'singleton' | 'transient'; } /** * Container health status with warnings. */ interface ContainerHealth { totalProviders: number; resolved: string[]; unresolved: string[]; warnings: ContainerWarning[]; } /** * A warning detected by the container's runtime analysis. */ interface ContainerWarning { type: 'scope_mismatch' | 'async_init_error'; message: string; details: Record; } //#endregion //#region src/application/container-builder.d.ts /** * Fluent builder that constructs a typed DI container incrementally. * * Two modes, one class: * - `container()` — contract mode: keys restricted to `keyof AppDeps`, return types constrained * - `container()` — free mode: keys are any `string`, types inferred freely * * Each `.add()` call accumulates the type so that subsequent factories * receive a fully-typed `c` parameter with all previously registered deps. */ declare class ContainerBuilder = Record, TBuilt extends Record = {}> { private readonly factories; /** * Registers a dependency — factory (lazy) or instance (eager). * * Convention: `typeof value === 'function'` → factory. Otherwise → instance (wrapped in `() => value`). * To register a function as a value: `add('fn', () => myFunction)`. */ add, V extends TContract[K]>(key: NonReservedKey, factoryOrInstance: FactoryOrInstance): ContainerBuilder>; /** * Registers a transient dependency (new instance on every access). */ addTransient, V extends TContract[K]>(key: NonReservedKey, factory: (c: TBuilt) => V): ContainerBuilder>; /** * Applies a module — a function that chains `.add()` calls on this builder. * * `TDepsM` (the module's expected prereqs) is inferred independently from the * builder's current `TBuilt`. Prereq satisfaction is NOT enforced at the type * level on purpose: in global mode (`defineModule()` typed against `AppDeps`) * the prereq surface is the full app, never the partial builder state. The * runtime guarantees correctness via `ProviderNotFoundError` if a key is * missing at resolution time. */ addModule, TNew extends Record>(module: (builder: ContainerBuilder) => ContainerBuilder): ContainerBuilder>; /** * Merges a standalone builder into this one. All factories of `other` are copied. * The accumulated type becomes `TBuilt & TOther`, so subsequent factories can * consume keys from either side. * * Use this to compose builders that were defined independently: * ```typescript * const dbModule = container().add('db', () => new DB()); * const di = container().add('logger', () => new Logger()).merge(dbModule).build(); * ``` * * Cross-builder dependencies are resolved at build time. Reserved keys throw. * Duplicate keys throw `DuplicateKeyError` — use `.extend()` or `.scope()` for intentional overrides. */ merge>(other: ContainerBuilder, TOther>): ContainerBuilder>; /** * Returns the accumulated factories as a plain record. * @internal Used by `module()` on the container and `merge()` on builders. */ _toRecord(): Record; /** * Builds and returns the final container. */ build(): Container; private validateKey; private validateReservedKey; } /** * Creates a new container builder. * * @example Contract mode (interface-first): * ```typescript * interface AppDeps { logger: Logger; db: Database } * * const app = container() * .add('logger', () => new ConsoleLogger()) * .add('db', (c) => new PgDatabase(c.logger)) * .build() * ``` * * @example Free mode: * ```typescript * const app = container() * .add('logger', () => new ConsoleLogger()) * .add('db', (c) => new PgDatabase(c.logger)) * .build() * ``` */ declare function container = Record>(): ContainerBuilder; //#endregion //#region src/application/define-module.d.ts /** * A reusable module: a function that extends a builder with new bindings. * * `TDeps` are the bindings the module **expects already present** on the target builder. * `TBuilt` is the full set of bindings present after the module is applied (`TDeps` + what the module adds). * * Use {@link defineModule} to build a `Module` with strong inference. */ type Module = AppDeps, TBuilt extends Record = TDeps> = (builder: ContainerBuilder, TDeps>) => ContainerBuilder, TBuilt>; /** * Defines a reusable, strongly-typed module. * * Two modes, picked by whether you pass `` explicitly: * * - **Global mode** (`defineModule()`, no generic): `c` is typed as `AppDeps`, * the augmentable global interface. Each module file augments `AppDeps` with * what it provides via `declare module 'inwire' { interface AppDeps { … } }`. * Cross-module forward references work transparently — `c.X` resolves even * when `X` is added by another module. * - **Local mode** (`defineModule()`): `c` is typed as `TDeps`, declared * locally inline. No global augmentation needed. Use when the module's * prerequisites are a tight, fixed surface. * * The output type is always **inferred** from the chained `.add()` calls. * * ### Why the double-call signature `defineModule()(fn)`? * * TypeScript enforces an all-or-nothing rule on generic type arguments: if you * specify one explicitly, you must specify them all. A single-call signature * `defineModule(fn)` would force you to write `TBuilt` by hand, * defeating the inference. The curry splits the two parameters across two calls: * * - 1st call `defineModule()` — fixes `TDeps` manually (or falls back to `AppDeps`). * - 2nd call `(fn)` — `TBuilt` is inferred from the `.add()` chain in `fn`. * * Tracking issue: https://github.com/microsoft/TypeScript/issues/26242 (partial * type argument inference). Same workaround used by zod, TanStack Query, RTK. * * @example Global mode (Pinia-style): * ```typescript * declare module 'inwire' { * interface AppDeps { * IUserRepository: IUserRepository; * SignInUseCase: SignInUseCase; * } * } * * export const authModule = defineModule()((b) => * b * .add('IUserRepository', () => new DrizzleUserRepository()) * .add('SignInUseCase', (c) => new SignInUseCase(c.IUserRepository, c.IAuthProvider)), * // ^^^^^^^^^^^^^^^ * // provided by another module — typed via AppDeps * ); * ``` * * @example Local mode (explicit prerequisites): * ```typescript * const billingModule = defineModule<{ eventBus: EventBus }>()((b) => * b.add('subscribeUseCase', (c) => new SubscribeUseCase(c.eventBus)), * ); * ``` */ declare function defineModule = AppDeps>(): >(fn: (builder: ContainerBuilder, TDeps>) => ContainerBuilder, TBuilt>) => Module; /** * Extracts the prerequisite deps of a `Module`. */ type InferModuleDeps = M extends Module ? D : never; /** * Extracts the full set of bindings present after a `Module` is applied * (prerequisites + bindings the module adds). */ type InferModuleBuilt = M extends Module ? B : never; //#endregion //#region src/domain/lifecycle.d.ts /** * Implement this interface (or just add an `onInit` method) to run * initialization logic when the dependency is first resolved. * * @example * ```typescript * class Database implements OnInit { * async onInit() { await this.connect(); } * } * ``` */ interface OnInit { onInit(): void | Promise; } /** * Implement this interface (or just add an `onDestroy` method) to run * cleanup logic when `container.dispose()` is called. * * @example * ```typescript * class Database implements OnDestroy { * async onDestroy() { await this.disconnect(); } * } * ``` */ interface OnDestroy { onDestroy(): void | Promise; } //#endregion //#region src/infrastructure/transient.d.ts /** * Wraps a factory function to produce a new instance on every access, * instead of the default singleton behavior. * * @example * ```typescript * import { container, transient } from 'inwire'; * * const app = container() * .add('logger', () => new LoggerService()) // singleton (default) * .addTransient('requestId', () => crypto.randomUUID()) // new instance every access * .build(); * * app.requestId; // 'abc-123' * app.requestId; // 'def-456' (different!) * ``` */ declare function transient(factory: (container: C) => T): (container: C) => T; //#endregion export { type AppDeps, AsyncInitErrorWarning, CircularDependencyError, type Container, ContainerBuilder, ContainerConfigError, ContainerError, type ContainerGraph, type ContainerHealth, type ContainerWarning, DuplicateKeyError, type Factory, FactoryError, type IContainer, type IContainerBuilder, type InferModuleBuilt, type InferModuleDeps, type Module, type OnDestroy, type OnInit, type ProviderInfo, ProviderNotFoundError, ReservedKeyError, ScopeMismatchWarning, type ScopeOptions, UndefinedReturnError, container, defineModule, transient }; //# sourceMappingURL=index.d.mts.map