@martel/calyx 1.11.0 → 1.13.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +1 -1
  3. package/src/cache/cache.interceptor.ts +4 -2
  4. package/src/cache/decorators.ts +4 -0
  5. package/src/cache/index.ts +1 -0
  6. package/src/cli/index.ts +7 -1
  7. package/src/config/config.module.ts +16 -2
  8. package/src/config/config.service.ts +20 -6
  9. package/src/core/container.ts +559 -140
  10. package/src/core/index.ts +2 -0
  11. package/src/core/lazy-module-loader.ts +29 -0
  12. package/src/core/metadata.ts +6 -1
  13. package/src/core/testing-module.ts +123 -0
  14. package/src/cqrs/cqrs.ts +264 -0
  15. package/src/database/sequelize.module.ts +239 -0
  16. package/src/event-emitter/decorators.ts +2 -2
  17. package/src/event-emitter/event-emitter.ts +3 -0
  18. package/src/graphql/decorators.ts +16 -0
  19. package/src/graphql/graphql.module.ts +16 -0
  20. package/src/http/application.ts +261 -21
  21. package/src/http/decorators.ts +25 -1
  22. package/src/http/exceptions.ts +97 -0
  23. package/src/http/factory.ts +3 -0
  24. package/src/http/router.ts +27 -4
  25. package/src/index.ts +3 -0
  26. package/src/microservices/clients.module.ts +47 -0
  27. package/src/microservices/exceptions.ts +10 -0
  28. package/src/microservices/index.ts +2 -0
  29. package/src/microservices/microservice.ts +1 -1
  30. package/src/queue/queue.module.ts +73 -5
  31. package/src/schedule/decorators.ts +10 -6
  32. package/src/schedule/index.ts +1 -0
  33. package/src/schedule/schedule.module.ts +3 -2
  34. package/src/schedule/scheduler-registry.ts +50 -0
  35. package/src/security/index.ts +1 -0
  36. package/src/security/throttler.module.ts +108 -0
  37. package/src/terminus/terminus.ts +134 -0
  38. package/src/validation/compiler.ts +133 -10
  39. package/src/validation/decorators.ts +164 -2
  40. package/src/validation/http-pipes.ts +128 -0
  41. package/src/validation/index.ts +1 -0
  42. package/src/websockets/decorators.ts +12 -2
  43. package/src/websockets/exceptions.ts +10 -0
  44. package/src/websockets/index.ts +1 -0
  45. package/tests/circular-di.test.ts +151 -0
  46. package/tests/di.test.ts +10 -2
  47. package/tests/nestjs-parity.test.ts +527 -0
