{"_id":"@blue.ts/di","name":"@blue.ts/di","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@blue.ts/di","version":"0.2.0","module":"index.ts","type":"module","exports":{".":{"bun":"./index.ts","types":"./index.ts","import":"./index.ts","default":"./index.ts"}},"publishConfig":{"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}}},"scripts":{"build:types":"tsc -p tsconfig.build.json","build:js":"bun build ./index.ts --outdir ./dist --target node --format esm","build":"bun run build:types && bun run build:js","test":"bun test"},"devDependencies":{"@types/bun":"latest"},"peerDependencies":{"typescript":"^5"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/jjgsif/blue.ts.git","directory":"packages/di"},"homepage":"https://github.com/jjgsif/blue.ts","bugs":{"url":"https://github.com/jjgsif/blue.ts/issues"},"_id":"@blue.ts/di@0.2.0","gitHead":"bcb12fc19caf51dd8a4029b062044e22ec267682","description":"A lightweight, async-first dependency injection container for TypeScript. No decorators, no `reflect-metadata`, no runtime dependencies — works in Node.js, Bun, Deno, and edge runtimes.","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-nFPqtzq0BGwfj0BZ2IAqBNAMa6r9THSs3SGhvQir6B19xd+4jPQj/AWMPXl3PiYdAIrYq785a7C47RBfWj88Og==","shasum":"9bb221d841b408e59046e8d185f57155fc159af0","tarball":"https://registry.npmjs.org/@blue.ts/di/-/di-0.2.0.tgz","fileCount":11,"unpackedSize":18161,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDDKraq844sNyzg1E2NZZ34fzWUVeXgApTxSK+0qCn/UAIhAJh7cvH30+w+xZX0cAUo+IZ0XT5nNrgCNPItBbW3dkRh"}]},"_npmUser":{"name":"jjgsif","email":"jjgsif@gmail.com"},"directories":{},"maintainers":[{"name":"jjgsif","email":"jjgsif@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/di_0.2.0_1775875965440_0.14051192624794817"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-11T02:52:45.335Z","0.2.0":"2026-04-11T02:52:45.595Z","modified":"2026-04-11T02:52:45.835Z"},"maintainers":[{"name":"jjgsif","email":"jjgsif@gmail.com"}],"description":"A lightweight, async-first dependency injection container for TypeScript. No decorators, no `reflect-metadata`, no runtime dependencies — works in Node.js, Bun, Deno, and edge runtimes.","homepage":"https://github.com/jjgsif/blue.ts","repository":{"type":"git","url":"git+https://github.com/jjgsif/blue.ts.git","directory":"packages/di"},"bugs":{"url":"https://github.com/jjgsif/blue.ts/issues"},"license":"MIT","readme":"# @blue.ts/di\n\nA lightweight, async-first dependency injection container for TypeScript. No decorators, no `reflect-metadata`, no runtime dependencies — works in Node.js, Bun, Deno, and edge runtimes.\n\n## Installation\n\n```bash\nbun add @blue.ts/di\nnpm install @blue.ts/di\n```\n\n## Quick start\n\n```ts\nimport { Container, Token } from \"@blue.ts/di\";\n\n// 1. Define identifiers\nconst LoggerToken = new Token<Logger>(\"Logger\");\n\n// 2. Create a container and register services\nconst container = new Container();\n\ncontainer.register(LoggerToken, {\n  lifetime: \"singleton\",\n  factory: () => new Logger(),\n});\n\ncontainer.register(Database, {\n  lifetime: \"singleton\",\n  factory: async (r) => {\n    const logger = await r.get(LoggerToken);\n    return new Database(logger);\n  },\n});\n\n// 3. Resolve\nconst db = await container.get(Database);\n```\n\n---\n\n## Identifiers\n\nEvery registration is keyed by an **identifier**. There are two kinds:\n\n### `Token<T>`\n\nThe recommended identifier. Carries the type `T` so `get()` returns the correct type without a manual type parameter.\n\n```ts\nconst DbToken = new Token<Database>(\"Database\");\n\ncontainer.register(DbToken, { lifetime: \"singleton\", factory: () => new Database() });\n\nconst db = await container.get(DbToken); // typed as Database\n```\n\n### Constructor\n\nA class itself can be used as its own identifier.\n\n```ts\ncontainer.register(Database, { lifetime: \"singleton\", factory: () => new Database() });\n\nconst db = await container.get(Database); // typed as Database\n```\n\n---\n\n## Lifetimes\n\n### `singleton`\n\nOne instance for the lifetime of the container. Shared across all scopes.\n\n```ts\ncontainer.register(Database, { lifetime: \"singleton\", factory: () => new Database() });\n```\n\n### `scoped`\n\nOne instance per scope. Different scopes get different instances. Useful for per-request state.\n\n```ts\ncontainer.register(RequestContext, { lifetime: \"scoped\", factory: () => new RequestContext() });\n\nconst scope = container.createScope();\nconst ctx = await scope.get(RequestContext); // new instance per scope\n```\n\n### `transient`\n\nA new instance on every `get()` call. Never cached.\n\n```ts\ncontainer.register(Job, { lifetime: \"transient\", factory: () => new Job() });\n```\n\n### Value registration\n\nRegister a pre-constructed value as a singleton. Useful for config objects or third-party instances.\n\n```ts\ncontainer.register(ConfigToken, {\n  lifetime: \"singleton\",\n  value: { port: 3000, host: \"localhost\" },\n});\n```\n\n---\n\n## Async factories\n\nFactories can be async. The container resolves them transparently — callers always `await container.get(...)`.\n\n```ts\ncontainer.register(Database, {\n  lifetime: \"singleton\",\n  factory: async () => {\n    const db = new Database();\n    await db.connect(\"postgres://...\");\n    return db;\n  },\n});\n```\n\nConcurrent calls for the same singleton are deduplicated — the factory is called exactly once regardless of how many callers race.\n\n---\n\n## `autowire`\n\nGenerates a factory from a constructor and an ordered list of dependency identifiers. Resolves all dependencies in parallel.\n\n```ts\nimport { autowire } from \"@blue.ts/di\";\n\nclass UserService {\n  constructor(readonly db: Database, readonly logger: Logger) {}\n}\n\ncontainer.register(UserService, {\n  lifetime: \"singleton\",\n  factory: autowire(UserService, [Database, LoggerToken]),\n});\n```\n\nThis is equivalent to writing the factory manually:\n\n```ts\nfactory: async (r) => {\n  const [db, logger] = await Promise.all([r.get(Database), r.get(LoggerToken)]);\n  return new UserService(db, logger);\n}\n```\n\n---\n\n## Scopes\n\n`createScope()` creates a child container that shares the same registry and singleton cache but maintains its own scoped instance cache.\n\n```ts\n// HTTP server example\napp.use(async (req, res, next) => {\n  await using scope = req.container = container.createScope();\n  scope.register(RequestToken, { lifetime: \"singleton\", value: req });\n  next();\n});\n```\n\n`Container` implements `Symbol.asyncDispose`, so scopes work with `await using` — the scope is disposed automatically when the block exits.\n\n---\n\n## Dispose\n\nRegister a `dispose` callback on any factory or value registration. Callbacks are called in reverse resolution order (dependents before dependencies) when `dispose()` is called.\n\n```ts\ncontainer.register(Database, {\n  lifetime: \"singleton\",\n  factory: async () => {\n    const db = new Database();\n    await db.connect();\n    return db;\n  },\n  dispose: (db) => db.disconnect(),\n});\n\n// On shutdown:\nawait container.dispose();\n```\n\nIf multiple disposers fail, all of them are still called and the errors are collected into an `AggregateError`.\n\nScoped containers only dispose their own scoped instances. Root `dispose()` handles singletons.\n\n```ts\n{\n  await using scope = container.createScope();\n  // ... handle request\n} // scope.dispose() called automatically — scoped instances cleaned up\n```\n\n---\n\n## Error handling\n\n### `NotFoundException`\n\nThrown synchronously when `get()` is called for an unregistered identifier.\n\n```ts\ntry {\n  await container.get(UnknownToken);\n} catch (e) {\n  if (e instanceof NotFoundException) {\n    console.error(\"Not registered:\", e.message);\n  }\n}\n```\n\n### `ContainerException`\n\nThrown when a factory fails. Includes the full resolution chain so you can see exactly which dependency caused the failure.\n\n```\nContainerException: Error occurred while instantiating service - Database (singleton) [UserService → Database]\nCaused by: Error: ECONNREFUSED 127.0.0.1:5432\n```\n\nCircular dependencies are also reported with the chain:\n\n```\nContainerException: Circular dependency detected - ServiceA (singleton) [ServiceA → ServiceB → ServiceA]\n```\n\n---\n\n## API reference\n\n### `Container`\n\n| Method | Description |\n|--------|-------------|\n| `register(id, registration)` | Register a service. Re-registration invalidates the existing cached instance. |\n| `get<T>(id)` | Resolve a service. Returns `Promise<T>`. |\n| `has(id)` | Returns `true` if the identifier is registered. Does not guarantee resolution will succeed. |\n| `createScope()` | Creates a child container with its own scoped instance cache. |\n| `dispose()` | Disposes all tracked instances in reverse resolution order. Collects errors into `AggregateError`. |\n| `[Symbol.asyncDispose]()` | Alias for `dispose()`. Enables `await using`. |\n\n### `Token<T>`\n\n```ts\nconst MyToken = new Token<MyService>(\"MyService\");\n```\n\n### `autowire(constructor, dependencies)`\n\n```ts\nautowire(MyService, [DepA, DepB]): Factory<MyService>\n```\n\nReturns a `Factory<T>` that resolves `dependencies` in parallel and passes them to `constructor` in order.\n\n### `Resolver`\n\nThe object passed into every factory. Narrower than `Container` by design — factories can resolve dependencies but cannot register new ones.\n\n```ts\ninterface Resolver {\n  get<T>(identifier: Identifier<T>): Promise<T>;\n  has<T>(identifier: Identifier<T>): boolean;\n}\n```\n\n---\n\n## Requirements\n\n- TypeScript 5+\n- Any runtime that supports ES2021 (`AggregateError`, `Symbol.asyncDispose` requires ES2022 / `--lib ES2022`)","readmeFilename":"README.md","_rev":"1-cb44a7566ad5ed7d0ceabff6d9dd5815"}