@tsed/di 8.0.1 → 8.0.2

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.
Files changed (125) hide show
  1. package/package.json +10 -9
  2. package/src/common/constants/constants.ts +5 -0
  3. package/src/common/decorators/autoInjectable.spec.ts +135 -0
  4. package/src/common/decorators/autoInjectable.ts +59 -0
  5. package/src/common/decorators/configuration.spec.ts +45 -0
  6. package/src/common/decorators/configuration.ts +34 -0
  7. package/src/common/decorators/constant.spec.ts +67 -0
  8. package/src/common/decorators/constant.ts +72 -0
  9. package/src/common/decorators/controller.spec.ts +43 -0
  10. package/src/common/decorators/controller.ts +64 -0
  11. package/src/common/decorators/inject.spec.ts +298 -0
  12. package/src/common/decorators/inject.ts +144 -0
  13. package/src/common/decorators/injectable.spec.ts +43 -0
  14. package/src/common/decorators/injectable.ts +30 -0
  15. package/src/common/decorators/intercept.spec.ts +103 -0
  16. package/src/common/decorators/intercept.ts +77 -0
  17. package/src/common/decorators/interceptor.ts +17 -0
  18. package/src/common/decorators/lazyInject.spec.ts +53 -0
  19. package/src/common/decorators/lazyInject.ts +91 -0
  20. package/src/common/decorators/module.ts +52 -0
  21. package/src/common/decorators/opts.ts +51 -0
  22. package/src/common/decorators/overrideProvider.spec.ts +30 -0
  23. package/src/common/decorators/overrideProvider.ts +15 -0
  24. package/src/common/decorators/scope.spec.ts +25 -0
  25. package/src/common/decorators/scope.ts +13 -0
  26. package/src/common/decorators/service.ts +14 -0
  27. package/src/common/decorators/useOpts.spec.ts +90 -0
  28. package/src/common/decorators/useOpts.ts +61 -0
  29. package/src/common/decorators/value.spec.ts +59 -0
  30. package/src/common/decorators/value.ts +60 -0
  31. package/src/common/domain/Container.spec.ts +85 -0
  32. package/src/common/domain/Container.ts +80 -0
  33. package/src/common/domain/ControllerProvider.spec.ts +55 -0
  34. package/src/common/domain/ControllerProvider.ts +48 -0
  35. package/src/common/domain/InjectablePropertyType.ts +7 -0
  36. package/src/common/domain/LocalsContainer.spec.ts +20 -0
  37. package/src/common/domain/LocalsContainer.ts +11 -0
  38. package/src/common/domain/Provider.spec.ts +107 -0
  39. package/src/common/domain/Provider.ts +205 -0
  40. package/src/common/domain/ProviderScope.ts +5 -0
  41. package/src/common/domain/ProviderType.ts +8 -0
  42. package/src/common/errors/InjectionError.spec.ts +17 -0
  43. package/src/common/errors/InjectionError.ts +68 -0
  44. package/src/common/errors/InvalidPropertyTokenError.ts +13 -0
  45. package/src/common/fn/configuration.spec.ts +42 -0
  46. package/src/common/fn/configuration.ts +22 -0
  47. package/src/common/fn/constant.spec.ts +37 -0
  48. package/src/common/fn/constant.ts +7 -0
  49. package/src/common/fn/inject.spec.ts +54 -0
  50. package/src/common/fn/inject.ts +29 -0
  51. package/src/common/fn/injectMany.ts +16 -0
  52. package/src/common/fn/injectable.spec.ts +87 -0
  53. package/src/common/fn/injectable.ts +15 -0
  54. package/src/common/fn/injector.ts +26 -0
  55. package/src/common/fn/lazyInject.spec.ts +22 -0
  56. package/src/common/fn/lazyInject.ts +36 -0
  57. package/src/common/fn/localsContainer.ts +55 -0
  58. package/src/common/fn/refValue.spec.ts +47 -0
  59. package/src/common/fn/refValue.ts +33 -0
  60. package/src/common/index.ts +63 -0
  61. package/src/common/integration/async-factory.spec.ts +60 -0
  62. package/src/common/integration/di.spec.ts +153 -0
  63. package/src/common/integration/imports.spec.ts +39 -0
  64. package/src/common/integration/request.spec.ts +79 -0
  65. package/src/common/integration/singleton.spec.ts +125 -0
  66. package/src/common/interfaces/DIConfigurationOptions.ts +29 -0
  67. package/src/common/interfaces/DILogger.ts +13 -0
  68. package/src/common/interfaces/DILoggerOptions.ts +54 -0
  69. package/src/common/interfaces/ImportTokenProviderOpts.ts +29 -0
  70. package/src/common/interfaces/InterceptorContext.ts +13 -0
  71. package/src/common/interfaces/InterceptorMethods.ts +5 -0
  72. package/src/common/interfaces/InvokeOptions.ts +28 -0
  73. package/src/common/interfaces/OnDestroy.ts +3 -0
  74. package/src/common/interfaces/OnInit.ts +3 -0
  75. package/src/common/interfaces/ProviderOpts.ts +57 -0
  76. package/src/common/interfaces/RegistrySettings.ts +11 -0
  77. package/src/common/interfaces/ResolvedInvokeOptions.ts +14 -0
  78. package/src/common/interfaces/TokenProvider.ts +3 -0
  79. package/src/common/interfaces/TokenRoute.ts +6 -0
  80. package/src/common/registries/GlobalProviders.spec.ts +94 -0
  81. package/src/common/registries/GlobalProviders.ts +131 -0
  82. package/src/common/registries/ProviderRegistry.spec.ts +36 -0
  83. package/src/common/registries/ProviderRegistry.ts +32 -0
  84. package/src/common/services/DIConfiguration.spec.ts +60 -0
  85. package/src/common/services/DIConfiguration.ts +140 -0
  86. package/src/common/services/DILogger.spec.ts +22 -0
  87. package/src/common/services/DILogger.ts +8 -0
  88. package/src/common/services/InjectorService.spec.ts +793 -0
  89. package/src/common/services/InjectorService.ts +601 -0
  90. package/src/common/utils/colors.spec.ts +9 -0
  91. package/src/common/utils/colors.ts +11 -0
  92. package/src/common/utils/createContainer.spec.ts +18 -0
  93. package/src/common/utils/createContainer.ts +14 -0
  94. package/src/common/utils/discoverHooks.ts +15 -0
  95. package/src/common/utils/getConfiguration.spec.ts +40 -0
  96. package/src/common/utils/getConfiguration.ts +20 -0
  97. package/src/common/utils/getConstructorDependencies.ts +9 -0
  98. package/src/common/utils/providerBuilder.ts +81 -0
  99. package/src/index.ts +2 -0
  100. package/src/node/decorators/injectContext.spec.ts +60 -0
  101. package/src/node/decorators/injectContext.ts +29 -0
  102. package/src/node/domain/ContextLogger.spec.ts +277 -0
  103. package/src/node/domain/ContextLogger.ts +137 -0
  104. package/src/node/domain/DIContext.spec.ts +103 -0
  105. package/src/node/domain/DIContext.ts +115 -0
  106. package/src/node/fn/context.ts +24 -0
  107. package/src/node/fn/logger.spec.ts +46 -0
  108. package/src/node/fn/logger.ts +17 -0
  109. package/src/node/index.ts +16 -0
  110. package/src/node/integration/async-factory-invoke.spec.ts +58 -0
  111. package/src/node/integration/inject-context.spec.ts +29 -0
  112. package/src/node/integration/invoke-oninit.spec.ts +25 -0
  113. package/src/node/interfaces/AlterRunInContext.ts +3 -0
  114. package/src/node/services/DILogger.spec.ts +24 -0
  115. package/src/node/services/DILogger.ts +6 -0
  116. package/src/node/services/DITest.spec.ts +109 -0
  117. package/src/node/services/DITest.ts +119 -0
  118. package/src/node/utils/asyncHookContext.spec.ts +147 -0
  119. package/src/node/utils/asyncHookContext.ts +48 -0
  120. package/src/node/utils/attachLogger.spec.ts +14 -0
  121. package/src/node/utils/attachLogger.ts +7 -0
  122. package/src/node/utils/setLoggerConfiguration.spec.ts +43 -0
  123. package/src/node/utils/setLoggerConfiguration.ts +7 -0
  124. package/src/node/utils/setLoggerFormat.ts +33 -0
  125. package/src/node/utils/setLoggerLevel.ts +12 -0
