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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "decorator-dependency-injection",
3
- "version": "1.0.6",
4
- "description": "A simple library for dependency injection using decorators",
3
+ "version": "1.1.0",
4
+ "description": "Lightweight dependency injection (DI) library using native TC39 Stage 3 decorators. Zero dependencies, built-in mocking, TypeScript support.",
5
5
  "author": "Ravi Gairola <mallox@pyxzl.net>",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -16,20 +16,38 @@
16
16
  "dependency-injection",
17
17
  "di",
18
18
  "decorators",
19
- "mocking",
19
+ "tc39-decorators",
20
+ "stage-3-decorators",
21
+ "inject",
22
+ "injectable",
20
23
  "singleton",
21
- "factory"
24
+ "factory",
25
+ "ioc",
26
+ "inversion-of-control",
27
+ "container",
28
+ "mocking",
29
+ "mock",
30
+ "testing",
31
+ "jest",
32
+ "vitest",
33
+ "typescript",
34
+ "service-locator"
22
35
  ],
23
36
  "engines": {
24
- "node": ">=18.0.0"
37
+ "node": ">=20.0.0"
25
38
  },
26
39
  "main": "index.js",
27
40
  "types": "index.d.ts",
28
41
  "type": "module",
42
+ "sideEffects": false,
29
43
  "exports": {
30
44
  ".": {
31
45
  "types": "./index.d.ts",
32
46
  "import": "./index.js"
47
+ },
48
+ "./middleware": {
49
+ "types": "./src/integrations/middleware.d.ts",
50
+ "import": "./src/integrations/middleware.js"
33
51
  }
34
52
  },
