{"_id":"@dmytromykhailiuk/preact-injectable","_rev":"2-f1950214f7764967f99e889c2252a752","name":"@dmytromykhailiuk/preact-injectable","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dmytromykhailiuk/preact-injectable","version":"1.0.0","keywords":["preact","dependency-injection","di","ioc","container","injectable","inject","useinject","provider","context","module","nested-container","hierarchical","multi-provider","hooks","typescript","typed","esm","tree-shakeable","no-decorators"],"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","_id":"@dmytromykhailiuk/preact-injectable@1.0.0","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"homepage":"https://github.com/dmytromykhailiuk/preact-injectable#readme","bugs":{"url":"https://github.com/dmytromykhailiuk/preact-injectable/issues"},"dist":{"shasum":"871810ff77b9a9f8df4774f97bb7a9f1d2a895a7","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/preact-injectable/-/preact-injectable-1.0.0.tgz","fileCount":9,"integrity":"sha512-dAEoHsdy3OKWSUhsryBt9ymy4viPqVcB11WiYyH+xrPn7MCG0Ie/jNGkrk/F2V+bFBNVwTg1MucIQ6o+EidwoA==","signatures":[{"sig":"MEYCIQCvgQLfIgI1lBY1gN9gaNe5Kvcgl5XyV6MKd0z7i+Iv/AIhAI/A/atRuE9OaY3/OqWLWOi4tNACepqquJL8FulMpG4Q","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35835},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"98e366af5a5af4a17aeae48a24685ed64c0f2a33","scripts":{"dev":"tsup --watch","lint":"biome check .","test":"vitest run","build":"tsup","format":"biome format --write .","verify":"npm run lint && npm run typecheck && npm run test && npm run build","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","playground":"vite --config vite.playground.config.ts","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"repository":{"url":"git+https://github.com/dmytromykhailiuk/preact-injectable.git","type":"git"},"_npmVersion":"11.6.2","description":"Dependency injection for Preact — bind @dmytromykhailiuk/injectable to the component tree. Hierarchical container modules via context, plus a fully-typed useInject hook. No decorators.","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vite":"^5.4.11","jsdom":"^25.0.1","preact":"^10.25.4","vitest":"^2.1.8","typescript":"^5.7.3","@types/node":"^22.10.5","@biomejs/biome":"^1.9.4","@preact/preset-vite":"^2.10.1","@vitest/coverage-v8":"^2.1.8","@testing-library/preact":"^3.2.4","@dmytromykhailiuk/injectable":"file:../injectable"},"peerDependencies":{"preact":">=10.11.0","@dmytromykhailiuk/injectable":">=1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/preact-injectable_1.0.0_1784730100373_0.22333361300413213","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dmytromykhailiuk/preact-injectable","version":"1.0.1","description":"Dependency injection for Preact — bind @dmytromykhailiuk/injectable to the component tree. Hierarchical container modules via context, plus a fully-typed useInject hook. No decorators.","type":"module","sideEffects":false,"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","keywords":["preact","dependency-injection","di","ioc","container","injectable","inject","useinject","provider","context","module","nested-container","hierarchical","multi-provider","hooks","typescript","typed","esm","tree-shakeable","no-decorators"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","playground":"vite --config vite.playground.config.ts","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"biome check .","lint:fix":"biome check --write .","format":"biome format --write .","verify":"npm run lint && npm run typecheck && npm run test && npm run build","prepublishOnly":"npm run build"},"engines":{"node":">=18"},"peerDependencies":{"@dmytromykhailiuk/injectable":">=1.0.0","preact":">=10.11.0"},"devDependencies":{"@biomejs/biome":"^1.9.4","@dmytromykhailiuk/injectable":"file:../injectable","@preact/preset-vite":"^2.10.1","@testing-library/preact":"^3.2.4","@types/node":"^22.10.5","@vitest/coverage-v8":"^2.1.8","jsdom":"^25.0.1","preact":"^10.25.4","tsup":"^8.3.5","typescript":"^5.7.3","vite":"^5.4.11","vitest":"^2.1.8"},"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/preact-injectable.git"},"bugs":{"url":"https://github.com/dmytromykhailiuk/preact-injectable/issues"},"homepage":"https://dmytromykhailiuk.github.io/preact-injectable/","gitHead":"053d25d1ddfb0edaccebf919fbc134df15141f46","_id":"@dmytromykhailiuk/preact-injectable@1.0.1","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-wE7/mNTUqXd+UFO0wJNbzSssOrBx+tMZilLWtbWUvBgN9vZMVEvsDmnSAYgDQPGzjM5/IzdDisOoRzVuu0rOFQ==","shasum":"378c4693c4766cd3fc35b8b72777c09bbfa5c3c9","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/preact-injectable/-/preact-injectable-1.0.1.tgz","fileCount":9,"unpackedSize":35828,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHj0fx3GGj1ecmz8OPHUF94BjeFXgJKWxUFeGW2yoPGuAiEAzFaEy8RIxJcIdDWef1bvTY2VPzAagd+F4zCnSxbWLJk="}]},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"directories":{},"maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/preact-injectable_1.0.1_1786638725967_0.0479778219693614"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-22T14:21:40.066Z","modified":"2026-08-13T16:32:06.373Z","1.0.0":"2026-07-22T14:21:40.524Z","1.0.1":"2026-08-13T16:32:06.154Z"},"bugs":{"url":"https://github.com/dmytromykhailiuk/preact-injectable/issues"},"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","homepage":"https://dmytromykhailiuk.github.io/preact-injectable/","keywords":["preact","dependency-injection","di","ioc","container","injectable","inject","useinject","provider","context","module","nested-container","hierarchical","multi-provider","hooks","typescript","typed","esm","tree-shakeable","no-decorators"],"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/preact-injectable.git"},"description":"Dependency injection for Preact — bind @dmytromykhailiuk/injectable to the component tree. Hierarchical container modules via context, plus a fully-typed useInject hook. No decorators.","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"readme":"# @dmytromykhailiuk/preact-injectable\n\n> **Full documentation:** open [Docs](https://dmytromykhailiuk.github.io/preact-injectable/)\n\n**Dependency injection for Preact — the [`@dmytromykhailiuk/injectable`](https://dmytromykhailiuk.github.io/injectable) container, wired to the component tree.**\n\nIf you like Angular/NestJS providers but want them in a Preact app, this is that model driven by your JSX: a `<Module>` owns a DI container for its subtree, nested modules inherit from the ones above them, and a single `useInject` hook pulls dependencies out — with the **exact call signature of `container.get`**. No decorators, no `reflect-metadata`, no compiler flags.\n\n```tsx\nimport { createDIModule, useInject } from \"@dmytromykhailiuk/preact-injectable\";\nimport { createInjectionToken } from \"@dmytromykhailiuk/injectable\";\n\nclass Logger {\n  log(message: string) {\n    console.log(`[log] ${message}`);\n  }\n}\n\nconst API_URL = createInjectionToken<string>(\"API_URL\");\n\n// A module is a component that owns a container for everything inside it.\nconst AppModule = createDIModule([\n  Logger,\n  { provide: API_URL, useValue: \"https://api.example.com\" },\n]);\n\nfunction Greeting() {\n  const logger = useInject(Logger); // -> Logger\n  const apiUrl = useInject(API_URL); // -> string (inferred from the token)\n  logger.log(`hello from ${apiUrl}`);\n  return <p>hello</p>;\n}\n\n// <AppModule> provides the container; <Greeting> resolves from it.\nrender(\n  <AppModule>\n    <Greeting />\n  </AppModule>,\n  document.body,\n);\n```\n\nThe whole surface is two things: **`createDIModule`** (build a module component) and **`useInject`** (resolve inside it). Everything about *what* you register — class / value / factory / alias providers, multi-providers, injection tokens — comes from `@dmytromykhailiuk/injectable` and is documented there.\n\n---\n\n## Contents\n\n- [Install](#install)\n- [How it works](#how-it-works)\n- [`createDIModule`](#creatediModule)\n- [`useInject`](#useinject)\n- [Nested modules](#nested-modules)\n- [Injection options](#injection-options)\n- [Constructor-style injection with `inject()`](#constructor-style-injection-with-inject)\n- [Lifecycle](#lifecycle)\n- [Recipes](#recipes)\n- [API reference](#api-reference)\n- [Limitations](#limitations)\n- [Development](#development)\n\n---\n\n## Install\n\n```sh\nnpm i @dmytromykhailiuk/preact-injectable @dmytromykhailiuk/injectable preact\n```\n\n`preact` and `@dmytromykhailiuk/injectable` are **peer dependencies** — you bring them, this package binds them together. Requires Node **18+** and TypeScript **5.0+**. Nothing to enable in `tsconfig` — no decorators, no `reflect-metadata`. ESM and CJS builds ship side by side with separate `.d.ts` / `.d.cts` declarations, and the package is side-effect-free and tree-shakeable.\n\n```ts\nimport {\n  createDIModule, // makes a <Module> component that owns a container\n  useInject, // resolves a dependency from the nearest <Module>\n} from \"@dmytromykhailiuk/preact-injectable\";\n```\n\nThe provider vocabulary you pass to `createDIModule` — `createInjectionToken`, `useValue` / `useCreate` / `useExisting`, `multi`, and the `inject()` you call inside services — all comes straight from `@dmytromykhailiuk/injectable`. The common types are re-exported here for convenience (`Container`, `ProviderOption`, `InjectionToken`, `InjectOptions`, `Resolver`, …).\n\n---\n\n## How it works\n\nThere is one shared Preact **context** that carries the active container down the tree. Each `<Module>`:\n\n1. reads the container of the nearest ancestor `<Module>` from that context;\n2. creates its **own** container as a child of it (`createContainer(parent)`) — so resolution is hierarchical;\n3. registers its providers;\n4. provides the container to its descendants;\n5. renders `children`.\n\n`useInject` reads the nearest container from the same context and calls `container.get(...)`. Because hierarchy is expressed through **nested `injectable` containers** (not nested context objects), a single global `useInject` works everywhere, and a child module transparently overrides or extends what its parents provide.\n\n---\n\n## `createDIModule`\n\n`createDIModule(providers)` takes an array of providers and returns a `Module` **component**. Everything rendered inside that component can resolve those providers.\n\n```tsx\nconst AuthModule = createDIModule([\n  AuthService,\n  TokenStore,\n  { provide: SESSION, useValue: loadSession() },\n]);\n\n<AuthModule>\n  <Dashboard />\n</AuthModule>;\n```\n\nProviders are registered once, when the module mounts, and each provider is a **singleton within that module's container** (same instance every time you resolve it). The `providers` array is anything `injectable`'s `register()` accepts — a bare class, or a `{ provide, useValue | useCreate | useExisting, multi? }` object. See the [`injectable` providers guide](https://dmytromykhailiuk.github.io/injectable#providers).\n\nThe returned component accepts only `children`. Define it **once** at module scope (not inside another component's render), so its identity — and its container — stay stable.\n\n---\n\n## `useInject`\n\n`useInject` resolves a provider from the nearest `<Module>`. Its type is `Resolver` — **byte-for-byte identical to `container.get`**:\n\n```tsx\nconst logger = useInject(Logger); // class      -> instance\nconst apiUrl = useInject(API_URL); // token      -> T inferred from the token\nconst plugins = useInject([Plugin]); // [Class]  -> Plugin[]\nconst url = useInject<string>(\"API_URL\"); // string -> needs an explicit generic\nconst maybe = useInject(Analytics, { optional: true }); // -> Analytics | undefined\n```\n\nA **token** infers its value type with no second generic. A **class** returns its instance. The `[Class]` tuple returns an array (multi sugar). `{ optional: true }` widens the return type to include `undefined`.\n\nCalled **outside** of any `<Module>`, `useInject` throws:\n\n```\nuseInject must be used within a <Module>. Did you forget to wrap your tree in a component from createDIModule()?\n```\n\n> `useInject` performs a **static** lookup during render — it resolves against the current container and does not subscribe to later registrations. Pass every provider to `createDIModule` up front (they are all registered at mount), which is the normal case.\n\n---\n\n## Nested modules\n\nNest `<Module>` components and resolution becomes hierarchical: a child checks itself first, then walks up to its parents.\n\n```tsx\nconst RootModule = createDIModule([\n  Logger,\n  { provide: API_URL, useValue: \"https://prod.example.com\" },\n]);\n\nconst FeatureModule = createDIModule([\n  { provide: API_URL, useValue: \"http://localhost:3000\" }, // override for this subtree\n]);\n\n<RootModule>\n  <Header /> {/* useInject(API_URL) -> \"https://prod.example.com\" */}\n  <FeatureModule>\n    <Panel /> {/* useInject(API_URL) -> \"http://localhost:3000\" (child wins) */}\n    {/* useInject(Logger) still resolves — inherited from RootModule */}\n  </FeatureModule>\n</RootModule>;\n```\n\nFor `multi` providers the arrays merge, child values first, then the parents' — the same behaviour `injectable` gives nested containers. This is how you build per-feature or per-route scopes that inherit the app-wide services above them.\n\n---\n\n## Injection options\n\n`useInject` forwards `injectable`'s options unchanged:\n\n```ts\ninterface InjectOptions {\n  host?: boolean; // resolve only from this module's container, ignore parents\n  skipSelf?: boolean; // skip this module, resolve from the parent chain\n  multi?: boolean; // treat the result as a multi-provider array\n  optional?: boolean; // return undefined instead of resolving to a missing value\n}\n```\n\n```tsx\nuseInject(Logger, { skipSelf: true }); // explicitly the parent module's Logger\nuseInject(Config, { host: true }); // only this module's Config\n```\n\n---\n\n## Constructor-style injection with `inject()`\n\nInside a service, declare dependencies with `injectable`'s `inject()` — it resolves against whichever module's container is building the service. No constructor plumbing reaches the component.\n\n```tsx\nimport { inject } from \"@dmytromykhailiuk/injectable\";\n\nclass GreetingService {\n  private logger = inject(Logger);\n  private apiUrl = inject<string>(API_URL);\n\n  greet(name: string) {\n    this.logger.log(`hello ${name} via ${this.apiUrl}`);\n  }\n}\n\nconst AppModule = createDIModule([\n  Logger,\n  GreetingService,\n  { provide: API_URL, useValue: \"https://api.example.com\" },\n]);\n\nfunction Greeter() {\n  const greeting = useInject(GreetingService); // its logger + apiUrl already wired\n  greeting.greet(\"world\");\n  return null;\n}\n```\n\n`inject()` is only valid while a provider is being built (a constructor, a field initializer, or a `useCreate` factory). From a component, use `useInject`. See [`inject()` vs `container.get()`](https://dmytromykhailiuk.github.io/injectable#inject-vs-containerget).\n\n---\n\n## Lifecycle\n\nA module's container is created when the module mounts and **destroyed when it unmounts** — `container.destroy()` clears every instance, drops subscribers, and detaches from the parent. Remounting a module builds a fresh container (and fresh singletons). This makes `<Module>` a natural fit for per-route or per-feature scopes that should not leak state across navigations.\n\n---\n\n## Recipes\n\n### App root + feature scope\n\n```tsx\nconst AppModule = createDIModule([ApiClient, Logger, AuthService]);\nconst CheckoutModule = createDIModule([CartService, { provide: FLOW, useValue: \"checkout\" }]);\n\n<AppModule>\n  <Shell>\n    <CheckoutModule>\n      <Checkout />\n    </CheckoutModule>\n  </Shell>\n</AppModule>;\n```\n\n### Swap a real service for a fake (Storybook / tests)\n\n```tsx\nconst StoryModule = createDIModule([{ provide: Mailer, useValue: new FakeMailer() }]);\n\n<AppModule>\n  <StoryModule>\n    <OrderForm /> {/* resolves the fake Mailer */}\n  </StoryModule>\n</AppModule>;\n```\n\n### Provide a per-render value\n\n```tsx\nfunction RequestScope({ req, children }) {\n  // Define the module once, outside render, when the providers are static.\n  // For a per-value provider, pass it through a token registered at the root.\n  return <>{children}</>;\n}\n```\n\n---\n\n## API reference\n\n```ts\n// Build a module component from a provider list.\ncreateDIModule(providers: ProviderOption[]): (props: { children?: ComponentChildren }) => VNode;\n\n// Resolve from the nearest <Module>. Same overloads as injectable's container.get.\nconst useInject: Resolver;\n//   useInject<T>(token: InjectionToken<T>, options?: InjectOptions): T\n//   useInject<T>(cls: new () => T, options?: InjectOptions): T\n//   useInject<T>(cls: [new () => T], options?: InjectOptions): T[]\n//   useInject<T = unknown>(key: string, options?: InjectOptions): T\n//   ...with { optional: true } widening the result to T | undefined\n\n// The shared context (advanced interop — read the raw container).\nconst DIContext: Context<Container | null>;\n\ninterface ModuleProps {\n  children?: ComponentChildren;\n}\n```\n\nThe following `@dmytromykhailiuk/injectable` types are re-exported so you can type providers and tokens without a second import: `Container`, `Resolver`, `ProviderOption`, `InjectOptions`, `OptionalInjectOptions`, `InjectionToken`, `Provider`, `ProviderClass`.\n\n---\n\n## Limitations\n\n- **Static resolution.** `useInject` resolves during render and does not re-render on later registrations. Register all providers via `createDIModule` at mount (the normal case).\n- **Inherits `injectable`'s model.** Synchronous only, no decorators, no constructor-type injection, and a genuinely missing dependency resolves to `undefined` rather than throwing. See the [`injectable` limitations](https://dmytromykhailiuk.github.io/injectable#limitations).\n- **Define modules at module scope.** Creating a `Module` inside another component's render gives it a new identity (and a new container) every render — hoist `createDIModule(...)` out.\n\n---\n\n## Development\n\n```sh\nnpm run playground   # a runnable Preact demo of nested modules + useInject\nnpm test             # the full test suite (vitest + @testing-library/preact)\nnpm run test:coverage\nnpm run typecheck    # tsc --noEmit\nnpm run lint         # biome\nnpm run verify       # lint + typecheck + test + build\n```\n\n## License\n\nMIT © Dmytro Mykhailiuk\n","readmeFilename":"README.md"}