di-craft 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  <p align="center">
8
8
  <b>A tiny, type-safe dependency injection container for TypeScript</b>
9
+ <br />
10
+ <span>Framework-agnostic core with an optional Next.js App Router / RSC adapter</span>
9
11
  </p>
10
12
 
11
13
  <p align="center">
@@ -17,35 +19,10 @@
17
19
  </a>
18
20
  </p>
19
21
 
20
- ## Contents
21
-
22
- - [Quick start](#quick-start)
23
- - [Philosophy](#philosophy)
24
- - [Features](#features)
25
- - [Install](#install)
26
- - [Core concepts](#core-concepts)
27
- - [Tokens](#tokens)
28
- - [Providers](#providers)
29
- - [Annotation-based class providers](#annotation-based-class-providers)
30
- - [Optional dependencies](#optional-dependencies)
31
- - [Container](#container)
32
- - [Scopes](#scopes)
33
- - [Disposal](#disposal)
34
- - [Child containers](#child-containers)
35
- - [Cycle detection](#cycle-detection)
36
- - [Async dependencies](#async-dependencies)
37
- - [Adapters](#adapters)
38
- - [Next.js App Router](#nextjs-app-router)
39
- - [Dependency injection vs service location](#dependency-injection-vs-service-location)
40
- - [Error handling](#error-handling)
41
- - [API reference](#api-reference)
42
- - [License](#license)
43
-
44
- ## Quick start
45
-
46
- Declare typed tokens, describe how each one is built with a provider, then create
47
- a container and resolve from it. Dependencies are wired explicitly through the
48
- `deps` map and resolved for you.
22
+ ## Quick Start
23
+
24
+ Declare typed tokens, describe how each value is built, then resolve at the
25
+ composition root.
49
26
 
50
27
  ```ts
51
28
  import {
@@ -56,15 +33,44 @@ import {
56
33
  type Provider,
57
34
  } from "di-craft";
58
35
 
59
- const CONFIG = createToken<Config>("config");
60
- const LOGGER = createToken<Logger>("logger");
61
- const USERS = createToken<UserService>("users");
36
+ type Config = {
37
+ readonly logPrefix: string;
38
+ };
39
+
40
+ class Logger {
41
+ private readonly prefix: string;
42
+
43
+ constructor(prefix: string) {
44
+ this.prefix = prefix;
45
+ }
46
+
47
+ info(message: string): string {
48
+ return `${this.prefix}: ${message}`;
49
+ }
50
+ }
51
+
52
+ class UserService {
53
+ private readonly logger: Logger;
62
54
 
63
- const providers: Provider[] = [
64
- provideValue(CONFIG, loadConfig()),
55
+ constructor(logger: Logger) {
56
+ this.logger = logger;
57
+ }
58
+
59
+ list(): readonly string[] {
60
+ this.logger.info("list users");
61
+ return ["Ada", "Grace"];
62
+ }
63
+ }
64
+
65
+ const CONFIG = createToken<Config>("CONFIG");
66
+ const LOGGER = createToken<Logger>("LOGGER");
67
+ const USERS = createToken<UserService>("USERS");
68
+
69
+ const providers: readonly Provider[] = [
70
+ provideValue(CONFIG, { logPrefix: "users" }),
65
71
  provideFactory(LOGGER, {
66
72
  deps: { config: CONFIG },
67
- useFactory: ({ config }) => new Logger(config.level),
73
+ useFactory: ({ config }) => new Logger(config.logPrefix),
68
74
  }),
69
75
  provideFactory(USERS, {
70
76
  deps: { logger: LOGGER },
@@ -73,30 +79,21 @@ const providers: Provider[] = [
73
79
  ];
74
80
 
75
81
  const container = createContainer(providers);
76
-
77
- const users = container.get(USERS); // UserService, fully typed
82
+ const users = container.get(USERS); // UserService
78
83
  ```
79
84
 
80
- ## Philosophy
81
-
82
- Dependency injection without hidden magic — no `reflect-metadata`, no runtime
83
- type guessing, and no framework coupling. You work with just **tokens**,
84
- **providers**, a **container**, **scopes**, and **cycle detection**. Standard
85
- JavaScript decorators are available as optional sugar for class providers, but
86
- they still use explicit tokens.
87
-
88
- ## Features
85
+ ## Why di-craft
89
86
 
90
87
  - Zero runtime dependencies
91
88
  - Type-safe tokens and factories
92
89
  - Optional `@Injectable` annotation for class providers
90
+ - Optional Next.js App Router / React Server Components adapter
93
91
  - Optional dependencies via `optional()`
94
92
  - Singleton, transient, and scoped lifetimes
95
- - Hierarchical child containers
93
+ - Hierarchical child containers for request-like lifecycles
96
94
  - Deterministic disposal with `onDispose` hooks
97
95
  - Circular dependency detection
98
- - Tree-shakable, tiny bundle size
99
- - ESM-only, ships with TypeScript declarations
96
+ - Tree-shakable, ESM-only, ships with TypeScript declarations
100
97
 
101
98
  ## Install
102
99
 
@@ -107,57 +104,36 @@ pnpm add di-craft
107
104
  yarn add di-craft
108
105
  ```
109
106
 
110
- Requires Node.js `>= 20`. This package is ESM-only — import it with `import`;
111
- CommonJS code can load it with a dynamic `import()`.
112
-
113
- ## Core concepts
114
-
115
- ### Tokens
116
-
117
- A token is a unique, type-carrying key. Identity is based on an internal `symbol`,
118
- **not** on the name — two tokens with the same name are still different.
119
-
120
- ```ts
121
- const PORT = createToken<number>("port");
122
-
123
- PORT.name; // "port" — used only for error messages
124
- ```
125
-
126
- The type argument flows everywhere: providers must produce a matching value, and
127
- `container.get(PORT)` returns `number`.
107
+ Requires Node.js `>= 20`. This package is ESM-only.
128
108
 
129
- ### Providers
130
-
131
- A provider tells the container how to produce the value for a token.
132
-
133
- `provideValue` — register an existing value:
109
+ ## Core API
134
110
 
135
111
  ```ts
136
- provideValue(PORT, 3000);
112
+ import {
113
+ Scopes,
114
+ createChildContainer,
115
+ createContainer,
116
+ createToken,
117
+ optional,
118
+ provideFactory,
119
+ provideValue,
120
+ } from "di-craft";
137
121
  ```
138
122
 
139
- `provideFactory` build the value lazily, with optional dependencies and scope:
123
+ Core concepts are documented in [docs/core.md](./docs/core.md).
140
124
 
141
- ```ts
142
- provideFactory(HTTP, {
143
- deps: { config: CONFIG }, // optional, keyed map of tokens
144
- scope: "singleton", // optional, defaults to "singleton"
145
- useFactory: ({ config }) => new HttpClient(config.apiUrl),
146
- });
147
- ```
148
-
149
- The keys in `deps` become the keys of the object passed to `useFactory`, each
150
- resolved to its token's type.
125
+ Typed examples are available in [examples/typed-docs](./examples/typed-docs):
151
126
 
152
- ### Annotation-based class providers
127
+ - [basic container](./examples/typed-docs/core/basic.ts)
128
+ - [scopes and child containers](./examples/typed-docs/core/scopes.ts)
129
+ - [annotation-based providers](./examples/typed-docs/annotations/injectable.ts)
130
+ - [Next.js request scope](./examples/typed-docs/next/request-scope.ts)
131
+ - [Next.js state hydration](./examples/typed-docs/next/hydration.ts)
153
132
 
154
- If you write services as classes, you can attach provider metadata to the class
155
- with standard JavaScript decorators, then turn the class into a normal provider
156
- with `provideInjectable`.
133
+ ## Annotation-Based Providers
157
134
 
158
- `@Injectable(options)` marks a class as a provider for `options.token`.
159
- `deps` is an ordered list of constructor dependencies. `scope` and
160
- `onDispose` behave exactly like they do in `provideFactory`.
135
+ `@Injectable` lets class-based services describe their token, constructor
136
+ dependencies, scope, and disposal hook next to the class.
161
137
 
162
138
  ```ts
163
139
  import {
@@ -165,27 +141,23 @@ import {
165
141
  Scopes,
166
142
  createContainer,
167
143
  createToken,
168
- optional,
169
144
  provideInjectable,
170
145
  provideValue,
171
146
  } from "di-craft";
172
147
 
173
- const CONFIG = createToken<Config>("config");
174
- const LOGGER = createToken<Logger>("logger");
175
- const USERS = createToken<UserService>("users");
148
+ const LOGGER = createToken<Logger>("LOGGER");
149
+ const USERS = createToken<UserService>("USERS");
176
150
 
177
151
  @Injectable({
178
152
  token: USERS,
179
- deps: [LOGGER, optional(CONFIG)],
153
+ deps: [LOGGER],
180
154
  scope: Scopes.Scoped,
181
155
  })
182
156
  class UserService {
183
157
  private readonly logger: Logger;
184
- private readonly config: Config | undefined;
185
158
 
186
- constructor(logger: Logger, config: Config | undefined) {
159
+ constructor(logger: Logger) {
187
160
  this.logger = logger;
188
- this.config = config;
189
161
  }
190
162
  }
191
163
 
@@ -193,265 +165,16 @@ const container = createContainer([
193
165
  provideValue(LOGGER, new Logger()),
194
166
  provideInjectable(UserService),
195
167
  ]);
196
-
197
- const users = container.get(USERS); // UserService
198
- ```
199
-
200
- `@Injectable` is the only annotation needed for class injection. It produces a
201
- regular factory provider internally, so all existing container behavior still
202
- applies: optional dependencies, scopes, child containers, disposal hooks,
203
- overrides, and cycle detection.
204
-
205
- di-craft does not use `reflect-metadata` or parameter decorators. Constructor
206
- types are erased by JavaScript at runtime, so dependency tokens stay explicit
207
- instead of being guessed from TypeScript types.
208
-
209
- ### Optional dependencies
210
-
211
- Wrap a token with `optional` to mark a dependency as not required. When no
212
- provider for it is registered anywhere in the container chain, the factory
213
- receives `undefined` instead of the resolution throwing `MissingProviderError`.
214
- The inferred type is widened to `T | undefined`, so TypeScript forces you to
215
- handle the absent case:
216
-
217
- ```ts
218
- import { optional, provideFactory } from "di-craft";
219
-
220
- provideFactory(USERS, {
221
- deps: { logger: optional(LOGGER) }, // LOGGER may or may not be registered
222
- useFactory: ({ logger }) => {
223
- logger?.info("creating users service"); // logger: Logger | undefined
224
- return new UsersService();
225
- },
226
- });
227
- ```
228
-
229
- The same descriptor works at the top level — `optional` can be passed anywhere a
230
- dependency is accepted, including `container.get`:
231
-
232
- ```ts
233
- const logger = container.get(optional(LOGGER)); // Logger | undefined
234
- ```
235
-
236
- Optional only affects the token itself: if a provider _is_ registered, it is
237
- resolved normally and its own errors (cycles, missing nested deps) still surface.
238
-
239
- ### Container
240
-
241
- The container holds your providers and resolves values on demand. Create one
242
- from a list of providers (all optional), then add more, check, resolve, and
243
- dispose:
244
-
245
- - `register(provider, options?)` — add a provider at any time.
246
- - `has(token)` — whether a provider for the token is registered.
247
- - `get(token)` — resolve the value, building and caching it as its scope dictates.
248
- Accepts `optional(token)` to get `undefined` instead of throwing when absent.
249
- - `dispose()` — run `onDispose` hooks and release tracked instances owned by this
250
- container.
251
-
252
- ```ts
253
- const container = createContainer(providers); // providers are optional
254
-
255
- container.register(provideValue(PORT, 3000)); // register more at any time
256
- container.has(PORT); // true
257
- container.get(PORT); // 3000
258
- await container.dispose(); // clears tracked instances and awaits disposal hooks
259
- ```
260
-
261
- Registering the same token twice throws `DuplicateProviderError`. To replace an
262
- existing provider on purpose (handy for tests, mocks, and environment-specific
263
- overrides), pass `{ allowOverride: true }`:
264
-
265
- ```ts
266
- container.register(provideValue(API, fakeApi), { allowOverride: true });
267
- ```
268
-
269
- Overriding a token whose value was already resolved as a singleton drops the
270
- cached instance, so the next `get` rebuilds it from the new provider. If that
271
- resolved instance has an `onDispose` hook, the override throws
272
- `InvalidProviderError` instead of silently dropping it — call `dispose()` first
273
- so the resource is released, then register the replacement.
274
-
275
- ### Scopes
276
-
277
- | Scope | Behavior |
278
- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
279
- | `singleton` (default) | The factory runs once; the same instance is returned every time. |
280
- | `transient` | The factory runs on every `get`, producing a fresh instance. |
281
- | `scoped` | One instance per container. In a child container each child gets its own instance, while the provider can still be declared once on the parent. |
282
-
283
- Use the `Scopes` helper for autocompletion, or pass the plain string — both work:
284
-
285
- ```ts
286
- import { Scopes, provideFactory } from "di-craft";
287
-
288
- provideFactory(ID, {
289
- scope: Scopes.Transient, // or scope: "transient"
290
- useFactory: () => crypto.randomUUID(),
291
- });
292
-
293
- container.get(ID) !== container.get(ID); // true
294
- ```
295
-
296
- A provider may only depend on dependencies that live **at least as long** as
297
- itself, so a longer-lived instance never captures a shorter-lived one. A
298
- transient may depend on anything; a scoped may depend on scoped or singleton; a
299
- singleton may depend only on singletons (and values). Violating this throws
300
- `InvalidProviderError` at resolution. A transient that depends on a singleton, for
301
- example, reuses the shared singleton instance.
302
-
303
- ### Disposal
304
-
305
- Factory providers can declare an `onDispose` hook to release resources (database
306
- pools, sockets, timers, subscriptions). Calling `container.dispose()` runs the
307
- hooks for every resolved cached instance owned by that container and releases the
308
- container's tracked instances:
309
-
310
- ```ts
311
- const DB = createToken<Pool>("db");
312
-
313
- const container = createContainer([
314
- provideFactory(DB, {
315
- useFactory: () => createPool(url),
316
- onDispose: (pool) => pool.end(), // may be sync or async
317
- }),
318
- ]);
319
-
320
- container.get(DB);
321
-
322
- await container.dispose(); // clears tracked instances and awaits disposal hooks
323
- ```
324
-
325
- Details:
326
-
327
- - Hooks run in reverse creation order (dependents before their dependencies).
328
- - `dispose()` returns a promise and awaits async hooks.
329
- - Instances are removed from the cache before hooks run, making disposal
330
- idempotent and re-entrancy safe.
331
- - Only resolved cached instances owned by that container are disposed: singletons
332
- owned by that container and scoped instances created for that container.
333
- Transient and never-resolved instances are not tracked.
334
- - `onDispose` is only meaningful for cached instances. Declaring it on a
335
- `transient` provider throws `InvalidProviderError`, since transient instances
336
- are never tracked and the hook could never run.
337
-
338
- ### Child containers
339
-
340
- `createChildContainer(parent, providers?)` creates a child that inherits
341
- everything from its parent but can add or override providers locally. This is the
342
- typical pattern for per-request isolation on a server: shared services live on
343
- the root, request-specific values live on a short-lived child.
344
-
345
- ```ts
346
- const root = createContainer([
347
- provideFactory(LOGGER, { useFactory: () => console }), // singleton, shared
348
- provideFactory(HANDLER, {
349
- scope: Scopes.Scoped, // one instance per child
350
- deps: { request: REQUEST },
351
- useFactory: ({ request }) => createHandler(request),
352
- }),
353
- ]);
354
-
355
- function handle(request: Request) {
356
- const child = createChildContainer(root, [provideValue(REQUEST, request)]);
357
-
358
- child.get(LOGGER); // same logger as the root and every other child
359
- child.get(HANDLER); // a fresh handler, unique to this child
360
-
361
- return child.dispose(); // release only this child's instances
362
- }
363
- ```
364
-
365
- How resolution works across the chain:
366
-
367
- - A token is looked up in the child first, then walks up to the parent.
368
- - `singleton` is cached on the container that **owns** the provider, so it is
369
- shared by the whole subtree.
370
- - `scoped` is cached on the **requesting** child, so each child gets its own
371
- instance — even when the provider is declared once on the parent.
372
- - A `scoped` provider resolves its dependencies from the requesting child, so it
373
- can depend on values registered only in that child (like `REQUEST`).
374
- - `dispose()` only releases the container it is called on; it does not cascade to
375
- parents or children.
376
-
377
- ### Cycle detection
378
-
379
- If providers form a dependency cycle, resolution throws `CircularDependencyError`
380
- with the full path instead of overflowing the stack.
381
-
382
- ```ts
383
- // A -> B -> A
384
- container.get(A); // throws: Circular dependency detected: A -> B -> A
385
- ```
386
-
387
- ### Async dependencies
388
-
389
- di-craft resolves synchronously by design — there is no `getAsync`, and async
390
- never colors the rest of your graph. Asynchronous values are handled with one of
391
- two patterns, which together cover the vast majority of cases.
392
-
393
- **Resolve first, then register.** Do the async work at your composition root and
394
- register the resolved value. Simplest and most common:
395
-
396
- ```ts
397
- const db = await connectDatabase(config);
398
- container.register(provideValue(DB, db));
399
168
  ```
400
169
 
401
- **Promise as value (lazy).** Register a factory that returns a promise. The
402
- container caches it like any other singleton, so the async work runs once and
403
- every consumer awaits the same promise:
404
-
405
- ```ts
406
- const POOL = createToken<Promise<Pool>>("pool");
407
-
408
- container.register(provideFactory(POOL, { useFactory: () => createPool() }));
170
+ Annotations are only syntax sugar over normal providers. There is no
171
+ `reflect-metadata`, parameter decorators, runtime type guessing, or global
172
+ container.
409
173
 
410
- const pool = await container.get(POOL);
411
- ```
174
+ ## Next.js App Router
412
175
 
413
- A factory that depends on `POOL` receives the promise and awaits it itself:
414
-
415
- ```ts
416
- provideFactory(USERS, {
417
- deps: { pool: POOL },
418
- useFactory: async ({ pool }) => new UsersRepo(await pool),
419
- });
420
- // USERS is now Token<Promise<UsersRepo>> — consumers await it too.
421
- ```
422
-
423
- When using `Promise<T>` as the token value, disposal hooks receive the promise
424
- itself. Await it inside `onDispose` if cleanup needs the resolved value:
425
-
426
- ```ts
427
- const POOL = createToken<Promise<Pool>>("pool");
428
-
429
- provideFactory(POOL, {
430
- useFactory: () => createPool(),
431
- onDispose: async (poolPromise) => {
432
- const pool = await poolPromise;
433
- await pool.end();
434
- },
435
- });
436
- ```
437
-
438
- ## Adapters
439
-
440
- Adapters are optional framework integrations built around the core container.
441
- They live behind subpath exports, so the root import stays framework-agnostic:
442
-
443
- ```ts
444
- import { createContainer } from "di-craft"; // core only
445
- ```
446
-
447
- ### Next.js App Router
448
-
449
- The Next adapter helps connect di-craft to the App Router request lifecycle
450
- without making React or Next.js part of the core import.
451
-
452
- Use `di-craft/next/server` from a server-only composition file. Pass React's
453
- `cache` function so React/Next owns request memoization while di-craft owns only
454
- the dependency graph:
176
+ The Next adapter lives behind subpath exports, so React and Next.js are not part
177
+ of the core import.
455
178
 
456
179
  ```ts
457
180
  // app/di.server.ts
@@ -460,11 +183,7 @@ import { cache } from "react";
460
183
  import { provideValue } from "di-craft";
461
184
  import { createNextDi } from "di-craft/next/server";
462
185
 
463
- export const {
464
- getRequestContainer,
465
- getRootContainer,
466
- runWithRequestContainer,
467
- } = createNextDi({
186
+ export const { getRequestContainer, runWithRequestContainer } = createNextDi({
468
187
  cache,
469
188
  providers,
470
189
  requestProviders: () => [
@@ -473,7 +192,7 @@ export const {
473
192
  });
474
193
  ```
475
194
 
476
- Then resolve dependencies in Server Components at the composition edge:
195
+ Resolve dependencies in Server Components at the composition edge:
477
196
 
478
197
  ```ts
479
198
  import { getRequestContainer } from "./di.server";
@@ -485,15 +204,11 @@ export default async function Page() {
485
204
  }
486
205
  ```
487
206
 
488
- Next.js does not expose a general "RSC render is finished" hook, so the adapter
489
- does not pretend it can automatically dispose a cached Server Component request
490
- container. If you own the lifecycle, for example in a Route Handler, Server
491
- Action, test, or job, use `runWithRequestContainer`. It creates a fresh child
492
- container and disposes it in a `finally` block:
207
+ For Route Handlers, Server Actions, tests, or jobs where you own the lifecycle,
208
+ use `runWithRequestContainer`. It creates a fresh child container and disposes it
209
+ in a `finally` block.
493
210
 
494
211
  ```ts
495
- import { runWithRequestContainer } from "./di.server";
496
-
497
212
  export async function GET() {
498
213
  return runWithRequestContainer({
499
214
  run: async (container) => {
@@ -505,151 +220,38 @@ export async function GET() {
505
220
  }
506
221
  ```
507
222
 
508
- State hydration is explicit. The server reads serializable snapshots with
509
- `di-craft/next/server`; the client restores them with `di-craft/next/client`.
510
- The DI container itself is never hydrated.
511
-
512
- Import boundary-specific runtime helpers from their own subpath:
513
-
514
- - `di-craft/next/server` — `createNextDi`, `dehydrate`, server-only adapter types.
515
- - `di-craft/next/client` — `hydrate`, client-boundary hydration types.
516
- - Shared hydration contracts like `Hydratable`, `HydrationSchema`, and
517
- `HydrationSnapshot` are exported from both subpaths for convenience.
223
+ State hydration is explicit:
518
224
 
519
- ```ts
520
- import {
521
- dehydrate,
522
- type Hydratable,
523
- type HydrationSchema,
524
- } from "di-craft/next/server";
525
-
526
- class UserState implements Hydratable<UserSnapshot> {
527
- dehydrate(): UserSnapshot {
528
- return { users: this.users };
529
- }
530
-
531
- hydrate(snapshot: UserSnapshot): void {
532
- this.users = snapshot.users;
533
- }
534
- }
535
-
536
- const hydration = {
537
- user: USER_STATE,
538
- } satisfies HydrationSchema;
539
- const snapshot = dehydrate({
540
- container: getRequestContainer(),
541
- schema: hydration,
542
- });
225
+ ```txt
226
+ server DI container -> serializable snapshot -> client state
543
227
  ```
544
228
 
545
- ```ts
546
- "use client";
547
-
548
- import { hydrate } from "di-craft/next/client";
549
-
550
- hydrate({
551
- container: clientContainer,
552
- schema: hydration,
553
- snapshot,
554
- });
555
- ```
556
-
557
- ## Dependency injection vs service location
558
-
559
- di-craft is built for **dependency injection**: dependencies are declared up
560
- front and handed to your code. The opposite is **service location**, where code
561
- reaches into a container at runtime to pull what it needs, hiding its real
562
- dependencies.
563
-
564
- Two habits keep usage canonical: call `container.get()` only at the **composition
565
- root** (entrypoint, framework hooks, route handlers), and never pass the
566
- container into your classes or functions. di-craft enforces the key half for you
567
- — **a factory only ever receives its declared `deps`, never the container** — so
568
- a provider physically cannot locate arbitrary services.
569
-
570
- ```ts
571
- // Dependency injection — deps are explicit, the class never sees the container.
572
- provideFactory(USERS, {
573
- deps: { repo: REPO, logger: LOGGER },
574
- useFactory: ({ repo, logger }) => new UserService(repo, logger),
575
- });
576
-
577
- const users = container.get(USERS); // resolved at the root, then injected down
578
- ```
579
-
580
- ```ts
581
- // Service location (anti-pattern) — the container is smuggled into domain code.
582
- class UserService {
583
- constructor(private container: Container) {}
584
-
585
- list() {
586
- const repo = this.container.get(REPO); // hidden, runtime-only dependency
587
- }
588
- }
589
- ```
590
-
591
- The second form compiles, but it hides dependencies and defeats DI. No runtime
592
- flag can forbid it — `get()` is the same call the composition root relies on — so
593
- keep resolution at the edges by convention, or enforce it with a lint rule that
594
- allows `.get()` only in your composition-root files.
229
+ The DI container itself is never hydrated.
595
230
 
596
- ## Error handling
231
+ Runtime subpaths:
597
232
 
598
- All errors extend the shared `DiError` base class, so you can catch any container
599
- error with a single check:
233
+ | Export | Description |
234
+ | ---------------------- | ------------------------------------------------------ |
235
+ | `di-craft/next/server` | `createNextDi`, `dehydrate`, server-side adapter types |
236
+ | `di-craft/next/client` | `hydrate`, client-boundary hydration types |
600
237
 
601
- ```ts
602
- import { DiError, MissingProviderError } from "di-craft";
238
+ ## API Reference
603
239
 
604
- try {
605
- container.get(SOME_TOKEN);
606
- } catch (error) {
607
- if (error instanceof MissingProviderError) {
608
- // a specific failure
609
- }
610
-
611
- if (error instanceof DiError) {
612
- // any di-craft error
613
- }
614
- }
615
- ```
616
-
617
- | Error | Thrown when |
618
- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
619
- | `MissingProviderError` | A token is resolved but no provider is registered. |
620
- | `DuplicateProviderError` | A token is registered more than once. |
621
- | `CircularDependencyError` | Providers form a dependency cycle. |
622
- | `InvalidDependencyError` | A declared dependency token is missing/undefined. |
623
- | `InvalidProviderError` | A provider is misconfigured (`onDispose` on a transient, or a dependency with a shorter lifetime than its consumer) or an override would drop a live disposable instance. |
624
-
625
- ## API reference
626
-
627
- | Export | Description |
628
- | ------------------------------------------ | --------------------------------------------------------------------------- |
629
- | `createToken<T>(name)` | Create a unique, typed token. |
630
- | `provideValue(token, value)` | Provider that returns an existing value. |
631
- | `provideFactory(token, options)` | Provider that builds a value via a factory. |
632
- | `@Injectable(options)` | Mark a class as a token-backed injectable provider. |
633
- | `provideInjectable(class)` | Create a factory provider from an injectable class. |
634
- | `optional(token)` | Mark a dependency as optional (resolves to `undefined` when absent). |
635
- | `createContainer(providers?)` | Create a container, optionally seeded with providers. |
636
- | `createChildContainer(parent, providers?)` | Create a child container that inherits from `parent`. |
637
- | `Scopes` | Object of scope values (`Scopes.Singleton`, `Scopes.Transient`, `Scopes.Scoped`). |
638
-
639
- Exported types: `Container`, `Token`, `Provider`, `ValueProvider`,
640
- `FactoryProvider`, `Dependency`, `OptionalDependency`, `Scope`, `DisposeHook`,
641
- `RegisterOptions`.
240
+ | Export | Description |
241
+ | ------------------------------------------ | ------------------------------------------------------------------- |
242
+ | `createToken<T>(name)` | Create a unique, typed token |
243
+ | `provideValue(token, value)` | Register an existing value |
244
+ | `provideFactory(token, options)` | Register a lazy factory with optional dependencies, scope, disposal |
245
+ | `@Injectable(options)` | Mark a class as a token-backed injectable provider |
246
+ | `provideInjectable(class)` | Create a factory provider from an injectable class |
247
+ | `optional(token)` | Mark a dependency as optional |
248
+ | `createContainer(providers?)` | Create a container |
249
+ | `createChildContainer(parent, providers?)` | Create a child container that inherits from `parent` |
250
+ | `Scopes` | `Singleton`, `Transient`, and `Scoped` scope constants |
642
251
 
643
252
  Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`,
644
253
  `CircularDependencyError`, `InvalidDependencyError`, `InvalidProviderError`.
645
254
 
646
- Subpath exports:
647
-
648
- | Export | Description |
649
- | --------------------------- | ------------------------------------------------------- |
650
- | `di-craft/next/server` | Next.js server adapter for request-scoped containers. |
651
- | `di-craft/next/client` | Client-boundary helpers for restoring state snapshots. |
652
-
653
255
  ## License
654
256
 
655
257
  [MIT](./LICENSE)