@@ -0,0 +1,40 @@
1
+ import {Configuration} from "../decorators/configuration.js";
2
+ import {getConfiguration} from "./getConfiguration.js";
3
+
4
+ class MyController1 {}
5
+
6
+ class MyController2 {}
7
+
8
+ describe("getConfiguration", () => {
9
+ it("should return configuration", () => {
10
+ @Configuration({test: "test"})
11
+ class MyModule {}
12
+
13
+ expect(getConfiguration(MyModule)).toEqual({
14
+ test: "test",
15
+ mount: {}
16
+ });
17
+ });
18
+
19
+ it("should return configuration (2)", () => {
20
+ @Configuration({
21
+ mount: {
22
+ "/v1": [MyController1]
23
+ }
24
+ })
25
+ class MyModule {}
26
+
27
+ expect(
28
+ getConfiguration(MyModule, {
29
+ mount: {
30
+ "/v2": [MyController2]
31
+ }
32
+ })
33
+ ).toEqual({
34
+ mount: {
35
+ "/v1": [MyController1],
36
+ "/v2": [MyController2]
37
+ }
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,20 @@
1
+ import {Store, Type} from "@tsed/core";
2
+
3
+ export function mergeMount(m1: any = {}, m2: any = {}) {
4
+ return Object.entries(m2).reduce((mount, [key, controllers]) => {
5
+ return {
6
+ ...mount,
7
+ [key]: (mount[key] || []).concat(controllers)
8
+ };
9
+ }, m1);
10
+ }
11
+
12
+ export function getConfiguration(module: Type<any>, configuration: any = {}) {
13
+ const store = Store.from(module).get("configuration") || {};
14
+
15
+ return {
16
+ ...store,
17
+ ...configuration,
18
+ mount: mergeMount(store.mount, configuration.mount)
19
+ };
20
+ }
@@ -0,0 +1,9 @@
1
+ import {Metadata} from "@tsed/core";
2
+
3
+ export function getConstructorDependencies(target: any, propertyKey?: string | symbol | undefined) {
4
+ return Metadata.getOwn("override:ctor:design:paramtypes", target, propertyKey) || [...Metadata.getParamTypes(target, propertyKey)] || [];
5
+ }
6
+
7
+ export function setConstructorDependencies(target: any, deps: any[]) {
8
+ Metadata.set("override:ctor:design:paramtypes", deps, target);
9
+ }
@@ -0,0 +1,81 @@
1
+ import "../registries/ProviderRegistry.js";
2
+
3
+ import {Store, type Type} from "@tsed/core";
4
+
5
+ import {ProviderType} from "../domain/ProviderType.js";
6
+ import {injector} from "../fn/injector.js";
7
+ import type {ProviderOpts} from "../interfaces/ProviderOpts.js";
8
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
9
+ import {GlobalProviders} from "../registries/GlobalProviders.js";
10
+
11
+ type ProviderBuilder<Token extends TokenProvider, BaseProvider, T extends object> = {
12
+ [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: (value: T[K]) => ProviderBuilder<Token, BaseProvider, T>;
13
+ } & {
14
+ inspect(): BaseProvider;
15
+ store(): Store;
16
+ token(): Token;
17
+ factory(f: (...args: unknown[]) => unknown): ProviderBuilder<Token, BaseProvider, T>;
18
+ asyncFactory(f: (...args: unknown[]) => Promise<unknown>): ProviderBuilder<Token, BaseProvider, T>;
19
+ value(v: unknown): ProviderBuilder<Token, BaseProvider, T>;
20
+ class(c: Type): ProviderBuilder<Token, BaseProvider, T>;
21
+ };
22
+
23
+ export function providerBuilder<Provider, Picked extends keyof Provider>(props: string[], baseOpts: Partial<ProviderOpts<Provider>> = {}) {
24
+ return <Token extends TokenProvider>(
25
+ token: Token,
26
+ options: Partial<ProviderOpts<Type>> = {}
27
+ ): ProviderBuilder<Token, Provider, Pick<Provider, Picked>> => {
28
+ const merged = {
29
+ global: !injector().isLoaded(),
30
+ ...options,
31
+ ...baseOpts,
32
+ token
33
+ };
34
+
35
+ const provider = GlobalProviders.merge(token, merged);
36
+
37
+ if (!merged.global) {
38
+ injector().setProvider(token, provider);
39
+ }
40
+
41
+ return props.reduce(
42
+ (acc, prop) => {
43
+ return {
44
+ ...acc,
45
+ [prop]: function (value: any) {
46
+ (provider as any)[prop] = value;
47
+ return this;
48
+ }
49
+ };
50
+ },
51
+ {
52
+ factory(factory: any) {
53
+ provider.useFactory = factory;
54
+ return this;
55
+ },
56
+ asyncFactory(asyncFactory: any) {
57
+ provider.useAsyncFactory = asyncFactory;
58
+ return this;
59
+ },
60
+ value(value: any) {
61
+ provider.useValue = value;
62
+ provider.type = ProviderType.VALUE;
63
+ return this;
64
+ },
65
+ class(k: any) {
66
+ provider.useClass = k;
67
+ return this;
68
+ },
69
+ store() {
70
+ return provider.store;
71
+ },
72
+ inspect() {
73
+ return provider;
74
+ },
75
+ token() {
76
+ return provider.token as Token;
77
+ }
78
+ } as ProviderBuilder<Token, Provider, Pick<Provider, Picked>>
79
+ );
80
+ };
81
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./common/index.js";
2
+ export * from "./node/index.js";
@@ -0,0 +1,60 @@
1
+ import {afterEach, beforeEach, expect} from "vitest";
2
+
3
+ import {Injectable} from "../../common/index.js";
4
+ import {DIContext} from "../domain/DIContext.js";
5
+ import {DITest} from "../services/DITest.js";
6
+ import {runInContext} from "../utils/asyncHookContext.js";
7
+ import {InjectContext} from "./injectContext.js";
8
+
9
+ describe("InjectContext", () => {
10
+ beforeEach(() => DITest.create());
11
+ afterEach(() => DITest.reset());
12
+
13
+ it("should inject a context", async () => {
14
+ @Injectable()
15
+ class MyService {
16
+ @InjectContext()
17
+ ctx: DIContext;
18
+ }
19
+
20
+ const $ctx = new DIContext({
21
+ id: "test",
22
+ logger: DITest.injector.logger,
23
+ injector: DITest.injector,
24
+ maxStackSize: 0
25
+ });
26
+
27
+ const myService = await DITest.invoke(MyService);
28
+
29
+ await runInContext($ctx, () => {
30
+ expect(myService.ctx).toBeInstanceOf(DIContext);
31
+ expect(myService.ctx).toEqual($ctx);
32
+ });
33
+
34
+ expect(myService.ctx).toBeInstanceOf(DIContext);
35
+ expect(myService.ctx).not.toEqual($ctx);
36
+ });
37
+ it("should inject a context and get value", async () => {
38
+ @Injectable()
39
+ class MyService {
40
+ @InjectContext((o) => o.get("test"))
41
+ test: string;
42
+ }
43
+
44
+ const $ctx = new DIContext({
45
+ id: "test",
46
+ logger: DITest.injector.logger,
47
+ injector: DITest.injector,
48
+ maxStackSize: 0
49
+ });
50
+ $ctx.set("test", "value");
51
+
52
+ const myService = await DITest.invoke(MyService);
53
+
54
+ await runInContext($ctx, () => {
55
+ expect(myService.test).toEqual("value");
56
+ });
57
+
58
+ expect(myService.test).toEqual(undefined);
59
+ });
60
+ });
@@ -0,0 +1,29 @@
1
+ import {catchError} from "@tsed/core";
2
+
3
+ import {DIContext} from "../domain/DIContext.js";
4
+ import {context} from "../fn/context.js";
5
+
6
+ /**
7
+ * Inject a context like PlatformContext or any BaseContext.
8
+ *
9
+ * ```typescript
10
+ * @Injectable()
11
+ * export class MyService {
12
+ * @InjectContext()
13
+ * ctx?: Context;
14
+ * }
15
+ * ```
16
+ *
17
+ * @returns {Function}
18
+ * @decorator
19
+ */
20
+ export function InjectContext(transform: ($ctx: DIContext) => unknown = (o) => o): PropertyDecorator {
21
+ return (target: any, propertyKey: string): any | void => {
22
+ catchError(() => Reflect.deleteProperty(target, propertyKey));
23
+ Reflect.defineProperty(target, propertyKey, {
24
+ get() {
25
+ return transform(context());
26
+ }
27
+ });
28
+ };
29
+ }
@@ -0,0 +1,277 @@
1
+ import {ContextLogger} from "./ContextLogger.js";
2
+
3
+ function getIgnoreLogFixture(ignore: string[], url: string) {
4
+ const ignoreReg = ignore.map((pattern: string | RegExp) => (typeof pattern === "string" ? new RegExp(pattern, "gi") : pattern));
5
+ return (ignore: boolean, data: any) => {
6
+ return !!ignoreReg.find((reg) => !!url.match(reg));
7
+ };
8
+ }
9
+
10
+ describe("ContextLogger", () => {
11
+ it("should create a new Context and log all", () => {
12
+ const logger = {
13
+ info: vi.fn(),
14
+ debug: vi.fn(),
15
+ warn: vi.fn(),
16
+ error: vi.fn(),
17
+ fatal: vi.fn(),
18
+ trace: vi.fn()
19
+ };
20
+
21
+ const contextLogger = new ContextLogger({
22
+ event: {
23
+ request: {},
24
+ response: {}
25
+ },
26
+ logger,
27
+ id: "id",
28
+ dateStart: new Date("2019-01-01")
29
+ });
30
+
31
+ contextLogger.alterIgnoreLog(getIgnoreLogFixture(["/admin"], "/"));
32
+ contextLogger.alterLog((o: any, level: "debug" | "info" | "warn" | "error" | "off" | "all", withRequest: boolean) => {
33
+ switch (level) {
34
+ case "info":
35
+ return {...o, minimal: "minimal"};
36
+ default:
37
+ return {...o, complete: "complete"};
38
+ }
39
+ });
40
+
41
+ vi.spyOn(contextLogger as any, "getDuration").mockReturnValue(1);
42
+
43
+ // WHEN
44
+ contextLogger.debug({test: "test"});
45
+ contextLogger.info({test: "test"});
46
+ contextLogger.info("message");
47
+ contextLogger.warn({test: "test"});
48
+ contextLogger.error({test: "test"});
49
+ contextLogger.fatal({test: "test"});
50
+ contextLogger.trace({test: "test"});
51
+
52
+ contextLogger.flush();
53
+
54
+ // THEN
55
+ expect(logger.info).toHaveBeenCalledWith({
56
+ minimal: "minimal",
57
+ duration: 1,
58
+ reqId: "id",
59
+ test: "test",
60
+ time: expect.any(Date)
61
+ });
62
+ expect(logger.info).toHaveBeenCalledWith({
63
+ minimal: "minimal",
64
+ duration: 1,
65
+ reqId: "id",
66
+ message: "message",
67
+ time: expect.any(Date)
68
+ });
69
+ expect(logger.debug).toHaveBeenCalledWith({
70
+ complete: "complete",
71
+ duration: 1,
72
+ reqId: "id",
73
+ test: "test",
74
+ time: expect.any(Date)
75
+ });
76
+ expect(logger.warn).toHaveBeenCalledWith({
77
+ complete: "complete",
78
+ duration: 1,
79
+ reqId: "id",
80
+ test: "test",
81
+ time: expect.any(Date)
82
+ });
83
+ expect(logger.error).toHaveBeenCalledWith({
84
+ complete: "complete",
85
+ duration: 1,
86
+ reqId: "id",
87
+ test: "test",
88
+ time: expect.any(Date)
89
+ });
90
+ expect(logger.fatal).toHaveBeenCalledWith({
91
+ complete: "complete",
92
+ duration: 1,
93
+ reqId: "id",
94
+ test: "test",
95
+ time: expect.any(Date)
96
+ });
97
+ expect(logger.trace).toHaveBeenCalledWith({
98
+ complete: "complete",
99
+ duration: 1,
100
+ reqId: "id",
101
+ test: "test",
102
+ time: expect.any(Date)
103
+ });
104
+ });
105
+ it("should create a new Context and log all (with minimalRequestPicker)", () => {
106
+ const logger = {
107
+ info: vi.fn(),
108
+ debug: vi.fn(),
109
+ warn: vi.fn(),
110
+ error: vi.fn(),
111
+ trace: vi.fn()
112
+ };
113
+
114
+ const contextLogger = new ContextLogger({
115
+ event: {
116
+ request: {},
117
+ response: {}
118
+ },
119
+ logger,
120
+ id: "id",
121
+ startDate: new Date("2019-01-01")
122
+ });
123
+
124
+ contextLogger.alterIgnoreLog(getIgnoreLogFixture(["/admin"], "/url"));
125
+
126
+ vi.spyOn(contextLogger as any, "getDuration").mockReturnValue(1);
127
+
128
+ // WHEN
129
+ contextLogger.debug({test: "test"});
130
+ contextLogger.info({test: "test"});
131
+ contextLogger.info("message");
132
+ contextLogger.warn({test: "test"});
133
+ contextLogger.error({test: "test"});
134
+ contextLogger.trace({test: "test"});
135
+
136
+ contextLogger.flush();
137
+
138
+ // THEN
139
+ expect(logger.info).toHaveBeenCalledWith({
140
+ duration: 1,
141
+ reqId: "id",
142
+ test: "test",
143
+ time: expect.any(Date)
144
+ });
145
+ expect(logger.info).toHaveBeenCalledWith({
146
+ duration: 1,
147
+ reqId: "id",
148
+ message: "message",
149
+ time: expect.any(Date)
150
+ });
151
+ expect(logger.debug).toHaveBeenCalledWith({
152
+ duration: 1,
153
+ reqId: "id",
154
+ test: "test",
155
+ time: expect.any(Date)
156
+ });
157
+ expect(logger.warn).toHaveBeenCalledWith({
158
+ duration: 1,
159
+ reqId: "id",
160
+ test: "test",
161
+ time: expect.any(Date)
162
+ });
163
+ expect(logger.error).toHaveBeenCalledWith({
164
+ duration: 1,
165
+ reqId: "id",
166
+ test: "test",
167
+ time: expect.any(Date)
168
+ });
169
+ expect(logger.trace).toHaveBeenCalledWith({
170
+ duration: 1,
171
+ reqId: "id",
172
+ test: "test",
173
+ time: expect.any(Date)
174
+ });
175
+ });
176
+ it("should create a new Context and log nothing when pattern match with url", () => {
177
+ const logger = {
178
+ info: vi.fn(),
179
+ debug: vi.fn(),
180
+ warn: vi.fn(),
181
+ error: vi.fn(),
182
+ trace: vi.fn()
183
+ };
184
+
185
+ const contextLogger = new ContextLogger({
186
+ logger,
187
+ event: {
188
+ request: {},
189
+ response: {}
190
+ },
191
+ id: "id",
192
+ startDate: new Date("2019-01-01"),
193
+ additionalProps: {
194
+ url: "/"
195
+ }
196
+ });
197
+
198
+ contextLogger.alterIgnoreLog(getIgnoreLogFixture(["/admin"], "/admin"));
199
+
200
+ vi.spyOn(contextLogger as any, "getDuration").mockReturnValue(1);
201
+
202
+ // WHEN
203
+ contextLogger.info({test: "test"});
204
+ contextLogger.flush();
205
+
206
+ // THEN
207
+ return expect(logger.info).not.toHaveBeenCalled();
208
+ });
209
+ it("should create a new Context and flush log when maxStackSize is reached", () => {
210
+ const logger = {
211
+ info: vi.fn(),
212
+ debug: vi.fn(),
213
+ warn: vi.fn(),
214
+ error: vi.fn(),
215
+ trace: vi.fn()
216
+ };
217
+
218
+ const contextLogger = new ContextLogger({
219
+ logger,
220
+ event: {
221
+ request: {},
222
+ response: {}
223
+ },
224
+ id: "id",
225
+ startDate: new Date("2019-01-01"),
226
+ maxStackSize: 2
227
+ });
228
+
229
+ contextLogger.maxStackSize = 2;
230
+
231
+ vi.spyOn(contextLogger as any, "getDuration").mockReturnValue(1);
232
+
233
+ // WHEN
234
+ contextLogger.info({test: "test"});
235
+ contextLogger.info({test: "test"});
236
+ contextLogger.info({test: "test"});
237
+ contextLogger.info({test: "test"});
238
+
239
+ // THEN
240
+ return expect(logger.info).toHaveBeenCalledTimes(3);
241
+ });
242
+ it("should do nothing when the log level is off", () => {
243
+ const logger = {
244
+ info: vi.fn(),
245
+ debug: vi.fn(),
246
+ warn: vi.fn(),
247
+ error: vi.fn(),
248
+ trace: vi.fn()
249
+ };
250
+
251
+ const contextLogger = new ContextLogger({
252
+ event: {
253
+ request: {},
254
+ response: {}
255
+ },
256
+ logger,
257
+ id: "id",
258
+ dateStart: new Date("2019-01-01"),
259
+ level: "off"
260
+ });
261
+
262
+ vi.spyOn(contextLogger as any, "getDuration").mockReturnValue(1);
263
+
264
+ // WHEN
265
+ contextLogger.debug({test: "test"});
266
+ contextLogger.info({test: "test"});
267
+ contextLogger.info("message");
268
+ contextLogger.warn({test: "test"});
269
+ contextLogger.error({test: "test"});
270
+ contextLogger.trace({test: "test"});
271
+
272
+ contextLogger.flush();
273
+
274
+ // THEN
275
+ expect(logger.info).not.toHaveBeenCalled();
276
+ });
277
+ });
@@ -0,0 +1,137 @@
1
+ import {Hooks} from "@tsed/hooks";
2
+ import {levels, LogLevel} from "@tsed/logger";
3
+
4
+ import {DILogger} from "../../common/index.js";
5
+ import {logger as injectLogger} from "../fn/logger.js";
6
+
7
+ export interface ContextLoggerOptions extends Record<string, any> {
8
+ id: string;
9
+ logger?: DILogger;
10
+ level?: "debug" | "info" | "warn" | "error" | "off" | "all";
11
+ maxStackSize?: number;
12
+ additionalProps?: Record<any, any>;
13
+ }
14
+
15
+ const LEVELS: Record<string, LogLevel> = levels();
16
+
17
+ export class ContextLogger {
18
+ readonly dateStart: Date;
19
+ readonly id: string;
20
+ readonly #additionalProps?: Record<string, unknown>;
21
+
22
+ maxStackSize: number;
23
+ level: LogLevel;
24
+
25
+ #hooks?: Hooks;
26
+ #stack?: any[];
27
+ #logger: any;
28
+
29
+ constructor({id, logger, dateStart = new Date(), level = "all", maxStackSize = 30, additionalProps}: ContextLoggerOptions) {
30
+ this.dateStart = dateStart;
31
+ this.id = id;
32
+ this.#logger = logger || injectLogger();
33
+ this.#additionalProps = additionalProps;
34
+ this.level = (LEVELS[level.toUpperCase()] || LEVELS.ALL) as LogLevel;
35
+ this.maxStackSize = maxStackSize;
36
+ }
37
+
38
+ get hooks() {
39
+ return (this.#hooks = this.#hooks || new Hooks());
40
+ }
41
+
42
+ private get stack() {
43
+ return (this.#stack = this.#stack || []);
44
+ }
45
+
46
+ alterLog(cb: (data: any, level: "debug" | "info" | "warn" | "error" | "all", withRequest: boolean) => any) {
47
+ return this.hooks.on("log", cb);
48
+ }
49
+
50
+ alterIgnoreLog(cb: (ignore: boolean, data: any) => boolean) {
51
+ return this.hooks.on("ignore", cb);
52
+ }
53
+
54
+ info(obj: any) {
55
+ this.run(levels().INFO, obj);
56
+ return this;
57
+ }
58
+
59
+ debug(obj: any) {
60
+ this.run(levels().DEBUG, obj);
61
+ return this;
62
+ }
63
+
64
+ warn(obj: any) {
65
+ this.run(levels().WARN, obj);
66
+ return this;
67
+ }
68
+
69
+ error(obj: any) {
70
+ this.run(levels().ERROR, obj);
71
+ return this;
72
+ }
73
+
74
+ fatal(obj: any) {
75
+ this.run(levels().FATAL, obj);
76
+ return this;
77
+ }
78
+
79
+ trace(obj: any) {
80
+ this.run(levels().TRACE, obj);
81
+ return this;
82
+ }
83
+
84
+ public flush(stream = false) {
85
+ if (this.stack.length) {
86
+ this.stack.forEach(({level, data}: any) => {
87
+ this.#logger[level](data);
88
+ });
89
+
90
+ this.#stack = [];
91
+ }
92
+
93
+ if (stream) {
94
+ this.maxStackSize = 0;
95
+ }
96
+ }
97
+
98
+ public isLevelEnabled(otherLevel: string | LogLevel) {
99
+ return this.level.isLessThanOrEqualTo(otherLevel);
100
+ }
101
+
102
+ /**
103
+ * Return the duration between the time when LogIncomingRequest has handle the request and now.
104
+ * @returns {number}
105
+ */
106
+ protected getDuration(): number {
107
+ return new Date().getTime() - this.dateStart.getTime();
108
+ }
109
+
110
+ protected getData(obj: any) {
111
+ if (typeof obj === "string") {
112
+ obj = {message: obj};
113
+ }
114
+
115
+ return {...this.#additionalProps, reqId: this.id, time: new Date(), duration: this.getDuration(), ...obj};
116
+ }
117
+
118
+ protected run(level: LogLevel, obj: any, withRequest?: boolean) {
119
+ if (!this.isLevelEnabled(level)) {
120
+ return;
121
+ }
122
+
123
+ const ignore = this.#hooks?.alter("ignore", false, [obj]);
124
+
125
+ if (!ignore) {
126
+ const levelStr = level.levelStr.toLowerCase();
127
+
128
+ obj = this.hooks.alter("log", this.getData(obj), [levelStr, withRequest]);
129
+
130
+ this.stack.push({level: levelStr, data: obj});
131
+ }
132
+
133
+ if (this.maxStackSize < this.stack.length || [levels().FATAL, levels().ERROR].includes(level)) {
134
+ this.flush();
135
+ }
136
+ }
137
+ }