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