Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for lazy injection #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 92 additions & 90 deletions src/core/dependency-inject/Injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, V> = {
k: K;
v: V;
e?: number;
k: K;
v: V;
e?: number;
};

export interface IContainer<K, V> {
set(key: K, value: V): boolean;

set(key: K, value: V): boolean;
get(key: K): V | undefined;

get(key: K): V | undefined;
dump(): Array<Entry<K, V>>;

dump(): Array<Entry<K, V>>;

load(cacheEntries: ReadonlyArray<Entry<K, V>>): void;
load(cacheEntries: ReadonlyArray<Entry<K, V>>): void;
}

export default class Injector {

private readonly container: IContainer<string, any>;

private constructor(container?: IContainer<string, any>) {
this.container = container || new LRUCache<string, any>();
}

static newInstance(container?: IContainer<string, any>) {
return new Injector(container);
}

_getContainer() {
return this.container;
}

get<T>(InjectedClass: Constructor<T>, 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<LRUEntry<string, any>> = Object.keys(snapshot).map(k => ({
k,
v: snapshot[k],
e: 0,
}));

this.container.load(cacheArray);
}

private readonly container: IContainer<string, any>;

private constructor(container?: IContainer<string, any>) {
this.container = container || new LRUCache<string, any>();
}

static newInstance(container?: IContainer<string, any>) {
return new Injector(container);
}

_getContainer() {
return this.container;
}

get<T>(
InjectedClass: Constructor<T>,
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<LRUEntry<string, any>> = Object.keys(
snapshot
).map(k => ({
k,
v: snapshot[k],
e: 0
}));

this.container.load(cacheArray);
}
}
85 changes: 46 additions & 39 deletions src/core/dependency-inject/decorators/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T>(InjectedClass?: Constructor<T>, ...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 <T>(
InjectedClass?: Constructor<T> | (() => Constructor<T>),
...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
};
};
39 changes: 22 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(model: IMmlpx<T>) {
return model[modelNameSymbol];
return model[modelNameSymbol];
}

export {
inject,
ViewModel,
Store,
postConstruct,
instantiate,
mock,
useStrict,
inject,
ViewModel,
Store,
postConstruct,
instantiate,
mock,
useStrict
};
Loading