UNPKG

scontainers

Version:

A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.

324 lines (229 loc) 7.3 kB
"use strict"; function _implSymbol(target, sym, value) { Object.defineProperty(target, sym, { value, configurable: true }); return target[sym]; } function _getSymbol(targetSymName, ...traitSets) { let symbol; traitSets.forEach(traitSet => { const sym = traitSet[targetSymName]; if (typeof sym === 'symbol') { if (!!symbol && symbol !== sym) { throw new Error(`Symbol ${targetSymName} offered by multiple trait sets.`); } symbol = sym; } }); if (!symbol) { throw new Error(`No trait set is providing symbol ${targetSymName}.`); } return symbol; } function _testTraitSet(traitSet) { if (!traitSet || typeof traitSet === 'boolean' || typeof traitSet === 'number' || typeof traitSet === 'string') { throw new Error(`${traitSet} cannot be used as a trait set.`); } } const straits = require('straits'); const { assert, id } = require('../utils_light.js'); _testTraitSet(straits.core); _testTraitSet(straits.utils); const _borrowTraits = _getSymbol("borrowTraits", straits.core, straits.utils); const utilTraits = straits.utils.TraitSet.fromKeys({ extractKeys(keys) {}, defaultGet(key, defaultConstructor) {}, addTraits(target, implementationObj) {}, addTraitFactories(target, factoryObj) {}, implScontainer(implementationObj) {}, wrapScontainer(decoratorFactoryFactory) {}, // return a function factory for the decorator `decoratorFactory` built on `this` describeScontainer() {}, implCoreGenerators() {}, implCoreTraits() {}, deriveTraits() {} }); utilTraits[_borrowTraits](straits.utils, [id`asFreeFunction`, id`asMethod`, id`impl`, id`implTraits`]); utilTraits[_borrowTraits](straits.common, [id`toString`]); _testTraitSet(utilTraits); const _impl = _getSymbol("impl", straits.core, straits.utils, utilTraits); const _addTraitFactories2 = _getSymbol("addTraitFactories", straits.core, straits.utils, utilTraits); const _addTraits = _getSymbol("addTraits", straits.core, straits.utils, utilTraits); const _defaultGet = _getSymbol("defaultGet", straits.core, straits.utils, utilTraits); const _extractKeys = _getSymbol("extractKeys", straits.core, straits.utils, utilTraits); // remove all the `keys` from `this`, and return an object made of all the removed keys and their values. _implSymbol(Object.prototype, _extractKeys, function (keys) { const result = {}; keys.forEach(key => { result[key] = this[key]; delete this[key]; }); return result; }); _implSymbol(Map.prototype, _defaultGet, function (key, defaultConstructor) { if (this.has(key)) { return this.get(key); } else { const value = defaultConstructor(key); this.set(key, value); return value; } }); // like `implTraits`, but without overriding already implemented traits _implSymbol(Object.prototype, _addTraits, function (target, implementationObj) { for (let name in implementationObj) { const sym = this[name]; assert(typeof sym === 'symbol', `No trait \`${name}\``); if (target.hasOwnProperty(sym)) { // trait already implemented; skipping continue; } sym[_impl](target, implementationObj[name]); } }); class Factory { constructor(factory, target) { this.target = target; if (!factory) { this.factory = function () { throw new Error(`No factory`); }; this.assignImmediately = false; this.canProduce = () => false; } else if (typeof factory === 'function') { this.factory = factory; this.assignImmediately = false; this.canProduce = function () { return this.factory.call(target) !== undefined; }; } else { this.factory = factory.factory; this.assignImmediately = factory.assignImmediately || false; this.canProduce = factory.canProduce || (() => true); } } produce() { const { target } = this; const fn = this.factory.call(target); assert(fn !== undefined, `A factory didn't produce anything`); fn.factory = function () { return fn; }; return fn; } } // `factoryObj` has symbol names as traits and factories as _implSymbol(Object.prototype, _addTraitFactories2, function (target, factoryObj) { for (let name in factoryObj) { const factory = new Factory(factoryObj[name], target); if (!factory.canProduce()) { // factory can't produce anything continue; } const sym = this[name]; assert(typeof sym === 'symbol', `No trait \`${name}\``); if (target.hasOwnProperty(sym)) { // trait already implemented; skipping continue; } if (factory.assignImmediately) { const fn = factory.produce(); sym[_impl](target, fn); } else { const lazyFn = function () { const fn = factory.produce(); sym[_impl](target, fn); return this[sym](...arguments); }; lazyFn.factory = function () { const fn = factory.produce(); sym[_impl](target, fn); return fn; }; sym[_impl](target, lazyFn); } } }); module.exports = utilTraits; if (require.main === module) { const traits = new straits.utils.TraitSet('a', 'b', 'c', 'x', 'y', 'z'); _testTraitSet(traits); const _addTraitFactories = _getSymbol("addTraitFactories", straits.core, straits.utils, utilTraits, traits); const _a = _getSymbol("a", straits.core, straits.utils, utilTraits, traits); const _b = _getSymbol("b", straits.core, straits.utils, utilTraits, traits); const _c = _getSymbol("c", straits.core, straits.utils, utilTraits, traits); const _x = _getSymbol("x", straits.core, straits.utils, utilTraits, traits); const _y = _getSymbol("y", straits.core, straits.utils, utilTraits, traits); const _z = _getSymbol("z", straits.core, straits.utils, utilTraits, traits); const logs = []; const log = function (arg) { logs.push(arg); }; // checks and clears logs const checkLogs = function (...expectedLogs) { assert.deepStrictEqual(logs, expectedLogs); logs.length = 0; }; const obj = {}; const factoryObj = { a() { log(`a factory`); return function () { log(`a`); }; }, b() { log(`b factory`); }, c: null, x: { assignImmediately: true, factory() { log(`x factory`); return function () { log(`x`); }; } }, y: { factory() { log(`y factory`); return function () { log(`y`); }; } }, z: { factory() { log(`z factory`); return function () { log(`z`); }; }, canProduce() { log(`z can produce`); return false; } } }; // nothing was logged until now checkLogs(); // so far the non-object factories were just tested to see whether they're valid traits[_addTraitFactories](obj, factoryObj); checkLogs(`a factory`, `b factory`, `x factory`, `z can produce`); obj[_a](); checkLogs(`a factory`, `a`); obj[_a](); checkLogs(`a`); assert.throws(() => obj[_b]()); assert.throws(() => obj[_c]()); obj[_x](); checkLogs(`x`); obj[_y](); checkLogs(`y factory`, `y`); obj[_y](); checkLogs(`y`); assert.throws(() => obj[_z]()); }