ts-ioc-container 57.0.0 → 57.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 CHANGED
@@ -43,6 +43,7 @@ provider pipelines, aliases, and custom injector strategies.
43
43
  - [Proxy](#proxy)
44
44
  - [Provider](#provider) `provider`
45
45
  - [Singleton](#singleton) `singleton`
46
+ - [Auto resolve](#auto-resolve) `autoResolve`
46
47
  - [Arguments](#arguments) `appendArgs` `appendArgsFn`
47
48
  - [Visibility](#visibility) `visible`
48
49
  - [Alias](#alias) `asAlias`
@@ -153,6 +154,7 @@ describe('Quickstart', function () {
153
154
  - Register value: `R.fromValue(config).bindTo('Config')`
154
155
  - Register factory: `R.fromFn((c) => createX(c)).bindTo('X')`
155
156
  - Singleton: `@register(singleton())`
157
+ - Eager service: `@register(autoResolve())` + `container.useModule(new AutoResolveModule())`
156
158
  - Scoped registration: `@register(scope((s) => s.hasTag('request')))`
157
159
  - Resolve by alias: `container.resolveByAlias('Alias')`
158
160
  - Current scope token: `select.scope.current`
@@ -1616,6 +1618,153 @@ describe('Singleton', function () {
1616
1618
 
1617
1619
  ```
1618
1620
 
1621
+ ### Auto resolve
1622
+
1623
+ Some services are never injected anywhere - they subscribe to a queue, start a
1624
+ timer, or warm a cache as soon as their scope exists. Lazy resolution would
1625
+ never create them, because nothing asks for them.
1626
+
1627
+ - `@register(autoResolve())` marks a provider as eager
1628
+ - `container.useModule(new AutoResolveModule())` enables eager resolution for
1629
+ every scope created afterwards
1630
+ - `container.autoResolve()` resolves the eager providers of one container on demand
1631
+
1632
+ Both accept an optional `AutoResolveOptions` (`{ args?: unknown[] }`) whose `args`
1633
+ are forwarded to every eagerly resolved provider, exactly as `resolve` forwards
1634
+ them - so they reach `@inject(arg(0))` parameters, `scopeAccess` rules and the
1635
+ `singleton()` cache key. `new AutoResolveModule({ args })` applies the same args
1636
+ to every created scope; call `scope.autoResolve({ args })` yourself when the
1637
+ value differs per scope.
1638
+
1639
+ > [!IMPORTANT]
1640
+ > `autoResolve()` on its own does nothing - the container has to opt in with
1641
+ > `AutoResolveModule`. The container the module is applied to is not a created
1642
+ > scope, so call `container.autoResolve()` explicitly to eagerly resolve its own
1643
+ > providers.
1644
+
1645
+ Eager resolution goes through the provider unchanged, so `singleton()` still
1646
+ returns the eagerly created instance later, and providers which are not
1647
+ registered in the created scope (`scope(...)`) or deny access to it
1648
+ (`scopeAccess(...)`) are skipped.
1649
+
1650
+ ```typescript
1651
+ import 'reflect-metadata';
1652
+ import {
1653
+ arg,
1654
+ autoResolve,
1655
+ AutoResolveModule,
1656
+ bindTo,
1657
+ Container,
1658
+ type IContainer,
1659
+ inject,
1660
+ register,
1661
+ Registration as R,
1662
+ scope,
1663
+ singleton,
1664
+ } from 'ts-ioc-container';
1665
+
1666
+ /**
1667
+ * User Management Domain - Eager Services
1668
+ *
1669
+ * Some services are never injected anywhere: they subscribe to a queue, start a
1670
+ * timer, or warm a cache as soon as their scope exists. Nothing resolves them,
1671
+ * so lazy resolution would never create them at all.
1672
+ *
1673
+ * `autoResolve()` marks such a provider as eager, and `AutoResolveModule`
1674
+ * resolves every eager provider of a scope right after the scope is created.
1675
+ */
1676
+
1677
+ const auditTrail: string[] = [];
1678
+ const openedLogs: string[] = [];
1679
+
1680
+ // Started for every request, even though no other class injects it
1681
+ @register(bindTo('IRequestAuditor'), scope((s) => s.hasTag('request')), autoResolve(), singleton())
1682
+ class RequestAuditor {
1683
+ constructor() {
1684
+ auditTrail.push('request started');
1685
+ }
1686
+ }
1687
+
1688
+ // Eager too, but parameterized - the args come from whoever triggers eager resolution
1689
+ @register(bindTo('IRequestLog'), scope((s) => s.hasTag('request')), autoResolve())
1690
+ class RequestLog {
1691
+ constructor(@inject(arg(0)) readonly requestId: string = 'anonymous') {
1692
+ openedLogs.push(requestId);
1693
+ }
1694
+ }
1695
+
1696
+ // Resolved on demand, the usual way
1697
+ @register(bindTo('IUserRepository'), singleton())
1698
+ class UserRepository {
1699
+ findById(id: string): string {
1700
+ return `user_${id}`;
1701
+ }
1702
+ }
1703
+
1704
+ describe('Auto resolve', function () {
1705
+ function createAppContainer(options: { args?: unknown[] } = {}): IContainer {
1706
+ return new Container({ tags: ['application'] })
1707
+ .useModule(new AutoResolveModule(options))
1708
+ .addRegistration(R.fromClass(RequestAuditor))
1709
+ .addRegistration(R.fromClass(RequestLog))
1710
+ .addRegistration(R.fromClass(UserRepository));
1711
+ }
1712
+
1713
+ beforeEach(() => {
1714
+ auditTrail.length = 0;
1715
+ openedLogs.length = 0;
1716
+ });
1717
+
1718
+ it('should create eager services as soon as a scope is created', function () {
1719
+ const app = createAppContainer();
1720
+
1721
+ // Nothing has been created yet - the application container is not a created scope
1722
+ expect(auditTrail).toEqual([]);
1723
+
1724
+ app.createScope({ tags: ['request'] });
1725
+ app.createScope({ tags: ['request'] });
1726
+
1727
+ // One auditor per request scope, without anybody resolving it
1728
+ expect(auditTrail).toEqual(['request started', 'request started']);
1729
+ });
1730
+
1731
+ it('should reuse the eagerly created instance', function () {
1732
+ const requestScope = createAppContainer().createScope({ tags: ['request'] });
1733
+
1734
+ const auditor = requestScope.resolve<RequestAuditor>('IRequestAuditor');
1735
+
1736
+ expect(auditTrail).toEqual(['request started']);
1737
+ expect(requestScope.resolve<RequestAuditor>('IRequestAuditor')).toBe(auditor);
1738
+ });
1739
+
1740
+ it('should treat resolve options as optional', function () {
1741
+ createAppContainer().createScope({ tags: ['request'] });
1742
+
1743
+ expect(openedLogs).toEqual(['anonymous']);
1744
+ });
1745
+
1746
+ it('should forward args to every eagerly resolved provider', function () {
1747
+ // The same args reach every scope the module creates...
1748
+ createAppContainer({ args: ['req-42'] }).createScope({ tags: ['request'] });
1749
+
1750
+ expect(openedLogs).toEqual(['req-42']);
1751
+
1752
+ // ...or pass a per-scope value by calling autoResolve yourself
1753
+ const requestScope = createAppContainer().createScope({ tags: ['request'] });
1754
+ requestScope.autoResolve({ args: ['req-43'] });
1755
+
1756
+ expect(openedLogs).toEqual(['req-42', 'anonymous', 'req-43']);
1757
+ });
1758
+
1759
+ it('should leave other providers lazy', function () {
1760
+ const requestScope = createAppContainer().createScope({ tags: ['request'] });
1761
+
1762
+ expect(requestScope.resolve<UserRepository>('IUserRepository').findById('1')).toBe('user_1');
1763
+ });
1764
+ });
1765
+
1766
+ ```
1767
+
1619
1768
  ### Arguments
1620
1769
 
1621
1770
  Sometimes you want to bind some arguments to provider.
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AutoResolveModule = void 0;
4
+ class AutoResolveModule {
5
+ options;
6
+ constructor(options = {}) {
7
+ this.options = options;
8
+ }
9
+ applyTo(container) {
10
+ container.addOnScopeCreatedHook((scope) => scope.autoResolve(this.options));
11
+ }
12
+ }
13
+ exports.AutoResolveModule = AutoResolveModule;
@@ -20,6 +20,7 @@ class Container {
20
20
  injector;
21
21
  onConstructHookList = [];
22
22
  onDisposeHookList = [];
23
+ onScopeCreatedHookList = [];
23
24
  constructor(options = {}) {
24
25
  this.injector = options.injector ?? new MetadataInjector_1.MetadataInjector();
25
26
  this.parent = options.parent ?? new EmptyContainer_1.EmptyContainer();
@@ -73,13 +74,26 @@ class Container {
73
74
  this.validateContainer();
74
75
  const scope = new Container({ injector: this.injector, parent: this, tags })
75
76
  .addOnConstructHook(...this.onConstructHookList)
76
- .addOnDisposeHook(...this.onDisposeHookList);
77
+ .addOnDisposeHook(...this.onDisposeHookList)
78
+ .addOnScopeCreatedHook(...this.onScopeCreatedHookList);
77
79
  for (const registration of this.getRegistrations()) {
78
80
  registration.applyTo(scope);
79
81
  }
80
82
  this.scopes.push(scope);
83
+ for (const onScopeCreated of this.onScopeCreatedHookList) {
84
+ onScopeCreated(scope);
85
+ }
81
86
  return scope;
82
87
  }
88
+ autoResolve({ args = [] } = {}) {
89
+ this.validateContainer();
90
+ for (const provider of this.providers.values()) {
91
+ if (provider.isAutoResolvable() && provider.hasAccess({ invocationScope: this, providerScope: this, args })) {
92
+ provider.resolve(this, { args });
93
+ }
94
+ }
95
+ return this;
96
+ }
83
97
  dispose() {
84
98
  this.validateContainer();
85
99
  this.isDisposed = true;
@@ -97,6 +111,7 @@ class Container {
97
111
  this.instances = [];
98
112
  this.registrations = [];
99
113
  this.onConstructHookList.length = 0;
114
+ this.onScopeCreatedHookList.length = 0;
100
115
  }
101
116
  addRegistration(registration) {
102
117
  this.registrations.push(registration);
@@ -117,6 +132,10 @@ class Container {
117
132
  this.onDisposeHookList.push(...hooks);
118
133
  return this;
119
134
  }
135
+ addOnScopeCreatedHook(...hooks) {
136
+ this.onScopeCreatedHookList.push(...hooks);
137
+ return this;
138
+ }
120
139
  addInstance(instance) {
121
140
  this.instances.push(instance);
122
141
  for (const onConstruct of this.onConstructHookList) {
@@ -27,6 +27,9 @@ class EmptyContainer {
27
27
  createScope() {
28
28
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
29
29
  }
30
+ autoResolve(options) {
31
+ throw new MethodNotImplementedError_1.MethodNotImplementedError();
32
+ }
30
33
  dispose() {
31
34
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
32
35
  }
@@ -67,5 +70,8 @@ class EmptyContainer {
67
70
  addOnConstructHook(...hooks) {
68
71
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
69
72
  }
73
+ addOnScopeCreatedHook(...hooks) {
74
+ throw new MethodNotImplementedError_1.MethodNotImplementedError();
75
+ }
70
76
  }
71
77
  exports.EmptyContainer = EmptyContainer;
package/cjm/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AddOnConstructAsyncHookModule = exports.onConstructAsync = exports.onConstructAsyncHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.prepend = exports.append = exports.prependHooks = exports.appendHooks = exports.hasHooks = exports.hook = exports.getHooks = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.UnexpectedHookResultError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = 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.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toMappedToken = exports.toToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onContainerDisposed = exports.onContainerDisposedHooksRunner = void 0;
3
+ exports.onConstructAsyncHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.prepend = exports.append = exports.prependHooks = exports.appendHooks = exports.hasHooks = exports.hook = exports.getHooks = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.UnexpectedHookResultError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = 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.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toMappedToken = exports.toToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onContainerDisposed = exports.onContainerDisposedHooksRunner = exports.AddOnConstructAsyncHookModule = exports.onConstructAsync = void 0;
5
5
  var IContainer_1 = require("./container/IContainer");
6
6
  Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
7
7
  var Container_1 = require("./container/Container");
8
8
  Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return Container_1.Container; } });
9
+ var AutoResolveModule_1 = require("./container/AutoResolveModule");
10
+ Object.defineProperty(exports, "AutoResolveModule", { enumerable: true, get: function () { return AutoResolveModule_1.AutoResolveModule; } });
9
11
  var EmptyContainer_1 = require("./container/EmptyContainer");
10
12
  Object.defineProperty(exports, "EmptyContainer", { enumerable: true, get: function () { return EmptyContainer_1.EmptyContainer; } });
11
13
  var IInjector_1 = require("./injector/IInjector");
@@ -29,6 +31,7 @@ Object.defineProperty(exports, "bindTo", { enumerable: true, get: function () {
29
31
  Object.defineProperty(exports, "scope", { enumerable: true, get: function () { return IRegistration_1.scope; } });
30
32
  Object.defineProperty(exports, "scopeAccess", { enumerable: true, get: function () { return IRegistration_1.scopeAccess; } });
31
33
  Object.defineProperty(exports, "lazy", { enumerable: true, get: function () { return IRegistration_1.lazy; } });
34
+ Object.defineProperty(exports, "autoResolve", { enumerable: true, get: function () { return IRegistration_1.autoResolve; } });
32
35
  Object.defineProperty(exports, "singleton", { enumerable: true, get: function () { return IRegistration_1.singleton; } });
33
36
  Object.defineProperty(exports, "decorate", { enumerable: true, get: function () { return IRegistration_1.decorate; } });
34
37
  Object.defineProperty(exports, "appendArgs", { enumerable: true, get: function () { return IRegistration_1.appendArgs; } });
@@ -18,6 +18,7 @@ class Provider {
18
18
  accessRules = [];
19
19
  mappers = [];
20
20
  isLazy = false;
21
+ isAutoResolve = false;
21
22
  cache = new Map();
22
23
  getKey;
23
24
  isDisposed = false;
@@ -54,6 +55,14 @@ class Provider {
54
55
  this.isLazy = true;
55
56
  return this;
56
57
  }
58
+ autoResolve() {
59
+ this.isAutoResolve = true;
60
+ return this;
61
+ }
62
+ isAutoResolvable() {
63
+ ProviderDisposedError_1.ProviderDisposedError.assert(!this.isDisposed, 'Provider is already disposed');
64
+ return this.isAutoResolve;
65
+ }
57
66
  addArgsFn(...fns) {
58
67
  this.argsFnList.push(...fns);
59
68
  return this;
@@ -70,6 +79,7 @@ class Provider {
70
79
  dispose() {
71
80
  ProviderDisposedError_1.ProviderDisposedError.assert(!this.isDisposed, 'Provider is already disposed');
72
81
  this.isDisposed = true;
82
+ this.isAutoResolve = false;
73
83
  this.getKey = undefined;
74
84
  this.cache.clear();
75
85
  this.accessRules.splice(0, this.accessRules.length);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.singleton = exports.decorate = exports.lazy = exports.scopeAccess = exports.appendArgsFn = exports.appendArgs = exports.scope = exports.bindTo = exports.register = exports.getTransformers = exports.registerPipe = exports.isProviderPipe = void 0;
3
+ exports.singleton = exports.decorate = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.appendArgsFn = exports.appendArgs = exports.scope = exports.bindTo = exports.register = exports.getTransformers = exports.registerPipe = exports.isProviderPipe = void 0;
4
4
  const IContainer_1 = require("../container/IContainer");
5
5
  const SingleToken_1 = require("../token/SingleToken");
6
6
  const BindToken_1 = require("../token/BindToken");
@@ -44,6 +44,8 @@ const scopeAccess = (rule) => (0, exports.registerPipe)((p) => p.addAccessRule(r
44
44
  exports.scopeAccess = scopeAccess;
45
45
  const lazy = () => (0, exports.registerPipe)((p) => p.lazy());
46
46
  exports.lazy = lazy;
47
+ const autoResolve = () => (0, exports.registerPipe)((p) => p.autoResolve());
48
+ exports.autoResolve = autoResolve;
47
49
  const decorate = (...fns) => (0, exports.registerPipe)((p) => p.map(...fns));
48
50
  exports.decorate = decorate;
49
51
  const singleton = (getCacheKey) => (0, exports.registerPipe)((p) => p.singleton(getCacheKey));
@@ -0,0 +1,9 @@
1
+ export class AutoResolveModule {
2
+ options;
3
+ constructor(options = {}) {
4
+ this.options = options;
5
+ }
6
+ applyTo(container) {
7
+ container.addOnScopeCreatedHook((scope) => scope.autoResolve(this.options));
8
+ }
9
+ }
@@ -17,6 +17,7 @@ export class Container {
17
17
  injector;
18
18
  onConstructHookList = [];
19
19
  onDisposeHookList = [];
20
+ onScopeCreatedHookList = [];
20
21
  constructor(options = {}) {
21
22
  this.injector = options.injector ?? new MetadataInjector();
22
23
  this.parent = options.parent ?? new EmptyContainer();
@@ -70,13 +71,26 @@ export class Container {
70
71
  this.validateContainer();
71
72
  const scope = new Container({ injector: this.injector, parent: this, tags })
72
73
  .addOnConstructHook(...this.onConstructHookList)
73
- .addOnDisposeHook(...this.onDisposeHookList);
74
+ .addOnDisposeHook(...this.onDisposeHookList)
75
+ .addOnScopeCreatedHook(...this.onScopeCreatedHookList);
74
76
  for (const registration of this.getRegistrations()) {
75
77
  registration.applyTo(scope);
76
78
  }
77
79
  this.scopes.push(scope);
80
+ for (const onScopeCreated of this.onScopeCreatedHookList) {
81
+ onScopeCreated(scope);
82
+ }
78
83
  return scope;
79
84
  }
85
+ autoResolve({ args = [] } = {}) {
86
+ this.validateContainer();
87
+ for (const provider of this.providers.values()) {
88
+ if (provider.isAutoResolvable() && provider.hasAccess({ invocationScope: this, providerScope: this, args })) {
89
+ provider.resolve(this, { args });
90
+ }
91
+ }
92
+ return this;
93
+ }
80
94
  dispose() {
81
95
  this.validateContainer();
82
96
  this.isDisposed = true;
@@ -94,6 +108,7 @@ export class Container {
94
108
  this.instances = [];
95
109
  this.registrations = [];
96
110
  this.onConstructHookList.length = 0;
111
+ this.onScopeCreatedHookList.length = 0;
97
112
  }
98
113
  addRegistration(registration) {
99
114
  this.registrations.push(registration);
@@ -114,6 +129,10 @@ export class Container {
114
129
  this.onDisposeHookList.push(...hooks);
115
130
  return this;
116
131
  }
132
+ addOnScopeCreatedHook(...hooks) {
133
+ this.onScopeCreatedHookList.push(...hooks);
134
+ return this;
135
+ }
117
136
  addInstance(instance) {
118
137
  this.instances.push(instance);
119
138
  for (const onConstruct of this.onConstructHookList) {
@@ -24,6 +24,9 @@ export class EmptyContainer {
24
24
  createScope() {
25
25
  throw new MethodNotImplementedError();
26
26
  }
27
+ autoResolve(options) {
28
+ throw new MethodNotImplementedError();
29
+ }
27
30
  dispose() {
28
31
  throw new MethodNotImplementedError();
29
32
  }
@@ -64,4 +67,7 @@ export class EmptyContainer {
64
67
  addOnConstructHook(...hooks) {
65
68
  throw new MethodNotImplementedError();
66
69
  }
70
+ addOnScopeCreatedHook(...hooks) {
71
+ throw new MethodNotImplementedError();
72
+ }
67
73
  }
package/esm/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  export { isDependencyKey, } from './container/IContainer.js';
2
2
  export { Container } from './container/Container.js';
3
+ export { AutoResolveModule } from './container/AutoResolveModule.js';
3
4
  export { EmptyContainer } from './container/EmptyContainer.js';
4
5
  export { Injector } from './injector/IInjector.js';
5
6
  export { MetadataInjector, inject, arg, args, argsFn, resolveArgs } from './injector/MetadataInjector.js';
6
7
  export { SimpleInjector } from './injector/SimpleInjector.js';
7
8
  export { ProxyInjector } from './injector/ProxyInjector.js';
8
9
  export { Provider } from './provider/Provider.js';
9
- export { register, bindTo, scope, scopeAccess, lazy, singleton, decorate, appendArgs, appendArgsFn, } from './registration/IRegistration.js';
10
+ export { register, bindTo, scope, scopeAccess, lazy, autoResolve, singleton, decorate, appendArgs, appendArgsFn, } from './registration/IRegistration.js';
10
11
  export { Registration } from './registration/Registration.js';
11
12
  export { ContainerError } from './errors/ContainerError.js';
12
13
  export { DependencyNotFoundError } from './errors/DependencyNotFoundError.js';
@@ -15,6 +15,7 @@ export class Provider {
15
15
  accessRules = [];
16
16
  mappers = [];
17
17
  isLazy = false;
18
+ isAutoResolve = false;
18
19
  cache = new Map();
19
20
  getKey;
20
21
  isDisposed = false;
@@ -51,6 +52,14 @@ export class Provider {
51
52
  this.isLazy = true;
52
53
  return this;
53
54
  }
55
+ autoResolve() {
56
+ this.isAutoResolve = true;
57
+ return this;
58
+ }
59
+ isAutoResolvable() {
60
+ ProviderDisposedError.assert(!this.isDisposed, 'Provider is already disposed');
61
+ return this.isAutoResolve;
62
+ }
54
63
  addArgsFn(...fns) {
55
64
  this.argsFnList.push(...fns);
56
65
  return this;
@@ -67,6 +76,7 @@ export class Provider {
67
76
  dispose() {
68
77
  ProviderDisposedError.assert(!this.isDisposed, 'Provider is already disposed');
69
78
  this.isDisposed = true;
79
+ this.isAutoResolve = false;
70
80
  this.getKey = undefined;
71
81
  this.cache.clear();
72
82
  this.accessRules.splice(0, this.accessRules.length);
@@ -31,5 +31,6 @@ export const appendArgs = (...extraArgs) => registerPipe((p) => p.addArgsFn((_,
31
31
  export const appendArgsFn = (fn) => registerPipe((p) => p.addArgsFn((scope, options) => [...(options?.args ?? []), ...fn(scope, options)]));
32
32
  export const scopeAccess = (rule) => registerPipe((p) => p.addAccessRule(rule));
33
33
  export const lazy = () => registerPipe((p) => p.lazy());
34
+ export const autoResolve = () => registerPipe((p) => p.autoResolve());
34
35
  export const decorate = (...fns) => registerPipe((p) => p.map(...fns));
35
36
  export const singleton = (getCacheKey) => registerPipe((p) => p.singleton(getCacheKey));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "57.0.0",
3
+ "version": "57.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
  "publishConfig": {
6
6
  "access": "public",
@@ -0,0 +1,6 @@
1
+ import { type AutoResolveOptions, type IContainer, type IContainerModule } from './IContainer.js';
2
+ export declare class AutoResolveModule implements IContainerModule {
3
+ private readonly options;
4
+ constructor(options?: AutoResolveOptions);
5
+ applyTo(container: IContainer): void;
6
+ }
@@ -1,4 +1,4 @@
1
- import { type CreateScopeOptions, type DependencyKey, type IContainer, type IContainerModule, type RegisterOptions, ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
1
+ import { type AutoResolveOptions, type CreateScopeOptions, type DependencyKey, type IContainer, type IContainerModule, type OnScopeCreatedHook, type RegisterOptions, ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
2
2
  import { type IInjector } from '../injector/IInjector.js';
3
3
  import { type IProvider } from '../provider/IProvider.js';
4
4
  import { type IRegistration } from '../registration/IRegistration.js';
@@ -17,6 +17,7 @@ export declare class Container implements IContainer {
17
17
  private readonly injector;
18
18
  private readonly onConstructHookList;
19
19
  private readonly onDisposeHookList;
20
+ private readonly onScopeCreatedHookList;
20
21
  constructor(options?: {
21
22
  injector?: IInjector;
22
23
  parent?: IContainer;
@@ -27,12 +28,14 @@ export declare class Container implements IContainer {
27
28
  resolveByAlias<T>(alias: DependencyKey, { args, child, lazy, excludedKeys }?: ResolveManyOptions): T[];
28
29
  resolveOneByAlias<T>(alias: DependencyKey, { args, child, lazy }?: ResolveOneOptions): T;
29
30
  createScope({ tags }?: CreateScopeOptions): IContainer;
31
+ autoResolve({ args }?: AutoResolveOptions): this;
30
32
  dispose(): void;
31
33
  addRegistration(registration: IRegistration): this;
32
34
  getRegistrations(): IRegistration[];
33
35
  hasRegistration(key: DependencyKey): boolean;
34
36
  addOnConstructHook(...hooks: OnConstructHook[]): this;
35
37
  addOnDisposeHook(...hooks: OnDisposeHook[]): this;
38
+ addOnScopeCreatedHook(...hooks: OnScopeCreatedHook[]): this;
36
39
  addInstance(instance: Instance): void;
37
40
  getScopes(): IContainer[];
38
41
  hasInstance(instance: object): boolean;
@@ -1,4 +1,4 @@
1
- import { type DependencyKey, type IContainer, type IContainerModule, type ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
1
+ import { type AutoResolveOptions, type DependencyKey, type IContainer, type IContainerModule, type OnScopeCreatedHook, type ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
2
2
  import { type IProvider } from '../provider/IProvider.js';
3
3
  import { type IRegistration } from '../registration/IRegistration.js';
4
4
  import { OnDisposeHook } from '../hooks/onContainerDisposed.js';
@@ -13,6 +13,7 @@ export declare class EmptyContainer implements IContainer {
13
13
  getInstances(): never[];
14
14
  hasInstance(instance: object): boolean;
15
15
  createScope(): IContainer;
16
+ autoResolve(options?: AutoResolveOptions): this;
16
17
  dispose(): void;
17
18
  register(key: DependencyKey, value: IProvider): this;
18
19
  hasTag(tag: Tag): boolean;
@@ -27,4 +28,5 @@ export declare class EmptyContainer implements IContainer {
27
28
  resolveOneByAlias<T>(alias: DependencyKey, options?: ResolveOneOptions): T;
28
29
  addOnDisposeHook(...hooks: OnDisposeHook[]): this;
29
30
  addOnConstructHook(...hooks: OnConstructHook[]): this;
31
+ addOnScopeCreatedHook(...hooks: OnScopeCreatedHook[]): this;
30
32
  }
@@ -2,6 +2,7 @@ import { type IProvider, ProviderOptions } from '../provider/IProvider.js';
2
2
  import { type IRegistration } from '../registration/IRegistration.js';
3
3
  import { OnConstructHook } from '../hooks/onConstruct.js';
4
4
  import { OnDisposeHook } from '../hooks/onContainerDisposed.js';
5
+ import { type WithArgs } from '../injector/IInjector.js';
5
6
  import { type constructor, Instance } from '../utils/basic.js';
6
7
  export type DependencyKey = string | symbol;
7
8
  export declare function isDependencyKey(target: unknown): target is DependencyKey;
@@ -28,6 +29,8 @@ export interface IContainerModule {
28
29
  applyTo(container: IContainer): void;
29
30
  }
30
31
  export type CreateScopeOptions = Partial<WithTags>;
32
+ export type AutoResolveOptions = Partial<WithArgs>;
33
+ export type OnScopeCreatedHook = (scope: IContainer) => void;
31
34
  export type RegisterOptions = {
32
35
  aliases?: DependencyKey[];
33
36
  };
@@ -35,6 +38,7 @@ export interface IContainer extends Tagged {
35
38
  readonly isDisposed: boolean;
36
39
  addOnConstructHook(...hooks: OnConstructHook[]): this;
37
40
  addOnDisposeHook(...hooks: OnDisposeHook[]): this;
41
+ addOnScopeCreatedHook(...hooks: OnScopeCreatedHook[]): this;
38
42
  register(key: DependencyKey, value: IProvider, options?: RegisterOptions): this;
39
43
  addRegistration(registration: IRegistration): this;
40
44
  getRegistrations(): IRegistration[];
@@ -43,6 +47,7 @@ export interface IContainer extends Tagged {
43
47
  resolveByAlias<T>(alias: DependencyKey, options?: ResolveManyOptions): T[];
44
48
  resolveOneByAlias<T>(alias: DependencyKey, options?: ResolveOneOptions): T;
45
49
  createScope(options?: CreateScopeOptions): IContainer;
50
+ autoResolve(options?: AutoResolveOptions): this;
46
51
  getScopes(): IContainer[];
47
52
  getScopeByInstanceOrFail(instance: object): IContainer;
48
53
  removeScope(child: IContainer): void;
@@ -1,5 +1,6 @@
1
- export { type IContainer, type Resolvable, type IContainerModule, type DependencyKey, type Tag, type Tagged, type ResolveOneOptions, type ResolveManyOptions, isDependencyKey, } from './container/IContainer.js';
1
+ export { type IContainer, type Resolvable, type IContainerModule, type DependencyKey, type Tag, type Tagged, type ResolveOneOptions, type ResolveManyOptions, type OnScopeCreatedHook, type AutoResolveOptions, isDependencyKey, } from './container/IContainer.js';
2
2
  export { Container } from './container/Container.js';
3
+ export { AutoResolveModule } from './container/AutoResolveModule.js';
3
4
  export { EmptyContainer } from './container/EmptyContainer.js';
4
5
  export { type IInjector, type InjectOptions, type IInjectFnResolver, Injector } from './injector/IInjector.js';
5
6
  export { MetadataInjector, inject, arg, args, argsFn, resolveArgs } from './injector/MetadataInjector.js';
@@ -7,7 +8,7 @@ export { SimpleInjector } from './injector/SimpleInjector.js';
7
8
  export { ProxyInjector } from './injector/ProxyInjector.js';
8
9
  export { type ResolveDependency, type IProvider, type DecorateFn, type ArgsFn, type ProviderOptions, type GetCacheKey, type ScopeAccessOptions, type ScopeAccessRule, } from './provider/IProvider.js';
9
10
  export { Provider } from './provider/Provider.js';
10
- export { type IRegistration, type ReturnTypeOfRegistration, type ScopeMatchRule, type ProviderPipe, register, bindTo, scope, scopeAccess, lazy, singleton, decorate, appendArgs, appendArgsFn, } from './registration/IRegistration.js';
11
+ export { type IRegistration, type ReturnTypeOfRegistration, type ScopeMatchRule, type ProviderPipe, register, bindTo, scope, scopeAccess, lazy, autoResolve, singleton, decorate, appendArgs, appendArgsFn, } from './registration/IRegistration.js';
11
12
  export { Registration } from './registration/Registration.js';
12
13
  export { ContainerError } from './errors/ContainerError.js';
13
14
  export { DependencyNotFoundError } from './errors/DependencyNotFoundError.js';
@@ -21,6 +21,8 @@ export interface IProvider<T = any> {
21
21
  addAccessRule(...rules: ScopeAccessRule[]): this;
22
22
  addArgsFn(argsFn: ArgsFn): this;
23
23
  lazy(): this;
24
+ autoResolve(): this;
25
+ isAutoResolvable(): boolean;
24
26
  singleton(getCacheKey?: GetCacheKey): this;
25
27
  dispose(): void;
26
28
  }
@@ -10,6 +10,7 @@ export declare class Provider<T = any> implements IProvider<T> {
10
10
  private readonly accessRules;
11
11
  private readonly mappers;
12
12
  private isLazy;
13
+ private isAutoResolve;
13
14
  private cache;
14
15
  private getKey;
15
16
  private isDisposed;
@@ -19,6 +20,8 @@ export declare class Provider<T = any> implements IProvider<T> {
19
20
  map(...mappers: DecorateFn<T>[]): this;
20
21
  addAccessRule(...rules: ScopeAccessRule[]): this;
21
22
  lazy(): this;
23
+ autoResolve(): this;
24
+ isAutoResolvable(): boolean;
22
25
  addArgsFn(...fns: ArgsFn[]): this;
23
26
  hasAccess(options: ScopeAccessOptions): boolean;
24
27
  singleton(getCacheKey?: GetCacheKey): this;
@@ -27,5 +27,6 @@ export declare const appendArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>
27
27
  export declare const appendArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
28
28
  export declare const scopeAccess: <T>(rule: ScopeAccessRule) => ProviderPipe<T>;
29
29
  export declare const lazy: <T>() => ProviderPipe<T>;
30
+ export declare const autoResolve: <T>() => ProviderPipe<T>;
30
31
  export declare const decorate: (...fns: DecorateFn[]) => ProviderPipe<unknown>;
31
32
  export declare const singleton: <T = unknown>(getCacheKey?: GetCacheKey) => ProviderPipe<T>;