meocord 2.0.0 → 2.1.1

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
@@ -513,6 +513,29 @@ edited.delete // → a mock fn
513
513
  expect(msg.delete).toHaveBeenCalledTimes(1)
514
514
  ```
515
515
 
516
+ ### `createMock`
517
+
518
+ Mocks any type without a runtime class — use it for the services a controller depends on. `createMockInteraction` needs a class to build a prototype chain from, which is what makes `instanceof` and the real type guards work; a service double needs none of that, and an injected dependency may be an interface that does not exist at runtime at all.
519
+
520
+ Every property is a mock fn, created on first access, so a double only declares what the test cares about. The result is assignable to `T`, so it goes straight into `useValue` with no cast — which matters because a class holding a `private` member (a logger, say) can never be satisfied by an object literal.
521
+
522
+ ```typescript
523
+ import { createMock, MeoCordTestingModule } from 'meocord/testing'
524
+ import { GreetingService } from '@src/services/greeting.service.js'
525
+
526
+ const greetingService = createMock<GreetingService>()
527
+ greetingService.buildGreeting.mockResolvedValue('Hello, Alice!')
528
+
529
+ const module = MeoCordTestingModule.create({
530
+ controllers: [GreetingSlashController],
531
+ providers: [{ provide: GreetingService, useValue: greetingService }],
532
+ }).compile()
533
+
534
+ expect(greetingService.buildGreeting).toHaveBeenCalledWith('Alice')
535
+ ```
536
+
537
+ Nested access works without declaring the shape first — `cache.store.flush()` is a mock fn on a mock fn. Properties passed as `createMock<T>({ ... })` are used exactly as given rather than wrapped, so call assertions do not apply to those.
538
+
516
539
  ### `overrideGuard`
517
540
 
518
541
  Replaces a guard class in the DI container with a stub. No guard dependencies need to be provided.
@@ -152,23 +152,16 @@ function isValueProvider(p) {
152
152
  * fn(2) // → 99
153
153
  * fn.mock.calls // → [[2], [2]]
154
154
  */ function createMockFn(impl) {
155
+ // One persistent implementation and one queue of single-use ones, which is how
156
+ // jest and vitest model this. mockReturnValue, mockResolvedValue,
157
+ // mockRejectedValue and mockImplementation all write the same slot, so the last
158
+ // call wins; the four `*Once` variants all push onto the same queue, so they are
159
+ // consumed in the order they were declared regardless of which kind they are.
160
+ // Keeping a slot per kind instead gave a fixed precedence, where a stored
161
+ // resolved value beat a later mockRejectedValue and the override was silently
162
+ // dropped.
155
163
  let currentImpl = impl;
156
- let returnOnce = [];
157
- let returnValue = {
158
- set: false,
159
- value: undefined
160
- };
161
- let resolvedOnce = [];
162
- let resolvedValue = {
163
- set: false,
164
- value: undefined
165
- };
166
- let rejectedOnce = [];
167
- let rejectedValue = {
168
- set: false,
169
- value: undefined
170
- };
171
- const implOnce = [];
164
+ let onceQueue = [];
172
165
  let name = 'vi.fn';
173
166
  const calls = [];
174
167
  const results = [];
@@ -179,20 +172,8 @@ function isValueProvider(p) {
179
172
  let type = 'return';
180
173
  let value;
181
174
  try {
182
- if (implOnce.length > 0) {
183
- value = implOnce.shift().apply(this, args);
184
- } else if (returnOnce.length > 0) {
185
- value = returnOnce.shift();
186
- } else if (returnValue.set) {
187
- value = returnValue.value;
188
- } else if (resolvedOnce.length > 0) {
189
- value = Promise.resolve(resolvedOnce.shift());
190
- } else if (resolvedValue.set) {
191
- value = Promise.resolve(resolvedValue.value);
192
- } else if (rejectedOnce.length > 0) {
193
- value = Promise.reject(rejectedOnce.shift());
194
- } else if (rejectedValue.set) {
195
- value = Promise.reject(rejectedValue.value);
175
+ if (onceQueue.length > 0) {
176
+ value = onceQueue.shift().apply(this, args);
196
177
  } else if (currentImpl !== undefined) {
197
178
  value = currentImpl.apply(this, args);
198
179
  } else {
@@ -234,36 +215,27 @@ function isValueProvider(p) {
234
215
  enumerable: false
235
216
  });
236
217
  mockFn.mockReturnValue = (v)=>{
237
- returnValue = {
238
- set: true,
239
- value: v
240
- };
218
+ currentImpl = ()=>v;
241
219
  return mockFn;
242
220
  };
243
221
  mockFn.mockReturnValueOnce = (v)=>{
244
- returnOnce.push(v);
222
+ onceQueue.push(()=>v);
245
223
  return mockFn;
246
224
  };
247
225
  mockFn.mockResolvedValue = (v)=>{
248
- resolvedValue = {
249
- set: true,
250
- value: v
251
- };
226
+ currentImpl = ()=>Promise.resolve(v);
252
227
  return mockFn;
253
228
  };
254
229
  mockFn.mockResolvedValueOnce = (v)=>{
255
- resolvedOnce.push(v);
230
+ onceQueue.push(()=>Promise.resolve(v));
256
231
  return mockFn;
257
232
  };
258
233
  mockFn.mockRejectedValue = (v)=>{
259
- rejectedValue = {
260
- set: true,
261
- value: v
262
- };
234
+ currentImpl = ()=>Promise.reject(v);
263
235
  return mockFn;
264
236
  };
265
237
  mockFn.mockRejectedValueOnce = (v)=>{
266
- rejectedOnce.push(v);
238
+ onceQueue.push(()=>Promise.reject(v));
267
239
  return mockFn;
268
240
  };
269
241
  mockFn.mockImplementation = (fn)=>{
@@ -271,7 +243,7 @@ function isValueProvider(p) {
271
243
  return mockFn;
272
244
  };
273
245
  mockFn.mockImplementationOnce = (fn)=>{
274
- implOnce.push(fn);
246
+ onceQueue.push(fn);
275
247
  return mockFn;
276
248
  };
277
249
  mockFn.mockClear = ()=>{
@@ -284,22 +256,7 @@ function isValueProvider(p) {
284
256
  calls.length = 0;
285
257
  results.length = 0;
286
258
  instances.length = 0;
287
- returnOnce = [];
288
- returnValue = {
289
- set: false,
290
- value: undefined
291
- };
292
- resolvedOnce = [];
293
- resolvedValue = {
294
- set: false,
295
- value: undefined
296
- };
297
- rejectedOnce = [];
298
- rejectedValue = {
299
- set: false,
300
- value: undefined
301
- };
302
- implOnce.length = 0;
259
+ onceQueue = [];
303
260
  currentImpl = impl;
304
261
  return mockFn;
305
262
  };
@@ -582,6 +539,104 @@ function findPrototypeMethod(instance, name) {
582
539
  return stubDeep(instance, stubs);
583
540
  }
584
541
  // ---------------------------------------------------------------------------
542
+ // createMock — class-free mock for services and interfaces
543
+ // ---------------------------------------------------------------------------
544
+ /**
545
+ * A mock fn that also answers property access with another one, so a nested call
546
+ * like `cache.store.flush()` works without declaring `store` up front.
547
+ *
548
+ * stubDeep cannot serve this: it decides between a mock fn and a nested object by
549
+ * looking up the prototype chain, and a service double has no prototype to read.
550
+ * Everything here is callable instead, which is the right default when the shape
551
+ * being mocked is an interface that erases at runtime.
552
+ */ function stubCallable() {
553
+ const fn = createMockFn();
554
+ const nested = new Map();
555
+ return new Proxy(fn, {
556
+ get (target, prop) {
557
+ if (typeof prop === 'symbol') return Reflect.get(target, prop, target);
558
+ const key = prop;
559
+ // Never thenable — otherwise awaiting a mock hangs on itself
560
+ if (key === 'then') return undefined;
561
+ // The mock's own API (`mock`, `mockReturnValue`, `_isMockFunction`, …) and
562
+ // the function intrinsics pass straight through.
563
+ if (key in target) return Reflect.get(target, prop, target);
564
+ if (!nested.has(key)) nested.set(key, stubCallable());
565
+ return nested.get(key);
566
+ },
567
+ set (target, prop, value) {
568
+ Object.defineProperty(target, prop, {
569
+ value,
570
+ writable: true,
571
+ enumerable: true,
572
+ configurable: true
573
+ });
574
+ return true;
575
+ }
576
+ });
577
+ }
578
+ /**
579
+ * Creates a mock of any type, with no runtime class required.
580
+ *
581
+ * `createMockInteraction` needs a class to build a prototype chain from, which is
582
+ * what makes `instanceof` and the real type guards work. A service double needs
583
+ * none of that, and frequently has no class to pass at all — an injected
584
+ * dependency may be an interface, which does not exist at runtime.
585
+ *
586
+ * Every property is a mock fn, created on first access and cached, so a double
587
+ * only has to declare what the test actually cares about. The result is assignable
588
+ * to `T`, so it can be handed to `useValue` or a constructor without a cast.
589
+ *
590
+ * Properties supplied through {@link MockProps} are used as given rather than
591
+ * wrapped, so call assertions do not apply to them.
592
+ *
593
+ * @example
594
+ * ```ts
595
+ * const notifications = createMock<NotificationService>()
596
+ * notifications.notify.mockResolvedValue('sent')
597
+ *
598
+ * const module = MeoCordTestingModule.create({
599
+ * controllers: [AlertController],
600
+ * providers: [{ provide: NotificationService, useValue: notifications }],
601
+ * }).compile()
602
+ *
603
+ * expect(notifications.notify).toHaveBeenCalledWith('hello')
604
+ * ```
605
+ */ function createMock(props) {
606
+ const target = {};
607
+ if (props !== undefined) {
608
+ for (const [key, value] of Object.entries(props)){
609
+ Object.defineProperty(target, key, {
610
+ value,
611
+ writable: true,
612
+ enumerable: true,
613
+ configurable: true
614
+ });
615
+ }
616
+ }
617
+ const stubs = new Map();
618
+ return new Proxy(target, {
619
+ get (instance, prop) {
620
+ if (typeof prop === 'symbol') return Reflect.get(instance, prop, instance);
621
+ const key = prop;
622
+ if (key === 'then') return undefined;
623
+ // An explicitly supplied prop wins over the auto-stub.
624
+ if (Object.prototype.hasOwnProperty.call(instance, key)) return Reflect.get(instance, prop, instance);
625
+ if (!stubs.has(key)) stubs.set(key, stubCallable());
626
+ return stubs.get(key);
627
+ },
628
+ set (instance, prop, value) {
629
+ Object.defineProperty(instance, prop, {
630
+ value,
631
+ writable: true,
632
+ enumerable: true,
633
+ configurable: true
634
+ });
635
+ return true;
636
+ }
637
+ });
638
+ }
639
+ // ---------------------------------------------------------------------------
585
640
  // Convenience wrappers for common discord.js classes
586
641
  // ---------------------------------------------------------------------------
587
642
  /** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */ const createMockUser = ()=>createMockInteraction(discord_js.User);
@@ -793,6 +848,7 @@ exports.MeoCordTestingModule = MeoCordTestingModule;
793
848
  exports.TestingModule = TestingModule;
794
849
  exports.TestingModuleBuilder = TestingModuleBuilder;
795
850
  exports.createChatInputOptions = createChatInputOptions;
851
+ exports.createMock = createMock;
796
852
  exports.createMockChannel = createMockChannel;
797
853
  exports.createMockClient = createMockClient;
798
854
  exports.createMockFn = createMockFn;
@@ -1,3 +1,3 @@
1
1
  export { MeoCordTestingModule, TestingModule, TestingModuleBuilder } from './meocord-testing-module.js';
2
- export { createChatInputOptions, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser } from './mock-interaction.js';
2
+ export { createChatInputOptions, createMock, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser } from './mock-interaction.js';
3
3
  export { createMockFn, isMockFunction } from './mock-fn.js';
@@ -30,23 +30,16 @@
30
30
  * fn(2) // → 99
31
31
  * fn.mock.calls // → [[2], [2]]
32
32
  */ function createMockFn(impl) {
33
+ // One persistent implementation and one queue of single-use ones, which is how
34
+ // jest and vitest model this. mockReturnValue, mockResolvedValue,
35
+ // mockRejectedValue and mockImplementation all write the same slot, so the last
36
+ // call wins; the four `*Once` variants all push onto the same queue, so they are
37
+ // consumed in the order they were declared regardless of which kind they are.
38
+ // Keeping a slot per kind instead gave a fixed precedence, where a stored
39
+ // resolved value beat a later mockRejectedValue and the override was silently
40
+ // dropped.
33
41
  let currentImpl = impl;
34
- let returnOnce = [];
35
- let returnValue = {
36
- set: false,
37
- value: undefined
38
- };
39
- let resolvedOnce = [];
40
- let resolvedValue = {
41
- set: false,
42
- value: undefined
43
- };
44
- let rejectedOnce = [];
45
- let rejectedValue = {
46
- set: false,
47
- value: undefined
48
- };
49
- const implOnce = [];
42
+ let onceQueue = [];
50
43
  let name = 'vi.fn';
51
44
  const calls = [];
52
45
  const results = [];
@@ -57,20 +50,8 @@
57
50
  let type = 'return';
58
51
  let value;
59
52
  try {
60
- if (implOnce.length > 0) {
61
- value = implOnce.shift().apply(this, args);
62
- } else if (returnOnce.length > 0) {
63
- value = returnOnce.shift();
64
- } else if (returnValue.set) {
65
- value = returnValue.value;
66
- } else if (resolvedOnce.length > 0) {
67
- value = Promise.resolve(resolvedOnce.shift());
68
- } else if (resolvedValue.set) {
69
- value = Promise.resolve(resolvedValue.value);
70
- } else if (rejectedOnce.length > 0) {
71
- value = Promise.reject(rejectedOnce.shift());
72
- } else if (rejectedValue.set) {
73
- value = Promise.reject(rejectedValue.value);
53
+ if (onceQueue.length > 0) {
54
+ value = onceQueue.shift().apply(this, args);
74
55
  } else if (currentImpl !== undefined) {
75
56
  value = currentImpl.apply(this, args);
76
57
  } else {
@@ -112,36 +93,27 @@
112
93
  enumerable: false
113
94
  });
114
95
  mockFn.mockReturnValue = (v)=>{
115
- returnValue = {
116
- set: true,
117
- value: v
118
- };
96
+ currentImpl = ()=>v;
119
97
  return mockFn;
120
98
  };
121
99
  mockFn.mockReturnValueOnce = (v)=>{
122
- returnOnce.push(v);
100
+ onceQueue.push(()=>v);
123
101
  return mockFn;
124
102
  };
125
103
  mockFn.mockResolvedValue = (v)=>{
126
- resolvedValue = {
127
- set: true,
128
- value: v
129
- };
104
+ currentImpl = ()=>Promise.resolve(v);
130
105
  return mockFn;
131
106
  };
132
107
  mockFn.mockResolvedValueOnce = (v)=>{
133
- resolvedOnce.push(v);
108
+ onceQueue.push(()=>Promise.resolve(v));
134
109
  return mockFn;
135
110
  };
136
111
  mockFn.mockRejectedValue = (v)=>{
137
- rejectedValue = {
138
- set: true,
139
- value: v
140
- };
112
+ currentImpl = ()=>Promise.reject(v);
141
113
  return mockFn;
142
114
  };
143
115
  mockFn.mockRejectedValueOnce = (v)=>{
144
- rejectedOnce.push(v);
116
+ onceQueue.push(()=>Promise.reject(v));
145
117
  return mockFn;
146
118
  };
147
119
  mockFn.mockImplementation = (fn)=>{
@@ -149,7 +121,7 @@
149
121
  return mockFn;
150
122
  };
151
123
  mockFn.mockImplementationOnce = (fn)=>{
152
- implOnce.push(fn);
124
+ onceQueue.push(fn);
153
125
  return mockFn;
154
126
  };
155
127
  mockFn.mockClear = ()=>{
@@ -162,22 +134,7 @@
162
134
  calls.length = 0;
163
135
  results.length = 0;
164
136
  instances.length = 0;
165
- returnOnce = [];
166
- returnValue = {
167
- set: false,
168
- value: undefined
169
- };
170
- resolvedOnce = [];
171
- resolvedValue = {
172
- set: false,
173
- value: undefined
174
- };
175
- rejectedOnce = [];
176
- rejectedValue = {
177
- set: false,
178
- value: undefined
179
- };
180
- implOnce.length = 0;
137
+ onceQueue = [];
181
138
  currentImpl = impl;
182
139
  return mockFn;
183
140
  };
@@ -269,6 +269,104 @@ function findPrototypeMethod(instance, name) {
269
269
  return stubDeep(instance, stubs);
270
270
  }
271
271
  // ---------------------------------------------------------------------------
272
+ // createMock — class-free mock for services and interfaces
273
+ // ---------------------------------------------------------------------------
274
+ /**
275
+ * A mock fn that also answers property access with another one, so a nested call
276
+ * like `cache.store.flush()` works without declaring `store` up front.
277
+ *
278
+ * stubDeep cannot serve this: it decides between a mock fn and a nested object by
279
+ * looking up the prototype chain, and a service double has no prototype to read.
280
+ * Everything here is callable instead, which is the right default when the shape
281
+ * being mocked is an interface that erases at runtime.
282
+ */ function stubCallable() {
283
+ const fn = createMockFn();
284
+ const nested = new Map();
285
+ return new Proxy(fn, {
286
+ get (target, prop) {
287
+ if (typeof prop === 'symbol') return Reflect.get(target, prop, target);
288
+ const key = prop;
289
+ // Never thenable — otherwise awaiting a mock hangs on itself
290
+ if (key === 'then') return undefined;
291
+ // The mock's own API (`mock`, `mockReturnValue`, `_isMockFunction`, …) and
292
+ // the function intrinsics pass straight through.
293
+ if (key in target) return Reflect.get(target, prop, target);
294
+ if (!nested.has(key)) nested.set(key, stubCallable());
295
+ return nested.get(key);
296
+ },
297
+ set (target, prop, value) {
298
+ Object.defineProperty(target, prop, {
299
+ value,
300
+ writable: true,
301
+ enumerable: true,
302
+ configurable: true
303
+ });
304
+ return true;
305
+ }
306
+ });
307
+ }
308
+ /**
309
+ * Creates a mock of any type, with no runtime class required.
310
+ *
311
+ * `createMockInteraction` needs a class to build a prototype chain from, which is
312
+ * what makes `instanceof` and the real type guards work. A service double needs
313
+ * none of that, and frequently has no class to pass at all — an injected
314
+ * dependency may be an interface, which does not exist at runtime.
315
+ *
316
+ * Every property is a mock fn, created on first access and cached, so a double
317
+ * only has to declare what the test actually cares about. The result is assignable
318
+ * to `T`, so it can be handed to `useValue` or a constructor without a cast.
319
+ *
320
+ * Properties supplied through {@link MockProps} are used as given rather than
321
+ * wrapped, so call assertions do not apply to them.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * const notifications = createMock<NotificationService>()
326
+ * notifications.notify.mockResolvedValue('sent')
327
+ *
328
+ * const module = MeoCordTestingModule.create({
329
+ * controllers: [AlertController],
330
+ * providers: [{ provide: NotificationService, useValue: notifications }],
331
+ * }).compile()
332
+ *
333
+ * expect(notifications.notify).toHaveBeenCalledWith('hello')
334
+ * ```
335
+ */ function createMock(props) {
336
+ const target = {};
337
+ if (props !== undefined) {
338
+ for (const [key, value] of Object.entries(props)){
339
+ Object.defineProperty(target, key, {
340
+ value,
341
+ writable: true,
342
+ enumerable: true,
343
+ configurable: true
344
+ });
345
+ }
346
+ }
347
+ const stubs = new Map();
348
+ return new Proxy(target, {
349
+ get (instance, prop) {
350
+ if (typeof prop === 'symbol') return Reflect.get(instance, prop, instance);
351
+ const key = prop;
352
+ if (key === 'then') return undefined;
353
+ // An explicitly supplied prop wins over the auto-stub.
354
+ if (Object.prototype.hasOwnProperty.call(instance, key)) return Reflect.get(instance, prop, instance);
355
+ if (!stubs.has(key)) stubs.set(key, stubCallable());
356
+ return stubs.get(key);
357
+ },
358
+ set (instance, prop, value) {
359
+ Object.defineProperty(instance, prop, {
360
+ value,
361
+ writable: true,
362
+ enumerable: true,
363
+ configurable: true
364
+ });
365
+ return true;
366
+ }
367
+ });
368
+ }
369
+ // ---------------------------------------------------------------------------
272
370
  // Convenience wrappers for common discord.js classes
