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 CHANGED
@@ -36,7 +36,7 @@ no global container objects.
36
36
  - [Setup](#setup)
37
37
  - [Quickstart](#quickstart)
38
38
  - [Cheatsheet](#cheatsheet)
39
- - [Spec-driven workflow](#spec-driven-workflow)
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) `setArgs` `setArgsFn`
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`, `setArgsFn`, ...). Use the fluent `bindToKey` chain only
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
- ## Spec-driven workflow
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
- ```typescript
188
- const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger).pipe(singleton()));
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
- function handleRequest() {
191
- const requestScope = app.createScope({ tags: ['request'] });
192
- const logger = requestScope.resolve<Logger>('Logger');
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
- ```typescript
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
- constructor(@inject('QueueClient') private queue: QueueClient) {}
271
+ readonly result: string;
272
+
273
+ constructor(@inject('QueueClient') private queue: QueueClient) {
274
+ this.result = this.queue.dequeue();
275
+ }
205
276
  }
206
277
 
207
- const worker = new Container({ tags: ['worker'] })
208
- .addRegistration(R.fromClass(QueueClient))
209
- .addRegistration(R.fromClass(JobHandler));
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
- ```typescript
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
- /* fetch flags */
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()) private flags: FeatureFlags) {}
339
+ constructor(@inject(select.token('FeatureFlags').lazy()) public flags: FeatureFlags) {}
224
340
  }
225
341
 
226
- const page = new Container({ tags: ['page'] })
227
- .addRegistration(R.fromClass(FeatureFlags))
228
- .addRegistration(R.fromClass(Widget));
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.setArgs(() => ['https://api.example.com', 5000]),
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
- setArgs,
1590
- setArgsFn,
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(setArgs('/var/data')),
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
- setArgsFn((scope) => [`postgres://${scope.resolve('DbPath')}`]),
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. This is what `ArgsProvider` is for.
1983
+ Sometimes you want to bind some arguments to provider.
1830
1984
 
1831
- - `provider(setArgs('someArgument'))`
1832
- - `provider(setArgsFn((container) => [container.resolve(Logger), 'someValue']))`
1833
- - `Provider.fromClass(Logger).pipe(setArgs('someArgument'))`
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('ArgsProvider', function () {
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(setArgs('/var/log/app.log'))
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('prioritizes provided args over resolve args', function () {
1908
- // 'FixedContext' wins over any runtime args
1909
- @register(setArgs('FixedContext'))
2061
+ it('appends configured args after resolve args', function () {
2062
+ @register(appendArgs('ConfiguredContext'))
1910
2063
  class Logger {
1911
- constructor(@inject(args(0)) public context: string) {}
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.context).toBe('FixedContext');
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(setArgsFn((scope) => [scope.resolve<Config>('Config').env]))
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.setArgs = exports.setArgsFn = 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;
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, "setArgsFn", { enumerable: true, get: function () { return IProvider_1.setArgsFn; } });
31
- Object.defineProperty(exports, "setArgs", { enumerable: true, get: function () { return IProvider_1.setArgs; } });
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.setArgsFn = exports.setArgs = void 0;
3
+ exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.appendArgsFn = exports.appendArgs = void 0;
4
4
  const ProviderPipe_1 = require("./ProviderPipe");
5
- const setArgs = (...extraArgs) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(() => extraArgs));
6
- exports.setArgs = setArgs;
7
- const setArgsFn = (fn) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(fn));
8
- exports.setArgsFn = setArgsFn;
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
- setArgs(argsFn) {
39
- this.decorated.setArgs(argsFn);
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() {
@@ -38,8 +38,14 @@ class Provider {
38
38
  this.isLazy = true;
39
39
  return this;
40
40
  }
41
- setArgs(argsFn) {
42
- this.argsFn = argsFn;
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.setArgs = exports.InjectionToken = void 0;
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, setArgsFn, setArgs, ProviderDecorator, } from './provider/IProvider';
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 setArgs = (...extraArgs) => registerPipe((p) => p.setArgs(() => extraArgs));
3
- export const setArgsFn = (fn) => registerPipe((p) => p.setArgs(fn));
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
- setArgs(argsFn) {
32
- this.decorated.setArgs(argsFn);
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() {
@@ -35,8 +35,14 @@ export class Provider {
35
35
  this.isLazy = true;
36
36
  return this;
37
37
  }
38
- setArgs(argsFn) {
39
- this.argsFn = argsFn;
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.0.0",
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.29.1",
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.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",
@@ -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, 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
- setArgs(argsFn: ArgsFn): this;
24
+ appendArgs(...extraArgs: unknown[]): this;
25
+ appendArgsFn(argsFn: ArgsFn): this;
25
26
  lazy(): this;
26
27
  }
27
- export declare const setArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>;
28
- export declare const setArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
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
- setArgs(argsFn: ArgsFn): this;
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
- setArgs(argsFn: ArgsFn): this;
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 { ArgsFn, ProviderOptions } from '../provider/IProvider';
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;