'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;
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.context, 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)) {
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;
}