Skip to content

Repository files navigation

inwire

Type-safe dependency injection for TypeScript. No decorators. No tokens. No reflect-metadata. Just a fluent builder, a Proxy, and full type inference. ~5 KB gzip, zero runtime dependencies.

NPM VersionCIBundle sizeNPM DownloadsTypeScriptLicenseZero Dependencies

import{container}from'inwire';constapp=container().add('logger',()=>newLogger()).add('db',(c)=>newDatabase(c.logger))// c.logger is typed.add('users',(c)=>newUserService(c.db))// c.db is typed.build();app.users.findById('42');// lazy, singleton, fully typed

Why inwire?

inwiretypical DI container
Type inferenceFull — c.db autocompletes from .add() historyManual generics or token strings
DecoratorsNoneRequired (@Injectable, @Inject)
Runtime metadataNonereflect-metadata polyfill needed
Circular depsCaught with full chain + fix hintStack overflow or cryptic crash
Async lifecycleFirst-class preload() with topological parallelismManual Promise.all plumbing
Introspectioninspect() returns JSON graph for LLMs / dashboardsNone
Bundle size~5 KB gzip10–50 KB
RuntimePure ES2022 — Node, Bun, Deno, Workers, browsersOften Node-only

The dependency graph is a side product: a tracking Proxy records which keys each factory accesses, so inspect() returns the real graph without you ever annotating it.


Install

pnpm add inwire # or npm i inwire / bun add inwire

Requires TypeScript ≥ 5.0 and an ESM-aware bundler / runtime.


Modular Setup (recommended)

For real-world apps, organize bindings per module file with Pinia-style global type augmentation. Each file declares what it provides by augmenting AppDeps; defineModule() types the factory's c against the merged interface — cross-module references resolve regardless of import order.

// modules/persistence.module.tsimport{defineModule}from'inwire';importtype{IUserRepository}from'../contracts/IUserRepository';import{DrizzleUserRepository}from'../infrastructure/DrizzleUserRepository';declare module 'inwire'{interfaceAppDeps{IUserRepository: IUserRepository;}}exportconstpersistenceModule=defineModule()((b)=>b.add('IUserRepository',(): IUserRepository=>newDrizzleUserRepository()),);
// modules/auth.module.tsimport{defineModule}from'inwire';importtype{IAuthProvider}from'../contracts/IAuthProvider';import{BetterAuthProvider}from'../infrastructure/BetterAuthProvider';import{SignInUseCase}from'../application/SignInUseCase';declare module 'inwire'{interfaceAppDeps{IAuthProvider: IAuthProvider;SignInUseCase: SignInUseCase;}}exportconstauthModule=defineModule()((b)=>b.add('IAuthProvider',(): IAuthProvider=>newBetterAuthProvider()).add('SignInUseCase',(c)=>newSignInUseCase(c.IUserRepository,c.IAuthProvider)),// ^^^^^^^^^^^^^^^^^^^^// provided by persistenceModule — typed via merged AppDeps);
// container.ts — single source of truthimport{container}from'inwire';import{persistenceModule}from'./modules/persistence.module';import{authModule}from'./modules/auth.module';exportconstdi=container().addModule(persistenceModule).addModule(authModule).build();exporttypeDi=typeofdi;// derived — never hand-written

Why this scales:

  • Locality. Each module is self-contained: it states what it provides, in its own file. No global shape interface to maintain.
  • Order-independent.authModule references c.IUserRepository even if persistenceModule is added later, in any file.
  • Familiar pattern. Mirrors Pinia's PiniaCustomProperties and Vue's ComponentCustomProperties. Augmentations are erased after type-check — zero runtime cost.
  • Derived types.type Di = typeof di — add a binding, Di grows; remove one, it shrinks. The compiler does the bookkeeping.

Other patterns are supported when this one doesn't fit — see Modules reference.


Core Concepts

The container is a Proxy

container() returns a fluent builder. Each .add(key, factory) accumulates the type so the next factory's c argument is typed with everything declared so far. .build() wraps the factories in an ES Proxy: property access triggers lazy resolution and caches the result.

constapp=container().add('db',()=>newDatabase()).build();app.db;// first access → factory runs, instance cachedapp.db;// subsequent access → cached instance returned

Auto-tracked dependency graph

The c argument passed to each factory is itself a tracking Proxy. Every property access is recorded — that's how inspect() returns the real graph without you annotating it.

