ts-ioc-container 50.0.0 → 50.2.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 +242 -46
- package/cjm/index.js +3 -3
- package/cjm/provider/IProvider.js +11 -7
- package/cjm/provider/Provider.js +8 -2
- package/cjm/token/InjectionToken.js +1 -3
- package/esm/index.js +1 -1
- package/esm/provider/IProvider.js +8 -4
- package/esm/provider/Provider.js +8 -2
- package/esm/token/InjectionToken.js +0 -1
- package/package.json +3 -3
- package/typings/index.d.ts +1 -1
- package/typings/provider/IProvider.d.ts +6 -4
- package/typings/provider/Provider.d.ts +2 -1
- package/typings/token/InjectionToken.d.ts +1 -2
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)
|
|
@@ -56,7 +56,7 @@ no global container objects.
|
|
|
56
56
|
- [Proxy](#proxy)
|
|
57
57
|
- [Provider](#provider) `provider`
|
|
58
58
|
- [Singleton](#singleton) `singleton`
|
|
59
|
-
- [Arguments](#arguments) `
|
|
59
|
+
- [Arguments](#arguments) `appendArgs` `appendArgsFn`
|
|
60
60
|
- [Visibility](#visibility) `visible`
|
|
61
61
|
- [Alias](#alias) `asAlias`
|
|
62
62
|
- [Decorator](#decorator) `decorate`
|
|
@@ -140,11 +140,11 @@ container.resolve(App).start();
|
|
|
140
140
|
> For classes, prefer the `@register(bindTo('Key'))` decorator over the fluent
|
|
141
141
|
> `R.fromClass(Class).bindToKey('Key')` chain. The decorator co-locates the binding
|
|
142
142
|
> with the class and reads consistently with other registration pipes
|
|
143
|
-
> (`scope`, `singleton`, `
|
|
143
|
+
> (`scope`, `singleton`, `appendArgsFn`, ...). Use the fluent `bindToKey` chain only
|
|
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
|
+
*/
|
|
201
|
+
|
|
202
|
+
@register(bindTo('ILogger'), singleton())
|
|
203
|
+
class Logger {
|
|
204
|
+
readonly messages: string[] = [];
|
|
189
205
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
logger.log('req started');
|
|
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
|
|
|
@@ -1288,7 +1442,7 @@ describe('lazy registerPipe', () => {
|
|
|
1288
1442
|
const container = new Container().addRegistration(
|
|
1289
1443
|
R.fromClass(ConfigService)
|
|
1290
1444
|
.pipe(
|
|
1291
|
-
(p) => p.
|
|
1445
|
+
(p) => p.appendArgs('https://api.example.com', 5000),
|
|
1292
1446
|
(p) => p.lazy(),
|
|
1293
1447
|
)
|
|
1294
1448
|
.pipe(singleton()),
|
|
@@ -1586,8 +1740,8 @@ Provider is dependency factory which creates dependency.
|
|
|
1586
1740
|
```typescript
|
|
1587
1741
|
import {
|
|
1588
1742
|
args,
|
|
1589
|
-
|
|
1590
|
-
|
|
1743
|
+
appendArgs,
|
|
1744
|
+
appendArgsFn,
|
|
1591
1745
|
bindTo,
|
|
1592
1746
|
Container,
|
|
1593
1747
|
inject,
|
|
@@ -1669,7 +1823,7 @@ describe('Provider', () => {
|
|
|
1669
1823
|
|
|
1670
1824
|
const container = new Container().register(
|
|
1671
1825
|
'FileService',
|
|
1672
|
-
Provider.fromClass(FileService).pipe(
|
|
1826
|
+
Provider.fromClass(FileService).pipe(appendArgs('/var/data')),
|
|
1673
1827
|
);
|
|
1674
1828
|
|
|
1675
1829
|
const service = container.resolve<FileService>('FileService');
|
|
@@ -1685,7 +1839,7 @@ describe('Provider', () => {
|
|
|
1685
1839
|
'Database',
|
|
1686
1840
|
Provider.fromClass(Database).pipe(
|
|
1687
1841
|
// Dynamically resolve connection string at creation time
|
|
1688
|
-
|
|
1842
|
+
appendArgsFn((scope) => [`postgres://${scope.resolve('DbPath')}`]),
|
|
1689
1843
|
),
|
|
1690
1844
|
);
|
|
1691
1845
|
|
|
@@ -1826,11 +1980,11 @@ describe('Singleton', function () {
|
|
|
1826
1980
|
|
|
1827
1981
|
### Arguments
|
|
1828
1982
|
|
|
1829
|
-
Sometimes you want to bind some arguments to provider.
|
|
1983
|
+
Sometimes you want to bind some arguments to provider.
|
|
1830
1984
|
|
|
1831
|
-
- `provider(
|
|
1832
|
-
- `provider(
|
|
1833
|
-
- `Provider.fromClass(Logger).pipe(
|
|
1985
|
+
- `provider(appendArgs('someArgument'))`
|
|
1986
|
+
- `provider(appendArgsFn((container) => [container.resolve(Logger), 'someValue']))`
|
|
1987
|
+
- `Provider.fromClass(Logger).pipe(appendArgs('someArgument'))`
|
|
1834
1988
|
|
|
1835
1989
|
### Token as argument
|
|
1836
1990
|
|
|
@@ -1863,13 +2017,13 @@ const userToken = ApiToken.args('https://users.api.com', 1000);
|
|
|
1863
2017
|
```typescript
|
|
1864
2018
|
import {
|
|
1865
2019
|
args,
|
|
2020
|
+
appendArgs,
|
|
2021
|
+
appendArgsFn,
|
|
1866
2022
|
bindTo,
|
|
1867
2023
|
Container,
|
|
1868
2024
|
inject,
|
|
1869
2025
|
register,
|
|
1870
2026
|
Registration as R,
|
|
1871
|
-
setArgs,
|
|
1872
|
-
setArgsFn,
|
|
1873
2027
|
SingleToken,
|
|
1874
2028
|
singleton,
|
|
1875
2029
|
} from 'ts-ioc-container';
|
|
@@ -1884,7 +2038,7 @@ import {
|
|
|
1884
2038
|
* - Generic classes (like Repositories) that need to know what they are managing
|
|
1885
2039
|
*/
|
|
1886
2040
|
|
|
1887
|
-
describe('
|
|
2041
|
+
describe('IProvider', function () {
|
|
1888
2042
|
function createContainer() {
|
|
1889
2043
|
return new Container();
|
|
1890
2044
|
}
|
|
@@ -1892,7 +2046,7 @@ describe('ArgsProvider', function () {
|
|
|
1892
2046
|
describe('Static Arguments', () => {
|
|
1893
2047
|
it('can pass static arguments to constructor', function () {
|
|
1894
2048
|
// Pre-configure the logger with a filename
|
|
1895
|
-
@register(
|
|
2049
|
+
@register(appendArgs('/var/log/app.log'))
|
|
1896
2050
|
class FileLogger {
|
|
1897
2051
|
constructor(@inject(args(0)) public filename: string) {}
|
|
1898
2052
|
}
|
|
@@ -1904,19 +2058,21 @@ describe('ArgsProvider', function () {
|
|
|
1904
2058
|
expect(logger.filename).toBe('/var/log/app.log');
|
|
1905
2059
|
});
|
|
1906
2060
|
|
|
1907
|
-
it('
|
|
1908
|
-
|
|
1909
|
-
@register(setArgs('FixedContext'))
|
|
2061
|
+
it('appends configured args after resolve args', function () {
|
|
2062
|
+
@register(appendArgs('ConfiguredContext'))
|
|
1910
2063
|
class Logger {
|
|
1911
|
-
constructor(
|
|
2064
|
+
constructor(
|
|
2065
|
+
@inject(args(0)) public runtimeContext: string,
|
|
2066
|
+
@inject(args(1)) public configuredContext: string,
|
|
2067
|
+
) {}
|
|
1912
2068
|
}
|
|
1913
2069
|
|
|
1914
2070
|
const root = createContainer().addRegistration(R.fromClass(Logger));
|
|
1915
2071
|
|
|
1916
|
-
// Even if we ask for 'RuntimeContext', we get 'FixedContext'
|
|
1917
2072
|
const logger = root.resolve<Logger>('Logger', { args: ['RuntimeContext'] });
|
|
1918
2073
|
|
|
1919
|
-
expect(logger.
|
|
2074
|
+
expect(logger.runtimeContext).toBe('RuntimeContext');
|
|
2075
|
+
expect(logger.configuredContext).toBe('ConfiguredContext');
|
|
1920
2076
|
});
|
|
1921
2077
|
});
|
|
1922
2078
|
|
|
@@ -1927,7 +2083,7 @@ describe('ArgsProvider', function () {
|
|
|
1927
2083
|
}
|
|
1928
2084
|
|
|
1929
2085
|
// Extract 'env' from Config service dynamically
|
|
1930
|
-
@register(
|
|
2086
|
+
@register(appendArgsFn((scope) => [scope.resolve<Config>('Config').env]))
|
|
1931
2087
|
class Service {
|
|
1932
2088
|
constructor(@inject(args(0)) public env: string) {}
|
|
1933
2089
|
}
|
|
@@ -1941,6 +2097,46 @@ describe('ArgsProvider', function () {
|
|
|
1941
2097
|
});
|
|
1942
2098
|
});
|
|
1943
2099
|
|
|
2100
|
+
describe('Appending Arguments', () => {
|
|
2101
|
+
it('can append static arguments after existing resolve arguments', function () {
|
|
2102
|
+
@register(appendArgs('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 runtime args', function () {
|
|
2118
|
+
class Config {
|
|
2119
|
+
tenant = 'tenant-a';
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
@register(appendArgs('fixed'), appendArgsFn((scope) => [scope.resolve<Config>('Config').tenant]))
|
|
2123
|
+
class Service {
|
|
2124
|
+
constructor(
|
|
2125
|
+
@inject(args(0)) public runtime: string,
|
|
2126
|
+
@inject(args(1)) public fixed: string,
|
|
2127
|
+
@inject(args(2)) public tenant: string,
|
|
2128
|
+
) {}
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
const root = createContainer().addRegistration(R.fromClass(Config)).addRegistration(R.fromClass(Service));
|
|
2132
|
+
|
|
2133
|
+
const service = root.resolve<Service>('Service', { args: ['runtime'] });
|
|
2134
|
+
expect(service.runtime).toBe('runtime');
|
|
2135
|
+
expect(service.fixed).toBe('fixed');
|
|
2136
|
+
expect(service.tenant).toBe('tenant-a');
|
|
2137
|
+
});
|
|
2138
|
+
});
|
|
2139
|
+
|
|
1944
2140
|
describe('Generic Repositories (Advanced Pattern)', () => {
|
|
1945
2141
|
// This example demonstrates how to implement the Generic Repository pattern
|
|
1946
2142
|
// where a generic EntityManager needs to know WHICH repository to use.
|
package/cjm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SingleToken = exports.ClassToken = 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.
|
|
3
|
+
exports.SingleToken = exports.ClassToken = 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.appendArgsFn = exports.appendArgs = 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 = void 0;
|
|
5
5
|
// Containers
|
|
6
6
|
var IContainer_1 = require("./container/IContainer");
|
|
@@ -27,8 +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, "
|
|
31
|
-
Object.defineProperty(exports, "
|
|
30
|
+
Object.defineProperty(exports, "appendArgs", { enumerable: true, get: function () { return IProvider_1.appendArgs; } });
|
|
31
|
+
Object.defineProperty(exports, "appendArgsFn", { enumerable: true, get: function () { return IProvider_1.appendArgsFn; } });
|
|
32
32
|
Object.defineProperty(exports, "ProviderDecorator", { enumerable: true, get: function () { return IProvider_1.ProviderDecorator; } });
|
|
33
33
|
var Provider_1 = require("./provider/Provider");
|
|
34
34
|
Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return Provider_1.Provider; } });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.
|
|
3
|
+
exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.appendArgsFn = exports.appendArgs = void 0;
|
|
4
4
|
const ProviderPipe_1 = require("./ProviderPipe");
|
|
5
|
-
const
|
|
6
|
-
exports.
|
|
7
|
-
const
|
|
8
|
-
exports.
|
|
5
|
+
const appendArgs = (...extraArgs) => (0, ProviderPipe_1.registerPipe)((p) => p.appendArgs(...extraArgs));
|
|
6
|
+
exports.appendArgs = appendArgs;
|
|
7
|
+
const appendArgsFn = (fn) => (0, ProviderPipe_1.registerPipe)((p) => p.appendArgsFn(fn));
|
|
8
|
+
exports.appendArgsFn = appendArgsFn;
|
|
9
9
|
const scopeAccess = (rule) => (0, ProviderPipe_1.registerPipe)((p) => p.setAccessRule(rule));
|
|
10
10
|
exports.scopeAccess = scopeAccess;
|
|
11
11
|
const lazy = () => (0, ProviderPipe_1.registerPipe)((p) => p.lazy());
|
|
@@ -35,8 +35,12 @@ class ProviderDecorator {
|
|
|
35
35
|
this.decorated = this.decorated.pipe(...fns);
|
|
36
36
|
return this;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
this.decorated.
|
|
38
|
+
appendArgs(...extraArgs) {
|
|
39
|
+
this.decorated.appendArgs(...extraArgs);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
appendArgsFn(argsFn) {
|
|
43
|
+
this.decorated.appendArgsFn(argsFn);
|
|
40
44
|
return this;
|
|
41
45
|
}
|
|
42
46
|
lazy() {
|
package/cjm/provider/Provider.js
CHANGED
|
@@ -38,8 +38,14 @@ class Provider {
|
|
|
38
38
|
this.isLazy = true;
|
|
39
39
|
return this;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
appendArgs(...extraArgs) {
|
|
42
|
+
const parentFn = this.argsFn;
|
|
43
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...extraArgs];
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
appendArgsFn(argsFn) {
|
|
47
|
+
const parentFn = this.argsFn;
|
|
48
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...argsFn(container, options)];
|
|
43
49
|
return this;
|
|
44
50
|
}
|
|
45
51
|
hasAccess(options) {
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.InjectionToken = void 0;
|
|
4
4
|
exports.isInjectionToken = isInjectionToken;
|
|
5
5
|
const basic_1 = require("../utils/basic");
|
|
6
6
|
class InjectionToken {
|
|
7
7
|
}
|
|
8
8
|
exports.InjectionToken = InjectionToken;
|
|
9
|
-
const setArgs = (...args) => (s) => args;
|
|
10
|
-
exports.setArgs = setArgs;
|
|
11
9
|
function isInjectionToken(target) {
|
|
12
10
|
return basic_1.Is.object(target) && 'resolve' in target && 'args' in target && 'argsFn' in target && 'lazy' in target;
|
|
13
11
|
}
|
package/esm/index.js
CHANGED
|
@@ -9,7 +9,7 @@ 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,
|
|
12
|
+
export { scopeAccess, lazy, appendArgs, appendArgsFn, ProviderDecorator, } from './provider/IProvider';
|
|
13
13
|
export { Provider } from './provider/Provider';
|
|
14
14
|
export { singleton, SingletonProvider } from './provider/SingletonProvider';
|
|
15
15
|
export { decorate } from './provider/DecoratorProvider';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isProviderPipe, registerPipe } from './ProviderPipe';
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
2
|
+
export const appendArgs = (...extraArgs) => registerPipe((p) => p.appendArgs(...extraArgs));
|
|
3
|
+
export const appendArgsFn = (fn) => registerPipe((p) => p.appendArgsFn(fn));
|
|
4
4
|
export const scopeAccess = (rule) => registerPipe((p) => p.setAccessRule(rule));
|
|
5
5
|
export const lazy = () => registerPipe((p) => p.lazy());
|
|
6
6
|
export class ProviderDecorator {
|
|
@@ -28,8 +28,12 @@ export class ProviderDecorator {
|
|
|
28
28
|
this.decorated = this.decorated.pipe(...fns);
|
|
29
29
|
return this;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
this.decorated.
|
|
31
|
+
appendArgs(...extraArgs) {
|
|
32
|
+
this.decorated.appendArgs(...extraArgs);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
appendArgsFn(argsFn) {
|
|
36
|
+
this.decorated.appendArgsFn(argsFn);
|
|
33
37
|
return this;
|
|
34
38
|
}
|
|
35
39
|
lazy() {
|
package/esm/provider/Provider.js
CHANGED
|
@@ -35,8 +35,14 @@ export class Provider {
|
|
|
35
35
|
this.isLazy = true;
|
|
36
36
|
return this;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
appendArgs(...extraArgs) {
|
|
39
|
+
const parentFn = this.argsFn;
|
|
40
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...extraArgs];
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
appendArgsFn(argsFn) {
|
|
44
|
+
const parentFn = this.argsFn;
|
|
45
|
+
this.argsFn = (container, options) => [...parentFn(container, options), ...argsFn(container, options)];
|
|
40
46
|
return this;
|
|
41
47
|
}
|
|
42
48
|
hasAccess(options) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Is } from '../utils/basic';
|
|
2
2
|
export class InjectionToken {
|
|
3
3
|
}
|
|
4
|
-
export const setArgs = (...args) => (s) => args;
|
|
5
4
|
export function isInjectionToken(target) {
|
|
6
5
|
return Is.object(target) && 'resolve' in target && 'args' in target && 'argsFn' in target && 'lazy' in target;
|
|
7
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-ioc-container",
|
|
3
|
-
"version": "50.
|
|
3
|
+
"version": "50.2.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,7 +6,7 @@ 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,
|
|
9
|
+
export { type ResolveDependency, type IProvider, scopeAccess, lazy, appendArgs, appendArgsFn, 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
12
|
export { decorate, type DecorateFn } from './provider/DecoratorProvider';
|
|
@@ -21,11 +21,12 @@ export interface IProvider<T = any> {
|
|
|
21
21
|
hasAccess(options: ScopeAccessOptions): boolean;
|
|
22
22
|
pipe(...mappers: (MapFn<IProvider<T>> | ProviderPipe<T>)[]): IProvider<T>;
|
|
23
23
|
setAccessRule(hasAccessWhen: ScopeAccessRule): this;
|
|
24
|
-
|
|
24
|
+
appendArgs(...extraArgs: unknown[]): this;
|
|
25
|
+
appendArgsFn(argsFn: ArgsFn): this;
|
|
25
26
|
lazy(): this;
|
|
26
27
|
}
|
|
27
|
-
export declare const
|
|
28
|
-
export declare const
|
|
28
|
+
export declare const appendArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>;
|
|
29
|
+
export declare const appendArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
|
|
29
30
|
export declare const scopeAccess: <T>(rule: ScopeAccessRule) => ProviderPipe<T>;
|
|
30
31
|
export declare const lazy: <T>() => ProviderPipe<T>;
|
|
31
32
|
export declare abstract class ProviderDecorator<T> implements IProvider<T> {
|
|
@@ -35,6 +36,7 @@ export declare abstract class ProviderDecorator<T> implements IProvider<T> {
|
|
|
35
36
|
hasAccess(options: ScopeAccessOptions): boolean;
|
|
36
37
|
resolve(container: IContainer, options: ProviderOptions): T;
|
|
37
38
|
pipe(...mappers: (MapFn<IProvider<T>> | ProviderPipe<T>)[]): IProvider<T>;
|
|
38
|
-
|
|
39
|
+
appendArgs(...extraArgs: unknown[]): this;
|
|
40
|
+
appendArgsFn(argsFn: ArgsFn): this;
|
|
39
41
|
lazy(): this;
|
|
40
42
|
}
|
|
@@ -16,6 +16,7 @@ export declare class Provider<T = any> implements IProvider<T> {
|
|
|
16
16
|
resolve(container: IContainer, { args, lazy }?: ProviderOptions): T;
|
|
17
17
|
setAccessRule(predicate: ScopeAccessRule): this;
|
|
18
18
|
lazy(): this;
|
|
19
|
-
|
|
19
|
+
appendArgs(...extraArgs: unknown[]): this;
|
|
20
|
+
appendArgsFn(argsFn: ArgsFn): this;
|
|
20
21
|
hasAccess(options: ScopeAccessOptions): boolean;
|
|
21
22
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type IContainer } from '../container/IContainer';
|
|
2
|
-
import {
|
|
2
|
+
import { ProviderOptions } from '../provider/IProvider';
|
|
3
3
|
export declare abstract class InjectionToken<T = any> {
|
|
4
4
|
abstract resolve(s: IContainer, options?: ProviderOptions): T;
|
|
5
5
|
abstract args(...deps: unknown[]): InjectionToken<T>;
|
|
6
6
|
abstract argsFn(getArgsFn: (s: IContainer) => unknown[]): InjectionToken<T>;
|
|
7
7
|
abstract lazy(): InjectionToken<T>;
|
|
8
8
|
}
|
|
9
|
-
export declare const setArgs: (...args: unknown[]) => ArgsFn;
|
|
10
9
|
export declare function isInjectionToken(target: unknown): target is InjectionToken;
|