-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(health-indicator): add mikro-orm health indicator
Resolves #1877
- Loading branch information
1 parent
bb14d7a
commit e67d939
Showing
8 changed files
with
403 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import * as request from 'supertest'; | ||
import { INestApplication } from '@nestjs/common'; | ||
import { bootstrapTestingModule, DynamicHealthEndpointFn } from '../helper'; | ||
|
||
describe('MikroOrmHealthIndicator', () => { | ||
let app: INestApplication; | ||
let setHealthEndpoint: DynamicHealthEndpointFn; | ||
|
||
beforeEach( | ||
() => | ||
(setHealthEndpoint = | ||
bootstrapTestingModule().withMikroOrm().setHealthEndpoint), | ||
); | ||
|
||
describe('#pingCheck', () => { | ||
it('should check if the mikroOrm is available', async () => { | ||
app = await setHealthEndpoint(({ healthCheck, mikroOrm }) => | ||
healthCheck.check([async () => mikroOrm.pingCheck('mikroOrm')]), | ||
).start(); | ||
const details = { mikroOrm: { status: 'up' } }; | ||
return request(app.getHttpServer()).get('/health').expect(200).expect({ | ||
status: 'ok', | ||
info: details, | ||
error: {}, | ||
details, | ||
}); | ||
}); | ||
|
||
it('should throw an error if runs into timeout error', async () => { | ||
app = await setHealthEndpoint(({ healthCheck, mikroOrm }) => | ||
healthCheck.check([ | ||
async () => mikroOrm.pingCheck('mikroOrm', { timeout: 1 }), | ||
]), | ||
).start(); | ||
|
||
const details = { | ||
mikroOrm: { | ||
status: 'down', | ||
message: 'timeout of 1ms exceeded', | ||
}, | ||
}; | ||
|
||
return request(app.getHttpServer()).get('/health').expect(503).expect({ | ||
status: 'error', | ||
info: {}, | ||
error: details, | ||
details, | ||
}); | ||
}); | ||
}); | ||
|
||
afterEach(async () => await app.close()); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { Injectable, NotImplementedException, Scope } from '@nestjs/common'; | ||
import { ModuleRef } from '@nestjs/core'; | ||
import { HealthCheckError } from '../../health-check/health-check.error'; | ||
|
||
import * as MikroOrmNestJS from '@mikro-orm/core'; | ||
import { TimeoutError } from '../../errors'; | ||
import { | ||
TimeoutError as PromiseTimeoutError, | ||
promiseTimeout, | ||
checkPackages, | ||
} from '../../utils'; | ||
import { HealthIndicator, HealthIndicatorResult } from '..'; | ||
|
||
export interface MikroOrmPingCheckSettings { | ||
/** | ||
* The connection which the ping check should get executed | ||
*/ | ||
connection?: any; | ||
/** | ||
* The amount of time the check should require in ms | ||
*/ | ||
timeout?: number; | ||
} | ||
|
||
/** | ||
* The MikroOrmHealthIndicator contains health indicators | ||
* which are used for health checks related to MikroOrm | ||
* | ||
* @publicApi | ||
* @module TerminusModule | ||
*/ | ||
@Injectable({ scope: Scope.TRANSIENT }) | ||
export class MikroOrmHealthIndicator extends HealthIndicator { | ||
/** | ||
* Initializes the MikroOrmHealthIndicator | ||
* | ||
* @param {ModuleRef} moduleRef The NestJS module reference | ||
*/ | ||
constructor(private moduleRef: ModuleRef) { | ||
super(); | ||
this.checkDependantPackages(); | ||
} | ||
|
||
/** | ||
* Checks if responds in (default) 1000ms and | ||
* returns a result object corresponding to the result | ||
* @param key The key which will be used for the result object | ||
* @param options The options for the ping | ||
* | ||
* @example | ||
* MikroOrmHealthIndicator.pingCheck('database', { timeout: 1500 }); | ||
*/ | ||
public async pingCheck( | ||
key: string, | ||
options: MikroOrmPingCheckSettings = {}, | ||
): Promise<HealthIndicatorResult> { | ||
this.checkDependantPackages(); | ||
|
||
const connection = options.connection || this.getContextConnection(); | ||
const timeout = options.timeout || 1000; | ||
|
||
if (!connection) { | ||
return this.getStatus(key, false); | ||
} | ||
|
||
try { | ||
await this.pingDb(connection, timeout); | ||
} catch (error) { | ||
// Check if the error is a timeout error | ||
if (error instanceof PromiseTimeoutError) { | ||
throw new TimeoutError( | ||
timeout, | ||
this.getStatus(key, false, { | ||
message: `timeout of ${timeout}ms exceeded`, | ||
}), | ||
); | ||
} | ||
|
||
// Check if the error is a connection not found error | ||
if (error instanceof Error) { | ||
throw new HealthCheckError( | ||
error.message, | ||
this.getStatus(key, false, { | ||
message: error.message, | ||
}), | ||
); | ||
} | ||
} | ||
|
||
return this.getStatus(key, true); | ||
} | ||
|
||
private checkDependantPackages() { | ||
checkPackages( | ||
['@mikro-orm/nestjs', '@mikro-orm/core'], | ||
this.constructor.name, | ||
); | ||
} | ||
|
||
/** | ||
* Returns the connection of the current DI context | ||
*/ | ||
private getContextConnection(): MikroOrmNestJS.Connection | null { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const { MikroORM } = require('@mikro-orm/core') as typeof MikroOrmNestJS; | ||
const mikro = this.moduleRef.get(MikroORM, { strict: false }); | ||
|
||
const connection: MikroOrmNestJS.Connection = mikro.em.getConnection(); | ||
|
||
if (!connection) { | ||
return null; | ||
} | ||
return connection; | ||
} | ||
|
||
/** | ||
* Pings a mikro-orm connection | ||
* | ||
* @param connection The connection which the ping should get executed | ||
* @param timeout The timeout how long the ping should maximum take | ||
* | ||
*/ | ||
private async pingDb(connection: MikroOrmNestJS.Connection, timeout: number) { | ||
let check: Promise<any>; | ||
const type = connection.getPlatform().getConfig().get('type'); | ||
|
||
switch (type) { | ||
case 'postgresql': | ||
case 'mysql': | ||
case 'mariadb': | ||
case 'sqlite': | ||
check = connection.execute('SELECT 1'); | ||
break; | ||
default: | ||
throw new NotImplementedException( | ||
`${type} ping check is not implemented yet`, | ||
); | ||
} | ||
return await promiseTimeout(timeout, check); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.