constapp=container().add('db',()=>newDatabase()).add('repo',(c)=>newUserRepo(c.db))// c.db touched → graph: repo → [db].build();app.inspect();// { providers: { db: { deps: [], ... }, repo: { deps: ['db'], ... } }}

Singleton by default, transient on demand

constapp=container().add('db',()=>newDatabase())// singleton (cached).addTransient('requestId',()=>crypto.randomUUID())// transient (fresh each access).build();app.db===app.db;// trueapp.requestId===app.requestId;// false

For scope() and extend(), use the transient() wrapper:

import{transient}from'inwire';constscoped=app.extend({timestamp: transient(()=>Date.now()),});

Eager instances

A non-function value passed to .add() is registered eagerly (wrapped in () => value):

container().add('config',{port: 3000})// eager — `{ port: 3000 }` is the value.add('db',(c)=>newDatabase(c.config))// lazy — function = factory.build();

To register a function as a value, wrap it: .add('handler', () => myFunction).

Lifecycle (duck-typed)

Implement onInit() / onDestroy() on any class. inwire detects them at runtime — no base class required.

importtype{OnInit,OnDestroy}from'inwire';classDatabaseimplementsOnInit,OnDestroy{asynconInit(){awaitthis.connect();}asynconDestroy(){awaitthis.disconnect();}privateasyncconnect(){/* open the pool */}privateasyncdisconnect(){/* drain the pool */}}

CRITICAL gotcha — sync property access cannot await. When you access app.db, onInit() is called but not awaited. Async errors are silently captured as health().warnings. To safely await async startup, use preload().


Cookbook

Async startup — preload()

