ts-ioc-container 55.9.0 → 56.0.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
@@ -2482,6 +2482,30 @@ describe('Container Modules', function () {
2482
2482
 
2483
2483
  Sometimes you need to invoke methods after construct or dispose of class. This is what hooks are for.
2484
2484
 
2485
+ The generic `@hook` decorator takes a hook key and a map function
2486
+ `(...prev: HookType[]) => HookType[]`, where `prev` is the list of hooks already
2487
+ registered on the class for the decorated member. Use `appendHooks` /
2488
+ `prependHooks` (exported as `append` / `prepend` too) to place new hooks around
2489
+ the existing ones:
2490
+
2491
+ ```typescript
2492
+ class OrderService {
2493
+ @hook('actions', append(validate, persist))
2494
+ @hook('actions', prepend(authorize))
2495
+ submit() {}
2496
+ }
2497
+ ```
2498
+
2499
+ Decorators are applied bottom-up, so `authorize` runs first, then `validate` and
2500
+ `persist`. Any other map function works as well — for example
2501
+ `(...prev) => [...prev].reverse()` to reorder, or `() => [onlyThisOne]` to
2502
+ replace the accumulated hooks.
2503
+
2504
+ `@onConstruct`, `@onConstructAsync` and `@onContainerDisposed` keep their
2505
+ variadic signature and compensate for the bottom-up application order, so
2506
+ stacked decorators run in declaration order: `@onConstruct(h1) @onConstruct(h2)`
2507
+ runs `h1` before `h2`.
2508
+
2485
2509
  ### OnConstruct
2486
2510
 
2487
2511
  ```typescript
@@ -2741,7 +2765,7 @@ describe('onContainerDisposed', function () {
2741
2765
 
2742
2766
  ```typescript
2743
2767
  import 'reflect-metadata';
2744
- import { Container, hook, HooksRunner, injectProp, Registration } from 'ts-ioc-container';
2768
+ import { append, Container, hook, HooksRunner, injectProp, Registration } from 'ts-ioc-container';
2745
2769
 
2746
2770
  /**
2747
2771
  * UI Components - Property Injection
@@ -2761,7 +2785,7 @@ describe('inject property', () => {
2761
2785
 
2762
2786
  class UserViewModel {
2763
2787
  // Inject 'GreetingService' into 'greeting' property during 'onInit'
2764
- @hook('onInit', injectProp('GreetingService'))
2788
+ @hook('onInit', append(injectProp('GreetingService')))
2765
2789
  greetingService!: string;
2766
2790
 
2767
2791
  display(): string {
@@ -2787,9 +2811,12 @@ describe('inject property', () => {
2787
2811
  let injectedValue: unknown;
2788
2812
 
2789
2813
  class UserViewModel {
2790
- @hook('onInit', injectProp('GreetingService'), (context) => {
2791
- injectedValue = context.getProperty();
2792
- })
2814
+ @hook(
2815
+ 'onInit',
2816
+ append(injectProp('GreetingService'), (context) => {
2817
+ injectedValue = context.getProperty();
2818
+ }),
2819
+ )
2793
2820
  greetingService!: string;
2794
2821
  }
2795
2822
 
package/cjm/hooks/hook.js CHANGED
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hook = exports.toHookFn = void 0;
3
+ exports.hook = exports.toHookFn = exports.prepend = exports.append = exports.prependHooks = exports.appendHooks = void 0;
4
4
  exports.getHooks = getHooks;
5
5
  exports.hasHooks = hasHooks;
6
6
  const basic_1 = require("../utils/basic");
7
7
  const proxy_1 = require("../utils/proxy");
8
+ const appendHooks = (...fns) => (...prev) => [...prev, ...fns];
9
+ exports.appendHooks = appendHooks;
10
+ const prependHooks = (...fns) => (...prev) => [...fns, ...prev];
11
+ exports.prependHooks = prependHooks;
12
+ exports.append = exports.appendHooks;
13
+ exports.prepend = exports.prependHooks;
8
14
  const isHookClassConstructor = (execute) => {
9
15
  return basic_1.Is.constructor(execute) && execute.prototype.execute;
10
16
  };
@@ -39,11 +45,11 @@ function hasHooks(target, key) {
39
45
  const reflectionTarget = getReflectionTarget(target);
40
46
  return getConstructorChain(reflectionTarget.constructor).some((ctor) => Reflect.hasOwnMetadata(key, ctor));
41
47
  }
42
- const hook = (key, ...fns) => (target, propertyKey) => {
48
+ const hook = (key, mapFn) => (target, propertyKey) => {
43
49
  const hooks = Reflect.hasOwnMetadata(key, target.constructor)
44
50
  ? Reflect.getOwnMetadata(key, target.constructor)
45
51
  : new Map();
46
- hooks.set(propertyKey, (hooks.get(propertyKey) ?? []).concat(fns));
52
+ hooks.set(propertyKey, mapFn(...(hooks.get(propertyKey) ?? [])));
47
53
  Reflect.defineMetadata(key, hooks, target.constructor);
48
54
  };
49
55
  exports.hook = hook;
@@ -4,7 +4,7 @@ exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHook
4
4
  const hook_1 = require("./hook");
5
5
  const HooksRunner_1 = require("./HooksRunner");
6
6
  exports.onConstructHooksRunner = new HooksRunner_1.HooksRunner('onConstruct');
7
- const onConstruct = (...fns) => (0, hook_1.hook)('onConstruct', ...fns);
7
+ const onConstruct = (...fns) => (0, hook_1.hook)('onConstruct', (0, hook_1.prependHooks)(...fns));
8
8
  exports.onConstruct = onConstruct;
9
9
  class AddOnConstructHookModule {
10
10
  onException;
@@ -4,7 +4,7 @@ exports.AddOnConstructAsyncHookModule = exports.onConstructAsync = exports.onCon
4
4
  const hook_1 = require("./hook");
5
5
  const HooksRunner_1 = require("./HooksRunner");
6
6
  exports.onConstructAsyncHooksRunner = new HooksRunner_1.HooksRunner('onConstructAsync');
7
- const onConstructAsync = (...fns) => (0, hook_1.hook)('onConstructAsync', ...fns);
7
+ const onConstructAsync = (...fns) => (0, hook_1.hook)('onConstructAsync', (0, hook_1.prependHooks)(...fns));
8
8
  exports.onConstructAsync = onConstructAsync;
9
9
  class AddOnConstructAsyncHookModule {
10
10
  onException;
@@ -4,7 +4,7 @@ exports.AddOnDisposeHookModule = exports.onContainerDisposed = exports.onContain
4
4
  const hook_1 = require("./hook");
5
5
  const HooksRunner_1 = require("./HooksRunner");
6
6
  exports.onContainerDisposedHooksRunner = new HooksRunner_1.HooksRunner('onContainerDisposed');
7
- const onContainerDisposed = (...fns) => (0, hook_1.hook)('onContainerDisposed', ...fns);
7
+ const onContainerDisposed = (...fns) => (0, hook_1.hook)('onContainerDisposed', (0, hook_1.prependHooks)(...fns));
8
8
  exports.onContainerDisposed = onContainerDisposed;
9
9
  class AddOnDisposeHookModule {
10
10
  applyTo(container) {
package/cjm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toGroupAlias = exports.GroupAliasToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onContainerDisposed = exports.onContainerDisposedHooksRunner = exports.AddOnConstructAsyncHookModule = exports.onConstructAsync = exports.onConstructAsyncHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.hasHooks = exports.hook = exports.getHooks = exports.CannonSingletonApplyTwiceError = exports.UnexpectedHookResultError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = 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.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 = void 0;
3
+ exports.AddOnDisposeHookModule = exports.onContainerDisposed = exports.onContainerDisposedHooksRunner = 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.CannonSingletonApplyTwiceError = exports.UnexpectedHookResultError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = 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.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.InjectionToken = exports.HooksRunner = 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");
@@ -54,6 +54,10 @@ var hook_1 = require("./hooks/hook");
54
54
  Object.defineProperty(exports, "getHooks", { enumerable: true, get: function () { return hook_1.getHooks; } });
55
55
  Object.defineProperty(exports, "hook", { enumerable: true, get: function () { return hook_1.hook; } });
56
56
  Object.defineProperty(exports, "hasHooks", { enumerable: true, get: function () { return hook_1.hasHooks; } });
57
+ Object.defineProperty(exports, "appendHooks", { enumerable: true, get: function () { return hook_1.appendHooks; } });
58
+ Object.defineProperty(exports, "prependHooks", { enumerable: true, get: function () { return hook_1.prependHooks; } });
59
+ Object.defineProperty(exports, "append", { enumerable: true, get: function () { return hook_1.append; } });
60
+ Object.defineProperty(exports, "prepend", { enumerable: true, get: function () { return hook_1.prepend; } });
57
61
  var HookContext_1 = require("./hooks/HookContext");
58
62
  Object.defineProperty(exports, "HookContext", { enumerable: true, get: function () { return HookContext_1.HookContext; } });
59
63
  Object.defineProperty(exports, "createHookContextFactory", { enumerable: true, get: function () { return HookContext_1.createHookContextFactory; } });
package/esm/hooks/hook.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { Is } from '../utils/basic.js';
2
2
  import { getProxyTarget, isProxy } from '../utils/proxy.js';
3
+ export const appendHooks = (...fns) => (...prev) => [...prev, ...fns];
4
+ export const prependHooks = (...fns) => (...prev) => [...fns, ...prev];
5
+ export const append = appendHooks;
6
+ export const prepend = prependHooks;
3
7
  const isHookClassConstructor = (execute) => {
4
8
  return Is.constructor(execute) && execute.prototype.execute;
5
9
  };
@@ -33,10 +37,10 @@ export function hasHooks(target, key) {
33
37
  const reflectionTarget = getReflectionTarget(target);
34
38
  return getConstructorChain(reflectionTarget.constructor).some((ctor) => Reflect.hasOwnMetadata(key, ctor));
35
39
  }
36
- export const hook = (key, ...fns) => (target, propertyKey) => {
40
+ export const hook = (key, mapFn) => (target, propertyKey) => {
37
41
  const hooks = Reflect.hasOwnMetadata(key, target.constructor)
38
42
  ? Reflect.getOwnMetadata(key, target.constructor)
39
43
  : new Map();
40
- hooks.set(propertyKey, (hooks.get(propertyKey) ?? []).concat(fns));
44
+ hooks.set(propertyKey, mapFn(...(hooks.get(propertyKey) ?? [])));
41
45
  Reflect.defineMetadata(key, hooks, target.constructor);
42
46
  };
@@ -1,7 +1,7 @@
1
- import { hook } from './hook.js';
1
+ import { hook, prependHooks } from './hook.js';
2
2
  import { HooksRunner } from './HooksRunner.js';
3
3
  export const onConstructHooksRunner = new HooksRunner('onConstruct');
4
- export const onConstruct = (...fns) => hook('onConstruct', ...fns);
4
+ export const onConstruct = (...fns) => hook('onConstruct', prependHooks(...fns));
5
5
  export class AddOnConstructHookModule {
6
6
  onException;
7
7
  constructor(onException) {
@@ -1,7 +1,7 @@
1
- import { hook } from './hook.js';
1
+ import { hook, prependHooks } from './hook.js';
2
2
  import { HooksRunner } from './HooksRunner.js';
3
3
  export const onConstructAsyncHooksRunner = new HooksRunner('onConstructAsync');
4
- export const onConstructAsync = (...fns) => hook('onConstructAsync', ...fns);
4
+ export const onConstructAsync = (...fns) => hook('onConstructAsync', prependHooks(...fns));
5
5
  export class AddOnConstructAsyncHookModule {
6
6
  onException;
7
7
  constructor(onException) {
@@ -1,7 +1,7 @@
1
- import { hook } from './hook.js';
1
+ import { hook, prependHooks } from './hook.js';
2
2
  import { HooksRunner } from './HooksRunner.js';
3
3
  export const onContainerDisposedHooksRunner = new HooksRunner('onContainerDisposed');
4
- export const onContainerDisposed = (...fns) => hook('onContainerDisposed', ...fns);
4
+ export const onContainerDisposed = (...fns) => hook('onContainerDisposed', prependHooks(...fns));
5
5
  export class AddOnDisposeHookModule {
6
6
  applyTo(container) {
7
7
  container.addOnDisposeHook((scope) => {
package/esm/index.js CHANGED
@@ -16,7 +16,7 @@ export { ContainerDisposedError } from './errors/ContainerDisposedError.js';
16
16
  export { ProviderDisposedError } from './errors/ProviderDisposedError.js';
17
17
  export { UnexpectedHookResultError } from './errors/UnexpectedHookResultError.js';
18
18
  export { CannonSingletonApplyTwiceError } from './errors/CannonSingletonApplyTwiceError.js';
19
- export { getHooks, hook, hasHooks } from './hooks/hook.js';
19
+ export { getHooks, hook, hasHooks, appendHooks, prependHooks, append, prepend, } from './hooks/hook.js';
20
20
  export { HookContext, createHookContextFactory, createHookContext } from './hooks/HookContext.js';
21
21
  export { injectProp } from './hooks/injectProp.js';
22
22
  export { onConstructHooksRunner, onConstruct, AddOnConstructHookModule, } from './hooks/onConstruct.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "55.9.0",
3
+ "version": "56.0.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",
@@ -104,7 +104,7 @@
104
104
  "@swc/core": "^1.15.24",
105
105
  "@types/node": "^25.5.2",
106
106
  "@typescript-eslint/eslint-plugin": "8.32.1",
107
- "@typescript-eslint/parser": "8.58.2",
107
+ "@typescript-eslint/parser": "8.59.1",
108
108
  "@vitest/coverage-v8": "^4.1.2",
109
109
  "cz-conventional-changelog": "^3.3.0",
110
110
  "eslint": "9.24.0",
@@ -7,8 +7,14 @@ export type HookFn<T extends IHookContext = IHookContext> = (context: T) => void
7
7
  export interface HookClass<T extends IHookContext = IHookContext> {
8
8
  execute(context: Omit<T, 'scope'>): void | Promise<void>;
9
9
  }
10
- export type HooksOfClass = Map<string, (HookFn | constructor<HookClass>)[]>;
10
+ export type HookType<T extends IHookContext = IHookContext> = HookFn<T> | constructor<HookClass<T>>;
11
+ export type MapHooksFn = (...prev: HookType[]) => HookType[];
12
+ export type HooksOfClass = Map<string, HookType[]>;
13
+ export declare const appendHooks: (...fns: HookType[]) => MapHooksFn;
14
+ export declare const prependHooks: (...fns: HookType[]) => MapHooksFn;
15
+ export declare const append: (...fns: HookType[]) => MapHooksFn;
16
+ export declare const prepend: (...fns: HookType[]) => MapHooksFn;
11
17
  export declare const toHookFn: <C extends IHookContext>(execute: HookFn<C> | constructor<HookClass<C>>) => HookFn<C>;
12
18
  export declare function getHooks(target: object, key: string | symbol): HooksOfClass;
13
19
  export declare function hasHooks(target: object, key: string | symbol): boolean;
14
- export declare const hook: (key: string | symbol, ...fns: (HookFn | constructor<HookClass>)[]) => (target: object, propertyKey: string | symbol) => void;
20
+ export declare const hook: (key: string | symbol, mapFn: MapHooksFn) => (target: object, propertyKey: string | symbol) => void;
@@ -1,10 +1,10 @@
1
- import { HookClass, HookFn } from './hook.js';
1
+ import { HookType } from './hook.js';
2
2
  import type { IContainer, IContainerModule } from '../container/IContainer.js';
3
- import { constructor, Instance } from '../utils/basic.js';
3
+ import { Instance } from '../utils/basic.js';
4
4
  import { HooksRunner } from './HooksRunner.js';
5
5
  import type { ExecutionContext } from '../ExecutionContext.js';
6
6
  export declare const onConstructHooksRunner: HooksRunner;
7
- export declare const onConstruct: (...fns: (HookFn | constructor<HookClass>)[]) => (target: object, propertyKey: string | symbol) => void;
7
+ export declare const onConstruct: (...fns: HookType[]) => (target: object, propertyKey: string | symbol) => void;
8
8
  export type OnConstructHook = (instance: Instance, scope: IContainer) => void;
9
9
  export type OnExceptionHandler = (ex: unknown, context: ExecutionContext) => void;
10
10
  export declare class AddOnConstructHookModule implements IContainerModule {
@@ -1,10 +1,9 @@
1
- import { HookClass, HookFn } from './hook.js';
1
+ import { HookType } from './hook.js';
2
2
  import type { IContainer, IContainerModule } from '../container/IContainer.js';
3
- import { constructor } from '../utils/basic.js';
4
3
  import { HooksRunner } from './HooksRunner.js';
5
4
  import type { OnExceptionHandler } from './onConstruct.js';
6
5
  export declare const onConstructAsyncHooksRunner: HooksRunner;
7
- export declare const onConstructAsync: (...fns: (HookFn | constructor<HookClass>)[]) => (target: object, propertyKey: string | symbol) => void;
6
+ export declare const onConstructAsync: (...fns: HookType[]) => (target: object, propertyKey: string | symbol) => void;
8
7
  export declare class AddOnConstructAsyncHookModule implements IContainerModule {
9
8
  private readonly onException?;
10
9
  constructor(onException?: OnExceptionHandler | undefined);
@@ -1,9 +1,8 @@
1
- import { HookClass, HookFn } from './hook.js';
1
+ import { HookType } from './hook.js';
2
2
  import type { IContainer, IContainerModule } from '../container/IContainer.js';
3
3
  import { HooksRunner } from './HooksRunner.js';
4
- import { type constructor } from '../utils/basic.js';
5
4
  export declare const onContainerDisposedHooksRunner: HooksRunner;
6
- export declare const onContainerDisposed: (...fns: (HookFn | constructor<HookClass>)[]) => (target: object, propertyKey: string | symbol) => void;
5
+ export declare const onContainerDisposed: (...fns: HookType[]) => (target: object, propertyKey: string | symbol) => void;
7
6
  export type OnDisposeHook = (scope: IContainer) => void;
8
7
  export declare class AddOnDisposeHookModule implements IContainerModule {
9
8
  applyTo(container: IContainer): void;
@@ -17,7 +17,7 @@ export { ContainerDisposedError } from './errors/ContainerDisposedError.js';
17
17
  export { ProviderDisposedError } from './errors/ProviderDisposedError.js';
18
18
  export { UnexpectedHookResultError } from './errors/UnexpectedHookResultError.js';
19
19
  export { CannonSingletonApplyTwiceError } from './errors/CannonSingletonApplyTwiceError.js';
20
- export { getHooks, hook, hasHooks, type HookFn, type HookClass, type InjectFn, type HooksOfClass } from './hooks/hook.js';
20
+ export { getHooks, hook, hasHooks, appendHooks, prependHooks, append, prepend, type HookFn, type HookClass, type HookType, type MapHooksFn, type InjectFn, type HooksOfClass, } from './hooks/hook.js';
21
21
  export { HookContext, createHookContextFactory, createHookContext, type IHookContext } from './hooks/HookContext.js';
22
22
  export { injectProp } from './hooks/injectProp.js';
23
23
  export { onConstructHooksRunner, onConstruct, AddOnConstructHookModule, type OnExceptionHandler, } from './hooks/onConstruct.js';