35
53
  "scripts": {
package/src/Container.js CHANGED
@@ -4,7 +4,6 @@
4
4
  * @property {Function} clazz - The class constructor for the instance.
5
5
  * @property {Function} [originalClazz] - The original class if this is a mock.
6
6
  * @property {Object} [instance] - The singleton instance, if created.
7
- * @property {Object} [originalInstance] - The original instance if this is a mock.
8
7
  * @property {boolean} [proxy=false] - If true, the mock will proxy to the original class for undefined methods/properties.
9
8
  */
10
9
 
@@ -18,23 +17,27 @@ export class Container {
18
17
  /** @type {Map<string|Function, InstanceContext>} */
19
18
  #instances = new Map()
20
19
 
21
- /** @type {boolean} Enable debug logging */
22
20
  #debug = false
23
21
 
24
- /**
25
- * Enable or disable debug logging.
26
- * When enabled, logs when instances are created.
27
- * @param {boolean} enabled Whether to enable debug mode
28
- */
22
+ get [Symbol.toStringTag]() {
23
+ return 'Container'
24
+ }
25
+
26
+ /** @yields {RegistrationInfo} */
27
+ *[Symbol.iterator]() {
28
+ yield* this.list()
29
+ }
30
+
31
+ /** @returns {number} */
32
+ get size() {
33
+ return this.#instances.size
34
+ }
35
+
36
+ /** @param {boolean} enabled */
29
37
  setDebug(enabled) {
30
38
  this.#debug = enabled
31
39
  }
32
40
 
33
- /**
34
- * Log a debug message if debug mode is enabled.
35
- * @param {string} message The message to log
36
- * @private
37
- */
38
41
  #log(message) {
39
42
  if (this.#debug) {
40
43
  console.log(`[DI] ${message}`)
@@ -42,30 +45,21 @@ export class Container {
42
45
  }
43
46
 
44
47
  /**
45
- * Register a class as a singleton.
46
- * @param {Function} clazz The class constructor
47
- * @param {string} [name] Optional name key
48
+ * @param {Function} clazz
49
+ * @param {string} [name]
48
50
  */
49
51
  registerSingleton(clazz, name) {
50
52
  this.#register(clazz, 'singleton', name)
51
53
  }
52
54
 
53
55
  /**
54
- * Register a class as a factory.
55
- * @param {Function} clazz The class constructor
56
- * @param {string} [name] Optional name key
56
+ * @param {Function} clazz
57
+ * @param {string} [name]
57
58
  */
58
59
  registerFactory(clazz, name) {
59
60
  this.#register(clazz, 'factory', name)
60
61
  }
61
62
 
62
- /**
63
- * Internal registration logic.
64
- * @param {Function} clazz The class constructor
65
- * @param {'singleton'|'factory'} type The registration type
66
- * @param {string} [name] Optional name key
67
- * @private
68
- */
69
63
  #register(clazz, type, name) {
70
64
  const key = name ?? clazz
71
65
  if (this.#instances.has(key)) {
@@ -79,41 +73,77 @@ export class Container {
79
73
  }
80
74
 
81
75
  /**
82
- * Get the context for a given class or name.
83
- * @param {string|Function} clazzOrName The class or name to look up
76
+ * @param {string|Function} clazzOrName
84
77
  * @returns {InstanceContext}
85
- * @throws {Error} If the context is not found
78
+ * @throws {Error} If not found
86
79
  */
87
80
  getContext(clazzOrName) {
88
- if (this.#instances.has(clazzOrName)) {
89
- return this.#instances.get(clazzOrName)
81
+ const context = this.#instances.get(clazzOrName)
82
+ if (context) {
83
+ return context
90
84
  }
91
- const available = Array.from(this.#instances.keys())
85
+
86
+ const name = clazzOrName?.name ?? clazzOrName
87
+ const nameStr = String(name)
88
+ const available = [...this.#instances.keys()]
92
89
  .map(k => typeof k === 'string' ? k : k.name)
93
90
  .join(', ')
91
+
92
+ // Detect if this looks like a mock class from a module mocking system
93
+ const looksLikeMock = /^Mock[A-Z]|mock/i.test(nameStr) ||
94
+ nameStr.includes('Mock') ||
95
+ nameStr.startsWith('vi_') ||
96
+ nameStr.startsWith('jest_')
97
+
98
+ const hint = looksLikeMock
99
+ ? `\n\nHint: The class name "${name}" suggests this may be a mock created by a module mocking system. ` +
100
+ `If you're using module mocking (e.g., vi.mock() or jest.mock()), consider using @Mock(OriginalService) instead, ` +
101
+ `which properly registers with the DI container.`
102
+ : ''
103
+
94
104
  throw new Error(
95
- `Cannot find injection source for "${clazzOrName?.name || clazzOrName}". ` +
96
- `Available: [${available}]`
105
+ `Cannot find injection source for "${name}". Available: [${available}]${hint}`
97
106
  )
98
107
  }
99
108
 
100
- /**
101
- * Check if a class or name is registered.
102
- * @param {string|Function} clazzOrName The class or name to check
103
- * @returns {boolean}
104
- */
109
+ /** @param {string|Function} clazzOrName */
105
110
  has(clazzOrName) {
106
111
  return this.#instances.has(clazzOrName)
107
112
  }
108
113
 
114
+ /** @param {string|Function} clazzOrName */
115
+ isMocked(clazzOrName) {
116
+ return !!this.#instances.get(clazzOrName)?.originalClazz
117
+ }
118
+
119
+ /**
120
+ * @param {string|Function} clazzOrName
121
+ * @returns {boolean} true if removed
122
+ */
123
+ unregister(clazzOrName) {
124
+ const removed = this.#instances.delete(clazzOrName)
125
+ if (removed) {
126
+ this.#log(`Unregistered: ${clazzOrName?.name ?? clazzOrName}`)
127
+ }
128
+ return removed
129
+ }
130
+
131
+ /** @returns {Array<{key: string|Function, name: string, type: 'singleton'|'factory', isMocked: boolean, hasInstance: boolean}>} */
132
+ list() {
133
+ return [...this.#instances.entries()].map(([key, context]) => ({
134
+ key,
135
+ name: typeof key === 'string' ? key : key.name,
136
+ type: context.type,
137
+ isMocked: !!context.originalClazz,
138
+ hasInstance: !!context.instance
139
+ }))
140
+ }
141
+
109
142
  /**
110
- * Resolve and return an instance by class or name.
111
- * This allows non-decorator code to retrieve instances from the container.
112
143
  * @template T
113
- * @param {string|Function} clazzOrName The class or name to resolve
114
- * @param {...*} params Parameters to pass to the constructor
115
- * @returns {T} The resolved instance
116
- * @throws {Error} If the class or name is not registered
144
+ * @param {string|Function} clazzOrName
145
+ * @param {...*} params
146
+ * @returns {T}
117
147
  */
118
148
  resolve(clazzOrName, ...params) {
119
149
  const instanceContext = this.getContext(clazzOrName)
@@ -121,25 +151,24 @@ export class Container {
121
151
  }
122
152
 
123
153
  /**
124
- * Get or create an instance based on the context.
125
- * @param {InstanceContext} instanceContext The instance context
126
- * @param {Array} params Constructor parameters
127
- * @returns {Object} The instance
154
+ * @param {InstanceContext} instanceContext
155
+ * @param {Array} params
156
+ * @returns {Object}
128
157
  */
129
158
  getInstance(instanceContext, params) {
130
- if (instanceContext.type === 'singleton' && !instanceContext.originalClazz && instanceContext.instance) {
159
+ if (instanceContext.type === 'singleton' && instanceContext.instance) {
131
160
  this.#log(`Returning cached singleton: ${instanceContext.clazz.name}`)
132
161
  return instanceContext.instance
133
162
  }
134
163
 
164
+ this.#log(`Creating ${instanceContext.type}: ${instanceContext.clazz.name}`)
135
165
  let instance
136
166
  try {
137
- this.#log(`Creating ${instanceContext.type}: ${instanceContext.clazz.name}`)
138
167
  instance = new instanceContext.clazz(...params)
139
168
  } catch (err) {
140
169
  if (err instanceof RangeError) {
141
170
  throw new Error(
142
- `Circular dependency detected for ${instanceContext.clazz.name || instanceContext.clazz}. ` +
171
+ `Circular dependency detected for ${instanceContext.clazz.name ?? instanceContext.clazz}. ` +
143
172
  `Use @InjectLazy to break the cycle.`
144
173
  )
145
174
  }
@@ -159,67 +188,107 @@ export class Container {
159
188
  }
160
189
 
161
190
  /**
162
- * Register a mock for an existing class.
163
- * @param {string|Function} targetClazzOrName The class or name to mock
164
- * @param {Function} mockClazz The mock class
165
- * @param {boolean} [useProxy=false] Whether to proxy unmocked methods to original
191
+ * @param {string|Function} targetClazzOrName
192
+ * @param {Function} mockClazz
193
+ * @param {boolean} [useProxy=false]
166
194
  */
167
195
  registerMock(targetClazzOrName, mockClazz, useProxy = false) {
168
196
  const instanceContext = this.getContext(targetClazzOrName)
169
197
  if (instanceContext.originalClazz) {
170
198
  throw new Error('Mock already defined, reset before mocking again')
171
199
  }
172
- instanceContext.originalClazz = instanceContext.clazz
173
- instanceContext.proxy = useProxy
174
- instanceContext.clazz = mockClazz
175
- const targetName = typeof targetClazzOrName === 'string' ? targetClazzOrName : targetClazzOrName.name
176
- this.#log(`Mocked ${targetName} with ${mockClazz.name}${useProxy ? ' (proxy)' : ''}`)
200
+ Object.assign(instanceContext, {
201
+ originalClazz: instanceContext.clazz,
202
+ clazz: mockClazz,
203
+ proxy: useProxy,
204
+ instance: undefined
205
+ })
206
+ this.#log(`Mocked ${targetClazzOrName?.name ?? targetClazzOrName} with ${mockClazz.name}${useProxy ? ' (proxy)' : ''}`)
177
207
  }
178
208
 
179
209
  /**
180
- * Reset a specific mock to its original class.
181
- * @param {string|Function} clazzOrName The class or name to reset
182
- * @throws {Error} If the class or name is not registered
210
+ * Remove mock and restore original. Does NOT clear mock call history.
211
+ * @param {string|Function} clazzOrName
183
212
  */
184
- resetMock(clazzOrName) {
213
+ removeMock(clazzOrName) {
185
214
  this.#restoreOriginal(this.#instances.get(clazzOrName), clazzOrName)
186
215
  }
187
216
 
217
+ /** Remove all mocks. Does NOT clear mock call history. */
218
+ removeAllMocks() {
219
+ for (const instanceContext of this.#instances.values()) {
220
+ this.#restoreOriginal(instanceContext)
221
+ }
222
+ }
223
+
188
224
  /**
189
- * Reset all mocks to their original classes.
225
+ * Clear cached singleton instances. They'll be recreated on next resolve.
226
+ * @param {Object} [options]
227
+ * @param {boolean} [options.preserveMocks=true]
190
228
  */
191
- resetAllMocks() {
229
+ resetSingletons(options = {}) {
230
+ const { preserveMocks = true } = options
231
+
192
232
  for (const instanceContext of this.#instances.values()) {
193
- this.#restoreOriginal(instanceContext)
233
+ delete instanceContext.instance
234
+ if (!preserveMocks && instanceContext.originalClazz) {
235
+ this.#restoreOriginal(instanceContext)
236
+ }
194
237
  }
238
+ this.#log(`Reset singletons (preserveMocks: ${preserveMocks})`)
195
239
  }
196
240
 
197
241
  /**
198
- * Clear all registered instances and mocks.
199
- * Useful for test isolation.
242
+ * Clear all registrations. Use resetSingletons() to keep registrations.
243
+ * @param {Object} [options]
244
+ * @param {boolean} [options.preserveRegistrations=false]
200
245
  */
201
- clear() {
202
- this.#instances.clear()
246
+ clear(options = {}) {
247
+ const { preserveRegistrations = false } = options
248
+
249
+ if (preserveRegistrations) {
250
+ for (const instanceContext of this.#instances.values()) {
251
+ delete instanceContext.instance
252
+ }
253
+ this.#log('Cleared instances (preserved registrations)')
254
+ } else {
255
+ this.#instances.clear()
256
+ this.#log('Cleared all registrations')
257
+ }
203
258
  }
204
259
 
205
260
  /**
206
- * Internal function to restore an instance context to its original.
207
- * @param {InstanceContext} instanceContext The instance context to reset
208
- * @param {string|Function} [clazzOrName] Optional identifier for error messages
209
- * @throws {Error} If instanceContext is null or undefined
210
- * @private
261
+ * @template T
262
+ * @param {string|Function} clazzOrName
263
+ * @param {...*} params
264
+ * @returns {T}
265
+ * @throws {Error} If not mocked
211
266
  */
267
+ getMockInstance(clazzOrName, ...params) {
268
+ const instanceContext = this.getContext(clazzOrName)
269
+
270
+ if (!instanceContext.originalClazz) {
271
+ const name = clazzOrName?.name ?? clazzOrName
272
+ throw new Error(
273
+ `"${name}" is not mocked. Use @Mock(${name}) to register a mock first.`
274
+ )
275
+ }
276
+
277
+ return this.getInstance(instanceContext, params)
278
+ }
279
+
212
280
  #restoreOriginal(instanceContext, clazzOrName) {
213
281
  if (!instanceContext) {
214
282
  const name = clazzOrName?.name || clazzOrName || 'unknown'
215
283
  throw new Error(`Cannot reset mock for "${name}": not registered`)
216
284
  }
217
285
  if (instanceContext.originalClazz) {
218
- instanceContext.clazz = instanceContext.originalClazz
219
- delete instanceContext.instance
220
- delete instanceContext.originalClazz
221
- delete instanceContext.originalInstance
222
- delete instanceContext.proxy
286
+ Object.assign(instanceContext, {
287
+ clazz: instanceContext.originalClazz,
288
+ instance: undefined,
289
+ originalClazz: undefined,
290
+ proxy: undefined
291
+ })
223
292
  }
224
293
  }
225
294
  }
@@ -0,0 +1,40 @@
1
+ import { Container, InjectionToken } from '../../index'
2
+
3
+ export type ContainerScope = 'request' | 'global'
4
+
5
+ export function getGlobalContainer(): Container
6
+ export function getContainer(): Container
7
+
8
+ export interface ResolveOptions {
9
+ scope?: ContainerScope
10
+ params?: any[]
11
+ }
12
+
13
+ export function resolve<T>(clazzOrName: InjectionToken<T>, options?: ResolveOptions): T
14
+
15
+ export function runWithContainer<T>(
16
+ container: Container,
17
+ fn: () => T,
18
+ options?: { scope?: ContainerScope }
19
+ ): T
20
+
21
+ export interface MiddlewareOptions {
22
+ scope?: ContainerScope
23
+ debug?: boolean
24
+ }
25
+
26
+ export interface ContainerRequest {
27
+ di: Container
28
+ }
29
+
30
+ export function containerMiddleware(
31
+ options?: MiddlewareOptions
32
+ ): (req: any, res: any, next: () => void) => void
33
+
34
+ export function koaContainerMiddleware(
35
+ options?: MiddlewareOptions
36
+ ): (ctx: any, next: () => Promise<void>) => Promise<void>
37
+
38
+ export function withContainer<T extends (...args: any[]) => any>(
39
+ options?: MiddlewareOptions
40
+ ): (handler: T) => T
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Server middleware integration for decorator-dependency-injection.
3
+ *
4
+ * Provides request-scoped containers using AsyncLocalStorage, enabling
5
+ * automatic per-request isolation without manual container management.
6
+ *
7
+ * IMPORTANT: When using this module, `resolve()` behaves differently than
8
+ * the main module's resolve:
9
+ * - Inside a request: Returns instances from the request-scoped container
10
+ * (singletons are isolated per-request, preventing data leaks between users)
11
+ * - Outside a request: Falls back to the global container
12
+ *
13
+ * @module decorator-dependency-injection/middleware
14
+ */
15
+
16
+ import { AsyncLocalStorage } from 'node:async_hooks'
17
+ import { Container } from '../Container.js'
18
+ import { defaultContainer as mainDefaultContainer } from '../../index.js'
19
+
20
+ /** @type {AsyncLocalStorage<{container: Container, scope: string}>} */
21
+ const requestContext = new AsyncLocalStorage()
22
+
23
+ /** @type {Container} */
24
+ const globalContainer = mainDefaultContainer
25
+
26
+ /** @returns {Container|null} */
27
+ export function getGlobalContainer() {
28
+ return globalContainer
29
+ }
30
+
31
+ function getRequestContext() {
32
+ return requestContext.getStore()
33
+ }
34
+
35
+ /** @returns {Container} */
36
+ export function getContainer() {
37
+ return getRequestContext()?.container ?? globalContainer
38
+ }
39
+
40
+ /**
41
+ * Request-aware resolve. Uses request-scoped container inside requests,
42
+ * falls back to global container outside. Auto-registers from global container.
43
+ *
44
+ * @template T
45
+ * @param {string|Function} clazzOrName
46
+ * @param {Object} [options]
47
+ * @param {'request'|'global'} [options.scope]
48
+ * @param {Array} [options.params]
49
+ * @returns {T}
50
+ */
51
+ export function resolve(clazzOrName, options = {}) {
52
+ const { scope, params = [] } = options
53
+ const ctx = getRequestContext()
54
+
55
+ if (scope === 'global' || ctx?.scope === 'global') {
56
+ return globalContainer.resolve(clazzOrName, ...params)
57
+ }
58
+
59
+ if (!ctx) {
60
+ if (scope === 'request') {
61
+ console.warn(
62
+ `[DI] resolve() called with scope='request' but no request context exists. ` +
63
+ `Did you forget to use containerMiddleware()? Falling back to global container.`
64
+ )
65
+ }
66
+ return globalContainer.resolve(clazzOrName, ...params)
67
+ }
68
+
69
+ const requestContainer = ctx.container
70
+
71
+ if (!requestContainer.has(clazzOrName) && globalContainer?.has(clazzOrName)) {
72
+ const globalContext = globalContainer.getContext(clazzOrName)
73
+ if (globalContext.type === 'singleton') {
74
+ requestContainer.registerSingleton(globalContext.clazz,
75
+ typeof clazzOrName === 'string' ? clazzOrName : undefined)
76
+ } else {
77
+ requestContainer.registerFactory(globalContext.clazz,
78
+ typeof clazzOrName === 'string' ? clazzOrName : undefined)
79
+ }
80
+ }
81
+
82
+ return requestContainer.resolve(clazzOrName, ...params)
83
+ }
84
+
85
+ /**
86
+ * @template T
87
+ * @param {Container} container
88
+ * @param {function(): T} fn
89
+ * @param {Object} [options]
90
+ * @param {'request'|'global'} [options.scope='request']
91
+ * @returns {T}
92
+ */
93
+ export function runWithContainer(container, fn, options = {}) {
94
+ const { scope = 'request' } = options
95
+ return requestContext.run({ container, scope }, fn)
96
+ }
97
+
98
+ /**
99
+ * @typedef {Object} MiddlewareOptions
100
+ * @property {'request'|'global'} [scope='request'] - Container scope mode:
101
+ * - 'request': Each request gets its own container with isolated singletons (default, SSR-safe)
102
+ * - 'global': All requests share the global container (use only for stateless services)
103
+ * @property {boolean} [debug=false] - Enable debug logging
104
+ */
105
+
106
+ /**
107
+ * Express/Connect middleware. scope='request' gives each request isolated singletons (SSR-safe).
108
+ * @param {MiddlewareOptions} [options={}]
109
+ * @returns {function(req, res, next): void}
110
+ */
111
+ export function containerMiddleware(options = {}) {
112
+ const { scope = 'request', debug = false } = options
113
+
114
+ return (req, res, next) => {
115
+ if (scope === 'global') {
116
+ requestContext.run({ container: globalContainer, scope: 'global' }, () => {
117
+ req.di = globalContainer
118
+ next()
119
+ })
120
+ return
121
+ }
122
+
123
+ const container = new Container()
124
+ if (debug) container.setDebug(true)
125
+ req.di = container
126
+ requestContext.run({ container, scope: 'request' }, () => next())
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Koa middleware. See containerMiddleware() for scope behavior.
132
+ * @param {MiddlewareOptions} [options={}]
133
+ * @returns {function(ctx, next): Promise<void>}
134
+ */
135
+ export function koaContainerMiddleware(options = {}) {
136
+ const { scope = 'request', debug = false } = options
137
+
138
+ return async (ctx, next) => {
139
+ if (scope === 'global') {
140
+ await requestContext.run({ container: globalContainer, scope: 'global' }, async () => {
141
+ ctx.di = globalContainer
142
+ await next()
143
+ })
144
+ return
145
+ }
146
+
147
+ const container = new Container()
148
+ if (debug) container.setDebug(true)
149
+ ctx.di = container
150
+ await requestContext.run({ container, scope: 'request' }, () => next())
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Hono/Fastify-style handler wrapper. See containerMiddleware() for scope behavior.
156
+ * @param {MiddlewareOptions} [options={}]
157
+ * @returns {function(handler): function}
158
+ */
159
+ export function withContainer(options = {}) {
160
+ const { scope = 'request', debug = false } = options
161
+
162
+ return (handler) => (...args) => {
163
+ if (scope === 'global') {
164
+ return requestContext.run({ container: globalContainer, scope: 'global' }, () => handler(...args))
165
+ }
166
+
167
+ const container = new Container()
168
+ if (debug) container.setDebug(true)
169
+ return requestContext.run({ container, scope: 'request' }, () => handler(...args))
170
+ }
171
+ }
package/src/proxy.js CHANGED
@@ -9,17 +9,15 @@
9
9
  export function createProxy(mock, original) {
10
10
  return new Proxy(mock, {
11
11
  get(target, prop, receiver) {
12
- if (prop in target) {
13
- return Reflect.get(target, prop, receiver)
14
- }
15
- return Reflect.get(original, prop, original)
12
+ return prop in target
13
+ ? Reflect.get(target, prop, receiver)
14
+ : Reflect.get(original, prop, original)
16
15
  },
17
16
 
18
17
  set(target, prop, value, receiver) {
19
- if (prop in target) {
20
- return Reflect.set(target, prop, value, receiver)
21
- }
22
- return Reflect.set(original, prop, value, original)
18
+ return prop in target
19
+ ? Reflect.set(target, prop, value, receiver)
20
+ : Reflect.set(original, prop, value, original)
23
21
  },
24
22
 
25
23
  has(target, prop) {
@@ -27,16 +25,18 @@ export function createProxy(mock, original) {
27
25
  },
28
26
 
29
27
  ownKeys(target) {
30
- const mockKeys = Reflect.ownKeys(target)
31
- const originalKeys = Reflect.ownKeys(original)
32
- return [...new Set([...mockKeys, ...originalKeys])]
28
+ return [...new Set([...Reflect.ownKeys(target), ...Reflect.ownKeys(original)])]
33
29
  },
34
30
 
35
31
  getOwnPropertyDescriptor(target, prop) {
36
- if (prop in target) {
37
- return Reflect.getOwnPropertyDescriptor(target, prop)
38
- }
39
- return Reflect.getOwnPropertyDescriptor(original, prop)
32
+ return prop in target
33
+ ? Reflect.getOwnPropertyDescriptor(target, prop)
34
+ : Reflect.getOwnPropertyDescriptor(original, prop)
35
+ },
36
+
37
+ getPrototypeOf() {
38
+ // Return original's prototype so instanceof checks work
39
+ return Object.getPrototypeOf(original)
40
40
  }
41
41
  })
42
42
  }