preload() is the only way to safely await async onInit(). It runs independent branches in parallel using a topological sort (Kahn's BFS), levels sequentially:

Level 0: [config] ← no deps
Level 1: [db] [cache] ← parallel, both depend on config
Level 2: [api] ← depends on db + cache
awaitapp.preload('db','cache');// specific keysawaitapp.preload();// everything

Errors from onInit() propagate as a single AggregateError if multiple fail. Wrap in try/catch for startup validation.

Canonical boot sequence. For any non-trivial app with async init (DB connections, queue workers, cache warmers), the recommended pattern is:

// 1. Build the container — no I/O happens here, just factory registration.constapp=container().add('config',()=>loadConfig()).add('db',(c)=>newDatabase(c.config))// implements OnInit (connect()).add('cache',(c)=>newRedis(c.config))// implements OnInit (connect()).add('queue',(c)=>newQueueConsumer(c.db))// implements OnInit (start consuming).build();// 2. Preload — runs onInit() in parallel where possible, surfaces errors.try{awaitapp.preload();}catch(err){console.error('Boot failed',err);// AggregateError if multiple onInit() rejectedprocess.exit(1);}// 3. Wire shutdown — LIFO onDestroy() in reverse resolution order.for(constsigof['SIGTERM','SIGINT']asconst){process.on(sig,async()=>{awaitapp.dispose();process.exit(0);});}// 4. App is ready.app.queue.start();

Without preload(), async onInit() errors are silently captured as health().warnings — fine for hot reloads, dangerous for production boot.

Per-request scopes

scope() creates a child container with extra bindings. The child inherits parent singletons via a parent-resolver chain; scoped bindings are isolated per scope.

constrequest=app.scope({requestId: ()=>crypto.randomUUID(),handler: (c)=>newHandler(c.logger,c.requestId),// c is typeof app},{name: 'request-123'},// optional, surfaces in inspect()/toString());request.requestId;// unique per scoperequest.logger;// shared from parent

Test overrides

Two patterns — pick what fits your test:

From scratch — build a parallel container with mocks:

functioncreateTestContainer(){returncontainer().add('logger',()=>({log: ()=>{}}))// silent.add('db',()=>newInMemoryDatabase())// mock.add('users',(c)=>newUserService(c.db,c.logger)).build();}

From a real container — override specific keys via .extend():

// production container is the source of truthconsttestApp=realApp.extend({db: ()=>newInMemoryDatabase(),// overrideemailService: ()=>({send: vi.fn()}),// mock});// All other bindings (loggers, repos, services...) come from realApp untouched.testApp.users.signup({email: 'a@b.c'});

.extend() shares the parent's singleton cache, so already-resolved real instances are reused. Overridden keys get fresh factories. This is the most ergonomic way to mock a slice of a real container without rebuilding the whole graph.

Plugin system — extend()

extend() returns a new container with additional bindings. Unlike scope(), the existing singleton cache is shared — already-resolved instances are reused.

constwithCsv=core.extend({csvParser: (c)=>newCsvParser(c.logger),});constapp=withCsv.extend({jobRunner: transient((c)=>newJobRunner(c.csvParser)),});
scope()extend()
TopologyParent-child chainFlat merged container
CacheIndependent per-scope cacheShares parent's resolved cache
Use forPer-request isolationAdditive composition / plugins

Graceful shutdown — dispose()

Calls onDestroy() on all resolved instances in LIFO order. Resilient: continues on errors, collects them into AggregateError.

process.on('SIGTERM',async()=>{awaitapp.dispose();process.exit(0);});

ES2023 explicit resource management — every container implements [Symbol.asyncDispose], so await using auto-disposes when the binding leaves scope:

asyncfunctionhandleRequest(req: Request){await using request=app.scope({requestId: ()=>crypto.randomUUID(),handler: (c)=>newHandler(c.logger,c.requestId),});returnrequest.handler.run(req);}// request.dispose() fires automatically here, even on throw

Requires TypeScript ≥ 5.2 and a runtime with Symbol.asyncDispose (Node ≥ 20.4, Bun, Deno).

Resetting cached singletons

app.db;// creates instanceapp.reset('db');// invalidates cache for a specific keyapp.db;// creates a NEW instance (factory re-runs, onInit re-fires)app.reset();// no args → invalidates ALL cached singletons in this scope

reset() is scope-local — it doesn't affect parent caches. The no-arg variant also clears initState, the recorded dependency graph, and any captured warnings — useful for fully rebuilding state in long-running tests.

Introspection for AI / observability

app.inspect();// ContainerGraph — full dependency graph (JSON)app.describe('users');// ProviderInfo for one bindingapp.health();// { totalProviders, resolved, unresolved, warnings }String(app);// human-readable one-liner
constgraph=JSON.stringify(app.inspect(),null,2);// Pipe to an LLM, render in a dashboard, diff in CI.

Modules reference

inwire offers four ways to compose modules. Pinia-style is the recommended default; the rest fit specific situations.

Pinia-style augmentation — recommended

See Modular Setup above for the full recipe. TL;DR:

declare module 'inwire'{interfaceAppDeps{IUserRepository: IUserRepository}}exportconstpersistenceModule=defineModule()((b)=>b.add('IUserRepository',(): IUserRepository=>newDrizzleUserRepository()),);
  • Each module declares what it provides.
  • c is typed as the merged AppDeps.
  • Cross-module forward references work, order-independent.

defineModule<TDeps>() — locally-declared prerequisites

When a module's prereqs are a tight, fixed surface and you'd rather not augment a global, declare what the module consumes inline. c is typed locally as TDeps:

constdbModule=defineModule<{logger: Logger}>()((b)=>b.add('db',(c)=>newDatabase(c.logger)).add('cache',(c)=>newRedis(c.logger)),);

Trade-offs vs Pinia-style:

PatternDeclaresCross-module forward refGlobal state
Pinia-style (defineModule() + declare module)what the module providesyes — order-independentaugments inwire's AppDeps
Local (defineModule<TDeps>())what the module consumesno — prereqs added firstnone

Both modes coexist: passing <TDeps> always overrides the global mode for that module.

Why the double-call signature defineModule<TDeps>()(fn)? TypeScript's generic inference is all-or-nothing — specifying <TDeps> in a flat single-call signature would force you to write <TBuilt> by hand too, defeating the inference of the .add() chain. The curry splits the two: first call fixes TDeps (or defaults to AppDeps), second call infers TBuilt from the factory return. Same workaround used by zod, TanStack Query, RTK. Tracking microsoft/TypeScript#26242.

addModule() does not enforce prereq satisfaction at the type level — missing keys raise ProviderNotFoundError at resolution time. This relaxation is what makes Pinia-style forward references possible.

.merge() — fuse standalone builders

When a module has no prerequisites, define it as a plain builder and merge it:

constdbModule=container().add('db',()=>newDatabase()).add('cache',(c)=>newRedis(c.db));constapp=container().add('logger',()=>newLogger()).merge(dbModule).add('api',(c)=>newApi(c.db,c.logger)).build();

Cross-builder dependencies are resolved at build time. Duplicate keys override (last write wins). Reserved keys throw.

Post-build — container.module()

Compose post-build using the same builder DX. Each .add() in the callback types c incrementally:

constcore=container().add('logger',()=>newLogger()).build();constwithDb=core.module((b)=>b.add('db',(c)=>newDatabase(c.logger)),);constfull=withDb.module((b)=>b.add('users',(c)=>newUserService(c.db,c.logger)),);

module() works on scope() and extend() results too. Internally it delegates to extend() after building the typed factory record.

Anti-pattern (avoid)

Older code may show this manual generic — verbose, couples the module to a global AppDeps, forces redeclaring prerequisites:

// ✗ Don't do this — use defineModule() instead.functiondbModule<Textends{logger: Logger}>(b: ContainerBuilder<AppDeps,T>,){returnb.add('db',(c)=>newDatabase(c.logger));}

Contract Mode (single-file containers)

For monolithic, single-file containers (no modules), pass an interface to container<T>() to constrain keys and return types at compile time:

interfaceAppDeps{ILogger: Logger;IDatabase: Database;IUserService: UserService;}constapp=container<AppDeps>().add('ILogger',()=>newConsoleLogger())// key: keyof AppDeps.add('IDatabase',(c)=>newPgDatabase(c.ILogger))// return must match Database.add('IUserService',(c)=>newUserService(c.IDatabase,c.ILogger)).build();app.ILogger;// typed as Logger (interface), not ConsoleLogger

The string key acts as a token (à la NestJS) but is type-safe at compile time. For multi-module apps, use Pinia-style instead — it scales across files; Contract Mode does not.

Note (Contract Mode typing nuance):container<AppDeps>().add('db', () => 'postgres') gives c.db the literal type 'postgres', not string. The contract constrains what you can add; it doesn't widen the inferred return type.


Scope — own vs inherited keys

scope() creates a parent-child resolver chain. Two views coexist — think JS prototype inheritance:

  • Own (child's local bindings only): size, Object.keys(), Symbol.iterator, inspect(), health(), toJSON(), and preload() with no arguments.
  • Inherited (walks the parent chain): property access child.db and the in operator.

extend() is not affected: it flattens everything into a single resolver, so its "own" view includes all bindings.

constchild=app.scope({extra: ()=>42});child.size;// 1 — only 'extra', the scope's own bindingchild.db;// resolved through the parent chain (inherited)'db'inchild;// true (inherited)Object.keys(child.inspect().providers);// ['extra'] — own view only

Known typing limitations

Two limitations exist today. The runtime works correctly in both cases; only the TypeScript types are affected.

1. Self-referencing in scope() — a factory cannot reference a key added in the same scope() call:

// ✗ TypeScript error — 'requestId' not yet visible in c at this call siteapp.scope({requestId: ()=>crypto.randomUUID(),handler: (c)=>newHandler(c.requestId),// c doesn't include 'requestId' here});// ✓ Split into two scope() callsconsts1=app.scope({requestId: ()=>crypto.randomUUID()});consts2=s1.scope({handler: (c)=>newHandler(c.requestId)});

Root cause: making E auto-referential in the scope() generic collapses all ReturnType<E[K]> to unknown.

2. .merge() prerequisites.merge() is designed for modules with no prerequisites. A standalone builder starts from TBuilt = {}, so its factories cannot declare dependencies on keys from the host. Use defineModule<TDeps>() instead:

// ✗ Type error — 'logger' not in the standalone builder's TBuiltconstwithLog=container().add('repo',(c)=>newRepo(c.logger));// ✓ Use defineModule to declare prereqsconstrepoModule=defineModule<{logger: Logger}>()((b)=>b.add('repo',(c)=>newRepo(c.logger)),);

Errors & Diagnostics

Every error extends ContainerError and carries:

  • hint: string — actionable fix suggestion
  • details: Record<string, unknown> — structured context for programmatic consumption

Designed to be parsed by both humans and LLMs.

Fuzzy missing-key suggestions

app.userServce;// typo// ProviderNotFoundError: Cannot resolve 'userServce'.// Registered: [userService, logger, db]// Did you mean 'userService'?// hint: Add 'userServce' to your container, or fix the typo.

Powered by Levenshtein distance. Default threshold: 50% similarity — configurable via container({ similarityThreshold: 0.8 }). Set to 1 to require an exact match (disables suggestions).

Circular dependency — full chain

// CircularDependencyError: Circular dependency detected while resolving 'authService'.// Cycle: authService → userService → authService

No stack overflow, no cryptic crash — just the resolution chain.

Reserved keys

scope, extend, module, preload, reset, inspect, describe, health, dispose, toString, toJSON, size cannot be used as dependency keys.

container().add('inspect',()=>'foo');// ReservedKeyError: 'inspect' is a reserved container method.// hint: Rename, e.g. 'inspectService' or 'myInspect'.

Scope mismatch detection (warning)

A singleton depending on a transient freezes the transient value. Surface via health():

app.health().warnings;// [{// type: 'scope_mismatch',// message: "Singleton 'userService' depends on transient 'requestId'.",// details: { singleton: 'userService', transient: 'requestId' },// }]

Async-init errors (warning)

When onInit() rejects during lazy access (no preload()), the rejection is captured as a warning rather than crashing your app.

app.health().warnings;// [{ type: 'async_init_error', message: "onInit() for 'db' rejected: connection refused", ... }]

Use preload() to surface these as proper errors.

Duplicate keys

.add() and .addTransient() throw DuplicateKeyError if the key is already registered — no silent overwrites:

container().add('logger',()=>newConsoleLogger()).add('logger',()=>newFileLogger());// throws DuplicateKeyError

For intentional overrides (test doubles, plugins, environment-specific bindings), use .extend() or .scope() on a built container — both are documented override mechanisms.

All error types

ErrorThrown when
ContainerErrorBase class for all errors. Every subclass carries hint + details.
ContainerConfigErrorNon-function value passed to scope() / extend() deps
ReservedKeyErrorReserved method name used as a key
DuplicateKeyError.add() or .addTransient() called twice with the same key
ProviderNotFoundErrorKey not registered (with fuzzy suggestion)
CircularDependencyErrorCycle detected during resolution
UndefinedReturnErrorFactory returned undefined
FactoryErrorFactory threw (wraps original error)
TopologicalSortErrorTopological sort in preload() could not complete (defensive guard — cycle detection via CircularDependencyError normally fires first)
ScopeMismatchWarningSingleton depends on transient (surfaced via health().warnings). Carries hint with refactor suggestions.
AsyncInitErrorWarningAsync onInit() rejected during lazy access (surfaced via health().warnings). Carries hint pointing to preload().

Examples

ExampleRunShowcases
06-pinia-augmentation.tsnpm run example:piniaRecommended modular pattern.declare module 'inwire' per file, order-independent cross-module typing
05-zod-style-typing.tsnpm run example:typingtype Di = typeof di derivation, Clean Arch contracts
04-modules.tsnpm run example:modulesdefineModule<TDeps>(), .merge(), module() post-build
03-plugin-system.tsnpm run example:pluginExtend chain, scoped jobs, JSON graph for LLM
02-modular-testing.tsnpm run example:testFree mode, instance values, test overrides
01-web-service.tsnpm run example:webContract mode, lifecycle, dependency inversion

API Reference

Functions & classes

ExportKindDescription
container<T?>(options?)functionCreates a ContainerBuilder. Pass T for Contract Mode. options?: ContainerOptions — e.g. container({ similarityThreshold: 0.8 }).
ContainerBuilderclassFluent builder class (rarely instantiated directly — container() is the entry point). Exported for type-only use and advanced composition.
defineModule<TDeps?>()(fn)functionDefines a typed reusable module. See Modules reference.
transient(factory)functionMarks a factory as transient (for scope() / extend()).

Builder methods

MethodDescription
.add(key, factoryOrInstance)Register a binding. Function = lazy factory; non-function = eager instance.
.addTransient(key, factory)Register a transient binding (fresh each access).
.addModule(module)Apply a Module (typically from defineModule()).
.merge(otherBuilder)Fuse a standalone builder's factories into this one.
.build()Build and return the container.

Container methods

MethodDescription
.scope(extra, options?)Child container with additional deps. Inherits parent singletons via parent chain.
.extend(extra)New container with additional deps. Shares singleton cache.
.module(fn)Post-build ContainerBuilder for typed c accumulation. Delegates to extend().
.preload(...keys)Eagerly resolve and awaitonInit(). No args = preload all.
.reset(...keys)Invalidate cached singletons. Scope-local.
.inspect()Full dependency graph (ContainerGraph).
.describe(key)Single binding info (ProviderInfo).
.health()Health snapshot + warnings (ContainerHealth).
.dispose()LIFO onDestroy() on all resolved instances.
[Symbol.asyncDispose]()Alias of .dispose() — enables await using container = ... (ES2023).
.sizereadonly number — count of registered providers.
.toJSON()Plain object of currently resolved (cached) deps. Does not trigger lazy resolution. Makes JSON.stringify(container) work.
[Symbol.iterator]()Yields [key, value] pairs for every registered provider. Triggers lazy resolution. Enables for...of, spread, Array.from.

Types

TypeDescription
AppDepsAugmentable global interface for Pinia-style typing.
Container<T>T & IContainer<T> — resolved deps + container methods.
ContainerBuilder<TContract, TBuilt>Fluent builder (also passed to module() callbacks).
IContainer<T>Container methods interface (scope, extend, module, preload, reset, inspect, describe, health, dispose, toJSON, size, [Symbol.iterator], [Symbol.asyncDispose]).
IContainerBuilder<TContract, TBuilt>Domain-level builder interface (used in module() / defineModule() callbacks).
ContainerOptions{ similarityThreshold?: number } — options for container(). Controls the fuzzy suggestion threshold in ProviderNotFoundError (default 0.5, range 0–1). Propagated through scope(), extend(), and module().
Module<TDeps, TBuilt>Module shape returned by defineModule().
InferModuleDeps<M> / InferModuleBuilt<M>Extract a module's prereqs / full output.
Factory<T>Raw factory signature (c: unknown) => T.
OnInit / OnDestroyLifecycle interfaces (duck-typed).
ContainerGraphReturn of inspect(){ name?, providers }.
ContainerHealthReturn of health(){ totalProviders, resolved, unresolved, warnings }.
ContainerWarning{ type: 'scope_mismatch' | 'async_init_error', message, details }.
ProviderInfoReturn of describe(){ key, resolved, deps, scope }.
ScopeOptions{ name?: string }.

Architecture

Clean Architecture with an enforced one-way dependency rule.

src/
index.ts # public barrel — only file consumers see
domain/ # pure contracts — no framework deps
types.ts # barrel re-exporting types/public.ts + types/internal.ts
types/public.ts # Container, IContainer, IContainerBuilder, AppDeps, helpers
types/internal.ts # IResolver, ICycleDetector, IDependencyTracker, IValidator
errors.ts # 9 error classes (ContainerError abstract + 8 concrete) + 2 warnings, each with hint + details
lifecycle.ts # OnInit / OnDestroy (duck-typed)
validation.ts # Validator (configurable similarity threshold), Levenshtein
infrastructure/ # mechanisms — depends on domain/ only
resolver.ts # lazy resolution, singleton cache, parent chain
cycle-detector.ts # circular dependency detection
dependency-tracker.ts # tracking Proxy + auto-built dependency graph
transient.ts # transient() marker (Symbol.for-based)
application/ # orchestration — depends on domain/ + infrastructure/
container-builder.ts # ContainerBuilder + container() factory ▸ Composition Root
container-proxy.ts # Proxy construction + dispatch ▸ Composition Root
scoper.ts # builds child resolvers for .scope() ▸ Composition Root
extender.ts # builds merged resolvers for .extend() ▸ Composition Root
define-module.ts # defineModule() — both modes
preloader.ts # topological sort (Kahn) + parallel onInit
disposer.ts # reverse-order onDestroy + resilient errors
introspection.ts # inspect / describe / health / toString

The Resolver receives its collaborators via constructor injection — no internal new, no hidden coupling. Application code depends on IResolver, never on the concrete class. The four Composition Roots (container-builder.ts, container-proxy.ts, scoper.ts, extender.ts) are the only files allowed to instantiate concrete infrastructure (Resolver, CycleDetector, DependencyTracker).


LLM / AI Integration

This package ships llms.txt files for AI-assisted development:

  • llms.txt — Concise index following the llms.txt standard
  • llms-full.txt — Complete API reference optimized for LLM context windows

Compatible with Context7 and any tool that supports the llms.txt standard. The inspect() output is also designed to be piped directly into an LLM for architecture analysis.


License

MIT

About

⚡ Type-safe DI for TypeScript. Zero decorators, zero tokens, full inference. Proxy-based lazy singletons, auto-built dependency graph, built-in introspection for AI tooling. Clean Architecture internals.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages