@jd4n14/wirets
v0.1.1
Published
Wire TypeScript apps with Angular/Nest-inspired DI — tokens, modules, scopes, async factories. No reflect-metadata.
Maintainers
Readme
@jd4n14/wirets
Wire your TypeScript apps with dependency injection inspired by Angular (tokens & providers) and NestJS (modules & scopes).
- No
reflect-metadata - No
emitDecoratorMetadata - No decorators required
- Explicit
injectlists - Sync + async factories (
await db.connect()) - Hierarchical injectors + request scopes
Install
bun add @jd4n14/wirets
# or
npm install @jd4n14/wiretsFrom a local path or git remote:
bun add ./path/to/wirets # or bun add github:jd4n14/wirets
Quick start
import {
InjectionToken,
defineModule,
provideClass,
provideFactory,
provideValue,
bootstrap,
} from '@jd4n14/wirets'
interface Logger {
info(msg: string): void
}
interface Database {
query(sql: string): Promise<unknown[]>
}
const LOGGER = new InjectionToken<Logger>('LOGGER')
const DATABASE = new InjectionToken<Database>('DATABASE')
const USER_SERVICE = new InjectionToken<UserService>('USER_SERVICE')
const REQUEST_ID = new InjectionToken<string>('REQUEST_ID')
class ConsoleLogger implements Logger {
info(msg: string) {
console.log(msg)
}
}
class UserService {
constructor(
private readonly db: Database,
private readonly logger: Logger,
private readonly requestId: string,
) {}
async getUser(id: string) {
this.logger.info(`[${this.requestId}] getUser ${id}`)
return this.db.query(`select * from users where id = '${id}'`)
}
}
const AppModule = defineModule({
name: 'AppModule',
providers: [
provideClass({
provide: LOGGER,
useClass: ConsoleLogger,
inject: [],
}),
provideFactory({
provide: DATABASE,
inject: [LOGGER] as const,
scope: 'singleton',
useFactory: async (logger) => {
const db = {
async query(sql: string) {
logger.info(sql)
return []
},
}
// async init is fine
await Promise.resolve()
return db
},
}),
provideValue({ provide: REQUEST_ID, useValue: 'bootstrap' }),
provideClass({
provide: USER_SERVICE,
useClass: UserService,
inject: [DATABASE, LOGGER, REQUEST_ID],
scope: 'scoped',
}),
],
exports: [USER_SERVICE],
})
const app = await bootstrap(AppModule)
const scope = app.createScope([
provideValue({ provide: REQUEST_ID, useValue: 'req-1' }),
])
const users = scope.get(USER_SERVICE)
await users.getUser('u1')Core concepts
Tokens
const CONFIG = new InjectionToken<AppConfig>('CONFIG')Interfaces disappear at runtime — always bind implementations to tokens.
Providers
| Helper | Purpose |
| --- | --- |
| provideValue({ provide, useValue }) | Constant / instance |
| provideClass({ provide, useClass, inject?, scope? }) | new useClass(...deps) |
| provideFactory({ provide, inject, useFactory, scope? }) | Sync or async factory |
| provideExisting({ provide, useExisting }) | Alias |
inject lists tokens in constructor / factory argument order.
If provideClass omits inject, it falls back to useClass.inject (static).
Scopes
| Scope | Lifetime |
| --- | --- |
| singleton (default) | One instance per registering injector (usually root) |
| scoped | One instance per child injector (createScope) |
| transient | New instance every get |
Modules (Nest-like)
const DatabaseModule = defineModule({
name: 'DatabaseModule',
imports: [ConfigModule, LoggingModule],
providers: [/* … */],
exports: [DATABASE], // only these are visible to importers
})At compile time, a provider may only depend on:
- providers declared in the same module, and
- tokens exported by modules in
imports.
Bootstrap
const app = await bootstrap(AppModule)- Compiles the module graph (visibility, exports, circular imports)
- Builds the root
Injector - Preloads singleton providers (awaits async factories)
| API | Description |
| --- | --- |
| app.get(token) | Sync resolve (after preload / for sync providers) |
| app.getAsync(token) | Resolve allowing async factories |
| app.createScope(providers?) | Child injector (request scope) |
| app.injector | Underlying root Injector |
| app.modules | Compiled module names |
You can also use Injector / compileModules directly without modules.
API surface
// tokens & types
InjectionToken, Scope, Provider, ModuleDef, Application, …
// helpers
defineModule, provideValue, provideClass, provideFactory, provideExisting
// runtime
Injector, compileModules, bootstrapDesign notes
- Explicit over magic — no constructor parameter reflection.
- Angular mental model —
InjectionToken+ provider objects. - Nest mental model —
imports/providers/exports. - Async first-class —
useFactorymay returnPromise<T>;bootstrapawaits singleton init.
Development
bun install
bun test
bun run typecheck
bun run buildLicense
MIT
