-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented everything to a working state
- Loading branch information
1 parent
305d763
commit cfdb9ad
Showing
18 changed files
with
448 additions
and
7 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Publish | ||
on: | ||
release: | ||
types: [created] | ||
|
||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions/setup-node@v1 | ||
with: | ||
node-version: '14.x' | ||
registry-url: 'https://registry.npmjs.org' | ||
- run: yarn install | ||
- run: yarn build | ||
- run: npm publish --access public | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} |
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,11 @@ | ||
src/ | ||
test/ | ||
tsconfig.json | ||
jest.config.json | ||
*.js.map | ||
.eslintrc.js | ||
typings | ||
.vscode | ||
.prettierrc | ||
.gitignore | ||
yarn.lock |
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
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,12 @@ | ||
import { IContainer } from "../ioc/ioc.interfaces"; | ||
import { ILogger } from "../util/logger.interfaces"; | ||
|
||
export interface IApplicationOptions { | ||
name: string; | ||
} | ||
|
||
export interface IApplication { | ||
onInitialization(): Promise<void>; | ||
onStart(): Promise<void>; | ||
onStop(): Promise<void>; | ||
} |
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,97 @@ | ||
import { IocContainer } from "../ioc/container"; | ||
import { IClassProvider, IContainer, IFactoryProvider, IProvider, IScope, IValueProvider } from "../ioc/ioc.interfaces"; | ||
import { isClassProvider, isFactoryProvider, isValueProvider } from "../ioc/util"; | ||
import { IModule, IModuleOptions } from "../module/module.interfaces"; | ||
import { Class } from "../types"; | ||
import { ILogger } from "../util/logger.interfaces"; | ||
import { BasicLogger } from "../util/basic.logger"; | ||
import { IApplication, IApplicationOptions } from "./application.interfaces"; | ||
|
||
export abstract class Application implements IApplication { | ||
|
||
protected readonly container: IContainer; | ||
protected readonly logger: ILogger; | ||
private readonly internalLogger: ILogger; | ||
|
||
protected readonly controllers: Class<any>[]; | ||
protected readonly providers: (Class<any> | IProvider<any>)[]; | ||
|
||
constructor( | ||
protected readonly mainModule: IModule, | ||
protected readonly options: IApplicationOptions, | ||
) { | ||
this.container = new IocContainer(); | ||
this.logger = new BasicLogger(options.name); | ||
this.internalLogger = new BasicLogger('Bootstrap'); | ||
this.controllers= []; | ||
this.providers = []; | ||
} | ||
|
||
public async onInitialization(): Promise<void> { | ||
this.internalLogger.info('Initializing', this.options.name, '...'); | ||
this.internalLogger.info('Preparing container providers...'); | ||
await this.registerModule(this.mainModule); | ||
} | ||
|
||
public async onStart(): Promise<void> { | ||
|
||
} | ||
|
||
public async onStop(): Promise<void> { | ||
|
||
} | ||
|
||
private async registerModule(module: IModule): Promise<void> { | ||
await this.registerProviders(module); | ||
await this.registerControllers(module); | ||
await this.registerImports(module); | ||
} | ||
|
||
private async registerProviders(module: IModule): Promise<void> { | ||
for(const provider of module.options.providers) { | ||
if(isClassProvider(<any>provider)) { | ||
const classProvider: IClassProvider<any> = <IClassProvider<any>>provider; | ||
this.container.register(classProvider.key).asClassProvider(classProvider.class, classProvider.scope); | ||
} else if(isFactoryProvider(<any>provider)) { | ||
const factoryProvider: IFactoryProvider<any> = <IFactoryProvider<any>>provider; | ||
this.container.register(factoryProvider.key).asFactoryProvider(factoryProvider.factory); | ||
} else if(isValueProvider(<any>provider)) { | ||
const valueProvider: IValueProvider<any> = <IValueProvider<any>>provider; | ||
this.container.register(valueProvider.key).asValueProvider(valueProvider.value); | ||
} else { | ||
const providerName: string = Reflect.getMetadata('injectable:name', provider); | ||
const providerScope: Class<IScope> = Reflect.getMetadata('injectable:scope', provider); | ||
if(providerName === undefined || providerScope === undefined) | ||
throw new Error(`Invalid provider found: ${provider}`); | ||
this.providers.push(provider); | ||
this.container.register(<Class<any>>provider).asClassProvider(<Class<any>>provider, new providerScope()); | ||
} | ||
} | ||
} | ||
|
||
private async registerControllers(module: IModule): Promise<void> { | ||
for(const controller of module.options.controllers) { | ||
const controllerName: string = Reflect.getMetadata('injectable:name', controller); | ||
const controllerScope: Class<IScope> = Reflect.getMetadata('injectable:scope', controller); | ||
if(controllerName === undefined || controllerScope === undefined) | ||
throw new Error(`Invalid controller found: ${controller}`); | ||
this.controllers.push(controller); | ||
this.container.register(controllerName).asClassProvider(controller, new controllerScope()); | ||
} | ||
} | ||
|
||
private async registerImports(module: IModule): Promise<void> { | ||
for(const importedModule of module.options.imports) { | ||
const moduleName: string = Reflect.getMetadata('module:name', importedModule) | ||
const moduleOptions: IModuleOptions = Reflect.getMetadata('module:options', importedModule); | ||
if(moduleName === undefined || moduleOptions === undefined) | ||
throw new Error(`Invalid imported module found: ${importedModule}`); | ||
const instance: any = new importedModule(); | ||
if('onInitialization' in instance) | ||
await instance['onInitialization'](); | ||
const generatedModule: IModule = { name: moduleName, moduleClass: importedModule, options: moduleOptions }; | ||
await this.registerModule(generatedModule); | ||
} | ||
} | ||
|
||
} |
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,13 @@ | ||
import 'reflect-metadata'; | ||
|
||
export * from './mooncake'; | ||
export * from './types'; | ||
export * from './util/logger.interfaces'; | ||
export * from './util/basic.logger'; | ||
export * from './module/module.interfaces'; | ||
export * from './ioc/container'; | ||
export * from './ioc/ioc.interfaces'; | ||
export * from './ioc/scopes'; | ||
export * from './ioc/util'; | ||
export * from './application/application.interfaces'; | ||
export * from './application/application'; |
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,80 @@ | ||
import { TransientScope } from ".."; | ||
import { Class } from "../types"; | ||
import { IClassProvider, IContainer, IContainerEntry, IFactoryProvider, IProvider, IScope, IValueProvider } from "./ioc.interfaces"; | ||
import { isClassProvider, isFactoryProvider, isValueProvider } from "./util"; | ||
|
||
export class ContainerEntry<T> implements IContainerEntry<T> { | ||
|
||
public provider: IProvider<T>; | ||
|
||
constructor( | ||
public key: string, | ||
) {} | ||
|
||
public asClassProvider(value: Class<T>, scope: IScope = new TransientScope()): void { | ||
this.provider = <IClassProvider<T>>{ | ||
key: this.key, | ||
class: value, | ||
scope: scope, | ||
}; | ||
} | ||
|
||
public asFactoryProvider(value: Function): void { | ||
this.provider = <IFactoryProvider<T>>{ | ||
key: this.key, | ||
factory: value | ||
}; | ||
} | ||
|
||
public asValueProvider(value: T): void { | ||
this.provider = <IValueProvider<T>>{ | ||
key: this.key, | ||
value: value | ||
}; | ||
} | ||
|
||
} | ||
|
||
export class IocContainer implements IContainer { | ||
|
||
private readonly entries: IContainerEntry<any>[]; | ||
|
||
constructor() { | ||
this.entries = new Array<IContainerEntry<any>>(); | ||
} | ||
|
||
public register<T>(key: Class<T> | string): IContainerEntry<T> { | ||
const generatedKey: string = this.generateKey(key); | ||
const entries: IContainerEntry<any>[] = this.entries.filter((entry: IContainerEntry<any>) => entry.provider.key === generatedKey); | ||
if(entries.length !== 0) | ||
throw new Error(`There is already an entry with that key!`); | ||
let entry: IContainerEntry<T> = new ContainerEntry<T>(generatedKey); | ||
this.entries.push(entry); | ||
return entry; | ||
} | ||
|
||
public resolve<T>(key: string | Class<T>): T { | ||
const generatedKey: string = this.generateKey(key); | ||
const entry: IContainerEntry<any> = this.entries.find((entry: IContainerEntry<any>) => entry.provider.key === generatedKey); | ||
if(entry === undefined) | ||
throw new Error(`There is no entry with that key: ${key}`); | ||
if(isClassProvider(entry.provider)) | ||
return (<IClassProvider<T>>entry.provider).scope.resolve<T>((<IClassProvider<T>>entry.provider), this); | ||
else if(isFactoryProvider(entry.provider)) | ||
return (<IFactoryProvider<T>>entry.provider).factory(); | ||
else if(isValueProvider(entry.provider)) | ||
return (<IValueProvider<T>>entry.provider).value; | ||
else | ||
throw new Error(`Invalid provider detected with key "${entry.provider.key}"`); | ||
} | ||
|
||
public generateKey(key: any): string { | ||
if(typeof(key) === 'string') | ||
return key; | ||
const name: string = Reflect.getMetadata('injectable:name', key); | ||
if(name === undefined) | ||
return key.name; | ||
return name; | ||
} | ||
|
||
} |
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,47 @@ | ||
import { Class } from "../types"; | ||
import { TransientScope } from "./scopes"; | ||
import { makeTargetInjectable } from "./util"; | ||
|
||
export interface IScope { | ||
resolve<T>(provider: IClassProvider<T>, container: IContainer): T; | ||
} | ||
|
||
export interface IProvider<T> { | ||
key: string, | ||
} | ||
|
||
export interface IFactoryProvider<T> extends IProvider<T> { | ||
factory: Function, | ||
} | ||
|
||
export interface IClassProvider<T> extends IProvider<T> { | ||
class: Class<T>, | ||
scope: IScope, | ||
} | ||
|
||
export interface IValueProvider<T> extends IProvider<T> { | ||
value: T, | ||
} | ||
|
||
export interface IContainerEntry<T> { | ||
provider: IProvider<T>; | ||
asClassProvider(value: Class<T>, scope?: IScope): void; | ||
asFactoryProvider(factory: Function): void; | ||
asValueProvider(value: T): void; | ||
} | ||
|
||
export interface IContainer { | ||
register<T>(key: Class<T> | string): IContainerEntry<T>; | ||
resolve<T>(key: Class<T> | string): T; | ||
generateKey(key: any): string; | ||
} | ||
|
||
export const Injectable = (name?: string, scope: Class<IScope> = TransientScope): ClassDecorator => <TFunction extends Function>(target: TFunction): void => { | ||
makeTargetInjectable(target, name, scope); | ||
} | ||
|
||
export const Inject = (name: string): ParameterDecorator => (target: Object, propertyKey: string | symbol, parameterIndex: number): void => { | ||
const keys: any = Reflect.getMetadata('injection:keys', target) ?? []; | ||
keys.push({ key: name, index: parameterIndex }); | ||
Reflect.defineMetadata('injection:keys', keys, target); | ||
} |
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,42 @@ | ||
import { IClassProvider, IContainer, IProvider, IScope } from "./ioc.interfaces"; | ||
|
||
export class SingletonScope implements IScope { | ||
|
||
private instance: any; | ||
|
||
public resolve<T>(provider: IClassProvider<T>, container: IContainer): T { | ||
if(this.instance !== undefined) | ||
return this.instance; | ||
const parameterTypes: any[] = Reflect.getMetadata('design:paramtypes', provider.class) ?? []; | ||
const injectionKeys: any[] = Reflect.getMetadata('injection:keys', provider.class) ?? []; | ||
const resolvedParameters: any[] = []; | ||
parameterTypes.forEach((parameter: any, index: number) => { | ||
const key: string = injectionKeys.find((value: any) => value.index === index)?.key; | ||
const resolved: any = container.resolve(key ?? parameter); | ||
if(resolved === undefined) | ||
throw new Error(`Couldn't resolve parameter "${parameter}"!`); | ||
resolvedParameters.push(resolved); | ||
}); | ||
this.instance = new provider.class(...resolvedParameters); | ||
return this.instance; | ||
} | ||
|
||
} | ||
|
||
export class TransientScope implements IScope { | ||
|
||
public resolve<T>(provider: IClassProvider<T>, container: IContainer): T { | ||
const parameterTypes: any[] = Reflect.getMetadata('design:paramtypes', provider.class) ?? []; | ||
const injectionKeys: any[] = Reflect.getMetadata('injection:keys', provider.class) ?? []; | ||
const resolvedParameters: any[] = []; | ||
parameterTypes.forEach((parameter: any, index: number) => { | ||
const key: string = injectionKeys.find((value: any) => value.index === index)?.key; | ||
const resolved: any = container.resolve(key ?? parameter); | ||
if(resolved === undefined) | ||
throw new Error(`Couldn't resolve parameter "${parameter}"!`); | ||
resolvedParameters.push(resolved); | ||
}); | ||
return new provider.class(...resolvedParameters); | ||
} | ||
|
||
} |
Oops, something went wrong.