decorator-dependency-injection 1.0.6 → 1.1.0

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.
@@ -0,0 +1,808 @@
1
+ # Framework and Environment Integration
2
+
3
+ This guide shows how to use decorator-dependency-injection with frontend frameworks, SSR, and various deployment targets.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Quick Setup](#quick-setup)
8
+ - [React](#react)
9
+ - [Vue 3](#vue-3)
10
+ - [Svelte](#svelte)
11
+ - [Project Structure](#project-structure)
12
+ - [Frontend Frameworks](#frontend-frameworks)
13
+ - [React](#react-1)
14
+ - [Vue 3](#vue-3-1)
15
+ - [Svelte](#svelte-1)
16
+ - [Angular](#angular)
17
+ - [Server-Side Rendering](#server-side-rendering)
18
+ - [Node.js Server Middleware](#nodejs-server-middleware)
19
+ - [Bundler Configuration](#bundler-configuration)
20
+ - [Vite](#vite)
21
+ - [Webpack](#webpack-create-react-app-etc)
22
+ - [esbuild](#esbuild)
23
+ - [Bun](#bun)
24
+ - [Runtime Environments](#runtime-environments)
25
+ - [Node.js](#nodejs)
26
+ - [AWS Lambda](#aws-lambda)
27
+ - [Cloudflare Workers / Vercel Edge](#cloudflare-workers--vercel-edge)
28
+ - [Electron](#electron)
29
+ - [Troubleshooting](#troubleshooting)
30
+
31
+ ---
32
+
33
+ ## Quick Setup
34
+
35
+ ### React
36
+
37
+ ```jsx
38
+ // services/UserService.js
39
+ import { Singleton } from 'decorator-dependency-injection'
40
+
41
+ @Singleton()
42
+ export class UserService {
43
+ async getUser(id) {
44
+ const res = await fetch(`/api/users/${id}`)
45
+ return res.json()
46
+ }
47
+ }
48
+
49
+ // components/UserProfile.jsx
50
+ import { resolve } from 'decorator-dependency-injection'
51
+ import { useState, useEffect, useMemo } from 'react'
52
+ import { UserService } from '../services/UserService'
53
+
54
+ export function UserProfile({ userId }) {
55
+ const [user, setUser] = useState(null)
56
+ const userService = useMemo(() => resolve(UserService), [])
57
+
58
+ useEffect(() => {
59
+ userService.getUser(userId).then(setUser)
60
+ }, [userId, userService])
61
+
62
+ return <div>{user?.name}</div>
63
+ }
64
+ ```
65
+
66
+ ### Vue 3
67
+
68
+ ```javascript
69
+ // services/UserService.js
70
+ import { Singleton } from 'decorator-dependency-injection'
71
+
72
+ @Singleton()
73
+ export class UserService {
74
+ async getUser(id) {
75
+ const res = await fetch(`/api/users/${id}`)
76
+ return res.json()
77
+ }
78
+ }
79
+ ```
80
+
81
+ ```vue
82
+ <!-- components/UserProfile.vue -->
83
+ <script setup>
84
+ import { ref, onMounted } from 'vue'
85
+ import { resolve } from 'decorator-dependency-injection'
86
+ import { UserService } from '../services/UserService'
87
+
88
+ const userService = resolve(UserService)
89
+ const user = ref(null)
90
+
91
+ onMounted(async () => {
92
+ user.value = await userService.getUser(1)
93
+ })
94
+ </script>
95
+
96
+ <template>
97
+ <div>{{ user?.name }}</div>
98
+ </template>
99
+ ```
100
+
101
+ ### Svelte
102
+
103
+ ```javascript
104
+ // services/UserService.js
105
+ import { Singleton } from 'decorator-dependency-injection'
106
+
107
+ @Singleton()
108
+ export class UserService {
109
+ async getUser(id) {
110
+ const res = await fetch(`/api/users/${id}`)
111
+ return res.json()
112
+ }
113
+ }
114
+ ```
115
+
116
+ ```svelte
117
+ <!-- components/UserProfile.svelte -->
118
+ <script>
119
+ import { onMount } from 'svelte'
120
+ import { resolve } from 'decorator-dependency-injection'
121
+ import { UserService } from '../services/UserService'
122
+
123
+ const userService = resolve(UserService)
124
+ let user = null
125
+
126
+ onMount(async () => {
127
+ user = await userService.getUser(1)
128
+ })
129
+ </script>
130
+
131
+ <div>{user?.name}</div>
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Project Structure
137
+
138
+ Recommended organization for DI-based projects:
139
+
140
+ ```
141
+ src/
142
+ ├── services/ # Classes with @Singleton/@Factory
143
+ │ ├── UserService.js
144
+ │ ├── AuthService.js
145
+ │ └── ApiClient.js
146
+ ├── components/ # UI components (use resolve())
147
+ │ └── UserProfile.jsx
148
+ ├── hooks/ # Optional: framework-specific wrappers
149
+ │ └── useService.js
150
+ └── index.js # App entry point
151
+ ```
152
+
153
+ **Key principle**: Keep your service layer (classes with decorators) separate from your UI layer (components that use `resolve()`).
154
+
155
+ ### Optional: Custom Hook/Composable
156
+
157
+ If you find yourself writing `useMemo(() => resolve(...), [])` repeatedly:
158
+
159
+ ```javascript
160
+ // hooks/useService.js (React)
161
+ import { useMemo } from 'react'
162
+ import { resolve } from 'decorator-dependency-injection'
163
+
164
+ export function useService(ServiceClass) {
165
+ return useMemo(() => resolve(ServiceClass), [ServiceClass])
166
+ }
167
+
168
+ // Usage
169
+ const userService = useService(UserService)
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Frontend Frameworks
175
+
176
+ ### React
177
+
178
+ The `resolve()` call should be memoized to avoid creating lookups on every render:
179
+
180
+ ```jsx
181
+ import { resolve } from 'decorator-dependency-injection'
182
+ import { useMemo } from 'react'
183
+
184
+ function MyComponent() {
185
+ // Memoize the resolution
186
+ const userService = useMemo(() => resolve(UserService), [])
187
+
188
+ // Now use userService in effects, handlers, etc.
189
+ }
190
+ ```
191
+
192
+ #### With TanStack Query
193
+
194
+ ```jsx
195
+ import { useQuery } from '@tanstack/react-query'
196
+ import { resolve } from 'decorator-dependency-injection'
197
+ import { useMemo } from 'react'
198
+
199
+ function UserProfile({ userId }) {
200
+ const userService = useMemo(() => resolve(UserService), [])
201
+
202
+ const { data: user } = useQuery({
203
+ queryKey: ['user', userId],
204
+ queryFn: () => userService.getUser(userId)
205
+ })
206
+
207
+ return <div>{user?.name}</div>
208
+ }
209
+ ```
210
+
211
+ ### Vue 3
212
+
213
+ In Vue's Composition API, `resolve()` can be called directly in `<script setup>`:
214
+
215
+ ```vue
216
+ <script setup>
217
+ import { resolve } from 'decorator-dependency-injection'
218
+
219
+ // Called once during setup - no memoization needed
220
+ const userService = resolve(UserService)
221
+ </script>
222
+ ```
223
+
224
+ #### With Pinia
225
+
226
+ ```javascript
227
+ import { defineStore } from 'pinia'
228
+ import { resolve } from 'decorator-dependency-injection'
229
+
230
+ export const useUserStore = defineStore('user', {
231
+ state: () => ({ user: null }),
232
+ actions: {
233
+ async fetchUser(id) {
234
+ const userService = resolve(UserService)
235
+ this.user = await userService.getUser(id)
236
+ }
237
+ }
238
+ })
239
+ ```
240
+
241
+ ### Svelte
242
+
243
+ In Svelte, `resolve()` in the `<script>` block runs once per component instance:
244
+
245
+ ```svelte
246
+ <script>
247
+ import { resolve } from 'decorator-dependency-injection'
248
+
249
+ const userService = resolve(UserService)
250
+ </script>
251
+ ```
252
+
253
+ ### Angular
254
+
255
+ Angular has its own DI system. If you need to bridge:
256
+
257
+ ```typescript
258
+ import { Injectable } from '@angular/core'
259
+ import { resolve } from 'decorator-dependency-injection'
260
+
261
+ @Injectable({ providedIn: 'root' })
262
+ export class UserServiceBridge {
263
+ private service = resolve(UserService)
264
+
265
+ getUser(id: number) {
266
+ return this.service.getUser(id)
267
+ }
268
+ }
269
+ ```
270
+
271
+ Generally, stick with Angular's native DI for Angular projects.
272
+
273
+ ---
274
+
275
+ ## Server-Side Rendering
276
+
277
+ ### The Problem
278
+
279
+ Singletons persist across requests on the server, potentially leaking user data:
280
+
281
+ ```javascript
282
+ @Singleton()
283
+ class AuthService {
284
+ currentUser = null // DANGER: Shared across ALL requests!
285
+ }
286
+ ```
287
+
288
+ ### The Solution
289
+
290
+ Create a new container per request:
291
+
292
+ ```javascript
293
+ import { Container } from 'decorator-dependency-injection'
294
+
295
+ async function handleRequest(req) {
296
+ // Each request gets its own container
297
+ const container = new Container()
298
+ container.registerSingleton(AuthService)
299
+ container.registerSingleton(UserService)
300
+
301
+ // This instance is isolated to this request
302
+ const auth = container.resolve(AuthService)
303
+ auth.currentUser = req.user
304
+
305
+ // Process request with isolated state
306
+ const userService = container.resolve(UserService)
307
+ return await userService.getData()
308
+ }
309
+ ```
310
+
311
+ This pattern works identically in **Next.js**, **Nuxt**, **SvelteKit**, **Remix**, or any SSR framework.
312
+
313
+ ### Client vs Server
314
+
315
+ | Context | Container | Why |
316
+ |---------|-----------|-----|
317
+ | Client (browser) | Global (default) | One user per browser tab |
318
+ | Server (SSR) | Per-request | Multiple users share the process |
319
+ | API Routes | Per-request | Same as SSR |
320
+
321
+ ### Example: Next.js App Router
322
+
323
+ ```javascript
324
+ // app/users/[id]/page.js
325
+ import { Container } from 'decorator-dependency-injection'
326
+
327
+ export default async function UserPage({ params }) {
328
+ const container = new Container()
329
+ container.registerSingleton(UserService)
330
+
331
+ const userService = container.resolve(UserService)
332
+ const user = await userService.getUser(params.id)
333
+
334
+ return <div>{user.name}</div>
335
+ }
336
+ ```
337
+
338
+ ```javascript
339
+ // Client component - uses global container (safe)
340
+ 'use client'
341
+ import { resolve } from 'decorator-dependency-injection'
342
+ import { useMemo } from 'react'
343
+
344
+ export function UserProfile({ userId }) {
345
+ const userService = useMemo(() => resolve(UserService), [])
346
+ // ...
347
+ }
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Node.js Server Middleware
353
+
354
+ For Express, Koa, Fastify, Hono, and other Node.js servers, we provide middleware that automatically creates **request-scoped containers** using `AsyncLocalStorage`.
355
+
356
+ ### ⚠️ Important: How Request Scoping Works
357
+
358
+ When you use this middleware, **a new container is created for each HTTP request**:
359
+
360
+ ```
361
+ Request 1 ──────────────────────────────────────────────────────►
362
+ │ Container A created │ UserService instance A │ Container A garbage collected
363
+
364
+ Request 2 ──────────────────────────────────────────────────────►
365
+ │ Container B created │ UserService instance B │ Container B garbage collected
366
+ ```
367
+
368
+ **Key points:**
369
+ - `resolve()` from `decorator-dependency-injection/middleware` returns instances from the **current request's container**
370
+ - Singletons are **isolated per-request** - each request gets its own instance
371
+ - This prevents data leaking between users (critical for SSR safety)
372
+ - Services are **auto-registered** from the global container - no need to list them explicitly
373
+
374
+ ### Basic Setup (Recommended)
375
+
376
+ ```javascript
377
+ import express from 'express'
378
+ import { containerMiddleware, resolve } from 'decorator-dependency-injection/middleware'
379
+
380
+ const app = express()
381
+
382
+ // Creates a new container for each request
383
+ app.use(containerMiddleware())
384
+
385
+ app.get('/users/:id', async (req, res) => {
386
+ // This UserService instance is ISOLATED to this request
387
+ // @Singleton() decorated services are auto-registered
388
+ const userService = resolve(UserService)
389
+ const user = await userService.getUser(req.params.id)
390
+ res.json(user)
391
+ })
392
+
393
+ app.listen(3000)
394
+ ```
395
+
396
+ ### Why Request Scoping Matters
397
+
398
+ Without request scoping, stateful services leak between users:
399
+
400
+ ```javascript
401
+ // ❌ DANGEROUS - shared across ALL requests
402
+ @Singleton()
403
+ class AuthService {
404
+ currentUser = null // User A's data leaks to User B!
405
+ }
406
+
407
+ // ✅ SAFE - with request-scoped containers
408
+ app.use(containerMiddleware())
409
+
410
+ app.get('/me', (req, res) => {
411
+ const auth = resolve(AuthService) // Fresh instance per request
412
+ auth.currentUser = req.user // Safe! Isolated to this request
413
+ res.json(auth.currentUser)
414
+ })
415
+ ```
416
+
417
+ ### Scope Options
418
+
419
+ You can control the scope per-middleware:
420
+
421
+ | Scope | Behavior | Use Case |
422
+ |-------|----------|----------|
423
+ | `'request'` (default) | New container per request, isolated singletons | Stateful services, SSR, user-specific data |
424
+ | `'global'` | Use the global container directly | Stateless services, connection pools, config |
425
+
426
+ ```javascript
427
+ // Request scope (default) - isolated singletons
428
+ app.use(containerMiddleware()) // or { scope: 'request' }
429
+
430
+ // Global scope - shared singletons (use carefully!)
431
+ app.use(containerMiddleware({ scope: 'global' }))
432
+ ```
433
+
434
+ **When to use global scope:**
435
+ - Database connection pools (should be shared)
436
+ - Configuration services (immutable, stateless)
437
+ - Caches (shared across requests)
438
+
439
+ **When to use request scope (default):**
440
+ - Authentication/session services
441
+ - Request-specific loggers
442
+ - Any service that holds user state
443
+
444
+ ### Mixing Scopes in the Same Handler
445
+
446
+ Sometimes you need both request-scoped and global services in the same handler. Use the `scope` option on `resolve()`:
447
+
448
+ ```javascript
449
+ import { containerMiddleware, resolve } from 'decorator-dependency-injection/middleware'
450
+
451
+ app.use(containerMiddleware())
452
+
453
+ app.get('/users/:id', async (req, res) => {
454
+ // Request-scoped (default) - isolated per request
455
+ const authService = resolve(AuthService)
456
+
457
+ // Explicitly global - shared across all requests
458
+ const dbPool = resolve(DatabasePool, { scope: 'global' })
459
+ const cache = resolve(CacheService, { scope: 'global' })
460
+
461
+ // Use both together
462
+ const user = await dbPool.query('SELECT * FROM users WHERE id = ?', [req.params.id])
463
+ authService.setUser(user) // Safe - isolated to this request
464
+
465
+ res.json(user)
466
+ })
467
+ ```
468
+
469
+ **Alternative:** Import `resolve` from the main module as `resolveGlobal`:
470
+
471
+ ```javascript
472
+ import { containerMiddleware, resolve } from 'decorator-dependency-injection/middleware'
473
+ import { resolve as resolveGlobal } from 'decorator-dependency-injection'
474
+
475
+ app.get('/users/:id', async (req, res) => {
476
+ const authService = resolve(AuthService) // Request-scoped
477
+ const dbPool = resolveGlobal(DatabasePool) // Global
478
+ // ...
479
+ })
480
+ ```
481
+
482
+ **Warning behavior:** If you explicitly request `{ scope: 'request' }` but no middleware is set up, a warning is logged:
483
+
484
+ ```javascript
485
+ // Outside any request context
486
+ const auth = resolve(AuthService, { scope: 'request' })
487
+ // ⚠️ Console: [DI] resolve() called with scope='request' but no request context exists...
488
+ ```
489
+
490
+ ### Koa
491
+
492
+ ```javascript
493
+ import Koa from 'koa'
494
+ import { koaContainerMiddleware, resolve } from 'decorator-dependency-injection/middleware'
495
+
496
+ const app = new Koa()
497
+ app.use(koaContainerMiddleware())
498
+
499
+ app.use(async (ctx) => {
500
+ const userService = resolve(UserService) // Request-scoped
501
+ ctx.body = await userService.getUser(ctx.params.id)
502
+ })
503
+ ```
504
+
505
+ ### Hono / Fastify (Handler Wrapper)
506
+
507
+ ```javascript
508
+ import { Hono } from 'hono'
509
+ import { withContainer, resolve } from 'decorator-dependency-injection/middleware'
510
+
511
+ const app = new Hono()
512
+
513
+ app.get('/users/:id', withContainer()(async (c) => {
514
+ const userService = resolve(UserService)
515
+ return c.json(await userService.getUser(c.req.param('id')))
516
+ }))
517
+ ```
518
+
519
+ ### How Auto-Registration Works
520
+
521
+ When you call `resolve(UserService)`:
522
+
523
+ 1. Middleware checks if `UserService` is registered in the request container
524
+ 2. If not, it looks up the registration in the **global container** (where `@Singleton()` registered it)
525
+ 3. It copies the registration type (singleton/factory) to the request container
526
+ 4. A new instance is created **in the request container**
527
+
528
+ The global container is automatically set to the default container from the main module when you import `decorator-dependency-injection/middleware` - no manual setup needed.
529
+
530
+ ### Direct Container Access
531
+
532
+ You can access the request's DI container directly via `req.di`:
533
+
534
+ ```javascript
535
+ app.get('/users/:id', async (req, res) => {
536
+ // Recommended: use resolve()
537
+ const userService = resolve(UserService)
538
+
539
+ // Alternative: direct container access via req.di
540
+ console.log(req.di.has(UserService)) // true after first resolve
541
+
542
+ // Register request-specific services
543
+ req.di.registerSingleton(RequestLogger)
544
+ })
545
+ ```
546
+
547
+ ### Testing with runWithContainer
548
+
549
+ For unit tests, use `runWithContainer` to control the container:
550
+
551
+ ```javascript
552
+ import { runWithContainer, resolve } from 'decorator-dependency-injection/middleware'
553
+ import { Container } from 'decorator-dependency-injection'
554
+
555
+ it('uses the mocked service', () => {
556
+ const testContainer = new Container()
557
+ testContainer.registerSingleton(MockUserService)
558
+
559
+ const result = runWithContainer(testContainer, () => {
560
+ return resolve(UserService).getUser(1) // Gets MockUserService
561
+ })
562
+
563
+ expect(result).toEqual({ id: 1, name: 'Mock User' })
564
+ })
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Bundler Configuration
570
+
571
+ Decorators require transpilation. Here's the setup for common bundlers:
572
+
573
+ ### Vite
574
+
575
+ ```bash
576
+ npm install -D @babel/core @babel/plugin-proposal-decorators
577
+ ```
578
+
579
+ ```javascript
580
+ // vite.config.js
581
+ import { defineConfig } from 'vite'
582
+ import react from '@vitejs/plugin-react'
583
+
584
+ export default defineConfig({
585
+ plugins: [
586
+ react({
587
+ babel: {
588
+ plugins: [
589
+ ['@babel/plugin-proposal-decorators', { version: '2023-11' }]
590
+ ]
591
+ }
592
+ })
593
+ ]
594
+ })
595
+ ```
596
+
597
+ ### Webpack (Create React App, etc.)
598
+
599
+ ```bash
600
+ npm install -D @babel/plugin-proposal-decorators
601
+ ```
602
+
603
+ ```javascript
604
+ // babel.config.js
605
+ module.exports = {
606
+ plugins: [
607
+ ['@babel/plugin-proposal-decorators', { version: '2023-11' }]
608
+ ]
609
+ }
610
+ ```
611
+
612
+ ### esbuild
613
+
614
+ esbuild doesn't support decorators natively. Use esbuild-plugin-babel:
615
+
616
+ ```bash
617
+ npm install -D esbuild-plugin-babel @babel/core @babel/plugin-proposal-decorators
618
+ ```
619
+
620
+ ```javascript
621
+ import babel from 'esbuild-plugin-babel'
622
+
623
+ esbuild.build({
624
+ plugins: [
625
+ babel({
626
+ filter: /\.js$/,
627
+ config: {
628
+ plugins: [['@babel/plugin-proposal-decorators', { version: '2023-11' }]]
629
+ }
630
+ })
631
+ ]
632
+ })
633
+ ```
634
+
635
+ ### Bun
636
+
637
+ Bun supports TC39 decorators natively - no configuration needed:
638
+
639
+ ```bash
640
+ bun run index.js
641
+ ```
642
+
643
+ ---
644
+
645
+ ## Runtime Environments
646
+
647
+ ### Node.js
648
+
649
+ Requires Babel:
650
+
651
+ ```bash
652
+ npm install -D @babel/core @babel/node @babel/plugin-proposal-decorators
653
+ npx babel-node index.js
654
+ ```
655
+
656
+ ### Bun
657
+
658
+ Native support, no setup required.
659
+
660
+ ### AWS Lambda
661
+
662
+ Works well - each Lambda instance has its own container. Singletons persist across warm invocations (efficient for connection pooling, etc.):
663
+
664
+ ```javascript
665
+ import { resolve } from 'decorator-dependency-injection'
666
+
667
+ export const handler = async (event) => {
668
+ const userService = resolve(UserService)
669
+ return await userService.getUser(event.userId)
670
+ }
671
+ ```
672
+
673
+ ### Cloudflare Workers / Vercel Edge
674
+
675
+ These environments have limited decorator support. Use programmatic registration:
676
+
677
+ ```javascript
678
+ import { Container } from 'decorator-dependency-injection'
679
+
680
+ // Define classes without decorators
681
+ class UserService {
682
+ getUser(id) { /* ... */ }
683
+ }
684
+
685
+ export default {
686
+ async fetch(request) {
687
+ const container = new Container()
688
+ container.registerSingleton(UserService)
689
+
690
+ const userService = container.resolve(UserService)
691
+ return Response.json(await userService.getUser(1))
692
+ }
693
+ }
694
+ ```
695
+
696
+ ### Electron
697
+
698
+ Main and renderer processes have separate JavaScript contexts - each has its own container automatically.
699
+
700
+ ---
701
+
702
+ ## Troubleshooting
703
+
704
+ ### "X is not registered"
705
+
706
+ **Cause**: The class wasn't decorated with `@Singleton()` or `@Factory()`, or the decorator hasn't run yet.
707
+
708
+ **Fix**:
709
+ 1. Ensure the class has `@Singleton()` or `@Factory()`
710
+ 2. Import the service file before calling `resolve()` (decorators run at import time)
711
+
712
+ ```javascript
713
+ // Wrong - UserService not imported yet
714
+ const userService = resolve(UserService) // Error!
715
+
716
+ // Correct
717
+ import { UserService } from './services/UserService' // Decorator runs here
718
+ const userService = resolve(UserService) // Works
719
+ ```
720
+
721
+ ### Stale singletons after code changes (HMR)
722
+
723
+ During development, singleton instances persist across hot reloads.
724
+
725
+ **Fix**: Clear the container on HMR:
726
+
727
+ ```javascript
728
+ // Vite
729
+ if (import.meta.hot) {
730
+ import.meta.hot.accept(() => {
731
+ clearContainer({ preserveRegistrations: true })
732
+ })
733
+ }
734
+
735
+ // Webpack
736
+ if (module.hot) {
737
+ module.hot.accept('./services', () => {
738
+ clearContainer({ preserveRegistrations: true })
739
+ })
740
+ }
741
+ ```
742
+
743
+ ### "Cannot use decorators" / Syntax Error
744
+
745
+ **Cause**: Bundler isn't configured to transpile decorators.
746
+
747
+ **Fix**: See [Bundler Configuration](#bundler-configuration) above.
748
+
749
+ ### SSR: Data leaking between requests
750
+
751
+ **Cause**: Using global container on server.
752
+
753
+ **Fix**: Create a new `Container()` per request. See [Server-Side Rendering](#server-side-rendering).
754
+
755
+ ### Mock not working in tests
756
+
757
+ **Cause**: Mock defined after the code that uses the service runs.
758
+
759
+ **Fix**: Define mocks before importing code that resolves services:
760
+
761
+ ```javascript
762
+ // test file
763
+ import { Mock, removeAllMocks } from 'decorator-dependency-injection'
764
+
765
+ // Define mock FIRST
766
+ @Mock(UserService)
767
+ class MockUserService {
768
+ getUser = vi.fn().mockResolvedValue({ id: 1 })
769
+ }
770
+
771
+ // THEN import the component that uses UserService
772
+ import { UserProfile } from './UserProfile'
773
+
774
+ afterEach(() => removeAllMocks())
775
+ ```
776
+
777
+ ### Circular dependency error
778
+
779
+ **Cause**: ServiceA imports ServiceB which imports ServiceA.
780
+
781
+ **Fix**: Use `@InjectLazy` to break the cycle:
782
+
783
+ ```javascript
784
+ @Singleton()
785
+ class ServiceA {
786
+ @InjectLazy(ServiceB) serviceB // Deferred until first access
787
+ }
788
+
789
+ @Singleton()
790
+ class ServiceB {
791
+ @Inject(ServiceA) serviceA
792
+ }
793
+ ```
794
+
795
+ ---
796
+
797
+ ## Environment Support Matrix
798
+
799
+ | Environment | Decorators | Notes |
800
+ |-------------|------------|-------|
801
+ | Node.js + Babel | Yes | Full support |
802
+ | Bun | Yes | Native support |
803
+ | Vite | Yes | With Babel plugin |
804
+ | Webpack | Yes | With Babel plugin |
805
+ | AWS Lambda | Yes | Singletons persist across warm starts |
806
+ | Cloudflare Workers | No | Use programmatic `Container` API |
807
+ | Vercel Edge | No | Use programmatic `Container` API |
808
+ | Electron | Yes | Separate container per process |