ts-ioc-container 50.0.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 +225 -26
- package/cjm/index.js +4 -2
- package/cjm/provider/IProvider.js +13 -1
- package/cjm/provider/Provider.js +10 -0
- package/esm/index.js +1 -1
- package/esm/provider/IProvider.js +10 -0
- package/esm/provider/Provider.js +10 -0
- package/package.json +3 -3
- package/typings/index.d.ts +1 -1
- package/typings/provider/IProvider.d.ts +6 -0
- package/typings/provider/Provider.d.ts +2 -0
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
|
+
*/
|
|
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
|
|
|
@@ -1826,7 +1980,7 @@ 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
1985
|
- `provider(setArgs('someArgument'))`
|
|
1832
1986
|
- `provider(setArgsFn((container) => [container.resolve(Logger), 'someValue']))`
|
|
@@ -1862,6 +2016,8 @@ const userToken = ApiToken.args('https://users.api.com', 1000);
|
|
|
1862
2016
|
|
|
1863
2017
|
```typescript
|
|
1864
2018
|
import {
|
|
2019
|
+
addArgs,
|
|
2020
|
+
addArgsFn,
|
|
1865
2021
|
args,
|
|
1866
2022
|
bindTo,
|
|
1867
2023
|
Container,
|
|
@@ -1884,7 +2040,7 @@ import {
|
|
|
1884
2040
|
* - Generic classes (like Repositories) that need to know what they are managing
|
|
1885
2041
|
*/
|
|
1886
2042
|
|
|
1887
|
-
describe('
|
|
2043
|
+
describe('IProvider', function () {
|
|
1888
2044
|
function createContainer() {
|
|
1889
2045
|
return new Container();
|
|
1890
2046
|
}
|
|
@@ -1941,6 +2097,49 @@ 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(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
|
+
|
|
1944
2143
|
describe('Generic Repositories (Advanced Pattern)', () => {
|
|
1945
2144
|
// This example demonstrates how to implement the Generic Repository pattern
|
|
1946
2145
|
// where a generic EntityManager needs to know WHICH repository to use.
|
package/cjm/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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;
|
|
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
|
+
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");
|
|
7
7
|
Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
|
|
@@ -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; } });
|
|
@@ -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
|
}
|
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, 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
15
|
export { decorate } from './provider/DecoratorProvider';
|
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-ioc-container",
|
|
3
|
-
"version": "50.
|
|
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,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, 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
12
|
export { decorate, type DecorateFn } from './provider/DecoratorProvider';
|
|
@@ -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
|
}
|