ts-ioc-container 72.0.0 → 72.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
@@ -52,6 +52,7 @@ provider pipelines, aliases, and custom injector strategies.
52
52
  - [Registration](#registration) `@register`
53
53
  - [Token](#token) `bindTo`
54
54
  - [Scope](#scope) `scope`
55
+ - [Composing decorators](#composing-decorators) `createComposeClassDecorator`
55
56
  - [Module](#module)
56
57
  - [Hook](#hook) `@hook`
57
58
  - [Hook domains](#hook-domains) `ScopeHook` `InjectorHook` `ProviderHook`
@@ -164,6 +165,7 @@ describe('Quickstart', function () {
164
165
  - Custom inject function: `@inject(({ scope, args }) => ...)`
165
166
  - Map an injected value: `@inject(pipe(by('Key'), sanitize(), validate()))`
166
167
  - Property inject: `@hook('onInit', injectProp(by('Key')))`
168
+ - Name a decorator stack: `const repository = (token) => createComposeClassDecorator(register(token, singleton()), addClassMeta('injection-token', () => token))`
167
169
 
168
170
  > [!TIP]
169
171
  > For classes, prefer the `@register(bindTo('Key'))` decorator over the fluent
@@ -2793,6 +2795,98 @@ describe('Scopes', function () {
2793
2795
 
2794
2796
  ```
2795
2797
 
2798
+ ### Composing decorators
2799
+
2800
+ A decorator stack repeated on every class of a layer is worth a name.
2801
+ `createComposeClassDecorator(...decorators)` gives it one: it returns a single
2802
+ `ClassDecorator` which applies the stack **bottom-up, exactly as stacking would**,
2803
+ so `@createComposeClassDecorator(a, b)` behaves like `@a @b`. A decorator which
2804
+ returns a replacement class hands it to the next one, the way the runtime threads
2805
+ a stack.
2806
+
2807
+ `createComposeMethodDecorator` and `createComposeParameterDecorator` do the same
2808
+ for methods and constructor parameters. The method form threads the property
2809
+ descriptor, so wrapping decorators (`@once`, `@throttle`, ...) compose too.
2810
+
2811
+ ```typescript
2812
+ import {
2813
+ addClassMeta,
2814
+ Container,
2815
+ createComposeClassDecorator,
2816
+ createComposeParameterDecorator,
2817
+ getClassMeta,
2818
+ inject,
2819
+ by,
2820
+ addParamLabel,
2821
+ getParamLabels,
2822
+ register,
2823
+ Registration as R,
2824
+ scope,
2825
+ singleton,
2826
+ SingleToken,
2827
+ } from 'ts-ioc-container';
2828
+
2829
+ /**
2830
+ * A decorator stack repeated on every class of a layer is worth a name.
2831
+ * `createComposeClassDecorator` (and its `createComposeMethodDecorator` /
2832
+ * `createComposeParameterDecorator` siblings) turns one into a single decorator,
2833
+ * applied bottom-up exactly as stacking would.
2834
+ */
2835
+ describe('composing decorators', () => {
2836
+ const INJECTION_TOKEN = 'injection-token';
2837
+
2838
+ // Every repository binds to its own token, is an application-scoped singleton,
2839
+ // and remembers the token it was registered under.
2840
+ const repository = <T>(token: SingleToken<T>) =>
2841
+ createComposeClassDecorator(
2842
+ register(
2843
+ token,
2844
+ scope((s) => s.hasTag('application')),
2845
+ singleton(),
2846
+ ),
2847
+ addClassMeta(INJECTION_TOKEN, () => token),
2848
+ );
2849
+
2850
+ it('should apply the whole stack the composed decorator stands for', () => {
2851
+ const UserRepositoryToken = new SingleToken<UserRepository>('IUserRepository');
2852
+
2853
+ @repository(UserRepositoryToken)
2854
+ class UserRepository {
2855
+ findById(id: string) {
2856
+ return { id };
2857
+ }
2858
+ }
2859
+
2860
+ const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(UserRepository));
2861
+
2862
+ expect(UserRepositoryToken.resolve(app)).toBeInstanceOf(UserRepository);
2863
+ // singleton() applied, so the same instance comes back
2864
+ expect(UserRepositoryToken.resolve(app)).toBe(UserRepositoryToken.resolve(app));
2865
+ // and the class still carries the metadata written beside the registration
2866
+ expect(getClassMeta(UserRepository, INJECTION_TOKEN)).toBe(UserRepositoryToken);
2867
+ });
2868
+
2869
+ it('should compose parameter decorators the same way', () => {
2870
+ const ConfigToken = new SingleToken<{ apiUrl: string }>('IConfig');
2871
+
2872
+ // @inject plus a label describing where the value came from
2873
+ const fromConfig = createComposeParameterDecorator(inject(by(ConfigToken)), addParamLabel('source', 'config'));
2874
+
2875
+ class ApiClient {
2876
+ constructor(@fromConfig public config: { apiUrl: string }) {}
2877
+ }
2878
+
2879
+ const app = new Container({ tags: ['application'] }).addRegistration(
2880
+ R.fromValue({ apiUrl: 'https://api.example.com' }).bindTo(ConfigToken),
2881
+ );
2882
+
2883
+ expect(app.resolve(ApiClient).config.apiUrl).toBe('https://api.example.com');
2884
+ expect(getParamLabels(ApiClient, 0).get('source')).toBe('config');
2885
+ });
2886
+ });
2887
+
2888
+ ```
2889
+
2796
2890
  ## Module
2797
2891
 
2798
2892
  Sometimes you want to encapsulate registration logic in separate module. This is what `IContainerModule` is for.
package/cjm/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createHookExecutionContext = exports.createHookContextFactory = exports.HookContext = exports.toHookFn = exports.hasHooks = exports.hook = exports.getHooks = exports.ArgumentNotFoundError = exports.TypedEventDisposedError = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.onResolve = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.toRegistrationFn = exports.toProviderFn = exports.toBindToken = exports.registerPipe = exports.isProviderPipe = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.by = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = exports.Container = exports.isDependencyKey = void 0;
4
- exports.TypedEvent = exports.getConstructorChain = exports.memoize = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.runAtOnce = exports.runInOrder = 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.resolveConstructor = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toToken = exports.forwardArgs = exports.isInjectionToken = exports.InjectionToken = exports.toTask = exports.HookCollector = exports.oncePerInstance = exports.parallel = exports.sequential = exports.injectProp = void 0;
5
- exports.isSerializable = exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.findOrFail = exports.pipe = exports.select = void 0;
4
+ exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.runAtOnce = exports.runInOrder = exports.handleAsyncError = exports.handleError = exports.createComposeMethodDecorator = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.createComposeParameterDecorator = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.createComposeClassDecorator = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.resolveConstructor = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toToken = exports.forwardArgs = exports.isInjectionToken = exports.InjectionToken = exports.toTask = exports.HookCollector = exports.oncePerInstance = exports.parallel = exports.sequential = exports.injectProp = void 0;
5
+ exports.isSerializable = exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.findOrFail = exports.pipe = exports.select = exports.TypedEvent = exports.getConstructorChain = exports.memoize = void 0;
6
6
  var IContainer_1 = require("./container/IContainer");
7
7
  Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
8
8
  var Container_1 = require("./container/Container");
@@ -118,6 +118,7 @@ Object.defineProperty(exports, "addClassLabel", { enumerable: true, get: functio
118
118
  Object.defineProperty(exports, "getClassLabels", { enumerable: true, get: function () { return class_1.getClassLabels; } });
119
119
  Object.defineProperty(exports, "addClassTag", { enumerable: true, get: function () { return class_1.addClassTag; } });
120
120
  Object.defineProperty(exports, "getClassTags", { enumerable: true, get: function () { return class_1.getClassTags; } });
121
+ Object.defineProperty(exports, "createComposeClassDecorator", { enumerable: true, get: function () { return class_1.createComposeClassDecorator; } });
121
122
  var parameter_1 = require("./metadata/parameter");
122
123
  Object.defineProperty(exports, "addParamMeta", { enumerable: true, get: function () { return parameter_1.addParamMeta; } });
123
124
  Object.defineProperty(exports, "getParamMeta", { enumerable: true, get: function () { return parameter_1.getParamMeta; } });
@@ -125,6 +126,7 @@ Object.defineProperty(exports, "addParamLabel", { enumerable: true, get: functio
125
126
  Object.defineProperty(exports, "getParamLabels", { enumerable: true, get: function () { return parameter_1.getParamLabels; } });
126
127
  Object.defineProperty(exports, "addParamTag", { enumerable: true, get: function () { return parameter_1.addParamTag; } });
127
128
  Object.defineProperty(exports, "getParamTags", { enumerable: true, get: function () { return parameter_1.getParamTags; } });
129
+ Object.defineProperty(exports, "createComposeParameterDecorator", { enumerable: true, get: function () { return parameter_1.createComposeParameterDecorator; } });
128
130
  var method_1 = require("./metadata/method");
129
131
  Object.defineProperty(exports, "addMethodMeta", { enumerable: true, get: function () { return method_1.addMethodMeta; } });
130
132
  Object.defineProperty(exports, "getMethodMeta", { enumerable: true, get: function () { return method_1.getMethodMeta; } });
@@ -132,6 +134,7 @@ Object.defineProperty(exports, "addMethodLabel", { enumerable: true, get: functi
132
134
  Object.defineProperty(exports, "getMethodLabels", { enumerable: true, get: function () { return method_1.getMethodLabels; } });
133
135
  Object.defineProperty(exports, "addMethodTag", { enumerable: true, get: function () { return method_1.addMethodTag; } });
134
136
  Object.defineProperty(exports, "getMethodTags", { enumerable: true, get: function () { return method_1.getMethodTags; } });
137
+ Object.defineProperty(exports, "createComposeMethodDecorator", { enumerable: true, get: function () { return method_1.createComposeMethodDecorator; } });
135
138
  var errorHandler_1 = require("./utils/errorHandler");
136
139
  Object.defineProperty(exports, "handleError", { enumerable: true, get: function () { return errorHandler_1.handleError; } });
137
140
  Object.defineProperty(exports, "handleAsyncError", { enumerable: true, get: function () { return errorHandler_1.handleAsyncError; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.addClassMeta = void 0;
3
+ exports.createComposeClassDecorator = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.addClassMeta = void 0;
4
4
  exports.getClassMeta = getClassMeta;
5
5
  const target_1 = require("./target");
6
6
  const addClassMeta = (key, mapFn) => (target) => {
@@ -19,3 +19,5 @@ const addClassTag = (tag) => (0, exports.addClassMeta)('tag', (prev = new Set())
19
19
  exports.addClassTag = addClassTag;
20
20
  const getClassTags = (target) => getClassMeta(target, 'tag') ?? new Set();
21
21
  exports.getClassTags = getClassTags;
22
+ const createComposeClassDecorator = (...decorators) => (target) => decorators.reduceRight((acc, decorate) => decorate(acc) ?? acc, target);
23
+ exports.createComposeClassDecorator = createComposeClassDecorator;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = void 0;
3
+ exports.createComposeMethodDecorator = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = void 0;
4
4
  const target_1 = require("./target");
5
5
  const addMethodMeta = (key, mapFn) => (target, propertyKey) => {
6
6
  const metadata = Reflect.getMetadata(key, target.constructor, propertyKey);
@@ -17,3 +17,5 @@ const addMethodTag = (tag) => (0, exports.addMethodMeta)('tag', (prev = new Set(
17
17
  exports.addMethodTag = addMethodTag;
18
18
  const getMethodTags = (target, propertyKey) => (0, exports.getMethodMeta)('tag', target, propertyKey) ?? new Set();
19
19
  exports.getMethodTags = getMethodTags;
20
+ const createComposeMethodDecorator = (...decorators) => (target, propertyKey, descriptor) => decorators.reduceRight((acc, decorate) => decorate(target, propertyKey, acc) ?? acc, descriptor);
21
+ exports.createComposeMethodDecorator = createComposeMethodDecorator;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = void 0;
3
+ exports.createComposeParameterDecorator = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = void 0;
4
4
  const target_1 = require("./target");
5
5
  const addParamMeta = (key, mapFn) => (target, _, parameterIndex) => {
6
6
  const metadata = Reflect.getOwnMetadata(key, target) ?? [];
@@ -32,3 +32,9 @@ const getParamTags = (target, parameterIndex) => {
32
32
  return all[parameterIndex] ?? new Set();
33
33
  };
34
34
  exports.getParamTags = getParamTags;
35
+ const createComposeParameterDecorator = (...decorators) => (target, propertyKey, parameterIndex) => {
36
+ for (let i = decorators.length - 1; i >= 0; i--) {
37
+ decorators[i](target, propertyKey, parameterIndex);
38
+ }
39
+ };
40
+ exports.createComposeParameterDecorator = createComposeParameterDecorator;
package/esm/index.js CHANGED
@@ -35,9 +35,9 @@ export { FunctionToken } from './token/FunctionToken.js';
35
35
  export { ConstantToken } from './token/ConstantToken.js';
36
36
  export { GroupInstanceToken } from './token/GroupInstanceToken.js';
37
37
  export { resolveConstructor } from './metadata/target.js';
38
- export { addClassMeta, getClassMeta, addClassLabel, getClassLabels, addClassTag, getClassTags } from './metadata/class.js';
39
- export { addParamMeta, getParamMeta, addParamLabel, getParamLabels, addParamTag, getParamTags, } from './metadata/parameter.js';
40
- export { addMethodMeta, getMethodMeta, addMethodLabel, getMethodLabels, addMethodTag, getMethodTags, } from './metadata/method.js';
38
+ export { addClassMeta, getClassMeta, addClassLabel, getClassLabels, addClassTag, getClassTags, createComposeClassDecorator, } from './metadata/class.js';
39
+ export { addParamMeta, getParamMeta, addParamLabel, getParamLabels, addParamTag, getParamTags, createComposeParameterDecorator, } from './metadata/parameter.js';
40
+ export { addMethodMeta, getMethodMeta, addMethodLabel, getMethodLabels, addMethodTag, getMethodTags, createComposeMethodDecorator, } from './metadata/method.js';
41
41
  export { handleError, handleAsyncError } from './utils/errorHandler.js';
42
42
  export { runInOrder, runAtOnce } from './utils/task.js';
43
43
  export { throttle } from './utils/throttle.js';
@@ -10,3 +10,4 @@ export const addClassLabel = (key, label) => addClassMeta('label', (prev = new M
10
10
  export const getClassLabels = (target) => getClassMeta(target, 'label') ?? new Map();
11
11
  export const addClassTag = (tag) => addClassMeta('tag', (prev = new Set()) => prev.add(tag));
12
12
  export const getClassTags = (target) => getClassMeta(target, 'tag') ?? new Set();
13
+ export const createComposeClassDecorator = (...decorators) => (target) => decorators.reduceRight((acc, decorate) => decorate(acc) ?? acc, target);
@@ -8,3 +8,4 @@ export const addMethodLabel = (key, label) => addMethodMeta('label', (prev = new
8
8
  export const getMethodLabels = (target, propertyKey) => getMethodMeta('label', target, propertyKey) ?? new Map();
9
9
  export const addMethodTag = (tag) => addMethodMeta('tag', (prev = new Set()) => prev.add(tag));
10
10
  export const getMethodTags = (target, propertyKey) => getMethodMeta('tag', target, propertyKey) ?? new Set();
11
+ export const createComposeMethodDecorator = (...decorators) => (target, propertyKey, descriptor) => decorators.reduceRight((acc, decorate) => decorate(target, propertyKey, acc) ?? acc, descriptor);
@@ -23,3 +23,8 @@ export const getParamTags = (target, parameterIndex) => {
23
23
  const all = getParamMeta('tag', target);
24
24
  return all[parameterIndex] ?? new Set();
25
25
  };
26
+ export const createComposeParameterDecorator = (...decorators) => (target, propertyKey, parameterIndex) => {
27
+ for (let i = decorators.length - 1; i >= 0; i--) {
28
+ decorators[i](target, propertyKey, parameterIndex);
29
+ }
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "72.0.0",
3
+ "version": "72.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",
@@ -36,9 +36,9 @@ export { FunctionToken } from './token/FunctionToken.js';
36
36
  export { ConstantToken } from './token/ConstantToken.js';
37
37
  export { type InstancePredicate, GroupInstanceToken } from './token/GroupInstanceToken.js';
38
38
  export { resolveConstructor } from './metadata/target.js';
39
- export { addClassMeta, getClassMeta, addClassLabel, getClassLabels, addClassTag, getClassTags } from './metadata/class.js';
40
- export { addParamMeta, getParamMeta, addParamLabel, getParamLabels, addParamTag, getParamTags, } from './metadata/parameter.js';
41
- export { addMethodMeta, getMethodMeta, addMethodLabel, getMethodLabels, addMethodTag, getMethodTags, } from './metadata/method.js';
39
+ export { addClassMeta, getClassMeta, addClassLabel, getClassLabels, addClassTag, getClassTags, createComposeClassDecorator, } from './metadata/class.js';
40
+ export { addParamMeta, getParamMeta, addParamLabel, getParamLabels, addParamTag, getParamTags, createComposeParameterDecorator, } from './metadata/parameter.js';
41
+ export { addMethodMeta, getMethodMeta, addMethodLabel, getMethodLabels, addMethodTag, getMethodTags, createComposeMethodDecorator, } from './metadata/method.js';
42
42
  export { handleError, handleAsyncError, type HandleErrorParams } from './utils/errorHandler.js';
43
43
  export { runInOrder, runAtOnce, type Task } from './utils/task.js';
44
44
  export { throttle } from './utils/throttle.js';
@@ -4,3 +4,4 @@ export declare const addClassLabel: (key: string, label: string) => ClassDecorat
4
4
  export declare const getClassLabels: (target: object) => Map<string, string>;
5
5
  export declare const addClassTag: (tag: string) => ClassDecorator;
6
6
  export declare const getClassTags: (target: object) => Set<string>;
7
+ export declare const createComposeClassDecorator: (...decorators: ClassDecorator[]) => ClassDecorator;
@@ -4,3 +4,4 @@ export declare const addMethodLabel: (key: string, label: string) => MethodDecor
4
4
  export declare const getMethodLabels: (target: object, propertyKey: string) => Map<string, string>;
5
5
  export declare const addMethodTag: (tag: string) => MethodDecorator;
6
6
  export declare const getMethodTags: (target: object, propertyKey: string) => Set<string>;
7
+ export declare const createComposeMethodDecorator: (...decorators: MethodDecorator[]) => MethodDecorator;
@@ -4,3 +4,4 @@ export declare const addParamLabel: (key: string, label: string) => ParameterDec
4
4
  export declare const getParamLabels: (target: object, parameterIndex: number) => Map<string, string>;
5
5
  export declare const addParamTag: (tag: string) => ParameterDecorator;
6
6
  export declare const getParamTags: (target: object, parameterIndex: number) => Set<string>;
7
+ export declare const createComposeParameterDecorator: (...decorators: ParameterDecorator[]) => ParameterDecorator;