From a07cc392a8875016626c7fc9e1130bb4160a39d7 Mon Sep 17 00:00:00 2001 From: Misael dos Santos <47726159+misaeldossantos@users.noreply.github.com> Date: Fri, 17 Apr 2020 13:04:34 -0300 Subject: [PATCH] support for lazy injection if the injection decorator receives a function, the store will be resolved when requested, after its creation (class). This will allow mutual references - one store references another, and this reference is the first --- src/core/dependency-inject/Injector.ts | 182 +++++++++--------- .../dependency-inject/decorators/inject.ts | 85 ++++---- src/index.ts | 39 ++-- tsconfig.json | 102 +++------- 4 files changed, 186 insertions(+), 222 deletions(-) diff --git a/src/core/dependency-inject/Injector.ts b/src/core/dependency-inject/Injector.ts index 6c97ce4..aa5d1bd 100644 --- a/src/core/dependency-inject/Injector.ts +++ b/src/core/dependency-inject/Injector.ts @@ -4,113 +4,115 @@ * @since 2017/12/25 */ -import LRUCache, { LRUEntry } from 'lru-cache'; -import hydrate from './hydrate'; -import { Constructor } from './meta'; +import LRUCache, { LRUEntry } from "lru-cache"; +import hydrate from "./hydrate"; +import { Constructor } from "./meta"; -export const enum Scope { - Singleton = 'singleton', - Prototype = 'prototype', +export enum Scope { + Singleton = "singleton", + Prototype = "prototype" } export type InjectionOptions = { - name?: string; - scope: Scope; + name?: string; + scope: Scope; }; export type Snapshot = { - [propName: string]: any; + [propName: string]: any; }; export type Entry = { - k: K; - v: V; - e?: number; + k: K; + v: V; + e?: number; }; export interface IContainer { + set(key: K, value: V): boolean; - set(key: K, value: V): boolean; + get(key: K): V | undefined; - get(key: K): V | undefined; + dump(): Array>; - dump(): Array>; - - load(cacheEntries: ReadonlyArray>): void; + load(cacheEntries: ReadonlyArray>): void; } export default class Injector { - - private readonly container: IContainer; - - private constructor(container?: IContainer) { - this.container = container || new LRUCache(); - } - - static newInstance(container?: IContainer) { - return new Injector(container); - } - - _getContainer() { - return this.container; - } - - get(InjectedClass: Constructor, options: InjectionOptions, ...args: any[]): T { - - const { scope, name } = options; - const { container } = this; - - let instance; - - switch (scope) { - - case Scope.Singleton: - - if (name) { - - instance = container.get(name); - if (!instance) { - instance = new InjectedClass(...args); - // only singleton injection will be stored - container.set(name, instance); - } else { - const hydration = hydrate(instance, InjectedClass, ...args); - // when the stored instance is deserialized object(from snapshot), we need to restore the hydration instance - if (instance !== hydration) { - instance = hydration; - container.set(name, hydration); - } - } - - break; - } - - throw new SyntaxError('A singleton injection must have a name!'); - - case Scope.Prototype: - instance = new InjectedClass(...args); - break; - - default: - throw new SyntaxError('You must set injected class as a mmlpx recognized model!'); - } - - return instance; - } - - dump(): Snapshot { - return this.container.dump().reduce((acc, entry) => ({ ...acc, [entry.k]: entry.v }), {}); - } - - load(snapshot: Snapshot) { - - const cacheArray: ReadonlyArray> = Object.keys(snapshot).map(k => ({ - k, - v: snapshot[k], - e: 0, - })); - - this.container.load(cacheArray); - } - + private readonly container: IContainer; + + private constructor(container?: IContainer) { + this.container = container || new LRUCache(); + } + + static newInstance(container?: IContainer) { + return new Injector(container); + } + + _getContainer() { + return this.container; + } + + get( + InjectedClass: Constructor, + options: InjectionOptions, + ...args: any[] + ): T { + const { scope, name } = options; + const { container } = this; + + let instance; + + switch (scope) { + case Scope.Singleton: + if (name) { + instance = container.get(name); + if (!instance) { + instance = new InjectedClass(...args); + // only singleton injection will be stored + container.set(name, instance); + } else { + const hydration = hydrate(instance, InjectedClass, ...args); + // when the stored instance is deserialized object(from snapshot), we need to restore the hydration instance + if (instance !== hydration) { + instance = hydration; + container.set(name, hydration); + } + } + + break; + } + + throw new SyntaxError("A singleton injection must have a name!"); + + case Scope.Prototype: + instance = new InjectedClass(...args); + break; + + default: + throw new SyntaxError( + "You must set injected class as a mmlpx recognized model!" + ); + } + + return instance; + } + + dump(): Snapshot { + return this.container + .dump() + .reduce((acc, entry) => ({ ...acc, [entry.k]: entry.v }), {}); + } + + load(snapshot: Snapshot) { + const cacheArray: ReadonlyArray> = Object.keys( + snapshot + ).map(k => ({ + k, + v: snapshot[k], + e: 0 + })); + + this.container.load(cacheArray); + } } diff --git a/src/core/dependency-inject/decorators/inject.ts b/src/core/dependency-inject/decorators/inject.ts index b6c3761..7fb4678 100644 --- a/src/core/dependency-inject/decorators/inject.ts +++ b/src/core/dependency-inject/decorators/inject.ts @@ -3,43 +3,50 @@ * @homepage https://github.com/kuitos/ * @since 2017-07-11 */ - -import 'reflect-metadata'; -import hydrate from '../hydrate'; -import instantiate from '../instantiate'; -import { Constructor } from '../meta'; - -export default (InjectedClass?: Constructor, ...args: any[]): any => (target: any, property: string) => { - - const symbol = Symbol(property); - - if (!InjectedClass) { - InjectedClass = Reflect.getMetadata('design:type', target, property); - /* istanbul ignore next */ - if (!InjectedClass) { - throw new SyntaxError('You must pass a Class for injection while you are not using typescript!' + - 'Or you may need to add "emitDecoratorMetadata: true" configuration to your tsconfig.json'); - } - } - - return { - enumerable: true, - configurable: true, - get(this: any) { - - if (!this[symbol]) { - - const initializedValue = instantiate.apply(this, [InjectedClass, ...args]); - this[symbol] = initializedValue; - return initializedValue; - - } else { - return hydrate(this[symbol], InjectedClass!, ...args); - } - }, - // @formatter:off - // tslint:disable-next-line - set() {}, - // @formatter:on - }; +import "reflect-metadata"; +import hydrate from "../hydrate"; +import instantiate from "../instantiate"; +import { Constructor } from "../meta"; + +export default ( + InjectedClass?: Constructor | (() => Constructor), + ...args: any[] +): any => (target: any, property: string) => { + const symbol = Symbol(property); + + if (!InjectedClass) { + InjectedClass = Reflect.getMetadata("design:type", target, property); + /* istanbul ignore next */ + if (!InjectedClass) { + throw new SyntaxError( + "You must pass a Class for injection while you are not using typescript!" + + 'Or you may need to add "emitDecoratorMetadata: true" configuration to your tsconfig.json' + ); + } + } + + return { + enumerable: true, + configurable: true, + get(this: any) { + if (typeof InjectedClass === "function") { + InjectedClass = InjectedClass(); + } + + if (!this[symbol]) { + const initializedValue = instantiate.apply(this, [ + InjectedClass, + ...args + ]); + this[symbol] = initializedValue; + return initializedValue; + } else { + return hydrate(this[symbol], InjectedClass!, ...args); + } + }, + // @formatter:off + // tslint:disable-next-line + set() {} + // @formatter:on + }; }; diff --git a/src/index.ts b/src/index.ts index 7bc7de3..72a6d63 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,27 +4,32 @@ * @since 2017-07-12 */ -import useStrict from './api/configure'; -import inject from './core/dependency-inject/decorators/inject'; -import postConstruct from './core/dependency-inject/decorators/postConstruct'; -import Store from './core/dependency-inject/decorators/Store'; -import ViewModel from './core/dependency-inject/decorators/ViewModel'; -import instantiate from './core/dependency-inject/instantiate'; -import { IMmlpx, modelNameSymbol } from './core/dependency-inject/meta'; -import mock from './utils/mock'; +import useStrict from "./api/configure"; +import inject from "./core/dependency-inject/decorators/inject"; +import postConstruct from "./core/dependency-inject/decorators/postConstruct"; +import Store from "./core/dependency-inject/decorators/Store"; +import ViewModel from "./core/dependency-inject/decorators/ViewModel"; +import instantiate from "./core/dependency-inject/instantiate"; +import { IMmlpx, modelNameSymbol } from "./core/dependency-inject/meta"; +import mock from "./utils/mock"; -export { onSnapshot, applySnapshot, patchSnapshot, getSnapshot } from './api/snapshot'; +export { + onSnapshot, + applySnapshot, + patchSnapshot, + getSnapshot +} from "./api/snapshot"; export function getModelName(model: IMmlpx) { - return model[modelNameSymbol]; + return model[modelNameSymbol]; } export { - inject, - ViewModel, - Store, - postConstruct, - instantiate, - mock, - useStrict, + inject, + ViewModel, + Store, + postConstruct, + instantiate, + mock, + useStrict }; diff --git a/tsconfig.json b/tsconfig.json index 218f79b..cf8d13c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,77 +1,27 @@ { - "compilerOptions": { - /* Basic Options */ - "target": "es5", - /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", - /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": [ - "es2015" - ], - /* Specify library files to be included in the compilation: */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true, - /* Generates corresponding '.d.ts' file. */ - // "sourceMap": true, - /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "dist/lib", - /* Redirect output structure to the directory. */ - "rootDir": "src", - /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - "importHelpers": true, - /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, - "strictPropertyInitialization": false, - /* Enable all strict type-checking options. */ - "noImplicitAny": true, - /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, - /* Enable strict null checks. */ - "noImplicitThis": true, - /* Raise error on 'this' expressions with an implied 'any' type. */ - "alwaysStrict": true, - /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - "noUnusedLocals": false, - /* Report errors on unused locals. */ - "noUnusedParameters": true, - /* Report errors on unused parameters. */ - "noImplicitReturns": true, - /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, - /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - - /* Source Map Options */ - // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - /* Enables experimental support for ES7 framework. */ - "emitDecoratorMetadata": true - /* Enables experimental support for emitting type metadata for framework. */ - } -} + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": [ + "es2015" + ], + "declaration": true, + "outDir": "dist/lib", + "rootDir": "src", + "importHelpers": true, + "strict": true, + "strictPropertyInitialization": false, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} \ No newline at end of file