Skip to content

Commit

Permalink
Release 1.0.0-alpha.6 🚀
Browse files Browse the repository at this point in the history
- Implemented model @Property decorator.
- Deprecated model schema in favour of property decorator.
- Decoupled HTTPServer
- Implemented middleware directory
- Decoupled schema provider from onix.server
- Implemented REST Server functionality
- Drastically improved dependency injection
- Implemented module level scopes (For DI)
- Implemented REST Apis (First Draft)
  • Loading branch information
Jonathan committed Mar 16, 2018
1 parent b036cf7 commit ff0fde5
Show file tree
Hide file tree
Showing 23 changed files with 762 additions and 264 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ $ npm install --save @onixjs/core
````
## Features

The **OnixJS FrameWork** is an ***Enterprise Grade*** **Node.JS** platform that implements only **Industry Standards** and **Patterns** in order provide the best development experience possible:

- High-Availability
- High-Performance
- Built-in TypeScript
- Dependency Injection
- Stand-Alone SDK (Can work in any framework: Angular, React, Vue, Even Native JS)
- Service Oriented Architecture (SOA - Micro Services)
- Module and Component Level LifeCycles (Not Opinionated Hooks)
- Remote Procedure Call and Streams API (RPC/RPCS)
- Back-end compatible with any ORM (Mongoose, Sequalize, TypeORM, Etc)
- Built-in **TypeScript**
- Identity Provider (**SSO**)
- **OpenID** Authentication
- **OAuth2** Resources Authorization
- **FrameWork Agnostic SDK** *(For ES6+/TypeScript Clents)*
- Dependency Injection (**DI**)
- **Service Oriented Architecture or Monolithic Architecture**.
- Representional State Transfer API (**REST API**)
- Remote Procedure Call and Streams API (**RPC/RPCS APIs**)
- Module and Component Level LifeCycles (**Hooks**)
- Back-end compatible with any **ORM** *(LoopBack's Datasource Juggler, Mongoose, Sequalize, TypeORM, Etc)*

## Phylosophy
The OnixJS phylosophy is to empower developers to decide which dependencies they want to install and maintain in their projects.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onixjs/core",
"version": "1.0.0-alpha.5",
"version": "1.0.0-alpha.6",
"description": "The High-Performance SOA Real-Time Framework for Node.JS",
"main": "dist/src/index.js",
"scripts": {
Expand Down
216 changes: 91 additions & 125 deletions src/core/app.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import {
IApp,
IAppConfig,
IModuleConfig,
IDataSource,
IComponent,
OperationType,
Constructor,
AppConstructor,
} from '../interfaces';
import {OnixRPC, Injector} from '../core';
import {getObjectMethods} from '..';
/**
* @class AppFactory
* @author Jonathan Casarrubias
Expand All @@ -23,6 +23,12 @@ export class AppFactory {
* @description The application reference
*/
public app: IApp;
/**
* @property scopes
* @description Module level scoped dependency injection
* directory.
*/
public scopes: {[key: string]: Injector} = {};
/**
* @constructor
* @param Class
Expand All @@ -32,73 +38,27 @@ export class AppFactory {
* inform this application has been created.
*/
constructor(private Class: AppConstructor, private config: IAppConfig) {
this.setupApp();
this.setupModules();
if (process.send)
process.send({
type: OperationType.APP_CREATE_RESPONSE,
message: this.schema(),
});
}
/**
* @method schema
* @description This method will build an app schema that will be exposed
* and published in a rest endpoint in order to provide enough information
* for client sdk to connect with this application.
*/
private schema(): any {
const copy = JSON.parse(JSON.stringify(this.config));
// Directory must be an object and not an array, this will
// allow SDK clients to perform much better.
copy.modules = {};
// Iterate modules
this.config.modules.forEach((Module: Constructor) => {
// Set reference
copy.modules[Module.name] = {};
// Get module metadata
const config: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.MODULE_CONFIG,
this.app[Module.name],
);
// Iterate over components to get the RPC references
config.components.forEach((Component: Constructor) => {
// Create reference for component methods
copy.modules[Module.name][Component.name] = {};
// Iterate Methods and verify which ones are RPC enabled
Object.getOwnPropertyNames(Component.prototype).forEach(
(method: string) => {
const rpcEnabled: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.RPC_METHOD,
this.app[Module.name][Component.name],
method,
);
const streamEnabled: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.STREAM_METHOD,
this.app[Module.name][Component.name],
method,
);
if (rpcEnabled || streamEnabled)
copy.modules[Module.name][Component.name][method] = rpcEnabled
? 'rpc'
: 'stream';
},
);
});
});
return copy;
}
/**
* @method setupApp
* @description This method simply creates a new class instance.
*/
private setupApp(): void {
// First of all create a new class instance
if (!this.app) this.app = new this.Class(new OnixRPC(this.Class));
if (!this.app.start || !this.app.stop)
// Then verify it is extending the base application
if (this.app.start && this.app.stop) {
// Now setup its modules
this.setupModules();
// Once finished send the schema back
// TODO: Potentially register to a provider in here
if (process.send)
process.send({
type: OperationType.APP_CREATE_RESPONSE,
message: this.schema(),
});
} else {
// If this is not a valid app, then throw an error
throw new Error(
`OnixJS: Invalid App "${
this.Class.name
}", it must extend from Application (import {Application} from '@onixjs/core')`,
);
}
}
/**
* @method setupModules
Expand All @@ -108,76 +68,28 @@ export class AppFactory {
*/
private setupModules() {
// Iterate list of module classes
this.config.modules.forEach((Module: Constructor) => {
if (this.app[Module.name]) return;
console.log('SETTING UP MODULE', Module.name);
// Create a module instance
this.app[Module.name] = new Module();
// Get module config from metadata
this.config.modules.forEach(async (Module: Constructor) => {
// Verify this is not a duplicated module
if (this.app.modules[Module.name]) return;
// Create a injection scope for this module
this.scopes[Module.name] = new Injector();
// Then create a module instance
this.app.modules[Module.name] = new Module();
// Get the module config from the metadata
const config: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.MODULE_CONFIG,
this.app[Module.name],
this.app.modules[Module.name],
);
// No config... Bad module then
if (!config)
throw new Error(
`OnixJS: Invalid Module "${
Module.name
}", it must provide a module config ({ models: [], services: [], components: [] })`,
);
// Models are stored within the Injector
if (config.models)
this.setupModels(config, this.app[Module.name], Module);
// Services are stored within the Injector
if (config.services)
this.setupServices(config, this.app[Module.name], Module);
// Components are stored within the Module Metadata
// Setup module components
if (config.components)
this.setupComponents(config, this.app[Module.name], Module);
});
}
/**
* @method setupModels
* @param config
* @description This method will setup models for a given module
* Models are specific to modules and won't be directly exposed
* from other modules.
*/
setupModels(config: IModuleConfig, moduleInstance, Module: Constructor) {
// Create Components Instances
config.models.forEach((Model: Constructor) => {
//const namespace: string = `${Module.name}.${Model.name}`;
const namespace: string = Model.name;
console.log('Initializing Model: ', namespace);
const datasource: IDataSource = Injector.get(
Reflect.getMetadata(ReflectionKeys.DATA_SOURCE, Model),
);
const schema: {[key: string]: any} = Reflect.getMetadata(
ReflectionKeys.MODEL_SCHEMA,
Model,
);
// TODO, Pass datasource reference maybe using injector maybe metadata
Injector.set(
namespace,
datasource.register(Model.name, new Model(), schema),
);
});
}
/**
* @method setupServices
* @param config
* @description This method will setup services for a given module
* Services are specific to modules and won't be directly exposed
* from other modules.
*/
setupServices(config: IModuleConfig, moduleInstance, Module: Constructor) {
// Create Components Instances
config.services.forEach((Service: Constructor) => {
//const namespace: string = `${Module.name}.${Service.name}`;
const namespace: string = Service.name;
console.log('Initializing Service: ', namespace);
if (!Injector.get(namespace)) {
Injector.set(namespace, new Service());
}
this.setupComponents(config, this.app.modules[Module.name], Module);
});
}
/**
Expand All @@ -186,17 +98,71 @@ export class AppFactory {
* @description This method will setup components for a given module
* Component methods will be exposed via RPC if these are public.
*
* private or protected methods won't be accessible to any caller.
* Injected models and services will be recursivelly instantiated.
* These will be singleton instances, so once instantiated, the same
* instance will be injected for each component.
*/
setupComponents(config: IModuleConfig, moduleInstance, Module: Constructor) {
// Create Components instances
config.components.forEach((Component: new () => IComponent) => {
// If component does not exist
if (!moduleInstance[Component.name]) {
console.log('Initializing Component: ', Component.name);
// Create a new component instance
moduleInstance[Component.name] = new Component();
moduleInstance[Component.name].init(); // Might be migrated to other point
// Then recursively inject models and services within this module
this.scopes[Module.name].inject(
Component,
moduleInstance[Component.name],
config,
);
}
});
}
/**
* @method schema
* @description This method will build an app schema that will be exposed
* and published in a rest endpoint in order to provide enough information
* for client sdk to connect with this application.
*/
private schema(): any {
const copy = JSON.parse(JSON.stringify(this.config));
// Directory must be an object and not an array, this will
// allow SDK clients to perform much better.
copy.modules = {};
// Iterate modules
this.config.modules.forEach((Module: Constructor) => {
// Set reference
copy.modules[Module.name] = {};
// Get module metadata
const config: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.MODULE_CONFIG,
this.app.modules[Module.name],
);
// Iterate over components to get the RPC references
config.components.forEach((Component: Constructor) => {
// Create reference for component methods
copy.modules[Module.name][Component.name] = {};
// Create crash-safe component reference
const component = this.app.modules[Module.name][Component.name] || {};
// Iterate Methods and verify which ones are RPC enabled
getObjectMethods(component).forEach((method: string) => {
const rpcEnabled: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.RPC_METHOD,
component,
method,
);
const streamEnabled: IModuleConfig = Reflect.getMetadata(
ReflectionKeys.STREAM_METHOD,
component,
method,
);
if (rpcEnabled || streamEnabled)
copy.modules[Module.name][Component.name][method] = rpcEnabled
? 'rpc'
: 'stream';
});
});
});
return copy;
}
}
19 changes: 17 additions & 2 deletions src/core/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IApp, OnixRPC, IAppConfig} from '../index';
import {IApp, OnixRPC, IAppConfig, IModuleDirectory} from '../index';
/**
* @class App
* @author Jonathan Casarrubias
Expand All @@ -9,6 +9,8 @@ import {IApp, OnixRPC, IAppConfig} from '../index';
* isolated components and modules.
**/
export class Application implements IApp {
modules: IModuleDirectory = {};

isAlive(): boolean {
return true;
}
Expand All @@ -32,7 +34,20 @@ export class Application implements IApp {
* by custom App level functionality
*/
async start(): Promise<null> {
return new Promise<null>((resolve, reject) => resolve());
return new Promise<null>((resolve, reject) => {
Object.keys(this.modules).forEach((moduleName: string) => {
Object.keys(this.modules[moduleName]).forEach(
(componentName: string) => {
if (
typeof this.modules[moduleName][componentName].init === 'function'
) {
this.modules[moduleName][componentName].init();
}
},
);
});
resolve();
});
}
/**
* @method stop
Expand Down
12 changes: 7 additions & 5 deletions src/core/call.responser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ export class CallResponser {
mainHook: Function = () => null,
slaveHook: Function | null = null;
// If segments are exactly 2, then it is an application level call
// Only god can do this type of calls.
// Only god can remotly execute this type of calls.
if (segments.length === 2) {
scope = this.factory.app;
method = this.factory.app[segments[1]];
mainHook = this.lifecycle.onAppMethodCall;
}
// Module level call (System only, not exposed)
if (segments.length > 2) {
scope = this.factory.app[segments[1]];
method = this.factory.app[segments[1]][segments[2]];
scope = this.factory.app.modules[segments[1]];
method = this.factory.app.modules[segments[1]][segments[2]];
const moduleConfig = Reflect.getMetadata(
ReflectionKeys.MODULE_CONFIG,
scope,
Expand All @@ -70,8 +70,10 @@ export class CallResponser {
}
// Component level method, RPC Exposed
if (segments.length === 4) {
scope = this.factory.app[segments[1]][segments[2]];
method = this.factory.app[segments[1]][segments[2]][segments[3]];
scope = this.factory.app.modules[segments[1]][segments[2]];
method = this.factory.app.modules[segments[1]][segments[2]][
segments[3]
];
const componentConfig = Reflect.getMetadata(
ReflectionKeys.COMPONENT_CONFIG,
scope,
Expand Down
Loading

0 comments on commit ff0fde5

Please sign in to comment.