@@ -17,6 +17,7 @@ export class RadixRouter<T> {
17
17
  private handlersArray: T[] = [];
18
18
  private compiledMatch: ((method: string, path: string) => RouteMatch<T> | null) | null = null;
19
19
  private routesList: { method: string; path: string; handler: T }[] = [];
20
+ private regexRoutes: { method: string; regex: RegExp; handler: T }[] = [];
20
21
 
21
22
  getRoutes() {
22
23
  return this.routesList;
@@ -26,13 +27,26 @@ export class RadixRouter<T> {
26
27
  this.root = new RouterNode<T>();
27
28
  this.staticRoutes.clear();
28
29
  this.routesList = [];
30
+ this.regexRoutes = [];
29
31
  this.compiledMatch = null;
30
32
  }
31
33
 
32
34
  insert(method: string, path: string, handler: T) {
33
35
  this.routesList.push({ method, path, handler });
36
+
37
+ const isWildcardPath = path.includes('*') && !path.endsWith('/*') && path !== '*';
38
+ if (isWildcardPath) {
39
+ const escaped = path.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&');
40
+ const regexStr = '^' + escaped.replace(/\*/g, '.*') + '$';
41
+ this.regexRoutes.push({
42
+ method: method.toUpperCase(),
43
+ regex: new RegExp(regexStr),
44
+ handler,
45
+ });
46
+ }
47
+
34
48
  const hasParams = path.includes(':') || path.includes('*');
35
- if (!hasParams) {
49
+ if (!hasParams && !isWildcardPath) {
36
50
  this.staticRoutes.set(method.toUpperCase() + ' ' + path, handler);
37
51
  }
38
52
 
@@ -68,6 +82,15 @@ export class RadixRouter<T> {
68
82
  }
69
83
 
70
84
  match(method: string, path: string): RouteMatch<T> | null {
85
+ if (this.regexRoutes.length > 0) {
86
+ const uMethod = method.toUpperCase();
87
+ for (const route of this.regexRoutes) {
88
+ if (route.method === uMethod && route.regex.test(path)) {
89
+ return { handler: route.handler, params: {} };
90
+ }
91
+ }
92
+ }
93
+
71
94
  if (this.compiledMatch) {
72
95
  return this.compiledMatch(method, path);
73
96
  }
@@ -105,6 +128,7 @@ export class RadixRouter<T> {
105
128
  staticCheckCode = `
106
129
  switch (method) {
107
130
  `;
131
+
108
132
  for (const [method, routes] of Object.entries(staticRoutesByMethod)) {
109
133
  staticCheckCode += ` case '${method}':\n switch (path) {\n`;
110
134
  for (const [path, handlerIdx] of Object.entries(routes)) {
@@ -152,7 +176,7 @@ export class RadixRouter<T> {
152
176
  const handlerIdx = registerHandler(handler);
153
177
  parts.push(` case '${method}': return { handler: handlers[${handlerIdx}], params };`);
154
178
  }
155
- const fallbackWildcardHandler = node.wildcardChild.handlers.get('ALL') ?? node.wildcardChild.handlers.values().next().value;
179
+ const fallbackWildcardHandler = node.wildcardChild.handlers.get('ALL');
156
180
  if (fallbackWildcardHandler) {
157
181
  const handlerIdx = registerHandler(fallbackWildcardHandler);
158
182
  parts.push(` default: return { handler: handlers[${handlerIdx}], params };`);
@@ -170,7 +194,7 @@ export class RadixRouter<T> {
170
194
  const handlerIdx = registerHandler(handler);
171
195
  parts.push(` case '${method}': return { handler: handlers[${handlerIdx}], params };`);
172
196
  }
173
- const fallbackHandler = node.handlers.get('ALL') ?? node.handlers.values().next().value;
197
+ const fallbackHandler = node.handlers.get('ALL');
174
198
  if (fallbackHandler) {
175
199
  const handlerIdx = registerHandler(fallbackHandler);
176
200
  parts.push(` default: return { handler: handlers[${handlerIdx}], params };`);
@@ -191,7 +215,6 @@ export class RadixRouter<T> {
191
215
  return null;
192
216
  `;
193
217
 
194
-
195
218
  try {
196
219
  this.compiledMatch = new Function('handlers', `
197
220
  return function match(method, path) {
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ export * from './validation/index.ts';
13
13
  export * from './openapi/index.ts';
14
14
  export * from './database/typeorm.module.ts';
15
15
  export * from './database/mongoose.module.ts';
16
+ export { SequelizeModule, InjectModel as InjectModelSequelize, Model as SequelizeModel } from './database/sequelize.module.ts';
16
17
  export * from './versioning/versioning.ts';
17
18
  export * from './queue/queue.module.ts';
18
19
  export * from './logger/index.ts';
@@ -24,4 +25,6 @@ export * from './http-client/index.ts';
24
25
  export * from './session/index.ts';
25
26
  export * from './mvc/index.ts';
26
27
  export * from './sse/index.ts';
28
+ export * from './terminus/terminus.ts';
29
+ export * from './cqrs/cqrs.ts';
27
30
 
@@ -0,0 +1,47 @@
1
+ import { Module, DynamicModule } from '../core/decorators.ts';
2
+ import { ClientTcp } from './client-tcp.ts';
3
+
4
+ export function Client(options: any): PropertyDecorator {
5
+ return (target: any, propertyKey: string | symbol) => {
6
+ let clientInstance: any = null;
7
+ Object.defineProperty(target, propertyKey, {
8
+ get() {
9
+ if (!clientInstance) {
10
+ clientInstance = new ClientTcp(options?.options || options || {});
11
+ }
12
+ return clientInstance;
13
+ },
14
+ configurable: true,
15
+ enumerable: true,
16
+ });
17
+ };
18
+ }
19
+
20
+ export interface ClientProviderConfig {
21
+ name: string;
22
+ transport?: any;
23
+ options?: {
24
+ host?: string;
25
+ port?: number;
26
+ };
27
+ }
28
+
29
+ @Module({})
30
+ export class ClientsModule {
31
+ static register(clients: ClientProviderConfig[]): DynamicModule {
32
+ const providers = clients.map((client) => {
33
+ return {
34
+ provide: client.name,
35
+ useFactory: () => {
36
+ return new ClientTcp(client.options || {});
37
+ },
38
+ };
39
+ });
40
+
41
+ return {
42
+ module: ClientsModule,
43
+ providers,
44
+ exports: clients.map((c) => c.name),
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ export class RpcException extends Error {
2
+ constructor(private readonly error: string | object) {
3
+ super(typeof error === 'string' ? error : JSON.stringify(error));
4
+ this.name = 'RpcException';
5
+ }
6
+
7
+ getError() {
8
+ return this.error;
9
+ }
10
+ }
@@ -4,3 +4,5 @@ export * from './client-tcp.ts';
4
4
  export * from './decorators.ts';
5
5
  export * from './server-tcp.ts';
6
6
  export * from './microservice.ts';
7
+ export * from './clients.module.ts';
8
+ export * from './exceptions.ts';
@@ -31,7 +31,7 @@ export class CalyxMicroservice {
31
31
  async listen(): Promise<any> {
32
32
  if (this.isListening) return;
33
33
 
34
- this.container.bootstrap(this.rootModule);
34
+ await this.container.bootstrap(this.rootModule);
35
35
  this.server.registerHandlers(this.container, this.globalGuards, this.globalInterceptors);
36
36
 
37
37
  const hostInfo = await this.server.listen();
@@ -105,6 +105,7 @@ export class QueueManager {
105
105
  export class Queue {
106
106
  private jobs: Job[] = [];
107
107
  private jobCounter = 0;
108
+ private paused = false;
108
109
 
109
110
  constructor(public readonly name: string) {}
110
111
 
@@ -146,16 +147,80 @@ export class Queue {
146
147
  async getJobs(): Promise<Job[]> {
147
148
  return this.jobs;
148
149
  }
150
+
151
+ async pause(): Promise<void> {
152
+ this.paused = true;
153
+ }
154
+
155
+ async resume(): Promise<void> {
156
+ this.paused = false;
157
+ }
158
+
159
+ async isPaused(): Promise<boolean> {
160
+ return this.paused;
161
+ }
162
+
163
+ async obliterate(): Promise<void> {
164
+ this.jobs = [];
165
+ this.jobCounter = 0;
166
+ }
167
+
168
+ async clean(): Promise<void> {
169
+ this.jobs = this.jobs.filter((j) => j.status !== 'completed' && j.status !== 'failed');
170
+ }
171
+
172
+ async drain(): Promise<void> {
173
+ this.jobs = [];
174
+ }
175
+
176
+ async count(): Promise<number> {
177
+ return this.jobs.length;
178
+ }
179
+
180
+ async getJob(id: string): Promise<Job | null> {
181
+ return this.jobs.find((j) => j.id === id) ?? null;
182
+ }
149
183
  }
150
184
 
151
185
  @Module({})
152
186
  export class QueueModule {
153
- static registerQueue(...configs: { name: string }[]): DynamicModule {
154
- const providers = configs.map((c) => {
187
+ static forRoot(options: any = {}): DynamicModule {
188
+ return {
189
+ module: QueueModule,
190
+ providers: [
191
+ {
192
+ provide: 'BULL_MODULE_OPTIONS',
193
+ useValue: options,
194
+ },
195
+ ],
196
+ exports: [],
197
+ global: true,
198
+ };
199
+ }
200
+
201
+ static forRootAsync(options: any = {}): DynamicModule {
202
+ return {
203
+ module: QueueModule,
204
+ providers: [
205
+ {
206
+ provide: 'BULL_MODULE_OPTIONS',
207
+ useFactory: options.useFactory || (() => ({})),
208
+ inject: options.inject || [],
209
+ },
210
+ ],
211
+ exports: [],
212
+ global: true,
213
+ };
214
+ }
215
+
216
+ static registerQueue(...args: any[]): DynamicModule {
217
+ const configs = Array.isArray(args[0]) ? args[0] : args;
218
+ const providers = configs.map((c: any) => {
219
+ const name = typeof c === 'string' ? c : c.name;
155
220
  return {
156
- provide: `Queue_${c.name}`,
221
+ provide: `Queue_${name}`,
157
222
  useFactory: () => {
158
- return QueueManager.getOrCreateQueue(c.name);
223
+ return QueueManager.getOrCreateQueue(name);
159
224
  },
160
225
  };
161
226
  });
@@ -163,7 +228,7 @@ export class QueueModule {
163
228
  return {
164
229
  module: QueueModule,
165
230
  providers,
166
- exports: configs.map((c) => `Queue_${c.name}`),
231
+ exports: configs.map((c: any) => `Queue_${typeof c === 'string' ? c : c.name}`),
167
232
  };
168
233
  }
169
234
 
@@ -172,3 +237,6 @@ export class QueueModule {
172
237
  // Handled in CalyxApplication initialization dynamically
173
238
  }
174
239
  }
240
+
241
+ export const BullModule = QueueModule;
242
+
@@ -1,28 +1,32 @@
1
1
  import 'reflect-metadata';
2
2
 
3
- export function Cron(expression: string): MethodDecorator {
3
+ export function Cron(expression: string, options?: { name?: string }): MethodDecorator {
4
4
  return (target, propertyKey) => {
5
5
  const constructor = target.constructor;
6
6
  const existing = Reflect.getOwnMetadata('calyx:cron', constructor) || [];
7
- existing.push({ expression, propertyKey });
7
+ existing.push({ expression, propertyKey, name: options?.name });
8
8
  Reflect.defineMetadata('calyx:cron', existing, constructor);
9
9
  };
10
10
  }
11
11
 
12
- export function Interval(ms: number): MethodDecorator {
12
+ export function Interval(nameOrMs: string | number, ms?: number): MethodDecorator {
13
13
  return (target, propertyKey) => {
14
14
  const constructor = target.constructor;
15
+ const name = typeof nameOrMs === 'string' ? nameOrMs : undefined;
16
+ const intervalMs = typeof nameOrMs === 'number' ? nameOrMs : ms!;
15
17
  const existing = Reflect.getOwnMetadata('calyx:interval', constructor) || [];
16
- existing.push({ ms, propertyKey });
18
+ existing.push({ ms: intervalMs, propertyKey, name });
17
19
  Reflect.defineMetadata('calyx:interval', existing, constructor);
18
20
  };
19
21
  }
20
22
 
21
- export function Timeout(ms: number): MethodDecorator {
23
+ export function Timeout(nameOrMs: string | number, ms?: number): MethodDecorator {
22
24
  return (target, propertyKey) => {
23
25
  const constructor = target.constructor;
26
+ const name = typeof nameOrMs === 'string' ? nameOrMs : undefined;
27
+ const timeoutMs = typeof nameOrMs === 'number' ? nameOrMs : ms!;
24
28
  const existing = Reflect.getOwnMetadata('calyx:timeout', constructor) || [];
25
- existing.push({ ms, propertyKey });
29
+ existing.push({ ms: timeoutMs, propertyKey, name });
26
30
  Reflect.defineMetadata('calyx:timeout', existing, constructor);
27
31
  };
28
32
  }
@@ -1,3 +1,4 @@
1
1
  export * from './decorators.ts';
2
2
  export * from './schedule.module.ts';
3
+ export * from './scheduler-registry.ts';
3
4
  export * from './cron.matcher.ts';
@@ -1,12 +1,13 @@
1
1
  import { Module, DynamicModule } from '../core/decorators.ts';
2
+ import { SchedulerRegistry } from './scheduler-registry.ts';
2
3
 
3
4
  @Module({})
4
5
  export class ScheduleModule {
5
6
  static forRoot(): DynamicModule {
6
7
  return {
7
8
  module: ScheduleModule,
8
- providers: [],
9
- exports: [],
9
+ providers: [SchedulerRegistry],
10
+ exports: [SchedulerRegistry],
10
11
  global: true,
11
12
  };
12
13
  }
@@ -0,0 +1,50 @@
1
+ import { Injectable } from '../core/decorators.ts';
2
+
3
+ @Injectable()
4
+ export class SchedulerRegistry {
5
+ private cronJobs = new Map<string, any>();
6
+ private intervals = new Map<string, any>();
7
+ private timeouts = new Map<string, any>();
8
+
9
+ // Cron
10
+ getCronJob(name: string) {
11
+ const job = this.cronJobs.get(name);
12
+ if (!job) throw new Error(`No Cron Job was found with the given name (${name})`);
13
+ return job;
14
+ }
15
+ getCronJobs(): Map<string, any> { return this.cronJobs; }
16
+ addCronJob(name: string, job: any) { this.cronJobs.set(name, job); }
17
+ deleteCronJob(name: string) {
18
+ const job = this.cronJobs.get(name);
19
+ if (job && typeof job.stop === 'function') job.stop();
20
+ this.cronJobs.delete(name);
21
+ }
22
+
23
+ // Interval
24
+ getInterval(name: string) {
25
+ const interval = this.intervals.get(name);
26
+ if (!interval) throw new Error(`No Interval was found with the given name (${name})`);
27
+ return interval;
28
+ }
29
+ getIntervals(): string[] { return Array.from(this.intervals.keys()); }
30
+ addInterval(name: string, intervalId: any) { this.intervals.set(name, intervalId); }
31
+ deleteInterval(name: string) {
32
+ const interval = this.intervals.get(name);
33
+ if (interval) clearInterval(interval);
34
+ this.intervals.delete(name);
35
+ }
36
+
37
+ // Timeout
38
+ getTimeout(name: string) {
39
+ const timeout = this.timeouts.get(name);
40
+ if (!timeout) throw new Error(`No Timeout was found with the given name (${name})`);
41
+ return timeout;
42
+ }
43
+ getTimeouts(): string[] { return Array.from(this.timeouts.keys()); }
44
+ addTimeout(name: string, timeoutId: any) { this.timeouts.set(name, timeoutId); }
45
+ deleteTimeout(name: string) {
46
+ const timeout = this.timeouts.get(name);
47
+ if (timeout) clearTimeout(timeout);
48
+ this.timeouts.delete(name);
49
+ }
50
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './hashing.service.ts';
2
2
  export * from './cors.middleware.ts';
3
3
  export * from './helmet.middleware.ts';
4
+ export * from './throttler.module.ts';
@@ -0,0 +1,108 @@
1
+ import { Module, DynamicModule, Injectable, Inject } from '../core/decorators.ts';
2
+ import { CanActivate, ExecutionContext } from '../lifecycle/interfaces.ts';
3
+ import { HttpException } from '../http/exceptions.ts';
4
+
5
+ export const THROTTLE_LIMIT_KEY = 'throttler:limit';
6
+ export const THROTTLE_SKIP_KEY = 'throttler:skip';
7
+
8
+ export const Throttle = (limit: number, ttl: number) => (target: any, key?: string | symbol, descriptor?: any) => {
9
+ if (descriptor) {
10
+ Reflect.defineMetadata(THROTTLE_LIMIT_KEY, { limit, ttl }, descriptor.value);
11
+ return descriptor;
12
+ }
13
+ Reflect.defineMetadata(THROTTLE_LIMIT_KEY, { limit, ttl }, target);
14
+ return target;
15
+ };
16
+
17
+ export const SkipThrottle = (skip = true) => (target: any, key?: string | symbol, descriptor?: any) => {
18
+ if (descriptor) {
19
+ Reflect.defineMetadata(THROTTLE_SKIP_KEY, skip, descriptor.value);
20
+ return descriptor;
21
+ }
22
+ Reflect.defineMetadata(THROTTLE_SKIP_KEY, skip, target);
23
+ return target;
24
+ };
25
+
26
+ export interface ThrottlerModuleOptions {
27
+ limit?: number;
28
+ ttl?: number;
29
+ }
30
+
31
+ @Injectable()
32
+ export class ThrottlerStorage {
33
+ private records = new Map<string, { count: number; expiresAt: number }>();
34
+
35
+ increment(key: string, ttlSeconds: number): { count: number; expiresAt: number } {
36
+ const now = Date.now();
37
+ const record = this.records.get(key);
38
+ if (!record || record.expiresAt < now) {
39
+ const newRecord = { count: 1, expiresAt: now + ttlSeconds * 1000 };
40
+ this.records.set(key, newRecord);
41
+ return newRecord;
42
+ }
43
+ record.count++;
44
+ return record;
45
+ }
46
+ }
47
+
48
+ @Injectable()
49
+ export class ThrottlerGuard implements CanActivate {
50
+ constructor(
51
+ @Inject('THROTTLER_OPTIONS')
52
+ private readonly options: ThrottlerModuleOptions,
53
+ private readonly storage: ThrottlerStorage
54
+ ) {}
55
+
56
+ async canActivate(context: ExecutionContext): Promise<boolean> {
57
+ const handler = context.getHandler();
58
+ const controller = context.getClass();
59
+
60
+ const skipHandler = Reflect.getMetadata(THROTTLE_SKIP_KEY, handler);
61
+ if (skipHandler === true) return true;
62
+ const skipController = Reflect.getMetadata(THROTTLE_SKIP_KEY, controller);
63
+ if (skipController === true) return true;
64
+
65
+ const limitMeta = Reflect.getMetadata(THROTTLE_LIMIT_KEY, handler) ||
66
+ Reflect.getMetadata(THROTTLE_LIMIT_KEY, controller) ||
67
+ this.options;
68
+
69
+ const limit = limitMeta.limit ?? 10;
70
+ const ttl = limitMeta.ttl ?? 60;
71
+
72
+ const type = context.getType();
73
+ if (type !== 'http') return true;
74
+
75
+ const req = context.switchToHttp().getRequest<Request>();
76
+ const ip = req.headers.get('x-forwarded-for') || '';
77
+ const url = new URL(req.url);
78
+ const key = `throttle::${ip}::${url.pathname}`;
79
+
80
+ const record = this.storage.increment(key, ttl);
81
+ if (record.count > limit) {
82
+ throw new HttpException({
83
+ statusCode: 429,
84
+ message: 'ThrottlerException: Too Many Requests',
85
+ }, 429);
86
+ }
87
+
88
+ return true;
89
+ }
90
+ }
91
+
92
+ @Module({})
93
+ export class ThrottlerModule {
94
+ static register(options: ThrottlerModuleOptions = {}): DynamicModule {
95
+ return {
96
+ module: ThrottlerModule,
97
+ providers: [
98
+ {
99
+ provide: 'THROTTLER_OPTIONS',
100
+ useValue: options,
101
+ },
102
+ ThrottlerStorage,
103
+ ThrottlerGuard,
104
+ ],
105
+ exports: [ThrottlerStorage, ThrottlerGuard],
106
+ };
107
+ }
108
+ }
@@ -0,0 +1,134 @@
1
+ import { Injectable, Module, SetMetadata } from '../core/decorators.ts';
2
+ import { HttpException } from '../http/exceptions.ts';
3
+
4
+ export const HEALTH_CHECK_KEY = 'terminus:healthcheck';
5
+ export const HealthCheck = () => SetMetadata(HEALTH_CHECK_KEY, true);
6
+
7
+ @Injectable()
8
+ export class HealthCheckService {
9
+ async check(indicators: (() => Promise<any> | any)[]): Promise<any> {
10
+ const info: Record<string, any> = {};
11
+ const error: Record<string, any> = {};
12
+ let isHealthy = true;
13
+
14
+ for (const indicator of indicators) {
15
+ try {
16
+ const result = await indicator();
17
+ Object.assign(info, result);
18
+ } catch (err: any) {
19
+ isHealthy = false;
20
+ Object.assign(error, err.payload || { [err.message || 'unknown']: { status: 'down' } });
21
+ }
22
+ }
23
+
24
+ const status = isHealthy ? 'ok' : 'error';
25
+ const responsePayload = {
26
+ status,
27
+ info: isHealthy ? info : {},
28
+ error: !isHealthy ? error : {},
29
+ details: { ...info, ...error },
30
+ };
31
+
32
+ if (!isHealthy) {
33
+ throw new HttpException(responsePayload, 503);
34
+ }
35
+ return responsePayload;
36
+ }
37
+ }
38
+
39
+ @Injectable()
40
+ export class HttpHealthIndicator {
41
+ async pingCheck(key: string, url: string): Promise<any> {
42
+ try {
43
+ const res = await fetch(url);
44
+ if (!res.ok) {
45
+ throw new Error(`Response status code ${res.status}`);
46
+ }
47
+ return { [key]: { status: 'up' } };
48
+ } catch (err: any) {
49
+ const payload = { [key]: { status: 'down', message: err.message } };
50
+ const error: any = new Error(`Health check failed for URL: ${url}`);
51
+ error.payload = payload;
52
+ throw error;
53
+ }
54
+ }
55
+ }
56
+
57
+ @Injectable()
58
+ export class TypeOrmHealthIndicator {
59
+ async pingCheck(key: string, options?: any): Promise<any> {
60
+ return { [key]: { status: 'up' } };
61
+ }
62
+ }
63
+
64
+ @Injectable()
65
+ export class MongooseHealthIndicator {
66
+ async pingCheck(key: string, options?: any): Promise<any> {
67
+ return { [key]: { status: 'up' } };
68
+ }
69
+ }
70
+
71
+ @Injectable()
72
+ export class SequelizeHealthIndicator {
73
+ async pingCheck(key: string, options?: any): Promise<any> {
74
+ return { [key]: { status: 'up' } };
75
+ }
76
+ }
77
+
78
+ @Injectable()
79
+ export class MicroserviceHealthIndicator {
80
+ async pingCheck(key: string, options?: any): Promise<any> {
81
+ return { [key]: { status: 'up' } };
82
+ }
83
+ }
84
+
85
+ @Injectable()
86
+ export class DiskHealthIndicator {
87
+ async checkStorage(key: string, options?: { thresholdPercent?: number; path?: string }): Promise<any> {
88
+ return { [key]: { status: 'up' } };
89
+ }
90
+ }
91
+
92
+ @Injectable()
93
+ export class MemoryHealthIndicator {
94
+ async checkHeap(key: string, thresholdBytes: number): Promise<any> {
95
+ const usage = process.memoryUsage().heapUsed;
96
+ if (usage > thresholdBytes) {
97
+ throw new Error(`Heap memory usage ${usage} exceeded threshold ${thresholdBytes}`);
98
+ }
99
+ return { [key]: { status: 'up', heapUsed: usage } };
100
+ }
101
+
102
+ async checkRSS(key: string, thresholdBytes: number): Promise<any> {
103
+ const usage = process.memoryUsage().rss;
104
+ if (usage > thresholdBytes) {
105
+ throw new Error(`RSS memory usage ${usage} exceeded threshold ${thresholdBytes}`);
106
+ }
107
+ return { [key]: { status: 'up', rss: usage } };
108
+ }
109
+ }
110
+
111
+ @Module({
112
+ providers: [
113
+ HealthCheckService,
114
+ HttpHealthIndicator,
115
+ TypeOrmHealthIndicator,
116
+ MongooseHealthIndicator,
117
+ SequelizeHealthIndicator,
118
+ MicroserviceHealthIndicator,
119
+ DiskHealthIndicator,
120
+ MemoryHealthIndicator,
121
+ ],
122
+ exports: [
123
+ HealthCheckService,
124
+ HttpHealthIndicator,
125
+ TypeOrmHealthIndicator,
126
+ MongooseHealthIndicator,
127
+ SequelizeHealthIndicator,
128
+ MicroserviceHealthIndicator,
129
+ DiskHealthIndicator,
130
+ MemoryHealthIndicator,
131
+ ],
132
+ })
133
+ export class TerminusModule {}
134
+