Skip to content

Commit

Permalink
feat(bootstrap): Implement async bootstrapping and mock store
Browse files Browse the repository at this point in the history
  • Loading branch information
zakhenry committed Jun 17, 2016
1 parent 44f3e3e commit 135bcf9
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 36 deletions.
1 change: 1 addition & 0 deletions .env.example
@@ -1 +1,2 @@
VERBOSITY=silly
API_BASE=/api
1 change: 0 additions & 1 deletion env/.default.env

This file was deleted.

5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -34,6 +34,7 @@
"@angular/platform-browser-dynamic": "^2.0.0-rc.1",
"@angular/router-deprecated": "^2.0.0-rc.1",
"chalk": "^1.1.3",
"chance": "^1.0.3",
"core-js": "^2.4.0",
"dotenv": "^2.0.0",
"express": "^4.13.4",
Expand All @@ -52,9 +53,9 @@
"zone.js": "^0.6.12"
},
"devDependencies": {
"@ubiquits/toolchain": "^0.1.19"
"@ubiquits/toolchain": "^0.1.21"
},
"directories": {
"doc": "docs"
}
}
}
2 changes: 0 additions & 2 deletions src/browser/stores/http.store.spec.ts
Expand Up @@ -71,8 +71,6 @@ describe('Http store', () => {
body: [{id: 1, name: 'foo'}],
status: 200,
headers: null,
statusText: 'OK',
type: 0,
url: `${process.env.API_BASE}/tests`,
merge: null
}));
Expand Down
1 change: 1 addition & 0 deletions src/common/stores/index.ts
@@ -1 +1,2 @@
export * from './store';
export * from './mock.store';
37 changes: 37 additions & 0 deletions src/common/stores/mock.store.ts
@@ -0,0 +1,37 @@
import { identifier, ModelStatic, Model } from '../models/model';
import { Collection } from '../models/collection';
import { Store, Query } from './store';
import {Chance} from 'chance';
import * as _ from 'lodash';

export type ChanceInstance = Chance.Chance;

export abstract class MockStore<T extends Model> extends Store<T> {

protected chanceInstance: ChanceInstance;

constructor(modelStatic: ModelStatic<T>) {
super(modelStatic);
}

protected chance(seed?: any): ChanceInstance {
if (!this.chanceInstance || !!seed) {
this.chanceInstance = new Chance(seed);
}
return this.chanceInstance;
}

protected abstract getMock(id?:identifier):T;

public findOne(id: identifier): Promise<T> {
return Promise.resolve(this.getMock(id));
}

public findMany(query?:any): Promise<Collection<T>> {

const models = _.times(10, () => this.getMock());

return Promise.resolve(new Collection(models));
}

}
2 changes: 1 addition & 1 deletion src/common/validation/maxLength.decorator.ts
@@ -1,6 +1,6 @@
export function maxLength(length: number): PropertyDecorator {
return (target: any, propertyKey: string) => {
let type = Reflect.getMetadata("design:type", target, propertyKey);
console.log(length, type);
// console.log(length, type);
};
}
71 changes: 51 additions & 20 deletions src/server/bootstrap.ts
Expand Up @@ -9,14 +9,16 @@ import {
} from '@angular/core';
import { Server } from './servers/abstract.server';
import { AbstractController } from './controllers/abstract.controller';
import { Logger } from '../common/services/logger.service';
import { Logger, LogLevel } from '../common/services/logger.service';
import { coreInjector } from './main';
export {provide} from '@angular/core';

export type ProviderDefinition = Type | Provider | {
export type ProviderType = Type | Provider | {
[k: string]: any;
} | any[];

export type ProviderDefinition = ProviderType | Promise<ProviderType> | Promise<ProviderType[]>;

export interface BootstrapResponse {
injector: ReflectiveInjector;
server: Server;
Expand All @@ -27,44 +29,73 @@ export interface ControllerDictionary<T extends AbstractController> {
[key:string]: T;
}

export function bootstrap(controllers: ControllerDictionary<any>, providers: ProviderDefinition[] = []): BootstrapResponse {
export interface DeferredLog{
level: LogLevel;
messages: any[];
}

let deferredLogs:DeferredLog[] = [];
export function deferredLog(level:LogLevel, ...messages:any[]) {
deferredLogs.push({level, messages});
}

export function bootstrap(controllers: ControllerDictionary<any>, providers: ProviderDefinition[] = []): Promise<BootstrapResponse> {

let logger: Logger;

try {

let controllerArray = Object.keys(controllers).map(key => controllers[key]);

// resolve all controllers
let resolvedControllerProviders = ReflectiveInjector.resolve(controllerArray);

// resolve all other user classes
const resolvedProviders:ResolvedReflectiveProvider[] = ReflectiveInjector.resolve(providers)
.concat(resolvedControllerProviders);
return Promise.all(providers).then((providers: ProviderType[]) => {

// get an injector from the resolutions, using the core injector as parent
const injector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, coreInjector);
// resolve all other user classes
const resolvedProviders:ResolvedReflectiveProvider[] = ReflectiveInjector.resolve(providers)
.concat(resolvedControllerProviders);

// assign logger instance as soon as possible so the error handler might use it
logger = injector.get(Logger).source('bootstrap');
// get an injector from the resolutions, using the core injector as parent
const injector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, coreInjector);

// iterate over the controller providers, instantiating them to register their routes
resolvedControllerProviders.forEach((resolvedControllerProvider: ResolvedReflectiveProvider) => {
logger.info(`initializing ${resolvedControllerProvider.key.displayName}`);
injector.instantiateResolved(resolvedControllerProvider)
.registerInjector(injector)
.registerRoutes();
});
// assign logger instance as soon as possible so the error handler might use it
logger = injector.get(Logger).source('bootstrap');
deferredLogs.forEach((log:DeferredLog) => {
logger[log.level](...log.messages);
});

// iterate over the controller providers, instantiating them to register their routes
resolvedControllerProviders.forEach((resolvedControllerProvider: ResolvedReflectiveProvider) => {
logger.info(`initializing ${resolvedControllerProvider.key.displayName}`);
injector.instantiateResolved(resolvedControllerProvider)
.registerInjector(injector)
.registerRoutes();
});

// get vars for the bootstrapper
const server: Server = injector.get(Server);

// get vars for the bootstrapper
const server: Server = injector.get(Server);
return {injector, server, logger};

return {injector, server, logger};
});

} catch (e) {

if (logger){
deferredLogs.forEach((log:DeferredLog) => {
logger[log.level](...log.messages);
});

logger.critical(e);

} else {
console.error('Failed to initialize Logger, falling back to console');

deferredLogs.forEach((log:DeferredLog) => {
console.log(log.level, ...log.messages);
});

console.error(e);
}
process.exit(1);
Expand Down
2 changes: 0 additions & 2 deletions src/server/controllers/resource.controller.ts
Expand Up @@ -28,8 +28,6 @@ export abstract class ResourceController<M extends Model> extends AbstractContro
@Route('GET', '/:id')
public getOne(request: Request, response: Response): Promise<Response> {

this.logger.debug('reading params', request);

return this.modelStore
.findOne(request.params().get('id'))
.then((model:M) => response.data(model));
Expand Down
1 change: 1 addition & 0 deletions src/server/index.ts
@@ -1,4 +1,5 @@
export * from './main';
export * from './bootstrap';
export * from './controllers/index';
export * from './servers/index';
export * from './services/index';
Expand Down
7 changes: 5 additions & 2 deletions src/server/main.ts
Expand Up @@ -9,17 +9,20 @@ import { Logger } from '../common/services/logger.service';
import { ConsoleLogger } from '../common/services/consoleLogger.service';
import { DebugLogMiddleware } from './middleware/debugLog.middleware';
import { ExpressServer } from './servers/express.server';
export {provide} from '@angular/core';
export { provide } from '@angular/core';
import * as dotenv from 'dotenv';
import * as path from 'path';
import * as _ from 'lodash';

/**
* Load .env variables into process.env.*
*/
dotenv.config({
path: path.resolve(process.cwd(), './env/.default.env')
path: path.resolve(process.cwd(), '.env')
});

process.env = _.mapKeys(process.env, (value:any, key:string) => key.replace(/^PUBLIC_/, ''));

/**
* The core injector is exported so implementations can pick up already registered injectables
* without having to register them themselves.
Expand Down
25 changes: 19 additions & 6 deletions src/server/services/database.service.ts
Expand Up @@ -24,12 +24,7 @@ export class Database {
this.logger = loggerBase.source('database');

this.logger.info('Connecting to database');
this.driver = new Sequelize(process.env.DB_DATABASE, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
port: process.env.DB_PORT,
logging: (message: string, ...logs: any[]) => this.logger.debug(message, ...logs),
});
this.driver = Database.connect((message: string, ...logs: any[]) => this.logger.debug(message, ...logs));

// const schemaName = process.env.DB_USERNAME;
//
Expand All @@ -56,6 +51,15 @@ export class Database {
// });
}

public static connect(logFunction?:Function):Sequelize.Sequelize {
return new Sequelize(process.env.DB_DATABASE, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
logging: logFunction,
});
}

/**
* Retrive the driver instance
* @returns {Sequelize.Sequelize}
Expand Down Expand Up @@ -88,4 +92,13 @@ export class Database {
return this.driver.query(sql, options);
}

/**
* Check there is a connection
* @returns {Promise<void>}
*/
public static checkDatabase():Promise<any>{

return Database.connect().authenticate();
};

}
1 change: 1 addition & 0 deletions typings.json
@@ -1,5 +1,6 @@
{
"globalDependencies": {
"chance": "registry:dt/chance#0.7.3+20160317120654",
"dotenv": "registry:dt/dotenv#2.0.0+20160327131627",
"express": "registry:dt/express#4.0.0+20160317120654",
"express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160602151406",
Expand Down

0 comments on commit 135bcf9

Please sign in to comment.