diff --git a/README.md b/README.md index e3e34ea..e3e70f3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ _Mono repository for stamp related packages._ -Documentation: https://stampit.js.org +Documentation: https://stampit.js.org/ecosystem * `@stamp/arg-over-prop` - Assign properties passed to the stamp factory * `@stamp/check-compose` - Command line tool to test your 'compose' function implementation diff --git a/__tests__/privatize-plus-collision.js b/__tests__/privatize-plus-collision.js index 843ecb8..457e15d 100644 --- a/__tests__/privatize-plus-collision.js +++ b/__tests__/privatize-plus-collision.js @@ -1,9 +1,9 @@ -var compose = require('../packages/compose'); -var Collision = require('../packages/collision'); -var Privatize = require('../packages/privatize'); +var compose = require("../packages/compose"); +var Collision = require("../packages/collision"); +var Privatize = require("../packages/privatize"); -describe('Collision + Privatize', function () { - it('work together', function () { +describe("Collision + Privatize", function () { + it("work together", function () { var Stamp = compose( Collision, Privatize, @@ -13,47 +13,49 @@ describe('Collision + Privatize', function () { } } ) - .collisionProtectAnyMethod() - .privatizeMethods('privateMethod'); + .collisionSetup({ methods: "forbid" }) + .privatizeMethods("privateMethod"); var obj = Stamp(); expect(obj.privateMethod).toBeUndefined(); }); - it('privatizes deferred methods', function () { + it("privatizes deferred methods", function () { // General purpose behavior to defer "draw()" method collisions - var DeferDraw = Collision.collisionSetup({defer: ['draw']}); + var DeferDraw = Collision.collisionSetup({ methods: { draw: "defer" } }); + var draw1 = jest.fn(); // Spy function + var draw2 = jest.fn(); // Spy function var Border = compose(DeferDraw, { methods: { - draw: jest.fn() // Spy function + draw: draw1 } }); var Button = compose(DeferDraw, { methods: { - draw: jest.fn() // Spy function + draw: draw2 } }); // General purpose behavior to privatize the "draw()" method - var PrivateDraw = Privatize.privatizeMethods('draw'); + var PrivateDraw = Privatize.privatizeMethods("draw"); // General purpose behavior to forbid the "redraw()" method collision - var ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); + var ForbidRedrawCollision = Collision.collisionSetup({ methods: { redraw: "forbid" } }); // The aggregating component var ModalDialog = compose(PrivateDraw) // privatize the "draw()" method - .compose(Border, Button) // modal will consist of Border and Button - .compose(ForbidRedrawCollision) // there can be only one "redraw()" method - .compose({ - methods: { - // the public method which calls the deferred private methods - redraw: function (int) { - this.draw(int); - } + .compose(Border, Button) // modal will consist of Border and Button + .compose(ForbidRedrawCollision) // there can be only one "redraw()" method + .compose({ + methods: { + // the public method which calls the deferred private methods + redraw: function (int) { + this.draw(int); } - }); + } + }); // Creating an instance of the stamp var dialog = ModalDialog(); @@ -65,11 +67,11 @@ describe('Collision + Privatize', function () { dialog.redraw(42); // Check if the spy "draw()" deferred functions were actually invoked - expect(Border.compose.methods.draw).toBeCalledWith(42); - expect(Button.compose.methods.draw).toBeCalledWith(42); + expect(draw1).toBeCalledWith(42); + expect(draw2).toBeCalledWith(42); // Make sure the ModalDialog throws every time on the "redraw()" collisions - var HaveRedraw = compose({methods: {redraw: jest.fn()}}); - expect(function () {compose(ModalDialog, HaveRedraw)}).toThrow(); + var HaveRedraw = compose({ methods: { redraw: jest.fn() } }); + expect(function () {compose(ModalDialog, HaveRedraw);}).toThrow(); }); }); diff --git a/package.json b/package.json index 02f3b92..6bbb46d 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ } }, "jest": { + "testEnvironment": "node", "coverageReporters": [ "html", "text" diff --git a/packages/collision/README.md b/packages/collision/README.md index d1b3073..8143bb6 100644 --- a/packages/collision/README.md +++ b/packages/collision/README.md @@ -1,6 +1,6 @@ # @stamp/collision -_Controls collision behavior: forbid or defer_ +_Controls collision behavior: forbid, defer or pipe_ This stamp (aka behavior) will check if there are any conflicts on every `compose` call. Throws an `Error` in case of a forbidden collision or ambiguous setup. @@ -10,44 +10,111 @@ Throws an `Error` in case of a forbidden collision or ambiguous setup. ```js import Collision from '@stamp/collision'; -const ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); +const ForbidRedrawCollision = Collision.collisionSetup({methods: {redraw: "forbid" }}); ``` Or if you don't want to import the stamp you can import only the method: ```js import {collisionSetup} from '@stamp/collision'; -const ForbidRedrawCollision = collisionSetup({forbid: ['redraw']}); +const ForbidRedrawCollision = collisionSetup({methods: {redraw: "forbid" }}); ``` -The `defer` collects same named methods and wraps them into a single method. +The `defer` collects same named methods and wraps them into a single method which returns an array of returned values. ```js import Collision from '@stamp/collision'; import {Border, Button, Graph} from './drawable/primitives'; -const UiComponent = Collision.collisionSetup({defer: ['draw']}) +const UiComponent = Collision.collisionSetup({methods: {draw: "defer" }}) .compose(Border, Button, Graph); const component = UiComponent(); -component.draw(); // will draw() all three primitives +const array = component.draw(42); // will draw() all three primitives +``` + + +The `pipe` collects same named methods and wraps them into a single method which pipes method returned values one into another +```js +import Collision from '@stamp/collision'; + +const UserDetails = stampit({ + props: { + firstName: "John", + lastName: "Smith" + }, + methods: { + toJSON(previous) { + return {...previous, firstName: this.firstName, lastName: this.lastName}; + } + } +}); +const UserAuth = stampit({ + props: { + email: "john@example.com", + password: "hashedpassword" + }, + methods: { + toJSON(previous) { + return {...previous, email: this.email, password: this.password}; + } + } +}); + +const User = Collision.collisionSetup({methods: {toJSON: "pipe" }}) +.compose(UserDetails, UserAuth); + +const user = User(); +const json = user.toJSON(); +/* +{ email: 'john@example.com', + password: 'hashedpassword', + firstName: 'John', + lastName: 'Smith' } +*/ ``` ## API -### Static methods +### Forbid, Defer or Pipe an exclusive method +`stamp.collisionSetup({ [META]: { [NAME]: TYPE } }) -> Stamp` +Where: +* `META` - String, one of "methods", "properties", "deepProperties", "propertyDescriptors", "staticProperties", "staticDeepProperties", "staticPropertyDescriptors", "configuration", "deepConfiguration". +* `NAME` - String, the name of your method, property, etc. +* `TYPE` - String, one of "forbid", "defer", "pipe". -#### collisionSetup -Forbid or Defer an exclusive method -`stamp.collisionSetup({forbid: ['methodName1'], defer: ['methodName2']}) -> Stamp` +Example: +```js +MyStamp = MyStamp.collisionSetup({ methods: { setPassword: "forbid" }}); +``` + +### Forbid, Defer or Pipe an all the methods +`stamp.collisionSetup({ [META]: TYPE }) -> Stamp` + +Example: +```js +MyStamp = MyStamp.collisionSetup({ methods: "forbid" }); +``` + +### Forbid, Defer or Pipe everything (NOT RECOMMENDED TO USE) +`stamp.collisionSetup(TYPE) -> Stamp` + +Example - effectively disables any further composition: +```js +MyStamp = MyStamp.collisionSetup("forbid"); +``` + +### Remove (reset) all the collision settings +```js +MyStamp = MyStamp.setupCollision(null); +``` + +### Remove (reset) some collision settings +```js +MyStamp = MyStamp.setupCollision({ methods: null }); +``` -#### collisionProtectAnyMethod -Forbid any collisions, excluding those allowed -`stamp.collisionProtectAnyMethod({allow: ['methoName']}) -> Stamp` -#### collisionSettingsReset -Remove any Collision settings from the stamp -`stamp.collisionSettingsReset() -> Stamp` ## Example @@ -58,16 +125,18 @@ import Collision from '@stamp/collision'; import Privatize from '@stamp/privatize'; // General purpose behavior to defer "draw()" method collisions -const DeferDraw = Collision.collisionSetup({defer: ['draw']}); +const DeferDraw = Collision.collisionSetup({methods: {draw: "defer"}}); +const draw1 = jest.fn(); // Spy function const Border = compose(DeferDraw, { methods: { - draw: jest.fn() // Spy function + draw: draw1 } }); +const draw2 = jest.fn(); // Spy function const Button = compose(DeferDraw, { methods: { - draw: jest.fn() // Spy function + draw: draw2 } }); @@ -75,7 +144,7 @@ const Button = compose(DeferDraw, { const PrivateDraw = Privatize.privatizeMethods('draw'); // General purpose behavior to forbid the "redraw()" method collision -const ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); +const ForbidRedrawCollision = Collision.collisionSetup({methods: {redraw: "forbid"}}); // The aggregating component const ModalDialog = compose(PrivateDraw) // privatize the "draw()" method @@ -100,10 +169,10 @@ expect(dialog.draw).toBeUndefined(); dialog.redraw(42); // Check if the spy "draw()" deferred functions were actually invoked -expect(Border.compose.methods.draw).toBeCalledWith(42); -expect(Button.compose.methods.draw).toBeCalledWith(42); +expect(draw1).toBeCalledWith(42); +expect(draw2).toBeCalledWith(42); // Make sure the ModalDialog throws every time on the "redraw()" collisions -const HaveRedraw = compose({methods: {redraw() {}}}) +const HaveRedraw = compose({methods: {redraw() {}}}); expect(() => compose(ModalDialog, HaveRedraw)).toThrow(); ``` diff --git a/packages/collision/__tests__/index.js b/packages/collision/__tests__/index.js index 574a613..cde9931 100644 --- a/packages/collision/__tests__/index.js +++ b/packages/collision/__tests__/index.js @@ -1,20 +1,21 @@ -var compose = require('@stamp/compose'); -var Collision = require('..'); +var compose = require("@stamp/compose"); +var assign = require("@stamp/core").assign; +var Collision = require(".."); -describe('@stamp/collision', function () { - it('defer', function () { +describe("@stamp/collision", function () { + it("defer", function () { var draw1 = jest.fn(); - draw1.mockReturnValueOnce({a: 1}); + draw1.mockReturnValueOnce({ a: 1 }); var Defer1 = compose({ methods: { draw: draw1 } }, - Collision.collisionSetup({defer: ['draw']}) + Collision.collisionSetup({ methods: { draw: "defer" } }) ); var draw2 = jest.fn(); - draw2.mockReturnValueOnce({b: 2}); - var Defer2 = Collision.collisionSetup({defer: ['draw']}).compose({ + draw2.mockReturnValueOnce({ b: 2 }); + var Defer2 = Collision.collisionSetup({ methods: { draw: "defer" } }).compose({ methods: { draw: draw2 } @@ -25,33 +26,33 @@ describe('@stamp/collision', function () { var result = obj.draw(); - expect(result).toEqual([{a: 1}, {b: 2}]); + expect(result).toEqual([{ a: 1 }, { b: 2 }]); expect(draw1).toBeCalled(); expect(draw2).toBeCalled(); }); - it('defer + regular', function () { + it("defer + regular", function () { var Defer = compose({ methods: { draw: function () {} } }, - Collision.collisionSetup({defer: ['draw']}) + Collision.collisionSetup({ methods: { draw: "defer" } }) ); - var Regular = compose({methods: {draw: function () {}}}); + var Regular = compose({ methods: { draw: function () {} } }); - expect(function () {compose(Defer, Regular)}).toThrow(); - expect(function () {compose(Regular, Defer)}).toThrow(); + expect(function () {compose(Defer, Regular);}).toThrow(); + expect(function () {compose(Regular, Defer);}).toThrow(); }); - it('defer without conflicts', function () { + it("defer without conflicts", function () { var Defer = compose({ methods: { draw: function () {} } }, - Collision.collisionSetup({defer: ['draw']}) + Collision.collisionSetup({ methods: { draw: "defer" } }) ); var Regular = compose({ methods: { @@ -59,138 +60,141 @@ describe('@stamp/collision', function () { } }); - expect(function () {compose(Defer, Regular)}).not.toThrow(); - expect(function () {compose(Regular, Defer)}).not.toThrow(); + expect(function () {compose(Defer, Regular);}).not.toThrow(); + expect(function () {compose(Regular, Defer);}).not.toThrow(); }); - it('forbid', function () { - var Forbid = compose({ + it("pipe", function () { + function toJSON1(previous) { return assign({}, previous, { a: 1 });} + + var Pipe1 = compose({ methods: { - draw: function () {} + toJSON: toJSON1 } }, - Collision.collisionSetup({forbid: ['draw']}) + Collision.collisionSetup({ methods: { toJSON: "pipe" } }) ); - var Defer = compose({ + + function toJSON2(previous) { return assign({}, previous, { b: 2 });} + + var Pipe2 = Collision.collisionSetup({ methods: { toJSON: "pipe" } }).compose({ + methods: { + toJSON: toJSON2 + } + }); + + var StampCombined = compose(Pipe1, Pipe2); + var obj = StampCombined(); + + var result = obj.toJSON(); + + expect(result).toEqual({ a: 1, b: 2 }); + }); + + it("pipe + regular", function () { + var Pipe = compose({ methods: { - draw: function () {} + toJSON: function () {} } }, - Collision.collisionSetup({defer: ['draw']}) + Collision.collisionSetup({ methods: { toJSON: "pipe" } }) ); - var Regular = compose({ + var Regular = compose({ methods: { toJSON: function () {} } }); + + expect(function () {compose(Pipe, Regular);}).toThrow(); + expect(function () {compose(Regular, Pipe);}).toThrow(); + }); + + + it("pipe without conflicts", function () { + var Pipe = compose({ methods: { - draw: function () {} + toJSON: function () {} } - } + }, + Collision.collisionSetup({ methods: { toJSON: "pipe" } }) ); + var Regular = compose({ + methods: { + whatever: function () {} + } + }); - expect(function () {compose(Forbid, Defer)}).toThrow(); - expect(function () {compose(Defer, Forbid)}).toThrow(); - - expect(function () {compose(Regular, Forbid)}).toThrow(); - expect(function () {compose(Forbid, Regular)}).toThrow(); + expect(function () {compose(Pipe, Regular);}).not.toThrow(); + expect(function () {compose(Regular, Pipe);}).not.toThrow(); }); - it('collisionProtectAnyMethod', function () { - var FooBar = compose({ + it("forbid", function () { + var Forbid = compose({ methods: { - foo: function () {}, - bar: function () {} + draw: function () {} } }, - Collision.collisionProtectAnyMethod() + Collision.collisionSetup({ methods: { draw: "forbid" } }) ); - var Foo = compose({ + var Defer = compose({ methods: { - foo: function () {} + draw: function () {} } }, - Collision.collisionProtectAnyMethod() + Collision.collisionSetup({ methods: { draw: "defer" } }) ); - var Bar = compose({ + var Regular = compose({ methods: { - bar: function () {} + draw: function () {} } - }, - Collision.collisionProtectAnyMethod() + } ); - expect(function () {compose(FooBar, Foo)}).toThrow(); - expect(function () {compose(Foo, FooBar)}).toThrow(); - expect(function () {compose(FooBar, Bar)}).toThrow(); - expect(function () {compose(Bar, FooBar)}).toThrow(); - expect(function () {compose(Foo, Bar)}).not.toThrow(); - expect(function () {compose(Bar, Foo)}).not.toThrow(); - }); + expect(function () {compose(Forbid, Defer);}).toThrow(/Collision/); + expect(function () {compose(Defer, Forbid);}).toThrow(/Collision/); - it('collisionProtectAnyMethod multiple times same stamp', function () { - var Base = compose({ - methods: { - foo: function () {}, - bar: function () {} - } - }, - Collision.collisionProtectAnyMethod() - ); - var Stamp1 = compose(Base); - var Stamp2 = compose(Base); - var Stamp3 = compose(Base); - - expect(function () {compose(Stamp1, Stamp2)}).not.toThrow(); - expect(function () {compose(Stamp1, Stamp2, Stamp3)}).not.toThrow(); - expect(function () {compose(Stamp1).compose(Stamp2, Stamp3)}).not.toThrow(); - expect(function () {compose(Stamp1, Stamp2).compose(Stamp3)}).not.toThrow(); + expect(function () {compose(Regular, Forbid);}).toThrow(/Collision/); + expect(function () {compose(Forbid, Regular);}).toThrow(/Collision/); }); - it('collisionProtectAnyMethod + allow', function () { - var FooBar = compose({ + it("forbid everything", function () { + var Forbid = compose({ methods: { - foo: function () {}, - bar: function () {} + draw: function () {} + }, + properties: { + height: 0 } }, - Collision.collisionProtectAnyMethod({allow: ['foo']}) + Collision.collisionSetup("forbid") ); - var Foo = compose({ + var Method = compose({ methods: { - foo: function () {} + draw: function () {} } - }, - Collision.collisionProtectAnyMethod() + } ); - var Bar = compose({ - methods: { - bar: function () {} + var Property = compose({ + properties: { + height: null } - }, - Collision.collisionProtectAnyMethod() + } ); - expect(function () {compose(FooBar, Foo)}).not.toThrow(); - expect(function () {compose(Foo, FooBar)}).not.toThrow(); - expect(function () {compose(FooBar, Bar)}).toThrow(); - expect(function () {compose(Bar, FooBar)}).toThrow(); - expect(function () {compose(Foo, Bar)}).not.toThrow(); - expect(function () {compose(Bar, Foo)}).not.toThrow(); + expect(function () {compose(Forbid, Method);}).toThrow(/Collision/); + expect(function () {compose(Method, Forbid);}).toThrow(/Collision/); + expect(function () {compose(Property, Forbid);}).toThrow(/Collision/); + expect(function () {compose(Forbid, Property);}).toThrow(/Collision/); }); - it('forbid + allow', function () { - expect(function () {Collision.collisionSetup({allow: ['foo'], forbid: ['foo']})}).toThrow(/Collision/); - }); - - it('can be used as a standalone function', function () { + it("can be used as a standalone function", function () { var collisionSetup = Collision.collisionSetup; - expect(function () {collisionSetup({allow: ['foo'], forbid: ['foo']})}).toThrow(/Collision/); + expect(typeof collisionSetup({ methods: { foo: "allow" } })).toBe("function"); }); - it('forbid without conflicts', function () { + it("forbid without conflicts", function () { var Forbid = compose({ methods: { draw: function () {} } }, - Collision.collisionSetup({forbid: ['draw']}) + Collision.collisionSetup({ methods: { draw: "forbid" } }) ); var Regular = compose({ methods: { @@ -199,27 +203,27 @@ describe('@stamp/collision', function () { } ); - expect(function () {compose(Forbid, Regular)}).not.toThrow(); - expect(function () {compose(Regular, Forbid)}).not.toThrow(); + expect(function () {compose(Forbid, Regular);}).not.toThrow(); + expect(function () {compose(Regular, Forbid);}).not.toThrow(); }); - it('collisionSettingsReset', function () { + it("resetting settings", function () { var Forbid = compose({ methods: { draw: function () {} } }, - Collision.collisionSetup({forbid: ['draw']}) + Collision.collisionSetup({ methods: { draw: "forbid" } }) ); - var Resetted1 = Forbid.collisionSettingsReset(); + var Resetted1 = Forbid.collisionSetup(null); var Defer = compose({ methods: { draw: function () {} } }, - Collision.collisionSetup({defer: ['draw']}) + Collision.collisionSetup({ methods: { draw: "defer" } }) ); - var Resetted2 = Defer.collisionSettingsReset(); + var Resetted2 = Defer.collisionSetup(null); var Regular = compose({ methods: { draw: function () {} @@ -227,20 +231,25 @@ describe('@stamp/collision', function () { } ); - expect(function () {compose(Resetted1, Resetted2)}).not.toThrow(); - expect(function () {compose(Resetted1, Regular)}).not.toThrow(); - expect(function () {compose(Resetted2, Resetted1)}).not.toThrow(); - expect(function () {compose(Regular, Resetted1)}).not.toThrow(); + expect(function () {compose(Resetted1, Resetted2);}).not.toThrow(); + expect(function () {compose(Resetted1, Regular);}).not.toThrow(); + expect(function () {compose(Resetted2, Resetted1);}).not.toThrow(); + expect(function () {compose(Regular, Resetted1);}).not.toThrow(); }); - it('broken metadata', function () { + it("broken metadata", function () { var UndefinedMethod = Collision - .collisionSetup({forbid: ['foo']}) - .compose({ - methods: { - draw: null - } - }); + .collisionSetup({ methods: { foo: "forbid" } }) + .compose({ + methods: { + draw: null + } + }) + .compose({ + methods: { + draw: "BAADFOOD" + } + }); var NoDefer = compose(Collision, { methods: { @@ -248,6 +257,8 @@ describe('@stamp/collision', function () { } } ); + NoDefer.compose.deepConfiguration = {}; + NoDefer.compose.deepConfiguration.Collision = {}; NoDefer.compose.deepConfiguration.Collision.defer = null; var NoForbid = compose(Collision, { @@ -256,10 +267,25 @@ describe('@stamp/collision', function () { } } ); + NoForbid.compose.deepConfiguration = {}; + NoForbid.compose.deepConfiguration.Collision = {}; NoForbid.compose.deepConfiguration.Collision.forbid = null; + NoForbid.compose.methods.random = "not function"; + + var Malformed = compose(Collision, { + methods: { + draw: function () {} + } + } + ); + Malformed.compose.deepConfiguration = {}; + Malformed.compose.deepConfiguration.Collision = null; + + + expect(function () {compose(UndefinedMethod, NoForbid);}).not.toThrow(); + expect(function () {compose(UndefinedMethod, NoDefer);}).not.toThrow(); - expect(function () {compose(UndefinedMethod, NoForbid)}).not.toThrow(); - expect(function () {compose(UndefinedMethod, NoDefer)}).not.toThrow(); + expect(function () {compose(null, Malformed, UndefinedMethod, NoDefer, Malformed, { compose: null });}).not.toThrow(); }); }); diff --git a/packages/collision/index.js b/packages/collision/index.js index c7761ad..22a74d3 100644 --- a/packages/collision/index.js +++ b/packages/collision/index.js @@ -1,186 +1,176 @@ -var compose = require('@stamp/compose'); -var assign = require('@stamp/core/assign'); -var isStamp = require('@stamp/is/stamp'); -var isObject = require('@stamp/is/object'); -var isArray = require('@stamp/is/array'); - -function dedupe(array) { - var result = []; - for (var i = 0; i < array.length; i++) { - var item = array[i]; - if (result.indexOf(item) < 0) result.push(item); +var compose = require("@stamp/compose"); +var is = require("@stamp/is"); + +function forEach(maybeArray, fn) { + if (!is.isArray(maybeArray) && is.isObject(maybeArray)) maybeArray = Object.keys(maybeArray); + if (is.isArray(maybeArray) && maybeArray.length > 0) { + for (var i = 0; i < maybeArray.length; i++) { + if (fn(maybeArray[i]) === "BREAK") break; + } } - return result; } -function makeProxyFunction(functions, name) { - function deferredFn() { - 'use strict'; - const results = []; +function forEachMetadata(maybeDescriptor, fn) { + forEach(maybeDescriptor, function (metadataType) { + if (!is.isPlainObject(maybeDescriptor[metadataType])) return; + forEach(maybeDescriptor[metadataType], function (metadataName) { + fn(metadataType, metadataName); + }); + }); +} + +function deferredFn(functions) { + return function deferredFn() { + "use strict"; + var results = []; for (var i = 0; i < functions.length; i++) { results.push(functions[i].apply(this, arguments)); // jshint ignore:line } return results; + }; +} + +function pipedFn(functions) { + return function pipedFn() { + "use strict"; + for (var i = 0, result; i < functions.length; i++) { + result = functions[i].call(this, result); // jshint ignore:line + } + return result; + }; +} + +var delayTypes = { + defer: deferredFn, + pipe: pipedFn +}; + +function makeProxyFunction(functions, name, delayType) { + var fns = []; + var haveRegularFns = false, haveProxiesFns = false; + for (var i = 0; i < functions.length; i++) { + var f = functions[i]; + + if (is.isArray(f._proxiedFunctions)) { + haveProxiesFns = true; + fns = fns.concat(f._proxiedFunctions); + } else { + haveRegularFns = true; + fns.push(f); + } + + if (haveRegularFns && haveProxiesFns) + throw new Error("Attempt to " + delayType + " with regular method"); } - Object.defineProperty(deferredFn, 'name', { + var fn = delayTypes[delayType](fns); + fn._proxiedFunctions = fns; + Object.defineProperty(fn, "name", { value: name, configurable: true }); - return deferredFn; + return fn; } -function getCollisionSettings(descriptor) { - return descriptor && - descriptor.deepConfiguration && - descriptor.deepConfiguration.Collision; +function isForbidden(settings, metadataType, metadataName) { + return ( + settings === "forbid" || + (settings && settings[metadataType] === "forbid") || + (settings && settings[metadataType] && settings[metadataType][metadataName] === "forbid") + ); } -function descriptorHasSetting(descriptor, setting, methodName) { - var settings = getCollisionSettings(descriptor); - var settingsFor = settings && settings[setting]; - return isArray(settingsFor) && settingsFor.indexOf(methodName) >= 0; +function getValidSettings(composables) { + var finalSettings = {}; + forEach(composables, function (composable) { + var settings = getSettings(composable); + + if (settings === "forbid") { + finalSettings = "forbid"; + return "BREAK"; + } + forEach(settings, function (metadataType) { + if (settings[metadataType] === "forbid") { + finalSettings[metadataType] = "forbid"; + return "BREAK"; + } + + if (!is.isPlainObject(settings[metadataType])) return; + forEach(settings[metadataType], function (metadataName) { + + finalSettings[metadataType] = finalSettings[metadataType] || {}; + if (settings[metadataType][metadataName] === "forbid") { + finalSettings[metadataType][metadataName] = "forbid"; + return "BREAK"; + } + + var newValue = settings[metadataType][metadataName]; + var existingValue = finalSettings[metadataType][metadataName]; + if (existingValue && existingValue !== newValue) { + throw new Error("Ambiguous Collision settings. The `" + metadataName + "` " + metadataType + " can't be both " + existingValue + " and " + newValue); + } + + finalSettings[metadataType][metadataName] = newValue; + }); + }); + }); + + return finalSettings; } -function forbidsCollision(descriptor, methodName) { - var settings = getCollisionSettings(descriptor); - if (settings && settings.forbidAll) { - return !descriptorHasSetting(descriptor, 'allow', methodName); - } - return descriptorHasSetting(descriptor, 'forbid', methodName); +function checkCollisions(finalSettings, finalDescriptor, composables) { + var aggregatedComposables = {}; + forEach(composables, function (composable) { + var descriptor = composable.compose || composable; + forEachMetadata(descriptor, function (metadataType, metadataName) { + aggregatedComposables[metadataType] = aggregatedComposables[metadataType] || {}; + var array = aggregatedComposables[metadataType][metadataName] = aggregatedComposables[metadataType][metadataName] || []; + array.push(descriptor[metadataType][metadataName]); + + if (array.length > 1) { + if (isForbidden(finalSettings, metadataType, metadataName)) { + throw Error("Collisions of `" + metadataName + "` " + metadataType + " are forbidden."); + } + } + }); + }); + + return aggregatedComposables; } -function defersCollision(descriptor, methodName) { - return descriptorHasSetting(descriptor, 'defer', methodName); +function mutateFinalDescriptor(finalSettings, finalDescriptor, aggregatedComposables) { + forEach(delayTypes, function (delayType) { + forEachMetadata(aggregatedComposables, function (metadataType, metadataName) { + if (finalSettings[metadataType] && finalSettings[metadataType][metadataName] === delayType) { + finalDescriptor[metadataType][metadataName] = makeProxyFunction(aggregatedComposables[metadataType][metadataName], metadataName, delayType); + } + }); + }); +} + +function getSettings(composable) { + var descriptor = composable.compose || composable; + return descriptor && descriptor.deepConfiguration && descriptor.deepConfiguration.Collision; } var Collision = compose({ - deepConfiguration: {Collision: {defer: [], forbid: []}}, staticProperties: { collisionSetup: function (opts) { - 'use strict'; - var Stamp = this && this.compose ? this : Collision; - return Stamp.compose({deepConfiguration: {Collision: opts}}); - - }, - collisionSettingsReset: function () { - return this.collisionSetup(null); - }, - collisionProtectAnyMethod: function (opts) { - return this.collisionSetup(assign({}, opts, {forbidAll: true})); + "use strict"; + var Stamp = is.isStamp(this) ? this : Collision; + return Stamp.compose({ deepConfiguration: { Collision: opts } }); } }, composers: [function (opts) { - var descriptor = opts.stamp.compose; - var settings = getCollisionSettings(descriptor); - if (!isObject(settings)) return; - - var i, methodName; - // Deduping is an important part of the logic - if (isArray(settings.defer)) { - settings.defer = dedupe(settings.defer); - } - if (isArray(settings.forbid)) { - settings.forbid = dedupe(settings.forbid); - } - if (isArray(settings.allow)) { - settings.allow = dedupe(settings.allow); - } - - // Make sure settings are not ambiguous - if (isArray(settings.forbid)) { - var checkDefer = isArray(settings.defer) && settings.defer.length > 0; - var checkAllow = isArray(settings.allow) && settings.allow.length > 0; - if (checkDefer || checkAllow) { - for (i = 0; i < settings.forbid.length; i++) { - var forbiddenMethodName = settings.forbid[i]; - if (checkDefer && settings.defer.indexOf(forbiddenMethodName) >= 0) { - throw new Error('Ambiguous Collision settings. The `' + - forbiddenMethodName + '` is both deferred and forbidden'); - } - if (checkAllow && settings.allow.indexOf(forbiddenMethodName) >= 0) { - throw new Error('Ambiguous Collision settings. The `' + - forbiddenMethodName + '` is both allowed and forbidden'); - } - } - } - } - - if (settings.forbidAll || - isArray(settings.defer) && settings.defer.length > 0 || - isArray(settings.forbid) && settings.forbid.length > 0 - ) { - var d, j, oneMetadata; - - var methodsMetadata = {}; // methods aggregation - var composables = opts.composables; - for (i = 0; i < composables.length; i++) { - d = composables[i]; - d = isStamp(d) ? d.compose : d; - if (!isObject(d.methods)) continue; - - var methodNames = Object.keys(d.methods); - for (j = 0; j < methodNames.length; j++) { - methodName = methodNames[j]; - var method = d.methods[methodName]; - if (!method) continue; - - var existingMetadata = methodsMetadata[methodName]; - - // Checking by reference if the method is already present - if (existingMetadata && - (existingMetadata === method || - (isArray(existingMetadata) && existingMetadata.indexOf(method) >= 0))) { - continue; - } - - // Process Collision.forbid - if (existingMetadata && forbidsCollision(descriptor, methodName)) { - throw new Error('Collision of method `' + methodName + - '` is forbidden'); - } - - // Process Collision.defer - if (defersCollision(d, methodName)) { - if (existingMetadata && !isArray(existingMetadata)) { - throw new Error('Ambiguous Collision settings. The `' + - methodName + '` is both deferred and regular'); - } - var arr = existingMetadata || []; - arr.push(method); - methodsMetadata[methodName] = arr; - continue; - } - - // Process no Collision settings - if (isArray(existingMetadata)) { - throw new Error('Ambiguous Collision settings. The `' + - methodName + '` is both deferred and regular'); - } - - methodsMetadata[methodName] = method; - } - } - - var methods = opts.stamp.compose.methods; - var allMetadataMethods = Object.keys(methodsMetadata); - for (i = 0; i < allMetadataMethods.length; i++) { - methodName = allMetadataMethods[i]; - oneMetadata = methodsMetadata[methodName]; - if (isArray(oneMetadata)) { - if (oneMetadata.length === 1) { - methods[methodName] = oneMetadata[0]; - } else { - // Some collisions aggregated to a single method - // Mutating the resulting stamp - methods[methodName] = makeProxyFunction(oneMetadata, methodName); - } - } else { - methods[methodName] = oneMetadata; - } - } - } + var finalDescriptor = opts.stamp.compose; + var allSettings = getSettings(finalDescriptor); + if (!is.isObject(allSettings) && !is.isString(allSettings)) return; + + var finalSettings = getValidSettings(opts.composables); // recompose the settings. Also, checks for ambiguous setup + finalDescriptor.deepConfiguration.Collision = finalSettings; + var aggregatedComposables = checkCollisions(finalSettings, finalDescriptor, opts.composables); + mutateFinalDescriptor(finalSettings, finalDescriptor, aggregatedComposables); }] }); diff --git a/packages/core/merge.js b/packages/core/merge.js index 3edb9a5..9ea60f8 100644 --- a/packages/core/merge.js +++ b/packages/core/merge.js @@ -7,7 +7,7 @@ var isArray = require('@stamp/is/array'); * The returned values is always of the same type as the 'src'. * @param dst The object to merge into * @param src The object to merge from - * @returns {*} + * @returns {*} The mutated `dst` argument or a new object. */ function mergeOne(dst, src) { if (src === undefined) return dst; diff --git a/packages/instanceof/index.js b/packages/instanceof/index.js index c5e2daf..25628fb 100644 --- a/packages/instanceof/index.js +++ b/packages/instanceof/index.js @@ -1,16 +1,15 @@ -const compose = require('@stamp/compose'); +var compose = require('@stamp/compose'); -const stampSymbol = Symbol.for('stamp'); - -module.exports = compose({ +module.exports = typeof Symbol === 'undefined' ? compose() : compose({ methods: {}, - composers: [({ stamp }) => { + composers: [function (opts) { + var stamp = opts.stamp; // Attaching to object prototype to save memory - stamp.compose.methods[stampSymbol] = stamp; + stamp.compose.methods[Symbol.for('stamp')] = stamp; Object.defineProperty(stamp, Symbol.hasInstance, { - value (obj) { - return obj && obj[stampSymbol] === stamp; + value: function value (obj) { + return obj && obj[Symbol.for('stamp')] === stamp; } }); }]