ts-ioc-container 49.1.0 → 50.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/README.md +329 -177
- package/cjm/index.js +3 -4
- package/cjm/injector/inject.js +1 -4
- package/cjm/provider/IProvider.js +13 -1
- package/cjm/provider/Provider.js +10 -0
- package/cjm/provider/SingletonProvider.js +9 -9
- package/cjm/token/toToken.js +4 -1
- package/esm/index.js +1 -2
- package/esm/injector/inject.js +1 -4
- package/esm/provider/IProvider.js +10 -0
- package/esm/provider/Provider.js +10 -0
- package/esm/provider/SingletonProvider.js +9 -9
- package/esm/token/toToken.js +3 -1
- package/package.json +3 -3
- package/typings/index.d.ts +1 -2
- package/typings/provider/IProvider.d.ts +6 -0
- package/typings/provider/Provider.d.ts +2 -0
- package/typings/provider/SingletonProvider.d.ts +7 -5
- package/typings/token/toToken.d.ts +1 -0
- package/cjm/provider/Cache.js +0 -41
- package/esm/provider/Cache.js +0 -35
- package/typings/provider/Cache.d.ts +0 -24
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ no global container objects.
|
|
|
36
36
|
- [Setup](#setup)
|
|
37
37
|
- [Quickstart](#quickstart)
|
|
38
38
|
- [Cheatsheet](#cheatsheet)
|
|
39
|
-
- [
|
|
39
|
+
- [Specs-driven workflow](#specs-driven-workflow)
|
|
40
40
|
- [Product capability map](#product-capability-map)
|
|
41
41
|
- [Acceptance specs](#acceptance-specs)
|
|
42
42
|
- [tsyringe alternative](https://igorbabkin.github.io/ts-ioc-container/tsyringe-alternative)
|
|
@@ -144,7 +144,7 @@ container.resolve(App).start();
|
|
|
144
144
|
> for `R.fromValue(...)` and `R.fromFn(...)` (which have no class to decorate)
|
|
145
145
|
> or for third-party classes you don't own.
|
|
146
146
|
|
|
147
|
-
##
|
|
147
|
+
## Specs-driven workflow
|
|
148
148
|
|
|
149
149
|
Public behavior is described as product capabilities before it is implemented.
|
|
150
150
|
The repository keeps the same chain visible in specs, tests, docs, and this
|
|
@@ -184,49 +184,203 @@ tests stay in the feature folders under `__tests__/`.
|
|
|
184
184
|
|
|
185
185
|
### Express/Next handler (per-request scope)
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
import 'reflect-metadata';
|
|
188
|
+
import { bindTo, Container, inject, register, Registration as R, singleton } from 'ts-ioc-container';
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Web Framework Integration - Per-Request Scope
|
|
192
|
+
*
|
|
193
|
+
* In Express/Next.js applications, each HTTP request typically gets its own
|
|
194
|
+
* scope. This ensures request-specific state (logger context, current user,
|
|
195
|
+
* correlation IDs) is isolated between concurrent requests.
|
|
196
|
+
*
|
|
197
|
+
* Scope hierarchy:
|
|
198
|
+
* Application (singleton services — live for entire app lifetime)
|
|
199
|
+
* └── Request (per-request services — created and disposed per request)
|
|
200
|
+
*/
|
|
189
201
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
202
|
+
@register(bindTo('ILogger'), singleton())
|
|
203
|
+
class Logger {
|
|
204
|
+
readonly messages: string[] = [];
|
|
205
|
+
|
|
206
|
+
log(message: string) {
|
|
207
|
+
this.messages.push(message);
|
|
208
|
+
}
|
|
194
209
|
}
|
|
195
|
-
|
|
210
|
+
|
|
211
|
+
describe('Express/Next per-request scope', () => {
|
|
212
|
+
it('should give each request its own Logger instance', () => {
|
|
213
|
+
const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
|
|
214
|
+
|
|
215
|
+
// Simulate two concurrent HTTP requests
|
|
216
|
+
const request1Scope = app.createScope({ tags: ['request'] });
|
|
217
|
+
const request2Scope = app.createScope({ tags: ['request'] });
|
|
218
|
+
|
|
219
|
+
const logger1 = request1Scope.resolve<Logger>('ILogger');
|
|
220
|
+
const logger2 = request2Scope.resolve<Logger>('ILogger');
|
|
221
|
+
|
|
222
|
+
logger1.log('req 1 started');
|
|
223
|
+
logger2.log('req 2 started');
|
|
224
|
+
|
|
225
|
+
// Each request has its own Logger — logs don't leak between requests
|
|
226
|
+
expect(logger1.messages).toEqual(['req 1 started']);
|
|
227
|
+
expect(logger2.messages).toEqual(['req 2 started']);
|
|
228
|
+
expect(logger1).not.toBe(logger2);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should resolve the same Logger within a single request', () => {
|
|
232
|
+
const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
|
|
233
|
+
|
|
234
|
+
const requestScope = app.createScope({ tags: ['request'] });
|
|
235
|
+
|
|
236
|
+
const logger1 = requestScope.resolve<Logger>('ILogger');
|
|
237
|
+
const logger2 = requestScope.resolve<Logger>('ILogger');
|
|
238
|
+
|
|
239
|
+
// Within one request, singleton is maintained
|
|
240
|
+
expect(logger1).toBe(logger2);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
196
244
|
|
|
197
245
|
### Background worker (singleton client, transient jobs)
|
|
198
246
|
|
|
199
|
-
|
|
247
|
+
import 'reflect-metadata';
|
|
248
|
+
import { Container, inject, register, Registration as R, singleton } from 'ts-ioc-container';
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Background Worker - Singleton Client, Transient Jobs
|
|
252
|
+
*
|
|
253
|
+
* A queue worker typically needs:
|
|
254
|
+
* - A single shared QueueClient (expensive to create, holds connections)
|
|
255
|
+
* - A new JobHandler per job (stateful — holds job-specific data)
|
|
256
|
+
*
|
|
257
|
+
* singleton() on QueueClient ensures one shared connection pool.
|
|
258
|
+
* No singleton on JobHandler gives a fresh instance per resolve.
|
|
259
|
+
*/
|
|
260
|
+
|
|
200
261
|
@register(singleton())
|
|
201
|
-
class QueueClient {
|
|
262
|
+
class QueueClient {
|
|
263
|
+
readonly connected = true;
|
|
264
|
+
|
|
265
|
+
dequeue(): string {
|
|
266
|
+
return 'job-payload';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
202
269
|
|
|
203
270
|
class JobHandler {
|
|
204
|
-
|
|
271
|
+
readonly result: string;
|
|
272
|
+
|
|
273
|
+
constructor(@inject('QueueClient') private queue: QueueClient) {
|
|
274
|
+
this.result = this.queue.dequeue();
|
|
275
|
+
}
|
|
205
276
|
}
|
|
206
277
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
278
|
+
describe('Background worker', () => {
|
|
279
|
+
function createWorker() {
|
|
280
|
+
return new Container({ tags: ['worker'] })
|
|
281
|
+
.addRegistration(R.fromClass(QueueClient))
|
|
282
|
+
.addRegistration(R.fromClass(JobHandler));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
it('should share a single QueueClient across all job handlers', () => {
|
|
286
|
+
const worker = createWorker();
|
|
287
|
+
|
|
288
|
+
const handler1 = worker.resolve(JobHandler);
|
|
289
|
+
const handler2 = worker.resolve(JobHandler);
|
|
290
|
+
|
|
291
|
+
const client1 = worker.resolve<QueueClient>('QueueClient');
|
|
292
|
+
const client2 = worker.resolve<QueueClient>('QueueClient');
|
|
293
|
+
|
|
294
|
+
// QueueClient is a singleton — same connection shared everywhere
|
|
295
|
+
expect(client1).toBe(client2);
|
|
296
|
+
expect(client1.connected).toBe(true);
|
|
297
|
+
|
|
298
|
+
// JobHandler is transient — fresh instance per job
|
|
299
|
+
expect(handler1).not.toBe(handler2);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should inject the shared QueueClient into each JobHandler', () => {
|
|
303
|
+
const worker = createWorker();
|
|
304
|
+
|
|
305
|
+
const handler = worker.resolve(JobHandler);
|
|
306
|
+
|
|
307
|
+
expect(handler.result).toBe('job-payload');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
211
311
|
|
|
212
312
|
### Frontend widget/page scope with lazy dependency
|
|
213
313
|
|
|
214
|
-
|
|
314
|
+
import 'reflect-metadata';
|
|
315
|
+
import { bindTo, Container, inject, register, Registration as R, select, singleton } from 'ts-ioc-container';
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Frontend Widget - Page Scope with Lazy Dependency
|
|
319
|
+
*
|
|
320
|
+
* In frontend applications, feature flags are fetched once per page load
|
|
321
|
+
* (singleton per page scope) but a widget may not need them on every render.
|
|
322
|
+
* Lazy injection defers instantiation until the widget actually reads the flags,
|
|
323
|
+
* avoiding unnecessary work for widgets that never display flag-gated content.
|
|
324
|
+
*
|
|
325
|
+
* Scope hierarchy:
|
|
326
|
+
* Application
|
|
327
|
+
* └── Page (singleton flags fetched once)
|
|
328
|
+
* └── Widget (lazy flag access)
|
|
329
|
+
*/
|
|
330
|
+
|
|
215
331
|
@register(bindTo('FeatureFlags'), singleton())
|
|
216
332
|
class FeatureFlags {
|
|
217
|
-
load() {
|
|
218
|
-
|
|
333
|
+
load(): Record<string, boolean> {
|
|
334
|
+
return { newDashboard: true };
|
|
219
335
|
}
|
|
220
336
|
}
|
|
221
337
|
|
|
222
338
|
class Widget {
|
|
223
|
-
constructor(@inject(select.token('FeatureFlags').lazy())
|
|
339
|
+
constructor(@inject(select.token('FeatureFlags').lazy()) public flags: FeatureFlags) {}
|
|
224
340
|
}
|
|
225
341
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
342
|
+
describe('Frontend widget/page scope with lazy dependency', () => {
|
|
343
|
+
function createPage() {
|
|
344
|
+
return new Container({ tags: ['page'] })
|
|
345
|
+
.addRegistration(R.fromClass(FeatureFlags))
|
|
346
|
+
.addRegistration(R.fromClass(Widget));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
it('should not instantiate FeatureFlags until the widget actually accesses it', () => {
|
|
350
|
+
const page = createPage();
|
|
351
|
+
|
|
352
|
+
const widget = page.resolve(Widget);
|
|
353
|
+
|
|
354
|
+
// Widget is resolved, but FeatureFlags has not been instantiated yet
|
|
355
|
+
let instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
356
|
+
expect(instances).toHaveLength(0);
|
|
357
|
+
|
|
358
|
+
// Accessing any property on the lazy proxy triggers instantiation
|
|
359
|
+
const _load = widget.flags.load;
|
|
360
|
+
expect(_load).toBeDefined();
|
|
361
|
+
|
|
362
|
+
instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
363
|
+
expect(instances).toHaveLength(1);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should share the same FeatureFlags singleton across widgets on the same page', () => {
|
|
367
|
+
const page = createPage();
|
|
368
|
+
|
|
369
|
+
const widget1 = page.resolve(Widget);
|
|
370
|
+
const widget2 = page.resolve(Widget);
|
|
371
|
+
|
|
372
|
+
// Trigger instantiation through both widgets
|
|
373
|
+
const _load1 = widget1.flags.load;
|
|
374
|
+
const _load2 = widget2.flags.load;
|
|
375
|
+
expect(_load1).toBeDefined();
|
|
376
|
+
expect(_load2).toBeDefined();
|
|
377
|
+
|
|
378
|
+
// Only one FeatureFlags instance was created across the whole page scope
|
|
379
|
+
const instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
380
|
+
expect(instances).toHaveLength(1);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
230
384
|
|
|
231
385
|
## Container
|
|
232
386
|
|
|
@@ -1461,51 +1615,22 @@ This type of injector just passes container to constructor with others arguments
|
|
|
1461
1615
|
```typescript
|
|
1462
1616
|
import { bindTo, Container, type IContainer, register, Registration as R, SimpleInjector } from 'ts-ioc-container';
|
|
1463
1617
|
|
|
1464
|
-
/**
|
|
1465
|
-
* Command Pattern - Simple Injector
|
|
1466
|
-
*
|
|
1467
|
-
* The SimpleInjector passes the container itself as the first argument to the constructor.
|
|
1468
|
-
* This is useful for:
|
|
1469
|
-
* - Service Locators (like Command Dispatchers or Routers)
|
|
1470
|
-
* - Factory classes that need to resolve dependencies dynamically
|
|
1471
|
-
* - Legacy code migration where passing the container is common
|
|
1472
|
-
*
|
|
1473
|
-
* In this example, a CommandDispatcher uses the container to dynamically
|
|
1474
|
-
* resolve the correct handler for each command type.
|
|
1475
|
-
*/
|
|
1476
|
-
|
|
1477
|
-
interface ICommand {
|
|
1478
|
-
type: string;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
interface ICommandHandler {
|
|
1482
|
-
handle(command: ICommand): string;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
class CreateUserCommand implements ICommand {
|
|
1486
|
-
readonly type = 'CreateUser';
|
|
1487
|
-
constructor(readonly username: string) {}
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
1618
|
@register(bindTo('HandlerCreateUser'))
|
|
1491
|
-
class CreateUserHandler
|
|
1492
|
-
handle(
|
|
1493
|
-
return `User ${
|
|
1619
|
+
class CreateUserHandler {
|
|
1620
|
+
handle(username: string): string {
|
|
1621
|
+
return `User ${username} created`;
|
|
1494
1622
|
}
|
|
1495
1623
|
}
|
|
1496
1624
|
|
|
1497
1625
|
describe('SimpleInjector', function () {
|
|
1498
|
-
it('should inject container to allow dynamic resolution
|
|
1499
|
-
// Dispatcher needs the container to find handlers dynamically based on command type
|
|
1626
|
+
it('should inject container to allow dynamic resolution', function () {
|
|
1500
1627
|
@register(bindTo('Dispatcher'))
|
|
1501
1628
|
class CommandDispatcher {
|
|
1502
1629
|
constructor(private container: IContainer) {}
|
|
1503
1630
|
|
|
1504
|
-
dispatch(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
const handler = this.container.resolve<ICommandHandler>(handlerKey);
|
|
1508
|
-
return handler.handle(command);
|
|
1631
|
+
dispatch(type: string, payload: string): string {
|
|
1632
|
+
const handler = this.container.resolve<CreateUserHandler>(`Handler${type}`);
|
|
1633
|
+
return handler.handle(payload);
|
|
1509
1634
|
}
|
|
1510
1635
|
}
|
|
1511
1636
|
|
|
@@ -1514,30 +1639,27 @@ describe('SimpleInjector', function () {
|
|
|
1514
1639
|
.addRegistration(R.fromClass(CreateUserHandler));
|
|
1515
1640
|
|
|
1516
1641
|
const dispatcher = container.resolve<CommandDispatcher>('Dispatcher');
|
|
1517
|
-
const result = dispatcher.dispatch(new CreateUserCommand('alice'));
|
|
1518
1642
|
|
|
1519
|
-
expect(
|
|
1643
|
+
expect(dispatcher.dispatch('CreateUser', 'alice')).toBe('User alice created');
|
|
1520
1644
|
});
|
|
1521
1645
|
|
|
1522
1646
|
it('should pass additional arguments alongside the container', function () {
|
|
1523
|
-
// Factory that creates widgets with a specific theme
|
|
1524
1647
|
class WidgetFactory {
|
|
1525
1648
|
constructor(
|
|
1526
1649
|
private container: IContainer,
|
|
1527
|
-
private theme: string,
|
|
1650
|
+
private theme: string,
|
|
1528
1651
|
) {}
|
|
1529
1652
|
|
|
1530
1653
|
createWidget(name: string): string {
|
|
1531
|
-
return `Widget ${name} with ${this.theme} theme (
|
|
1654
|
+
return `Widget ${name} with ${this.theme} theme (container: ${!!this.container})`;
|
|
1532
1655
|
}
|
|
1533
1656
|
}
|
|
1534
1657
|
|
|
1535
1658
|
const container = new Container({ injector: new SimpleInjector() }).addRegistration(R.fromClass(WidgetFactory));
|
|
1536
1659
|
|
|
1537
|
-
// Pass "dark" as the theme argument
|
|
1538
1660
|
const factory = container.resolve<WidgetFactory>('WidgetFactory', { args: ['dark'] });
|
|
1539
1661
|
|
|
1540
|
-
expect(factory.createWidget('Button')).toBe('Widget Button with dark theme (
|
|
1662
|
+
expect(factory.createWidget('Button')).toBe('Widget Button with dark theme (container: true)');
|
|
1541
1663
|
});
|
|
1542
1664
|
});
|
|
1543
1665
|
|
|
@@ -1562,16 +1684,11 @@ describe('ProxyInjector', function () {
|
|
|
1562
1684
|
}
|
|
1563
1685
|
}
|
|
1564
1686
|
|
|
1565
|
-
interface UserControllerDeps {
|
|
1566
|
-
logger: Logger;
|
|
1567
|
-
prefix: string;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
1687
|
class UserController {
|
|
1571
1688
|
private logger: Logger;
|
|
1572
1689
|
private prefix: string;
|
|
1573
1690
|
|
|
1574
|
-
constructor({ logger, prefix }:
|
|
1691
|
+
constructor({ logger, prefix }: { logger: Logger; prefix: string }) {
|
|
1575
1692
|
this.logger = logger;
|
|
1576
1693
|
this.prefix = prefix;
|
|
1577
1694
|
}
|
|
@@ -1586,21 +1703,14 @@ describe('ProxyInjector', function () {
|
|
|
1586
1703
|
.addRegistration(R.fromValue('USER:').bindToKey('prefix'))
|
|
1587
1704
|
.addRegistration(R.fromClass(UserController));
|
|
1588
1705
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
expect(controller.createUser('bob')).toBe('Logged: USER: bob');
|
|
1706
|
+
expect(container.resolve<UserController>('UserController').createUser('bob')).toBe('Logged: USER: bob');
|
|
1592
1707
|
});
|
|
1593
1708
|
|
|
1594
1709
|
it('should expose runtime args through the reserved "args" property', function () {
|
|
1595
|
-
@register(bindTo('database'))
|
|
1596
|
-
class Database {}
|
|
1597
|
-
|
|
1598
1710
|
class ReportGenerator {
|
|
1599
|
-
database: Database;
|
|
1600
1711
|
format: string;
|
|
1601
1712
|
|
|
1602
|
-
constructor({
|
|
1603
|
-
this.database = database;
|
|
1713
|
+
constructor({ args }: { args: string[] }) {
|
|
1604
1714
|
this.format = args[0];
|
|
1605
1715
|
}
|
|
1606
1716
|
|
|
@@ -1609,40 +1719,12 @@ describe('ProxyInjector', function () {
|
|
|
1609
1719
|
}
|
|
1610
1720
|
}
|
|
1611
1721
|
|
|
1612
|
-
const container = new Container({ injector: new ProxyInjector() })
|
|
1613
|
-
.addRegistration(R.fromClass(Database))
|
|
1614
|
-
.addRegistration(R.fromClass(ReportGenerator));
|
|
1722
|
+
const container = new Container({ injector: new ProxyInjector() }).addRegistration(R.fromClass(ReportGenerator));
|
|
1615
1723
|
|
|
1616
|
-
const generator = container.resolve<ReportGenerator>('ReportGenerator', {
|
|
1617
|
-
args: ['PDF'],
|
|
1618
|
-
});
|
|
1724
|
+
const generator = container.resolve<ReportGenerator>('ReportGenerator', { args: ['PDF'] });
|
|
1619
1725
|
|
|
1620
|
-
expect(generator.database).toBeInstanceOf(Database);
|
|
1621
1726
|
expect(generator.generate()).toBe('Report in PDF');
|
|
1622
1727
|
});
|
|
1623
|
-
|
|
1624
|
-
it('should resolve dependencies by alias when property name contains "alias"', function () {
|
|
1625
|
-
class FileLogger {}
|
|
1626
|
-
class ConsoleLogger {}
|
|
1627
|
-
|
|
1628
|
-
interface AppDeps {
|
|
1629
|
-
loggersAlias: any[];
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
class App {
|
|
1633
|
-
constructor(public deps: AppDeps) {}
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
const container = new Container({ injector: new ProxyInjector() });
|
|
1637
|
-
|
|
1638
|
-
const mockLoggers = [new FileLogger(), new ConsoleLogger()];
|
|
1639
|
-
container.resolveByAlias = vi.fn().mockReturnValue(mockLoggers);
|
|
1640
|
-
|
|
1641
|
-
const app = container.resolve(App);
|
|
1642
|
-
|
|
1643
|
-
expect(app.deps.loggersAlias).toBe(mockLoggers);
|
|
1644
|
-
expect(container.resolveByAlias).toHaveBeenCalledWith('loggersAlias');
|
|
1645
|
-
});
|
|
1646
1728
|
});
|
|
1647
1729
|
|
|
1648
1730
|
```
|
|
@@ -1898,7 +1980,7 @@ describe('Singleton', function () {
|
|
|
1898
1980
|
|
|
1899
1981
|
### Arguments
|
|
1900
1982
|
|
|
1901
|
-
Sometimes you want to bind some arguments to provider.
|
|
1983
|
+
Sometimes you want to bind some arguments to provider.
|
|
1902
1984
|
|
|
1903
1985
|
- `provider(setArgs('someArgument'))`
|
|
1904
1986
|
- `provider(setArgsFn((container) => [container.resolve(Logger), 'someValue']))`
|
|
@@ -1934,17 +2016,18 @@ const userToken = ApiToken.args('https://users.api.com', 1000);
|
|
|
1934
2016
|
|
|
1935
2017
|
```typescript
|
|
1936
2018
|
import {
|
|
1937
|
-
|
|
1938
|
-
|
|
2019
|
+
addArgs,
|
|
2020
|
+
addArgsFn,
|
|
2021
|
+
args,
|
|
1939
2022
|
bindTo,
|
|
1940
2023
|
Container,
|
|
1941
2024
|
inject,
|
|
1942
|
-
args,
|
|
1943
|
-
MultiCache,
|
|
1944
2025
|
register,
|
|
1945
2026
|
Registration as R,
|
|
1946
|
-
|
|
2027
|
+
setArgs,
|
|
2028
|
+
setArgsFn,
|
|
1947
2029
|
SingleToken,
|
|
2030
|
+
singleton,
|
|
1948
2031
|
} from 'ts-ioc-container';
|
|
1949
2032
|
|
|
1950
2033
|
/**
|
|
@@ -1957,7 +2040,7 @@ import {
|
|
|
1957
2040
|
* - Generic classes (like Repositories) that need to know what they are managing
|
|
1958
2041
|
*/
|
|
1959
2042
|
|
|
1960
|
-
describe('
|
|
2043
|
+
describe('IProvider', function () {
|
|
1961
2044
|
function createContainer() {
|
|
1962
2045
|
return new Container();
|
|
1963
2046
|
}
|
|
@@ -2014,6 +2097,49 @@ describe('ArgsProvider', function () {
|
|
|
2014
2097
|
});
|
|
2015
2098
|
});
|
|
2016
2099
|
|
|
2100
|
+
describe('Appending Arguments', () => {
|
|
2101
|
+
it('can append static arguments after existing resolve arguments', function () {
|
|
2102
|
+
@register(addArgs('configured'))
|
|
2103
|
+
class Service {
|
|
2104
|
+
constructor(
|
|
2105
|
+
@inject(args(0)) public runtime: string,
|
|
2106
|
+
@inject(args(1)) public configured: string,
|
|
2107
|
+
) {}
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
const root = createContainer().addRegistration(R.fromClass(Service));
|
|
2111
|
+
|
|
2112
|
+
const service = root.resolve<Service>('Service', { args: ['runtime'] });
|
|
2113
|
+
expect(service.runtime).toBe('runtime');
|
|
2114
|
+
expect(service.configured).toBe('configured');
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
it('can append dynamic arguments after an existing argsFn', function () {
|
|
2118
|
+
class Config {
|
|
2119
|
+
tenant = 'tenant-a';
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
@register(
|
|
2123
|
+
setArgs('fixed'),
|
|
2124
|
+
addArgsFn((scope, { args = [] } = {}) => [scope.resolve<Config>('Config').tenant, ...args]),
|
|
2125
|
+
)
|
|
2126
|
+
class Service {
|
|
2127
|
+
constructor(
|
|
2128
|
+
@inject(args(0)) public fixed: string,
|
|
2129
|
+
@inject(args(1)) public tenant: string,
|
|
2130
|
+
@inject(args(2)) public runtime: string,
|
|
2131
|
+
) {}
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
const root = createContainer().addRegistration(R.fromClass(Config)).addRegistration(R.fromClass(Service));
|
|
2135
|
+
|
|
2136
|
+
const service = root.resolve<Service>('Service', { args: ['runtime'] });
|
|
2137
|
+
expect(service.fixed).toBe('fixed');
|
|
2138
|
+
expect(service.tenant).toBe('tenant-a');
|
|
2139
|
+
expect(service.runtime).toBe('runtime');
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2017
2143
|
describe('Generic Repositories (Advanced Pattern)', () => {
|
|
2018
2144
|
// This example demonstrates how to implement the Generic Repository pattern
|
|
2019
2145
|
// where a generic EntityManager needs to know WHICH repository to use.
|
|
@@ -2044,7 +2170,7 @@ describe('ArgsProvider', function () {
|
|
|
2044
2170
|
|
|
2045
2171
|
@register(
|
|
2046
2172
|
bindTo(EntityManagerToken),
|
|
2047
|
-
singleton(
|
|
2173
|
+
singleton((arg1) => (arg1 as SingleToken).token), // Cache unique instance per repository type
|
|
2048
2174
|
)
|
|
2049
2175
|
class EntityManager {
|
|
2050
2176
|
constructor(@inject(args(0)) public repository: IRepository) {}
|
|
@@ -2574,37 +2700,91 @@ Sometimes you need to register provider only in scope which matches to certain c
|
|
|
2574
2700
|
- `Registration.fromClass(Logger).when((container) => container.hasTag('root'))`
|
|
2575
2701
|
|
|
2576
2702
|
```typescript
|
|
2577
|
-
import
|
|
2703
|
+
import 'reflect-metadata';
|
|
2704
|
+
import {
|
|
2705
|
+
bindTo,
|
|
2706
|
+
Container,
|
|
2707
|
+
DependencyNotFoundError,
|
|
2708
|
+
type IContainer,
|
|
2709
|
+
inject,
|
|
2710
|
+
register,
|
|
2711
|
+
Registration as R,
|
|
2712
|
+
scope,
|
|
2713
|
+
select,
|
|
2714
|
+
singleton,
|
|
2715
|
+
} from 'ts-ioc-container';
|
|
2578
2716
|
|
|
2579
2717
|
/**
|
|
2580
|
-
*
|
|
2718
|
+
* User Management Domain - Request Scopes
|
|
2581
2719
|
*
|
|
2582
|
-
*
|
|
2583
|
-
* This
|
|
2584
|
-
*
|
|
2720
|
+
* In web applications, each HTTP request typically gets its own scope.
|
|
2721
|
+
* This allows request-specific data (current user, request ID, etc.)
|
|
2722
|
+
* to be isolated between concurrent requests.
|
|
2723
|
+
*
|
|
2724
|
+
* Scope hierarchy:
|
|
2725
|
+
* Application (singleton services)
|
|
2726
|
+
* └── Request (per-request services)
|
|
2727
|
+
* └── Transaction (database transaction boundary)
|
|
2585
2728
|
*/
|
|
2586
2729
|
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
@register(
|
|
2592
|
-
bindTo('SharedState'),
|
|
2593
|
-
scope((s) => s.hasTag('application')), // Only register in application scope
|
|
2594
|
-
singleton(), // One instance per application
|
|
2595
|
-
)
|
|
2596
|
-
class SharedState {
|
|
2597
|
-
data = 'shared';
|
|
2598
|
-
}
|
|
2730
|
+
// SessionService is only available in request scope - not at application level
|
|
2731
|
+
@register(bindTo('ISessionService'), scope((s) => s.hasTag('request')), singleton())
|
|
2732
|
+
class SessionService {
|
|
2733
|
+
private userId: string | null = null;
|
|
2599
2734
|
|
|
2600
|
-
|
|
2601
|
-
|
|
2735
|
+
setCurrentUser(userId: string) {
|
|
2736
|
+
this.userId = userId;
|
|
2737
|
+
}
|
|
2602
2738
|
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2739
|
+
getCurrentUserId(): string | null {
|
|
2740
|
+
return this.userId;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2606
2743
|
|
|
2607
|
-
|
|
2744
|
+
describe('Scopes', function () {
|
|
2745
|
+
it('should isolate request-scoped services', function () {
|
|
2746
|
+
// Application container - lives for entire app lifetime
|
|
2747
|
+
const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(SessionService));
|
|
2748
|
+
|
|
2749
|
+
// Simulate two concurrent HTTP requests
|
|
2750
|
+
const request1Scope = appContainer.createScope({ tags: ['request'] });
|
|
2751
|
+
const request2Scope = appContainer.createScope({ tags: ['request'] });
|
|
2752
|
+
|
|
2753
|
+
// Each request has its own SessionService instance
|
|
2754
|
+
const session1 = request1Scope.resolve<SessionService>('ISessionService');
|
|
2755
|
+
const session2 = request2Scope.resolve<SessionService>('ISessionService');
|
|
2756
|
+
|
|
2757
|
+
session1.setCurrentUser('user-1');
|
|
2758
|
+
session2.setCurrentUser('user-2');
|
|
2759
|
+
|
|
2760
|
+
// Sessions are isolated - user data doesn't leak between requests
|
|
2761
|
+
expect(session1.getCurrentUserId()).toBe('user-1');
|
|
2762
|
+
expect(session2.getCurrentUserId()).toBe('user-2');
|
|
2763
|
+
expect(session1).not.toBe(session2);
|
|
2764
|
+
|
|
2765
|
+
// SessionService is NOT available at application level (security!)
|
|
2766
|
+
expect(() => appContainer.resolve('ISessionService')).toThrow(DependencyNotFoundError);
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
it('should create child scopes for transactions', function () {
|
|
2770
|
+
const appContainer = new Container({ tags: ['application'] });
|
|
2771
|
+
|
|
2772
|
+
// RequestHandler can create a transaction scope for database operations
|
|
2773
|
+
class RequestHandler {
|
|
2774
|
+
constructor(@inject(select.scope.create({ tags: ['transaction'] })) public transactionScope: IContainer) {}
|
|
2775
|
+
|
|
2776
|
+
executeInTransaction(): boolean {
|
|
2777
|
+
// Transaction scope inherits from request scope
|
|
2778
|
+
// Database operations can be rolled back together
|
|
2779
|
+
return this.transactionScope.hasTag('transaction');
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
const handler = appContainer.resolve(RequestHandler);
|
|
2784
|
+
|
|
2785
|
+
expect(handler.transactionScope).not.toBe(appContainer);
|
|
2786
|
+
expect(handler.transactionScope.hasTag('transaction')).toBe(true);
|
|
2787
|
+
expect(handler.executeInTransaction()).toBe(true);
|
|
2608
2788
|
});
|
|
2609
2789
|
});
|
|
2610
2790
|
|
|
@@ -2741,30 +2921,10 @@ Sometimes you need to invoke methods after construct or dispose of class. This i
|
|
|
2741
2921
|
### OnConstruct
|
|
2742
2922
|
|
|
2743
2923
|
```typescript
|
|
2744
|
-
import
|
|
2745
|
-
|
|
2746
|
-
Container,
|
|
2747
|
-
IHookContext,
|
|
2748
|
-
HookFn,
|
|
2749
|
-
inject,
|
|
2750
|
-
onConstruct,
|
|
2751
|
-
Registration as R,
|
|
2752
|
-
} from 'ts-ioc-container';
|
|
2753
|
-
|
|
2754
|
-
/**
|
|
2755
|
-
* Lifecycle - OnConstruct Hook
|
|
2756
|
-
*
|
|
2757
|
-
* The @onConstruct hook allows you to run logic immediately after an object is created.
|
|
2758
|
-
* This is useful for:
|
|
2759
|
-
* - Initialization logic that depends on injected services
|
|
2760
|
-
* - Setting up event listeners
|
|
2761
|
-
* - Establishing connections (though lazy is often better)
|
|
2762
|
-
* - Computing initial state
|
|
2763
|
-
*
|
|
2764
|
-
* Note: You must register the AddOnConstructHookModule or manually add the hook runner.
|
|
2765
|
-
*/
|
|
2924
|
+
import 'reflect-metadata';
|
|
2925
|
+
import { AddOnConstructHookModule, Container, type HookFn, inject, onConstruct, Registration as R } from 'ts-ioc-container';
|
|
2766
2926
|
|
|
2767
|
-
const execute: HookFn = (ctx
|
|
2927
|
+
const execute: HookFn = (ctx) => {
|
|
2768
2928
|
ctx.invokeMethod({ args: ctx.resolveArgs() });
|
|
2769
2929
|
};
|
|
2770
2930
|
|
|
@@ -2774,8 +2934,6 @@ describe('onConstruct', function () {
|
|
|
2774
2934
|
isConnected = false;
|
|
2775
2935
|
connectionString = '';
|
|
2776
2936
|
|
|
2777
|
-
// @onConstruct marks this method to be called after instantiation
|
|
2778
|
-
// Arguments are resolved from the container like constructor params
|
|
2779
2937
|
@onConstruct(execute)
|
|
2780
2938
|
connect(@inject('ConnectionString') connectionString: string) {
|
|
2781
2939
|
this.connectionString = connectionString;
|
|
@@ -2784,12 +2942,9 @@ describe('onConstruct', function () {
|
|
|
2784
2942
|
}
|
|
2785
2943
|
|
|
2786
2944
|
const container = new Container()
|
|
2787
|
-
// Enable @onConstruct support
|
|
2788
2945
|
.useModule(new AddOnConstructHookModule())
|
|
2789
|
-
// Register config
|
|
2790
2946
|
.addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
|
|
2791
2947
|
|
|
2792
|
-
// Resolve class - constructor is called, then @onConstruct method
|
|
2793
2948
|
const db = container.resolve(DatabaseConnection);
|
|
2794
2949
|
|
|
2795
2950
|
expect(db.isConnected).toBe(true);
|
|
@@ -2802,6 +2957,7 @@ describe('onConstruct', function () {
|
|
|
2802
2957
|
### OnDispose
|
|
2803
2958
|
|
|
2804
2959
|
```typescript
|
|
2960
|
+
import 'reflect-metadata';
|
|
2805
2961
|
import {
|
|
2806
2962
|
AddOnDisposeHookModule,
|
|
2807
2963
|
bindTo,
|
|
@@ -2829,10 +2985,6 @@ class LogsRepo {
|
|
|
2829
2985
|
|
|
2830
2986
|
@register(bindTo('logger'))
|
|
2831
2987
|
class Logger {
|
|
2832
|
-
@onDispose(({ instance, methodName }) => {
|
|
2833
|
-
// @ts-ignore
|
|
2834
|
-
instance[methodName].push('world');
|
|
2835
|
-
}) // <--- or extract it to @onDispose
|
|
2836
2988
|
private messages: string[] = [];
|
|
2837
2989
|
|
|
2838
2990
|
constructor(@inject('logsRepo') private logsRepo: LogsRepo) {}
|
|
@@ -2848,7 +3000,7 @@ class Logger {
|
|
|
2848
3000
|
}
|
|
2849
3001
|
|
|
2850
3002
|
describe('onDispose', function () {
|
|
2851
|
-
it('should invoke hooks on all instances', function () {
|
|
3003
|
+
it('should invoke hooks on all instances when container is disposed', function () {
|
|
2852
3004
|
const container = new Container()
|
|
2853
3005
|
.useModule(new AddOnDisposeHookModule())
|
|
2854
3006
|
.addRegistration(R.fromClass(Logger))
|
|
@@ -2860,7 +3012,7 @@ describe('onDispose', function () {
|
|
|
2860
3012
|
|
|
2861
3013
|
container.dispose();
|
|
2862
3014
|
|
|
2863
|
-
expect(logsRepo.savedLogs
|
|
3015
|
+
expect(logsRepo.savedLogs).toEqual(['Hello']);
|
|
2864
3016
|
});
|
|
2865
3017
|
});
|
|
2866
3018
|
|
package/cjm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onDispose = exports.onDisposeHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.hasHooks = exports.hook = exports.getHooks = exports.UnexpectedHookResultError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.DependencyNotFoundError = exports.Registration = exports.bindTo = exports.register = exports.scope = exports.decorate = exports.
|
|
3
|
+
exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onDispose = exports.onDisposeHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.hasHooks = exports.hook = exports.getHooks = exports.UnexpectedHookResultError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.DependencyNotFoundError = exports.Registration = exports.bindTo = exports.register = exports.scope = exports.decorate = exports.SingletonProvider = exports.singleton = exports.Provider = exports.ProviderDecorator = exports.setArgs = exports.setArgsFn = exports.addArgsFn = exports.addArgs = exports.lazy = exports.scopeAccess = exports.ProxyInjector = exports.SimpleInjector = exports.MetadataInjector = exports.Injector = exports.argsFn = exports.args = exports.resolveArgs = exports.inject = exports.EmptyContainer = exports.Container = exports.isDependencyKey = void 0;
|
|
4
4
|
exports.resolveConstructor = exports.Is = exports.pipe = exports.select = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.methodTag = exports.getMethodLabels = exports.methodLabel = exports.getMethodMeta = exports.methodMeta = exports.getParamTags = exports.paramTag = exports.getParamLabels = exports.paramLabel = exports.getParamMeta = exports.paramMeta = exports.getClassTags = exports.classTag = exports.getClassLabels = exports.classLabel = exports.getClassMeta = exports.classMeta = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = void 0;
|
|
5
5
|
// Containers
|
|
6
6
|
var IContainer_1 = require("./container/IContainer");
|
|
@@ -27,6 +27,8 @@ Object.defineProperty(exports, "ProxyInjector", { enumerable: true, get: functio
|
|
|
27
27
|
var IProvider_1 = require("./provider/IProvider");
|
|
28
28
|
Object.defineProperty(exports, "scopeAccess", { enumerable: true, get: function () { return IProvider_1.scopeAccess; } });
|
|
29
29
|
Object.defineProperty(exports, "lazy", { enumerable: true, get: function () { return IProvider_1.lazy; } });
|
|
30
|
+
Object.defineProperty(exports, "addArgs", { enumerable: true, get: function () { return IProvider_1.addArgs; } });
|
|
31
|
+
Object.defineProperty(exports, "addArgsFn", { enumerable: true, get: function () { return IProvider_1.addArgsFn; } });
|
|
30
32
|
Object.defineProperty(exports, "setArgsFn", { enumerable: true, get: function () { return IProvider_1.setArgsFn; } });
|
|
31
33
|
Object.defineProperty(exports, "setArgs", { enumerable: true, get: function () { return IProvider_1.setArgs; } });
|
|
32
34
|
Object.defineProperty(exports, "ProviderDecorator", { enumerable: true, get: function () { return IProvider_1.ProviderDecorator; } });
|
|
@@ -35,9 +37,6 @@ Object.defineProperty(exports, "Provider", { enumerable: true, get: function ()
|
|
|
35
37
|
var SingletonProvider_1 = require("./provider/SingletonProvider");
|
|
36
38
|
Object.defineProperty(exports, "singleton", { enumerable: true, get: function () { return SingletonProvider_1.singleton; } });
|
|
37
39
|
Object.defineProperty(exports, "SingletonProvider", { enumerable: true, get: function () { return SingletonProvider_1.SingletonProvider; } });
|
|
38
|
-
var Cache_1 = require("./provider/Cache");
|
|
39
|
-
Object.defineProperty(exports, "multiCache", { enumerable: true, get: function () { return Cache_1.multiCache; } });
|
|
40
|
-
Object.defineProperty(exports, "MultiCache", { enumerable: true, get: function () { return Cache_1.MultiCache; } });
|
|
41
40
|
var DecoratorProvider_1 = require("./provider/DecoratorProvider");
|
|
42
41
|
Object.defineProperty(exports, "decorate", { enumerable: true, get: function () { return DecoratorProvider_1.decorate; } });
|
|
43
42
|
// Registrations
|
package/cjm/injector/inject.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveArgs = exports.argsFn = exports.args = exports.inject = void 0;
|
|
4
|
-
const InjectionToken_1 = require("../token/InjectionToken");
|
|
5
|
-
const ConstantToken_1 = require("../token/ConstantToken");
|
|
6
4
|
const toToken_1 = require("../token/toToken");
|
|
7
5
|
const basic_1 = require("../utils/basic");
|
|
8
6
|
const parameter_1 = require("../metadata/parameter");
|
|
@@ -17,9 +15,8 @@ const args = (index) => (c, { args = [] }) => {
|
|
|
17
15
|
exports.args = args;
|
|
18
16
|
const argsFn = (fn) => (c, options) => fn(...(options.args ?? []));
|
|
19
17
|
exports.argsFn = argsFn;
|
|
20
|
-
const argToToken = (v) => ((0, InjectionToken_1.isInjectionToken)(v) ? v : new ConstantToken_1.ConstantToken(v));
|
|
21
18
|
const resolveArgs = (Target, methodName) => {
|
|
22
19
|
const tokens = (0, parameter_1.getParamMeta)(hookMetaKey(methodName), Target);
|
|
23
|
-
return (scope, { args = [], lazy }) => tokens.map((fn) => fn.resolve(scope, { args: args.map(argToToken).map((t) => t.resolve(scope)), lazy }));
|
|
20
|
+
return (scope, { args = [], lazy }) => tokens.map((fn) => fn.resolve(scope, { args: args.map(toToken_1.argToToken).map((t) => t.resolve(scope)), lazy }));
|
|
24
21
|
};
|
|
25
22
|
exports.resolveArgs = resolveArgs;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.setArgsFn = exports.setArgs = void 0;
|
|
3
|
+
exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.addArgsFn = exports.addArgs = exports.setArgsFn = exports.setArgs = void 0;
|
|
4
4
|
const ProviderPipe_1 = require("./ProviderPipe");
|
|
5
5
|
const setArgs = (...extraArgs) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(() => extraArgs));
|
|
6
6
|
exports.setArgs = setArgs;
|
|
7
7
|
const setArgsFn = (fn) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(fn));
|
|
8
8
|
exports.setArgsFn = setArgsFn;
|
|
9
|
+
const addArgs = (...extraArgs) => (0, ProviderPipe_1.registerPipe)((p) => p.addArgs(...extraArgs));
|
|
10
|
+
exports.addArgs = addArgs;
|
|
11
|
+
const addArgsFn = (fn) => (0, ProviderPipe_1.registerPipe)((p) => p.addArgsFn(fn));
|
|
12
|
+
exports.addArgsFn = addArgsFn;
|
|
9
13
|
const scopeAccess = (rule) => (0, ProviderPipe_1.registerPipe)((p) => p.setAccessRule(rule));
|
|
10
14
|
exports.scopeAccess = scopeAccess;
|
|
11
15
|
const lazy = () => (0, ProviderPipe_1.registerPipe)((p) => p.lazy());
|
|
@@ -39,6 +43,14 @@ class ProviderDecorator {
|
|
|
39
43
|
this.decorated.setArgs(argsFn);
|
|
40
44
|
return this;
|
|
41
45
|
}
|
|
46
|
+
addArgs(...extraArgs) {
|
|
47
|
+
this.decorated.addArgs(...extraArgs);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
addArgsFn(argsFn) {
|
|
51
|
+
this.decorated.addArgsFn(argsFn);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
42
54
|
lazy() {
|
|
43
55
|
this.decorated.lazy();
|
|
44
56
|
return this;
|
package/cjm/provider/Provider.js
CHANGED
|
@@ -42,6 +42,16 @@ class Provider {
|
|
|
42
42
|
this.argsFn = argsFn;
|
|
43
43
|
return this;
|
|
44
44
|
}
|
|
45
|
+
addArgs(...extraArgs) {
|
|
46
|
+
const parentFn = this.argsFn;
|
|
47
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...extraArgs];
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
addArgsFn(argsFn) {
|
|
51
|
+
const parentFn = this.argsFn;
|
|
52
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...argsFn(container, options)];
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
45
55
|
hasAccess(options) {
|
|
46
56
|
return this.checkAccess(options);
|
|
47
57
|
}
|
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.singleton = exports.SingletonProvider = void 0;
|
|
4
4
|
const IProvider_1 = require("./IProvider");
|
|
5
|
-
const Cache_1 = require("./Cache");
|
|
6
5
|
const ProviderPipe_1 = require("./ProviderPipe");
|
|
7
6
|
class SingletonProvider extends IProvider_1.ProviderDecorator {
|
|
8
7
|
provider;
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
getKey;
|
|
9
|
+
cache = new Map();
|
|
10
|
+
constructor(provider, getKey) {
|
|
11
11
|
super(provider);
|
|
12
12
|
this.provider = provider;
|
|
13
|
-
this.
|
|
13
|
+
this.getKey = getKey;
|
|
14
14
|
}
|
|
15
15
|
resolve(container, options) {
|
|
16
16
|
const { args = [] } = options;
|
|
17
|
-
const key = this.
|
|
18
|
-
if (!this.cache.
|
|
19
|
-
this.cache.
|
|
17
|
+
const key = this.getKey(...args);
|
|
18
|
+
if (!this.cache.has(key)) {
|
|
19
|
+
this.cache.set(key, this.provider.resolve(container, options));
|
|
20
20
|
}
|
|
21
|
-
return this.cache.
|
|
21
|
+
return this.cache.get(key);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
exports.SingletonProvider = SingletonProvider;
|
|
25
|
-
const singleton = (
|
|
25
|
+
const singleton = (getCacheKey = () => '1') => (0, ProviderPipe_1.registerPipe)((p) => new SingletonProvider(p, getCacheKey));
|
|
26
26
|
exports.singleton = singleton;
|
package/cjm/token/toToken.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toToken = void 0;
|
|
3
|
+
exports.argToToken = exports.toToken = void 0;
|
|
4
4
|
const IContainer_1 = require("../container/IContainer");
|
|
5
5
|
const SingleToken_1 = require("./SingleToken");
|
|
6
6
|
const ClassToken_1 = require("./ClassToken");
|
|
@@ -8,6 +8,7 @@ const FunctionToken_1 = require("./FunctionToken");
|
|
|
8
8
|
const UnsupportedTokenTypeError_1 = require("../errors/UnsupportedTokenTypeError");
|
|
9
9
|
const InjectionToken_1 = require("./InjectionToken");
|
|
10
10
|
const basic_1 = require("../utils/basic");
|
|
11
|
+
const ConstantToken_1 = require("./ConstantToken");
|
|
11
12
|
const toToken = (token) => {
|
|
12
13
|
if (token instanceof InjectionToken_1.InjectionToken) {
|
|
13
14
|
return token;
|
|
@@ -24,3 +25,5 @@ const toToken = (token) => {
|
|
|
24
25
|
throw new UnsupportedTokenTypeError_1.UnsupportedTokenTypeError(`Unknown token ${token}`);
|
|
25
26
|
};
|
|
26
27
|
exports.toToken = toToken;
|
|
28
|
+
const argToToken = (v) => ((0, InjectionToken_1.isInjectionToken)(v) ? v : new ConstantToken_1.ConstantToken(v));
|
|
29
|
+
exports.argToToken = argToToken;
|
package/esm/index.js
CHANGED
|
@@ -9,10 +9,9 @@ export { MetadataInjector } from './injector/MetadataInjector';
|
|
|
9
9
|
export { SimpleInjector } from './injector/SimpleInjector';
|
|
10
10
|
export { ProxyInjector } from './injector/ProxyInjector';
|
|
11
11
|
// Providers
|
|
12
|
-
export { scopeAccess, lazy, setArgsFn, setArgs, ProviderDecorator, } from './provider/IProvider';
|
|
12
|
+
export { scopeAccess, lazy, addArgs, addArgsFn, setArgsFn, setArgs, ProviderDecorator, } from './provider/IProvider';
|
|
13
13
|
export { Provider } from './provider/Provider';
|
|
14
14
|
export { singleton, SingletonProvider } from './provider/SingletonProvider';
|
|
15
|
-
export { multiCache, MultiCache } from './provider/Cache';
|
|
16
15
|
export { decorate } from './provider/DecoratorProvider';
|
|
17
16
|
// Registrations
|
|
18
17
|
export { scope, register, bindTo, } from './registration/IRegistration';
|
package/esm/injector/inject.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ConstantToken } from '../token/ConstantToken';
|
|
3
|
-
import { toToken } from '../token/toToken';
|
|
1
|
+
import { argToToken, toToken } from '../token/toToken';
|
|
4
2
|
import { Is } from '../utils/basic';
|
|
5
3
|
import { getParamMeta, paramMeta } from '../metadata/parameter';
|
|
6
4
|
const hookMetaKey = (methodName = 'constructor') => `inject:${methodName}`;
|
|
@@ -11,7 +9,6 @@ export const args = (index) => (c, { args = [] }) => {
|
|
|
11
9
|
return args[index];
|
|
12
10
|
};
|
|
13
11
|
export const argsFn = (fn) => (c, options) => fn(...(options.args ?? []));
|
|
14
|
-
const argToToken = (v) => (isInjectionToken(v) ? v : new ConstantToken(v));
|
|
15
12
|
export const resolveArgs = (Target, methodName) => {
|
|
16
13
|
const tokens = getParamMeta(hookMetaKey(methodName), Target);
|
|
17
14
|
return (scope, { args = [], lazy }) => tokens.map((fn) => fn.resolve(scope, { args: args.map(argToToken).map((t) => t.resolve(scope)), lazy }));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isProviderPipe, registerPipe } from './ProviderPipe';
|
|
2
2
|
export const setArgs = (...extraArgs) => registerPipe((p) => p.setArgs(() => extraArgs));
|
|
3
3
|
export const setArgsFn = (fn) => registerPipe((p) => p.setArgs(fn));
|
|
4
|
+
export const addArgs = (...extraArgs) => registerPipe((p) => p.addArgs(...extraArgs));
|
|
5
|
+
export const addArgsFn = (fn) => registerPipe((p) => p.addArgsFn(fn));
|
|
4
6
|
export const scopeAccess = (rule) => registerPipe((p) => p.setAccessRule(rule));
|
|
5
7
|
export const lazy = () => registerPipe((p) => p.lazy());
|
|
6
8
|
export class ProviderDecorator {
|
|
@@ -32,6 +34,14 @@ export class ProviderDecorator {
|
|
|
32
34
|
this.decorated.setArgs(argsFn);
|
|
33
35
|
return this;
|
|
34
36
|
}
|
|
37
|
+
addArgs(...extraArgs) {
|
|
38
|
+
this.decorated.addArgs(...extraArgs);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
addArgsFn(argsFn) {
|
|
42
|
+
this.decorated.addArgsFn(argsFn);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
35
45
|
lazy() {
|
|
36
46
|
this.decorated.lazy();
|
|
37
47
|
return this;
|
package/esm/provider/Provider.js
CHANGED
|
@@ -39,6 +39,16 @@ export class Provider {
|
|
|
39
39
|
this.argsFn = argsFn;
|
|
40
40
|
return this;
|
|
41
41
|
}
|
|
42
|
+
addArgs(...extraArgs) {
|
|
43
|
+
const parentFn = this.argsFn;
|
|
44
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...extraArgs];
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
addArgsFn(argsFn) {
|
|
48
|
+
const parentFn = this.argsFn;
|
|
49
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...argsFn(container, options)];
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
42
52
|
hasAccess(options) {
|
|
43
53
|
return this.checkAccess(options);
|
|
44
54
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { ProviderDecorator } from './IProvider';
|
|
2
|
-
import { SingleCache } from './Cache';
|
|
3
2
|
import { registerPipe } from './ProviderPipe';
|
|
4
3
|
export class SingletonProvider extends ProviderDecorator {
|
|
5
4
|
provider;
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
getKey;
|
|
6
|
+
cache = new Map();
|
|
7
|
+
constructor(provider, getKey) {
|
|
8
8
|
super(provider);
|
|
9
9
|
this.provider = provider;
|
|
10
|
-
this.
|
|
10
|
+
this.getKey = getKey;
|
|
11
11
|
}
|
|
12
12
|
resolve(container, options) {
|
|
13
13
|
const { args = [] } = options;
|
|
14
|
-
const key = this.
|
|
15
|
-
if (!this.cache.
|
|
16
|
-
this.cache.
|
|
14
|
+
const key = this.getKey(...args);
|
|
15
|
+
if (!this.cache.has(key)) {
|
|
16
|
+
this.cache.set(key, this.provider.resolve(container, options));
|
|
17
17
|
}
|
|
18
|
-
return this.cache.
|
|
18
|
+
return this.cache.get(key);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
export const singleton = (
|
|
21
|
+
export const singleton = (getCacheKey = () => '1') => registerPipe((p) => new SingletonProvider(p, getCacheKey));
|
package/esm/token/toToken.js
CHANGED
|
@@ -3,8 +3,9 @@ import { SingleToken } from './SingleToken';
|
|
|
3
3
|
import { ClassToken } from './ClassToken';
|
|
4
4
|
import { FunctionToken } from './FunctionToken';
|
|
5
5
|
import { UnsupportedTokenTypeError } from '../errors/UnsupportedTokenTypeError';
|
|
6
|
-
import { InjectionToken } from './InjectionToken';
|
|
6
|
+
import { InjectionToken, isInjectionToken } from './InjectionToken';
|
|
7
7
|
import { Is } from '../utils/basic';
|
|
8
|
+
import { ConstantToken } from './ConstantToken';
|
|
8
9
|
export const toToken = (token) => {
|
|
9
10
|
if (token instanceof InjectionToken) {
|
|
10
11
|
return token;
|
|
@@ -20,3 +21,4 @@ export const toToken = (token) => {
|
|
|
20
21
|
}
|
|
21
22
|
throw new UnsupportedTokenTypeError(`Unknown token ${token}`);
|
|
22
23
|
};
|
|
24
|
+
export const argToToken = (v) => (isInjectionToken(v) ? v : new ConstantToken(v));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-ioc-container",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "50.1.0",
|
|
4
4
|
"description": "Fast, lightweight TypeScript dependency injection container with a clean API, scoped lifecycles, decorators, tokens, hooks, lazy injection, customizable providers, and no global container objects.",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"docs"
|
|
@@ -90,11 +90,11 @@
|
|
|
90
90
|
"@swc/core": "^1.15.24",
|
|
91
91
|
"@types/node": "^25.5.2",
|
|
92
92
|
"@typescript-eslint/eslint-plugin": "8.32.1",
|
|
93
|
-
"@typescript-eslint/parser": "8.
|
|
93
|
+
"@typescript-eslint/parser": "8.58.2",
|
|
94
94
|
"@vitest/coverage-v8": "^4.1.2",
|
|
95
95
|
"cz-conventional-changelog": "^3.3.0",
|
|
96
96
|
"eslint": "9.24.0",
|
|
97
|
-
"eslint-config-prettier": "10.1.
|
|
97
|
+
"eslint-config-prettier": "10.1.8",
|
|
98
98
|
"eslint-plugin-prettier": "5.2.6",
|
|
99
99
|
"handlebars": "^4.7.8",
|
|
100
100
|
"husky": "^9.1.7",
|
package/typings/index.d.ts
CHANGED
|
@@ -6,10 +6,9 @@ export { type IInjector, type InjectOptions, type IInjectFnResolver, Injector }
|
|
|
6
6
|
export { MetadataInjector } from './injector/MetadataInjector';
|
|
7
7
|
export { SimpleInjector } from './injector/SimpleInjector';
|
|
8
8
|
export { ProxyInjector } from './injector/ProxyInjector';
|
|
9
|
-
export { type ResolveDependency, type IProvider, scopeAccess, lazy, setArgsFn, setArgs, type ArgsFn, ProviderDecorator, type IMapper, type ProviderOptions, } from './provider/IProvider';
|
|
9
|
+
export { type ResolveDependency, type IProvider, scopeAccess, lazy, addArgs, addArgsFn, setArgsFn, setArgs, type ArgsFn, ProviderDecorator, type IMapper, type ProviderOptions, } from './provider/IProvider';
|
|
10
10
|
export { Provider } from './provider/Provider';
|
|
11
11
|
export { singleton, SingletonProvider } from './provider/SingletonProvider';
|
|
12
|
-
export { type Cache, multiCache, MultiCache } from './provider/Cache';
|
|
13
12
|
export { decorate, type DecorateFn } from './provider/DecoratorProvider';
|
|
14
13
|
export { type ProviderPipe } from './provider/ProviderPipe';
|
|
15
14
|
export { type IRegistration, type ReturnTypeOfRegistration, scope, register, type ScopeMatchRule, bindTo, } from './registration/IRegistration';
|
|
@@ -22,10 +22,14 @@ export interface IProvider<T = any> {
|
|
|
22
22
|
pipe(...mappers: (MapFn<IProvider<T>> | ProviderPipe<T>)[]): IProvider<T>;
|
|
23
23
|
setAccessRule(hasAccessWhen: ScopeAccessRule): this;
|
|
24
24
|
setArgs(argsFn: ArgsFn): this;
|
|
25
|
+
addArgs(...extraArgs: unknown[]): this;
|
|
26
|
+
addArgsFn(argsFn: ArgsFn): this;
|
|
25
27
|
lazy(): this;
|
|
26
28
|
}
|
|
27
29
|
export declare const setArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>;
|
|
28
30
|
export declare const setArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
|
|
31
|
+
export declare const addArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>;
|
|
32
|
+
export declare const addArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
|
|
29
33
|
export declare const scopeAccess: <T>(rule: ScopeAccessRule) => ProviderPipe<T>;
|
|
30
34
|
export declare const lazy: <T>() => ProviderPipe<T>;
|
|
31
35
|
export declare abstract class ProviderDecorator<T> implements IProvider<T> {
|
|
@@ -36,5 +40,7 @@ export declare abstract class ProviderDecorator<T> implements IProvider<T> {
|
|
|
36
40
|
resolve(container: IContainer, options: ProviderOptions): T;
|
|
37
41
|
pipe(...mappers: (MapFn<IProvider<T>> | ProviderPipe<T>)[]): IProvider<T>;
|
|
38
42
|
setArgs(argsFn: ArgsFn): this;
|
|
43
|
+
addArgs(...extraArgs: unknown[]): this;
|
|
44
|
+
addArgsFn(argsFn: ArgsFn): this;
|
|
39
45
|
lazy(): this;
|
|
40
46
|
}
|
|
@@ -17,5 +17,7 @@ export declare class Provider<T = any> implements IProvider<T> {
|
|
|
17
17
|
setAccessRule(predicate: ScopeAccessRule): this;
|
|
18
18
|
lazy(): this;
|
|
19
19
|
setArgs(argsFn: ArgsFn): this;
|
|
20
|
+
addArgs(...extraArgs: unknown[]): this;
|
|
21
|
+
addArgsFn(argsFn: ArgsFn): this;
|
|
20
22
|
hasAccess(options: ScopeAccessOptions): boolean;
|
|
21
23
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { IContainer } from '../container/IContainer';
|
|
2
2
|
import type { IProvider } from './IProvider';
|
|
3
3
|
import { ProviderDecorator } from './IProvider';
|
|
4
|
-
import type { Cache } from './Cache';
|
|
5
4
|
import { InjectOptions } from '../injector/IInjector';
|
|
5
|
+
type GetCacheKey = (...args: unknown[]) => string | symbol;
|
|
6
6
|
export declare class SingletonProvider<T> extends ProviderDecorator<T> {
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
|
|
7
|
+
private provider;
|
|
8
|
+
private getKey;
|
|
9
|
+
private cache;
|
|
10
|
+
constructor(provider: IProvider<T>, getKey: GetCacheKey);
|
|
10
11
|
resolve(container: IContainer, options: InjectOptions): T;
|
|
11
12
|
}
|
|
12
|
-
export declare const singleton: <T = unknown>(
|
|
13
|
+
export declare const singleton: <T = unknown>(getCacheKey?: GetCacheKey) => import("./ProviderPipe").ProviderPipe<T>;
|
|
14
|
+
export {};
|
|
@@ -3,3 +3,4 @@ import { InjectionToken } from './InjectionToken';
|
|
|
3
3
|
import { InjectFn } from '../hooks/hook';
|
|
4
4
|
import { type constructor } from '../utils/basic';
|
|
5
5
|
export declare const toToken: <T = any>(token: InjectFn<T> | InjectionToken<T> | DependencyKey | constructor<T>) => InjectionToken<T>;
|
|
6
|
+
export declare const argToToken: (v: unknown) => InjectionToken<unknown>;
|
package/cjm/provider/Cache.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SingleCache = exports.multiCache = exports.MultiCache = void 0;
|
|
4
|
-
class MultiCache {
|
|
5
|
-
getKey;
|
|
6
|
-
instances = new Map();
|
|
7
|
-
static fromFirstArg() {
|
|
8
|
-
return new MultiCache((key) => key);
|
|
9
|
-
}
|
|
10
|
-
constructor(getKey = () => '1') {
|
|
11
|
-
this.getKey = getKey;
|
|
12
|
-
}
|
|
13
|
-
hasValue(token) {
|
|
14
|
-
return this.instances.has(token);
|
|
15
|
-
}
|
|
16
|
-
getValue(token) {
|
|
17
|
-
return this.instances.get(token);
|
|
18
|
-
}
|
|
19
|
-
setValue(token, value) {
|
|
20
|
-
this.instances.set(token, value);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
exports.MultiCache = MultiCache;
|
|
24
|
-
const multiCache = (getKey) => new MultiCache(getKey);
|
|
25
|
-
exports.multiCache = multiCache;
|
|
26
|
-
class SingleCache {
|
|
27
|
-
instance;
|
|
28
|
-
getKey(...args) {
|
|
29
|
-
return '1';
|
|
30
|
-
}
|
|
31
|
-
getValue(key) {
|
|
32
|
-
return this.instance.value;
|
|
33
|
-
}
|
|
34
|
-
hasValue(key) {
|
|
35
|
-
return this.instance !== undefined;
|
|
36
|
-
}
|
|
37
|
-
setValue(key, value) {
|
|
38
|
-
this.instance = { value };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
exports.SingleCache = SingleCache;
|
package/esm/provider/Cache.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export class MultiCache {
|
|
2
|
-
getKey;
|
|
3
|
-
instances = new Map();
|
|
4
|
-
static fromFirstArg() {
|
|
5
|
-
return new MultiCache((key) => key);
|
|
6
|
-
}
|
|
7
|
-
constructor(getKey = () => '1') {
|
|
8
|
-
this.getKey = getKey;
|
|
9
|
-
}
|
|
10
|
-
hasValue(token) {
|
|
11
|
-
return this.instances.has(token);
|
|
12
|
-
}
|
|
13
|
-
getValue(token) {
|
|
14
|
-
return this.instances.get(token);
|
|
15
|
-
}
|
|
16
|
-
setValue(token, value) {
|
|
17
|
-
this.instances.set(token, value);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export const multiCache = (getKey) => new MultiCache(getKey);
|
|
21
|
-
export class SingleCache {
|
|
22
|
-
instance;
|
|
23
|
-
getKey(...args) {
|
|
24
|
-
return '1';
|
|
25
|
-
}
|
|
26
|
-
getValue(key) {
|
|
27
|
-
return this.instance.value;
|
|
28
|
-
}
|
|
29
|
-
hasValue(key) {
|
|
30
|
-
return this.instance !== undefined;
|
|
31
|
-
}
|
|
32
|
-
setValue(key, value) {
|
|
33
|
-
this.instance = { value };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { DependencyKey } from '../container/IContainer';
|
|
2
|
-
export interface Cache<K, V> {
|
|
3
|
-
getKey(...args: unknown[]): K;
|
|
4
|
-
hasValue(key: K): boolean;
|
|
5
|
-
getValue(key: K): V;
|
|
6
|
-
setValue(key: K, value: V): void;
|
|
7
|
-
}
|
|
8
|
-
export declare class MultiCache<K, V> implements Cache<K, V> {
|
|
9
|
-
readonly getKey: (...args: unknown[]) => K;
|
|
10
|
-
private instances;
|
|
11
|
-
static fromFirstArg(): MultiCache<DependencyKey, unknown>;
|
|
12
|
-
constructor(getKey?: (...args: unknown[]) => K);
|
|
13
|
-
hasValue(token: K): boolean;
|
|
14
|
-
getValue(token: K): V;
|
|
15
|
-
setValue(token: K, value: V): void;
|
|
16
|
-
}
|
|
17
|
-
export declare const multiCache: <K, V>(getKey: (...args: unknown[]) => K) => MultiCache<K, V>;
|
|
18
|
-
export declare class SingleCache<V> implements Cache<string, V> {
|
|
19
|
-
private instance?;
|
|
20
|
-
getKey(...args: unknown[]): string;
|
|
21
|
-
getValue(key: string): V;
|
|
22
|
-
hasValue(key: string): boolean;
|
|
23
|
-
setValue(key: string, value: V): void;
|
|
24
|
-
}
|