{"_id":"@bosker-labs/di-assemble","name":"@bosker-labs/di-assemble","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bosker-labs/di-assemble","version":"0.1.0","description":"Composable DI modules and providers with an Inversify-backed container; optional React bindings (@bosker/di-assemble/react).","license":"MIT","keywords":["agent-skills","assemble","dependency-injection","di","inversify","modules","providers","react","react-native"],"engines":{"node":">=20.19.0"},"sideEffects":true,"type":"module","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./react":{"import":{"types":"./dist/react.d.mts","default":"./dist/react.mjs"},"require":{"types":"./dist/react.d.cts","default":"./dist/react.cjs"}},"./package.json":"./package.json"},"bin":{"di-assemble":"bin/di-assemble.js"},"scripts":{"build":"tsdown","test":"vitest run","test:watch":"vitest","di-assemble":"node ./bin/di-assemble.js","prepublishOnly":"tsdown"},"peerDependencies":{"inversify":"^8.0.0","react":">=18.0.0","reflect-metadata":"^0.2.2"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.0","@types/node":"^22","@types/react":"^19.1.0","happy-dom":"^17.4.4","inversify":"^8.1.0","react":"^19.1.0","react-dom":"^19.1.0","reflect-metadata":"^0.2.2","tsdown":"^0.21.9","typescript":"^5.8.3","vitest":"^3.2.0"},"_id":"@bosker-labs/di-assemble@0.1.0","gitHead":"c0d1cd23353a90027317ffa2609e39c2ecd0c554","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-4U8eWqCrUoqlbaqo7jKfcfcmz1LyAXYKz/JCF1OO+TVePaiTTK9hEORuAcP2GXsUqek1s6CLA1RIqKFsXamEkA==","shasum":"ccdddc06bc109436b88bc08d2c3a003e7807db48","tarball":"https://registry.npmjs.org/@bosker-labs/di-assemble/-/di-assemble-0.1.0.tgz","fileCount":21,"unpackedSize":92993,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGLqaZpyr2ep2etTaCh1p8achquu92coTzqlS7/e1KIfAiAxeiZjjksbeo/3ggkGSLpkVSeStbI3kEm4Fc8ySvQ6Xw=="}]},"_npmUser":{"name":"biportrait","email":"linhnguyen93x@gmail.com"},"directories":{},"maintainers":[{"name":"biportrait","email":"linhnguyen93x@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/di-assemble_0.1.0_1777478819350_0.6293244649456997"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-29T16:06:59.225Z","0.1.0":"2026-04-29T16:06:59.545Z","modified":"2026-04-29T16:06:59.829Z"},"maintainers":[{"name":"biportrait","email":"linhnguyen93x@gmail.com"}],"description":"Composable DI modules and providers with an Inversify-backed container; optional React bindings (@bosker/di-assemble/react).","keywords":["agent-skills","assemble","dependency-injection","di","inversify","modules","providers","react","react-native"],"license":"MIT","readme":"# di-assemble\n\nComposable **dependency injection** for TypeScript apps: module-style **`@Module`**, explicit **providers** (`useClass`, `useValue`, `useFactory`, `useExisting`), and an **Inversify** container behind a small API. Optional React bindings live under **`@bosker/di-assemble/react`**.\n\n## Install\n\n```bash\nyarn add @bosker/di-assemble inversify reflect-metadata\n# or: npm install @bosker/di-assemble inversify reflect-metadata\n```\n\nIf you use **`createAppProvider`** / **`useInjection`**, also install **`react`** (peer dependency).\n\n### TypeScript\n\nEnable legacy decorators for `@Module`, **`@Injectable`**, and **`@Inject`** (you do **not** need `emitDecoratorMetadata` if every injected constructor parameter has **`@Inject(token)`**):\n\n```json\n{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true\n  }\n}\n```\n\nIf you prefer Inversify’s reflection-based parameter types, set **`emitDecoratorMetadata`: true** and ensure **`reflect-metadata`** is loaded (see Inversify docs). The supported path in this library is explicit **`@Inject(createInjectionToken(...))`** on each parameter.\n\n### App entry\n\nImport **`reflect-metadata` once** before any module that uses `@Module` or **`@Injectable` / `@Inject`** (e.g. your root `index.js` or layout). The library’s injection entry also imports it, but a single top-level import keeps load order predictable.\n\n## Package exports\n\n| Import | Purpose |\n|--------|---------|\n| `@bosker/di-assemble` | `Module`, `bootstrapApplication`, `Injectable`, `Inject`, providers, `createInjectionToken`, … |\n| `@bosker/di-assemble/react` | `createAppProvider`, `useInjection`, `useApplicationContext` |\n\n## Quick start (core only)\n\n```ts\nimport 'reflect-metadata';\nimport { Module, bootstrapApplication, createInjectionToken } from '@bosker/di-assemble';\n\nconst GREETING = createInjectionToken<string>('greeting');\n\n@Module({\n  providers: [{ provide: GREETING, useValue: 'hello' }],\n  exports: [GREETING],\n})\nclass AppModule {}\n\nconst app = bootstrapApplication({ modules: [AppModule] });\napp.get(GREETING); // 'hello'\n```\n\n`createInjectionToken<T>()` keeps **TypeScript inference** for `app.get(...)` and `useInjection(...)`.\n\n### Constructor injection (`useClass`)\n\n**`useClass`** is bound with Inversify’s **`to(Class)`**: the container constructs the class and resolves constructor dependencies.\n\n- Use **`@Injectable()`** on the class and **`@Inject(TOKEN)`** on each injected constructor parameter.\n- **Zero-argument** classes do not require **`@Injectable()`**.\n\n```ts\nimport 'reflect-metadata';\nimport {\n  Inject,\n  Injectable,\n  Module,\n  bootstrapApplication,\n  createInjectionToken,\n} from '@bosker/di-assemble';\n\nconst DEP = createInjectionToken<string>('dep');\nconst CONSUMER = createInjectionToken<{ msg: string }>('consumer');\n\n@Injectable()\nclass ConsumerImpl {\n  constructor(@Inject(DEP) readonly msg: string) {}\n}\n\n@Module({\n  providers: [\n    { provide: DEP, useValue: 'wired' },\n    { provide: CONSUMER, useClass: ConsumerImpl },\n  ],\n  exports: [CONSUMER],\n})\nclass AppModule {}\n\nconst app = bootstrapApplication({ modules: [AppModule] });\napp.get(CONSUMER).msg; // 'wired'\n```\n\n## React (optional)\n\n```tsx\nimport { Slot } from 'expo-router';\nimport { createAppProvider } from '@bosker/di-assemble/react';\nimport { AppModule } from './app.module';\n\nconst { AppProvider } = createAppProvider({ modules: [AppModule] });\n\nexport default function RootLayout() {\n  return (\n    <AppProvider>\n      <Slot />\n    </AppProvider>\n  );\n}\n```\n\n```ts\nimport { useInjection } from '@bosker/di-assemble/react';\nimport { FEATURE_TOKENS } from './feature.tokens';\n\n// Type inferred when FEATURE_TOKENS.SomeUseCase is createInjectionToken<SomeUseCase>(...)\nconst useCase = useInjection(FEATURE_TOKENS.SomeUseCase);\n```\n\n### Testing / storybook\n\nPass a pre-built context to avoid double bootstrap:\n\n```tsx\nimport { bootstrapApplication } from '@bosker/di-assemble';\nimport { createAppProvider } from '@bosker/di-assemble/react';\n\nconst app = bootstrapApplication({ modules: [AppModule], overrides: [...] });\nconst { AppProvider } = createAppProvider({ modules: [AppModule] });\n\n<AppProvider app={app}>{children}</AppProvider>;\n```\n\n## Providers\n\n- **`useClass`** — Inversify instantiates the class (see **Constructor injection** above). Default **singleton**; use **`scope: 'transient'`** for a new instance per `get`.\n- **`useValue`** — constant.\n- **`useFactory`** — `(injector) => T` with access to `injector.get(...)`.\n- **`useExisting`** — alias another token.\n\n## Module rules\n\n- **`imports`** — other module classes.\n- **`providers`** — bindings registered when the app boots (child-first order by the internal graph).\n- **`exports`** — tokens that must be provided locally or re-exported from an import; invalid exports throw at bootstrap.\n\n## CLI (module scaffold)\n\nAfter install, **`di-assemble`** is on **`PATH`** via **`node_modules/.bin`**.\n\n```bash\nnpx di-assemble generate module <kebab-name> [options]\n# or: yarn di-assemble generate module billing\n```\n\n| Option | Default | Meaning |\n|--------|---------|--------|\n| **`--out-dir`** | **`./src/features`** | Directory that will contain **`<kebab-name>/`** |\n| **`--import-di`** | **`@bosker/di-assemble`** | Import path for **`Module`**, **`createInjectionToken`**, **`Inject`**, … |\n| **`--no-presentation`** | off | Skip **`presentation/hooks`** and hook export |\n| **`--force`** | off | Overwrite existing files |\n| **`--dry-run`** | off | Print paths only |\n\nGenerated layout matches the **sample** feature: **`services/`** (request, response, contract, impl), **`models/`** (presentation types + mapper stubs), **`usecases/`**, **`*.module.ts`**, **`*.tokens.ts`**, **`*.types.ts`**, **`index.ts`**. Replace **`throw new Error('… not implemented')`** stubs with real types and logic, then register **`\\*Module`** in your app module **`imports`**.\n\n## Agent skills ([`skills` CLI](https://www.npmjs.com/package/skills))\n\nThis package includes an [**Agent Skill**](https://agentskills.io) at **`skills/di-assemble/SKILL.md`** (name: **`di-assemble`**). It is discoverable from the repo root via the open-ecosystem [**`skills`**](https://www.npmjs.com/package/skills) tool ([`vercel-labs/skills`](https://github.com/vercel-labs/skills)), which can install into **Cursor**, **Claude Code**, **Codex**, and [many other agents](https://www.npmjs.com/package/skills#supported-agents).\n\n**After `yarn add @bosker/di-assemble` (or `npm install @bosker/di-assemble`):**\n\n```bash\n# List the skill(s) shipped in the package\nnpx skills add ./node_modules/@bosker/di-assemble --list\n\n# Install into Cursor (project scope; non-interactive)\nnpx skills add ./node_modules/@bosker/di-assemble --skill di-assemble -a cursor -y\n\n# Or global install for your user\nnpx skills add ./node_modules/@bosker/di-assemble --skill di-assemble -a cursor -g -y\n```\n\n**From a Git clone or monorepo path** (this library or a fork):\n\n```bash\nnpx skills add /path/to/di-assemble --skill di-assemble -a cursor -y\n```\n\n**From GitHub** (once published there):\n\n```bash\nnpx skills add https://github.com/<org>/di-assemble --skill di-assemble -a cursor -y\n```\n\nThe CLI symlinks or copies **`SKILL.md`** into each agent’s configured skills directory (for Cursor, typically **`.agents/skills/`** in the project, or **`~/.cursor/skills/`** when using **`-g`**). You can still copy **`skills/di-assemble/`** manually if you prefer not to use the CLI.\n\n## Monorepo / this repository\n\nLibrary source lives under `src/`:\n\n```txt\nsrc/\n  index.ts              # public entry (re-exports di)\n  react.ts              # public entry for @bosker/di-assemble/react\n  di/\n    index.ts            # core + tokens barrel\n    core/               # bootstrap, container, decorators, injection, …\n    tokens/\n  example/sample/       # optional reference layout (not published)\n    services/           # *.request.ts, *.response.ts, service contract + impl\n    models/             # presentation-oriented types + mappers (use case boundary)\n    usecases/\n    presentation/\n  bin/                  # di-assemble CLI (published)\n  skills/di-assemble/   # Agent SKILL.md (skills CLI + Cursor-compatible)\n```\n\nThe published package includes **`dist/`**, **`bin/`**, **`skills/`**, **`README.md`**, **`LICENSE`** (run **`yarn build`** before publish).\n\n## Develop & test (contributors)\n\n```bash\nyarn install\nyarn test        # Vitest\nyarn build       # tsdown → dist/\n```\n\n## Limits (today)\n\n- No request scope, async lifecycle hooks, or auto file scanning.\n- Circular **module** imports are rejected; circular **service** graphs depend on Inversify behavior.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-cf2ce26399eab08838f639c2e8459929"}