singletons
Version:
Helps create and manage families of singletons based on customizable conditions
239 lines (186 loc) • 8.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SingletonFactory = undefined;
var _keyfunc = require('keyfunc');
var _keyfunc2 = _interopRequireDefault(_keyfunc);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var getKeyFunc = function getKeyFunc(defaultKeyfunc) {
var keyfunc = defaultKeyfunc;
if (typeof keyfunc !== 'function') {
if (Array.isArray(keyfunc)) {
keyfunc = _keyfunc2.default.apply(undefined, _toConsumableArray(keyfunc));
} else {
throw new TypeError('Initializing keyFunc argument should be a function generating unique\nkeys from arguments, or an array of hints');
}
}
return keyfunc;
};
var idFunc = function idFunc(args) {
return args;
};
var SingletonFactory = exports.SingletonFactory = function SingletonFactory(Type) {
var defaultKeyfunc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (obj) {
return obj.toString();
};
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var keyfunc = getKeyFunc(defaultKeyfunc);
// Wholesale preprocessing (before everything except direct type conversion)
var preprocess = options.preprocess || idFunc;
// Wholesale postprocessing (last chance to update the current singleton)
var postprocess = options.postprocess || idFunc;
// If defined then this Type itself is spreadable (the singleton is some
// kind of container)
var spread = options.spread;
var shallowSpread = options.shallowSpread;
// If not undefined, some args will require special processing
var customArgs = options.customArgs && new Map(options.customArgs) || spread || shallowSpread && new Map([Type, { spread: spread, shallowSpread: shallowSpread }]);
if (options.customArgs && (spread || shallowSpread)) {
// If both are true, then customArgs already exists but is still missing
// this Type as special arg (or not! But then it's a user mistake that
// yields here a silent override)
customArgs.set(Type, { spread: spread, shallowSpread: shallowSpread });
}
// When special handling is activated, reduceableTypes will help merge
// several args into one
var reduceableTypes = customArgs ? new Set(Array.from(customArgs.keys()).filter(function (key) {
return customArgs.get(key).reduce;
})) : null;
// When special handling is activated, spreadableTypes will help split
// one arg into several
var spreadableTypes = customArgs ? new Set(Array.from(customArgs.keys()).filter(function (key) {
var customArg = customArgs.get(key);
return customArg.spread || customArg.shallowSpread;
})) : null;
// Helper function to split registered containerlike args into the args
// they contain
var spreadArgs = function spreadArgs(array, arg) {
var newArray = void 0;
var type = Object.getPrototypeOf(arg).constructor;
if (spreadableTypes.has(type)) {
var _customArgs$get = customArgs.get(type),
_spread = _customArgs$get.spread,
_shallowSpread = _customArgs$get.shallowSpread;
newArray = _spread === true ? Array.from(arg).reduce(spreadArgs, []) : _spread !== undefined ? _spread(arg).reduce(spreadArgs, []) : _shallowSpread === true ? Array.from(arg) : _shallowSpread(arg);
} else {
newArray = [arg];
}
return array.concat(newArray);
};
// Helper function to encapsulate all preprocessing
var preprocessAll = function preprocessAll(_args) {
var extractedArgs = void 0;
var convertedArgs = void 0;
var unreduceableArgs = void 0;
if (customArgs) {
// First distinguish between regular init args and special treatment args
extractedArgs = _args.filter(function (arg) {
return customArgs.has(Object.getPrototypeOf(arg).constructor);
});
// Pass through regular args as already converted;
// Spread spreadable special args;
// Convert convertible special args;
// Filter out all other known special args as they are meaningless
// to instanciate Type
convertedArgs = _args.reduce(spreadArgs, []).map(function (arg) {
var type = Object.getPrototypeOf(arg).constructor;
if (customArgs.has(type)) {
var _customArgs$get2 = customArgs.get(type),
convert = _customArgs$get2.convert,
_spread2 = _customArgs$get2.spread,
_shallowSpread2 = _customArgs$get2.shallowSpread;
return convert ? convert(arg) : _spread2 || _shallowSpread2 ? arg : null;
}
return arg;
}).filter(function (arg) {
return arg !== null;
});
// Filter special args that won't need postprocess reduction
unreduceableArgs = extractedArgs.filter(function (arg) {
var type = Object.getPrototypeOf(arg).constructor;
return !reduceableTypes.has(type);
});
}
// convertedArgs contains also regular args;
// After this, all args have the correct types and order to instanciate or
// recall a unique instance of type Type
var args = preprocess(convertedArgs || _args);
return { args: args, extractedArgs: extractedArgs, unreduceableArgs: unreduceableArgs };
};
// All singletons from this Singleton type ever created
var instances = new Map();
// The unique field marking every instance of type Type created by the
// following Singleton
var keySymb = Symbol();
// The custom Singleton factory out of this Singleton factory
var Singleton = function Singleton() {
for (var _len = arguments.length, _args = Array(_len), _key2 = 0; _key2 < _len; _key2++) {
_args[_key2] = arguments[_key2];
}
var _preprocessAll = preprocessAll(_args),
args = _preprocessAll.args,
extractedArgs = _preprocessAll.extractedArgs,
unreduceableArgs = _preprocessAll.unreduceableArgs;
var key = Singleton.key.apply(Singleton, _toConsumableArray(args));
var instance = instances.get(key);
// If the singleton doesn't exist, create it, mark it, register it
if (!instance) {
instance = new (Function.prototype.bind.apply(Type, [null].concat(_toConsumableArray(args))))();
instance[keySymb] = key;
instances.set(key, instance);
}
// If special args, now use them to update the singleton instance
if (customArgs) {
// Reduce reduceable special args and postprocess them
reduceableTypes.forEach(function (type) {
var _customArgs$get3 = customArgs.get(type),
reduce = _customArgs$get3.reduce,
postprocess = _customArgs$get3.postprocess;
postprocess.call(instance, reduce(extractedArgs.filter(function (arg) {
return arg instanceof type;
})));
});
// Postprocess unreduceable special args
unreduceableArgs.forEach(function (arg) {
var type = Object.getPrototypeOf(arg).constructor;
var _ref = customArgs.get(type) || {},
postprocess = _ref.postprocess;
if (postprocess) {
postprocess.call(instance, arg);
}
});
}
// Last call for preprocessing
postprocess.call(instance, args);
return instance;
};
Singleton.key = function (arg0) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key3 = 1; _key3 < _len2; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
if (arg0[keySymb] && !args.length) {
return arg0[keySymb];
}
return keyfunc.apply(undefined, [arg0].concat(args));
};
Singleton.singleton = function (_key) {
return instances.get(_key);
};
Singleton.get = function () {
return instances.get(Singleton.key.apply(Singleton, arguments));
};
Singleton.looseKey = function () {
for (var _len3 = arguments.length, _args = Array(_len3), _key4 = 0; _key4 < _len3; _key4++) {
_args[_key4] = arguments[_key4];
}
var _preprocessAll2 = preprocessAll(_args),
args = _preprocessAll2.args;
return Singleton.key.apply(Singleton, _toConsumableArray(args));
};
Singleton.looseGet = function () {
return instances.get(Singleton.looseKey.apply(Singleton, arguments));
};
return Singleton;
};