273
371
  // ---------------------------------------------------------------------------
274
372
  /** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */ const createMockUser = ()=>createMockInteraction(User);
@@ -476,4 +574,4 @@ function createChatInputOptions(opts = {}) {
476
574
  return stubDeep(base);
477
575
  }
478
576
 
479
- export { createChatInputOptions, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser };
577
+ export { createChatInputOptions, createMock, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser };
@@ -212,6 +212,35 @@ interface InteractionClass<T> {
212
212
  * ```
213
213
  */
214
214
  declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
215
+ /**
216
+ * Creates a mock of any type, with no runtime class required.
217
+ *
218
+ * `createMockInteraction` needs a class to build a prototype chain from, which is
219
+ * what makes `instanceof` and the real type guards work. A service double needs
220
+ * none of that, and frequently has no class to pass at all — an injected
221
+ * dependency may be an interface, which does not exist at runtime.
222
+ *
223
+ * Every property is a mock fn, created on first access and cached, so a double
224
+ * only has to declare what the test actually cares about. The result is assignable
225
+ * to `T`, so it can be handed to `useValue` or a constructor without a cast.
226
+ *
227
+ * Properties supplied through {@link MockProps} are used as given rather than
228
+ * wrapped, so call assertions do not apply to them.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * const notifications = createMock<NotificationService>()
233
+ * notifications.notify.mockResolvedValue('sent')
234
+ *
235
+ * const module = MeoCordTestingModule.create({
236
+ * controllers: [AlertController],
237
+ * providers: [{ provide: NotificationService, useValue: notifications }],
238
+ * }).compile()
239
+ *
240
+ * expect(notifications.notify).toHaveBeenCalledWith('hello')
241
+ * ```
242
+ */
243
+ declare function createMock<T extends object>(props?: MockProps<T>): DeepMocked<T>;
215
244
  /** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */
216
245
  declare const createMockUser: () => DeepMocked<User>;
217
246
  /**
@@ -274,5 +303,5 @@ interface ChatInputOptions {
274
303
  */
275
304
  declare function createChatInputOptions<Cached extends CacheType = any>(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver<Cached>>;
276
305
 
277
- export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, isMockFunction };
306
+ export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, isMockFunction };
278
307
  export type { ChatInputOptions, ClassProvider, DeepMocked, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, TestingModuleOptions, ValueProvider };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "meocord",
3
3
  "description": "Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.",
4
- "version": "2.0.0",
4
+ "version": "2.1.1",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22"