'use strict';
|
|
const assert = require('assert');
|
const is = require('is-type-of');
|
const FileLoader = require('./file_loader');
|
const CLASSLOADER = Symbol('classLoader');
|
const EXPORTS = FileLoader.EXPORTS;
|
const Utils = require('../utils');
|
|
class ClassLoader {
|
|
constructor(options) {
|
assert(options.ctx, 'options.ctx is required');
|
const properties = options.properties;
|
this._cache = new Map();
|
this._ctx = options.ctx;
|
|
for (const property in properties) {
|
this.defineProperty(property, properties[property]);
|
}
|
}
|
|
defineProperty(property, values) {
|
Object.defineProperty(this, property, {
|
get() {
|
let instance = this._cache.get(property);
|
if (!instance) {
|
instance = getInstance(values, this._ctx);
|
this._cache.set(property, instance);
|
}
|
return instance;
|
},
|
});
|
}
|
}
|
|
/**
|
* Same as {@link FileLoader}, but it will attach file to `inject[fieldClass]`. The exports will be lazy loaded, such as `ctx.group.repository`.
|
* @extends FileLoader
|
* @since 1.0.0
|
*/
|
class ContextLoader extends FileLoader {
|
|
/**
|
* @class
|
* @param {Object} options - options same as {@link FileLoader}
|
* @param {String} options.fieldClass - determine the field name of inject object.
|
*/
|
constructor(options) {
|
assert(options.property, 'options.property is required');
|
assert(options.inject, 'options.inject is required');
|
const target = options.target = {};
|
if (options.fieldClass) {
|
options.inject[options.fieldClass] = target;
|
}
|
super(options);
|
|
const app = this.options.inject;
|
const property = options.property;
|
|
// define ctx.service
|
Object.defineProperty(app, property, {
|
get() {
|
// distinguish property cache,
|
// cache's lifecycle is the same with this context instance
|
// e.x. ctx.service1 and ctx.service2 have different cache
|
if (!this[CLASSLOADER]) {
|
this[CLASSLOADER] = new Map();
|
}
|
const classLoader = this[CLASSLOADER];
|
|
let instance = classLoader.get(property);
|
if (!instance) {
|
instance = getInstance(target, this);
|
classLoader.set(property, instance);
|
}
|
return instance;
|
},
|
});
|
}
|
}
|
|
module.exports = ContextLoader;
|
|
|
function getInstance(values, ctx) {
|
// it's a directory when it has no exports
|
// then use ClassLoader
|
const Class = values[EXPORTS] ? values : null;
|
let instance;
|
if (Class) {
|
if (is.class(Class) || Utils.isBytecodeClass(Class)) {
|
instance = new Class(ctx);
|
} else {
|
// it's just an object
|
instance = Class;
|
}
|
// Can't set property to primitive, so check again
|
// e.x. module.exports = 1;
|
} else if (is.primitive(values)) {
|
instance = values;
|
} else {
|
instance = new ClassLoader({ ctx, properties: values });
|
}
|
return instance;
|
}
|