UNPKG

@straits/utils

Version:
295 lines (218 loc) 9.01 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 assert = require('assert'); class TraitSet { static fromKeys(obj) { return TraitSet.fromStrings(Object.keys(obj)); } static fromStrings(names) { const obj = {}; names.forEach(name => { obj[name] = Symbol(name); }); return new TraitSet(obj); } constructor(traitSet = {}) { for (let key in traitSet) { const sym = traitSet[key]; if (typeof sym === 'symbol') { this[key] = sym; } } } // the following will be implemented later, as an alias for `Object.prototype._Straits.traitsToFreeFunctions` //asFreeFunctions() {} } const traits = TraitSet.fromStrings([// traits for `Symbol` to aid its usage as traits `impl`, // sym( target, implementation ) => this `implDefault`, // sym( implementation ) => this `asFreeFunction`, // sym() => {fn} // traits for `Object` to aid its usage as trait sets // stuff to add new traits to `this` `addSymbol`, // obj( name, sym ) => symbol `defineTrait`, // obj( name ) => symbol `borrowTraits`, // obj( traitSet, names=undefined ) => this // stuff to create wrappers for traits from `this` `traitsToFreeFunctions`, // obj() => {fn} // stuff to implement many traits at once (and maybe define them too) `implTraits`, // obj( target, implementationObj ) => this `defineAndImplTraits`, // obj( target, implementationObj ) => this `defineAndImplMethodsAsTraits`, // obj( target, source, methodList ) => this `defineAndImplMemberFreeFunctionsAsTraits`]); _testTraitSet(traits); const _addSymbol = _getSymbol("addSymbol", traits); const _asFreeFunction = _getSymbol("asFreeFunction", traits); const _impl = _getSymbol("impl", traits); const _defineTrait = _getSymbol("defineTrait", traits); const _traitsToFreeFunctions = _getSymbol("traitsToFreeFunctions", traits); const _borrowTraits = _getSymbol("borrowTraits", traits); const _defineAndImplMemberFreeFunctionsAsTraits = _getSymbol("defineAndImplMemberFreeFunctionsAsTraits", traits); const _defineAndImplMethodsAsTraits = _getSymbol("defineAndImplMethodsAsTraits", traits); const _defineAndImplTraits = _getSymbol("defineAndImplTraits", traits); const _implTraits = _getSymbol("implTraits", traits); const _implDefault = _getSymbol("implDefault", traits); // we'll use `Symbol['@straits']` to store everything we want to be globally available // i.e. available to other packages we might be unaware of (including different versions of `@straits/utils`) const namespace = (() => { if (Symbol['@straits']) { return Symbol['@straits']; } return Symbol['@straits'] = { defaultImpls: new Map() // NOTE: a WeakMap would be better here, but: https://github.com/tc39/ecma262/issues/1194 }; })(); TraitSet.namespace = namespace; // implementing traits for traits for `Symbol` { // set `target[ this ]` to `implementation` _implSymbol(Symbol.prototype, _impl, function (target, implementation) { Object.defineProperty(target, this, { value: implementation, configurable: true }); return this; }); // set `implementation` as the default implementation for `this` trait // it will be used if `this` trait is called (as a method or free function) on something that doesn't implement `this` trait _implSymbol(Symbol.prototype, _implDefault, function (implementation) { namespace.defaultImpls.set(this, implementation); return this; }); // create and return a free function wrapping `this` _implSymbol(Symbol.prototype, _asFreeFunction, function () { const symName = String(this).slice(7, -1); const sym = this; return function (target, ...args) { if (target === undefined || target === null) { const { defaultImpls } = namespace; if (defaultImpls.has(sym)) { return defaultImpls.get(sym)(target, ...args); } throw new Error(`.*${symName} called on ${target}`); } if (!target[sym]) { const { defaultImpls } = namespace; if (defaultImpls.has(sym)) { return defaultImpls.get(sym)(target, ...args); } throw new Error(`.*${symName} called on ${target} that doesn't implement it.`); } return target[sym](...args); }; }); } // implementing traits for trait sets on `Object` { // add `symbol` to `this` with key `name` _implSymbol(Object.prototype, _addSymbol, function (name, sym) { assert(!this.hasOwnProperty(name), `Trying to re-define trait \`${name}\``); assert.equal(typeof sym, `symbol`, `Trying to add \`${name}\`, but it's not a symbol`); return this[name] = sym; }); // add a new trait called `name` to `this` _implSymbol(Object.prototype, _defineTrait, function (name) { return this[_addSymbol](name, Symbol(name)); }); // add to `this` trait set all the symbols from `traitSet` specified by `names` _implSymbol(Object.prototype, _borrowTraits, function (traitSet, names) { if (!names) { for (let key in traitSet) { const sym = traitSet[key]; if (typeof sym === 'symbol') { this[_addSymbol](key, sym); } } return this; } names.forEach(name => { this[_addSymbol](name, traitSet[name]); }); return this; }); // `this` is treated as a trait set: return a free function wrapping each trait _implSymbol(Object.prototype, _traitsToFreeFunctions, function () { const result = {}; for (let key in this) { const sym = this[key]; if (typeof sym === 'symbol') { result[key] = sym[_asFreeFunction](); } } return result; }); // `implementationObj` is an object whose keys are names of traits in `this` trait set // for each `key, value` entry of `implementationObj`, set `target[ this[key] ]` to `value` _implSymbol(Object.prototype, _implTraits, function (target, implementationObj) { for (let name in implementationObj) { const sym = this[name]; assert(typeof sym === 'symbol', `No trait \`${name}\``); sym[_impl](target, implementationObj[name]); } return this; }); // for each `key, value` entry of `implementationObj`, // create a new symbol called `key` in `this` trait set, and set `target[ this[key] ]` to `value` _implSymbol(Object.prototype, _defineAndImplTraits, function (target, implementationObj) { for (let name in implementationObj) { const value = implementationObj[name]; this[_defineTrait](name)[_impl](target, value); } return this; }); // `methodList` is a list of method names in `source` // for each method name `m` in `methodList`, create a new trait in `this` trait set, // and set `target[ this[m] ]` to a function wrapping `::source.m()` _implSymbol(Object.prototype, _defineAndImplMethodsAsTraits, function (target, source, methodList) { methodList.forEach(methodName => { this[_defineTrait](methodName)[_impl](target, { [methodName]() { return source[methodName](this, ...arguments); } }[methodName]); }); return this; }); // `functionObj` is an object whose values are free functions // for each `key, value` entry in `methodList`, create a new trait in `this` trait set, // and set `target[ this[key] ]` to a function wrapping `::source.m()` _implSymbol(Object.prototype, _defineAndImplMemberFreeFunctionsAsTraits, function (target, functionObj) { for (let fnName in functionObj) { const fn = functionObj[fnName]; if (typeof fn !== 'function') { continue; } this[_defineTrait](fnName)[_impl](target, { [fnName]() { return fn(this, ...arguments); } }[fnName]); } return this; }); } // finalizing and exporting { // adding missing methods to TraitSet's prototype TraitSet.prototype.asFreeFunctions = Object.prototype[_traitsToFreeFunctions]; // adding our traits to `TraitSet` TraitSet[_borrowTraits](traits); // adding functions to convert TraitSet's traits into free functions and methods TraitSet.prototype.asFreeFunctions = Object.prototype[_traitsToFreeFunctions]; // exporting TraitSet module.exports.TraitSet = TraitSet; }