Skip to content

Commit

Permalink
Merge 6435308 into b6d1b1d
Browse files Browse the repository at this point in the history
  • Loading branch information
mishok13 committed Nov 16, 2017
2 parents b6d1b1d + 6435308 commit 4d05e84
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 71 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,10 @@ adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
* `providers.TypeORMProvider`'s `entityManager` is now `readonly`
* TypeORM dependency has been updated to `0.1.1` release
* `Service` implementations must provide gRPC method name
* `TypeORMProvider` has been dropped, instead a new abstract class
`DbProvider` is now available for use as well as basic
implementation of Mariadb provider has been added under
`MariadbProvider` name.

## [0.5.1] - 2017-10-06

Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -62,9 +62,11 @@
"source-map-support": "^0.4.18",
"ts-node": "^3.3.0",
"tslint": "^5.7.0",
"tslint-consistent-codestyle": "^1.8.0",
"typescript": "^2.5.3"
},
"dependencies": {
"@types/deepmerge": "^1.3.2",
"async": "^2.5.0",
"body-parser": "^1.18.2",
"connect-timeout": "^1.9.0",
Expand Down
3 changes: 1 addition & 2 deletions src/events/event-emitter.ts
@@ -1,5 +1,4 @@
import { inject, injectable } from 'inversify';
import { Config } from './../config';

export interface Subscriber {
notify(event: Event): void;
Expand All @@ -16,7 +15,7 @@ export interface Event {

@injectable()
export class EventEmitter {
constructor(@inject('event-subscribers') private subscribers: Array<Subscriber>) {}
constructor( @inject('event-subscribers') private subscribers: Array<Subscriber>) { }

public emit(eventName: string, payload: { [s: string]: any } = {}) {
// Using setImmediate to postpone actual execution until the api call has finished
Expand Down
7 changes: 5 additions & 2 deletions src/grpc-server.ts
Expand Up @@ -5,7 +5,7 @@ import * as grpc from 'grpc';
import { Context } from './context';
import { Config } from './config';
import { Logger } from './logger';
import { Service, ServiceResponse, ServiceHandlerFunction } from './service';
import { ServiceResponse, ServiceHandlerFunction } from './service';
import { HealthManager } from './health';
import { ProtoConfig } from './proto-config';

Expand Down Expand Up @@ -83,7 +83,10 @@ export class GrpcServer {
this.logger.info(`GRPC request started ${serviceName}`);
const context = this.createContext(call.metadata);
service.handler(context, call.request)
.then((response: ServiceResponse) => {
.then((response: ServiceResponse | void) => {
if (!response) {
throw new Error('Empty response returned');
}
callback(null, response.content);
})
.catch((response: ServiceResponse) => {
Expand Down
23 changes: 11 additions & 12 deletions src/http-server.ts
Expand Up @@ -8,7 +8,7 @@ import * as timeout from 'connect-timeout';

import { HealthManager } from './health';
import { Config } from './config';
import { HttpMethod, ServiceHandlerFunction, Service, ServiceResponse, QueryMapping, UrlMapping } from './service';
import { HttpMethod, ServiceHandlerFunction, ServiceResponse, QueryMapping, UrlMapping } from './service';
import { Logger } from './logger';
import { Context } from './context';
import { deepSet } from './utils';
Expand Down Expand Up @@ -39,7 +39,7 @@ export class HttpServer {
this.server = express();
this.server.use(bodyParser.json({
type: (request) => {
let contentType: string = '';
let contentType = '';

if (request.headers && request.headers['content-type']) {
if (Array.isArray(request.headers['content-type'])) {
Expand All @@ -56,7 +56,7 @@ export class HttpServer {
// Register health check endpoint
const healthUrl = this.normalizeURL(this.config['healthCheckURL'] || '/check');

this.server.get(healthUrl, (request: Request, response: Response) => {
this.server.get(healthUrl, (_: Request, response: Response) => {
const report = healthManager.getReport();

if (healthManager.healthy) {
Expand All @@ -77,14 +77,13 @@ export class HttpServer {
const error = `Trying to register url: ${url} with the same HttpMethod (${service.method}) twice`;
this.logger.fatal(error);
throw new Error(error);
} else {
this.logger.info(`Registering HTTP handler: ${service.method || method} ${url}`);
this.registeredUrls[url] = service.method;

this.server[method](url, (request: Request, response: Response) => {
this.handleRequest(service, request, response);
});
}
this.logger.info(`Registering HTTP handler: ${service.method || method} ${url}`);
this.registeredUrls[url] = service.method;

this.server[method](url, (request: Request, response: Response) => {
this.handleRequest(service, request, response);
});
}

// Starts the http server
Expand All @@ -94,7 +93,7 @@ export class HttpServer {
this.server.use(timeout(connectTimeout));

// 404 middleware
this.server.use((request: Request, response: Response, next: NextFunction) => {
this.server.use((request: Request, response: Response, _: NextFunction) => {
this.logger.warn(`Unknown endpoint called: ${request.url}`);

response.status(httpStatus.NOT_FOUND).send({
Expand All @@ -103,7 +102,7 @@ export class HttpServer {
});

// Error middleware
this.server.use((error, request: Request, response: Response, next: NextFunction) => {
this.server.use((error, request: Request, response: Response, _: NextFunction) => {
this.logger.error(`Express error middleware error for ${request.url}`, error);
console.error(error);

Expand Down
2 changes: 1 addition & 1 deletion src/logger/handlers/console.ts
Expand Up @@ -2,7 +2,7 @@ import * as util from 'util';
import { Config } from './../../config';
import { LogRecord, LogLevel } from './../logger';

export function consoleHandler(config: Config) {
export function consoleHandler(_: Config) {
return (record: LogRecord) => {
console.log(JSON.stringify({
_time: record.time,
Expand Down
1 change: 0 additions & 1 deletion src/logger/logger.ts
@@ -1,5 +1,4 @@
import { injectable } from 'inversify';
import { Context } from './../context';
import { Config } from './../config';

import {
Expand Down
4 changes: 2 additions & 2 deletions src/logger/processors/assign-request.ts
@@ -1,8 +1,8 @@
import { Config } from './../../config';
import { LogRecord } from './../logger';

export function assignRequestId(config: Config) {
return (record: LogRecord): LogRecord => {
export function assignRequestId(_: Config) {
return (record: LogRecord): LogRecord => {
if (record.extra['context'] !== undefined) {
record.extra['request-id'] = record.extra['context']['requestId'];
}
Expand Down
1 change: 1 addition & 0 deletions src/providers/db/index.ts
@@ -0,0 +1 @@
export { MariadbProvider } from './mariadbProvider';
43 changes: 43 additions & 0 deletions src/providers/db/mariadbProvider.ts
@@ -0,0 +1,43 @@
import { Connection, ConnectionOptions, EntityManager } from 'typeorm';
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions';
import { injectable } from 'inversify';
import * as deepmerge from 'deepmerge';

import { Config, HealthManager, Logger } from '../..';
import { DbProvider, EntityProvider } from '..';

@injectable()
export class MariadbProvider extends DbProvider implements EntityProvider {
protected connectionOptions: ConnectionOptions = {
type: 'mariadb',
timezone: 'Z',
synchronize: false,
logging: ['schema', 'error', 'warn', 'migration']
};

public readonly entities: Array<Function>;
public connection: Connection;

constructor(
protected config: Config,
protected healthManager: HealthManager,
protected logger: Logger,
) {
super();

const providedOptions: Partial<MysqlConnectionOptions> = {
username: this.config['dbUser'],
password: this.config['dbPassword'] || '',
database: this.config['dbName'],
host: this.config['dbHost'],
port: this.config['dbPort'],
entities: this.entities
};
const options: ConnectionOptions = deepmerge(this.connectionOptions, providedOptions);
this.connect(options);
}

public get entityManager(): EntityManager {
return this.connection.manager;
}
}
2 changes: 1 addition & 1 deletion src/providers/simple-grpc-client.ts
@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify';
import { injectable } from 'inversify';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Config } from './../config';
import * as grpcExt from 'grpc/src/node/src/grpc_extension';
Expand Down
84 changes: 39 additions & 45 deletions src/providers/typeorm.ts
@@ -1,73 +1,52 @@
import { createConnection, getConnectionManager, Connection, ConnectionOptions, EntityManager } from 'typeorm';
import { getConnectionManager, Connection, ConnectionOptions, EntityManager, ObjectLiteral } from 'typeorm';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { injectable } from 'inversify';
import * as deepmerge from 'deepmerge';

import { Config, HealthManager, Logger } from '../';

@injectable()
export class TypeORMProvider {
public connection: Connection;
public readonly entityManager: EntityManager;
public health = new BehaviorSubject(false);
export abstract class DbProvider {
public abstract readonly entityManager: EntityManager;
public abstract readonly entities: Array<ObjectLiteral>;
public abstract connection: Connection;
public readonly health: BehaviorSubject<boolean>;

public defaultConnectionOptions = {
driver: {
type: 'mysql'
},
autoSchemaSync: false
};
protected abstract logger: Logger;
protected abstract config: Config;
protected abstract connectionOptions: ConnectionOptions;
protected healthManager: HealthManager;

private connectionOptions: ConnectionOptions;
private entities = [];
private checkInterval = 5000;
private reconnectTime = 3000;
protected reconnectTime = 3000;
protected checkInterval = 5000;

// Static method to pass options, will be deep merged with the default options
static setConnectionOptions(options: any) {
TypeORMProvider.prototype.connectionOptions = options;
return TypeORMProvider;
constructor() {
this.health = new BehaviorSubject(false);
this.healthManager.registerCheck('DB connection', this.health);
}

constructor(private config: Config, private healthManager: HealthManager, private logger: Logger) {
healthManager.registerCheck('DB connection', this.health);

const options = deepmerge(this.defaultConnectionOptions, this.connectionOptions);
options.driver.username = this.config['dbUser'];
options.driver.password = this.config['dbPassword'] || '';
options.driver.database = this.config['dbName'];
options.driver.host = this.config['dbHost'];
options.driver.port = this.config['dbPort'];

protected connect(options: ConnectionOptions): void {
// We dont support autoschema sync, because we want to have auto retrying connection
// we need to use connectionManager.create which doesn't support auto schema sync
if (options['autoSchemaSync'] === true) {
throw new Error('TypeORMProvider: autoSchemaSync not supported');
if (options.synchronize === true) {
throw new Error('DbProvider: synchronize option is explicitely forbidden');
}

const connectionManager = getConnectionManager();
this.connection = connectionManager.create(options);
this.entityManager = this.connection.manager;
this.connect();
}

private connect() {
this.connection.connect()
this.connection
.connect()
.then(() => {
this.health.next(true);
this.monitorHealth();
})
.catch((error: Error) => {
this.logger.exception(error, 'Failed to connect to db, retrying in: ${this.reconnectTime}ms');

setTimeout(() => {
this.connect();
}, this.reconnectTime);
setTimeout(
() => { this.connect(options); },
this.reconnectTime);
});
}

// Monitors database connection and will update the health accordingly
private monitorHealth() {
protected monitorHealth() {
setInterval(() => {
this.connection.manager.query('SELECT 1;')
.then(() => {
Expand All @@ -79,4 +58,19 @@ export class TypeORMProvider {
});
}, this.checkInterval);
}

}

export interface EntityProvider {
readonly entities: Array<Function>;
}

export interface EntityProviderT {
new(...args: any[]): EntityProvider;
};

export function ProvidesEntities<T extends EntityProviderT>(Base: T, entities: Array<Function>) {
return class extends Base {
public entities = entities;
}
}
2 changes: 1 addition & 1 deletion src/rservice.ts
@@ -1,4 +1,4 @@
import { Container, injectable } from 'inversify';
import { Container } from 'inversify';
import * as express from 'express';

import { Config, ConfigOption } from './config';
Expand Down
2 changes: 0 additions & 2 deletions src/service.ts
@@ -1,5 +1,3 @@
import { Request, Response } from 'express';
import { injectable } from 'inversify';
import { Context } from './context';

/**
Expand Down
11 changes: 9 additions & 2 deletions tslint.json
@@ -1,6 +1,13 @@
{
"rulesDirectory": [],
"rulesDirectory": ["tslint-consistent-codestyle"],
"rules": {
"no-as-type-assertion": true,
"no-collapsible-if": true,
"early-exit": false,
"curly": "always",
"no-unnecessary-else": true,
"no-unused": true,
"no-var-before-return": true,
"callable-types": true,
"class-name": true,
"comment-format": [
Expand Down Expand Up @@ -50,7 +57,7 @@
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": false,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
Expand Down

0 comments on commit 4d05e84

Please sign in to comment.