Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

props-model

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

props-model - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

43

dist/index.js

@@ -1,1 +0,42 @@

"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=exports.PropsModel=void 0;var _propsModel=require("./lib/props-model"),PropsModel=_propsModel.PropsModel;exports.PropsModel=PropsModel;var _default=PropsModel;exports.default=_default;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.PropsModel = void 0;
var _propsModel = require("./lib/props-model");
/**
* The main module for this package, it exports the {@link PropsModel} class.
*
* When used as an ES6 module, you can import as either the default import, or the named 'PropsModel' import, e.g.:
*
* ```javascript
* import PropsModel from 'props-model'
* // or, as a named import:
* import { PropsModel } from 'props-model'
* ```
*
* When using with node's `require` function, use the `PropsModel` property of the import, e.g.:
* ```javascript
* const PropsModel = require('props-model').PropsModel
* // or, with destructuring assignment:
* const { PropsModel } = require('props-model')
* ```
*
* @see {@link PropsModel}
* @module props-model
*/
/**
* The named 'PropsModel' export for the module, which is the same as the default export.
*
* @static
* @type {Constructor}
* @see {@link PropsModel}
*/
const PropsModel = _propsModel.PropsModel;
exports.PropsModel = PropsModel;
var _default = PropsModel;
exports.default = _default;

@@ -1,1 +0,755 @@

"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.PropsModel=PropsModel;function _toArray(a){return _arrayWithHoles(a)||_iterableToArray(a)||_nonIterableRest()}function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _toConsumableArray(a){return _arrayWithoutHoles(a)||_iterableToArray(a)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function _iterableToArray(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function _arrayWithoutHoles(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b<a.length;b++)c[b]=a[b];return c}}function _slicedToArray(a,b){return _arrayWithHoles(a)||_iterableToArrayLimit(a,b)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}function _iterableToArrayLimit(a,b){var c=[],d=!0,e=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(d=(g=h.next()).done)&&(c.push(g.value),!(b&&c.length===b));d=!0);}catch(a){e=!0,f=a}finally{try{d||null==h["return"]||h["return"]()}finally{if(e)throw f}}return c}function _arrayWithHoles(a){if(Array.isArray(a))return a}function PropsModel(a){this._eventEmitter=a,this._props={}}function NOOP(){}function defaultDidChange(a,b){return a!==b}PropsModel.prototype._firePropChangeEvent=function(a,b,c){this._eventEmitter.emit("".concat(a,"-changed"),a,b,c)},PropsModel.prototype.defineProp=function(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:NOOP,d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:defaultDidChange;if(this._props[a])throw new Error("Property already defined: ".concat(a));return this._props[a]={value:b,derived:!1,valueValidator:c,didChange:d},d(b,void 0)&&this._firePropChangeEvent(a,b,void 0),this},PropsModel.prototype.defineDerivedProp=function(a){var b=this,c=1<arguments.length&&void 0!==arguments[1]?arguments[1]:[],d=2<arguments.length?arguments[2]:void 0,e=3<arguments.length?arguments[3]:void 0,f=4<arguments.length&&void 0!==arguments[4]?arguments[4]:defaultDidChange;if(this._props[a])throw new Error("Property already defined: ".concat(a));var g=this.createUtilizer(c,d),h="undefined"==typeof e?g():e;return this._props[a]={value:h,derived:!0,valueValidator:function a(){},didChange:f},this._onAny(function(){},c,function(){b.set(a,g())}),f(h,void 0)&&this._firePropChangeEvent(a,h,void 0),this},PropsModel.prototype._set=function(a){for(var b=this,c=arguments.length,d=Array(1<c?c-1:0),e=1;e<c;e++)d[e-1]=arguments[e];if(1===d.length){var h=[];Object.keys(d[0]).forEach(function(a){if(!b._props[a])throw new Error("No such property '".concat(a,"'"))}),Object.keys(d[0]).forEach(a),Object.entries(d[0]).forEach(function(a){var c=_slicedToArray(a,2),d=c[0],e=c[1];b._props[d].valueValidator(e)}),Object.entries(d[0]).forEach(function(a){var c=_slicedToArray(a,2),d=c[0],e=c[1],f=b._props[d].value;b._props[d].value=e,b._props[d].didChange(e,f)&&h.push([d,e,f])}),h.forEach(function(a){return b._firePropChangeEvent.apply(b,_toConsumableArray(a))})}else{var f=d[0],g=d[1];if(!this._props[f])throw new Error("No such property '".concat(f,"'"));a(f),this._props[f].valueValidator(g);var i=this._props[f].value;this._props[f].value=g,this._props[f].didChange(g,i)&&this._firePropChangeEvent(f,g,i)}},PropsModel.prototype._get=function(a,b){if(a(b),!this._props[b])throw new Error("No such property '".concat(b,"'"));return this._props[b].value},PropsModel.prototype._toJSON=function(a){return Object.entries(this._props).filter(function(b){var c=_slicedToArray(b,1),d=c[0];return a(d)}).reduce(function(a,b){var c=_slicedToArray(b,2),d=c[0],e=c[1].value;return a[d]=e,a},{})};function createAccessorNames(a){for(var b=arguments.length,c=Array(1<b?b-1:0),d=1;d<b;d++)c[d-1]=arguments[d];return c.map(function(b){return"".concat(b).concat(a.replace(/^./,function(a){return a.toUpperCase()}))})}PropsModel.prototype._installAccessors=function(a,b,c,d){for(var e=this,f=Object.keys(d),g=0;g<f.length;g++){var h=f[g],i=d[h];if(!this._props[h])throw new Error("Cannot create accessors for non-existant property '".concat(h,"'"));switch(i.toLowerCase()){case"readonly":a(h);break;case"readwrite":a(h),b(h);break;case"none":break;default:throw new Error("Unknown access type '".concat(i,"' specified for property '").concat(h,"'"));}}for(var j=Object.keys(d),k=function(){var a,b=j[l],f=d[b];switch(f.toLowerCase()){case"readonly":var g=createAccessorNames(b,"get"),h=_slicedToArray(g,1),i=h[0],k=_defineProperty({},i,function(){return e._props[b].value})[i];c[i]=k;break;case"readwrite":var m=createAccessorNames(b,"get","set"),n=_slicedToArray(m,2),o=n[0],p=n[1],q=(a={},_defineProperty(a,o,function(){return e._props[b].value}),_defineProperty(a,p,function(a){e._set(function(){},b,a)}),a);c[o]=q[o],c[p]=q[p];break;default:}},l=0;l<j.length;l++)k()},PropsModel.prototype._createUtilizer=function(a,b,c){var d=this,e=_toArray(b),f=e.slice(0);return f.forEach(a),f.forEach(function(a){if(!d._props[a])throw new Error("Cannot create utilizer of unknown property '".concat(a,"'"))}),function(){for(var a=arguments.length,b=Array(a),e=0;e<a;e++)b[e]=arguments[e];return c.apply(void 0,_toConsumableArray(f.map(function(a){return d._props[a].value})).concat(b))}},PropsModel.prototype._onAny=function(a,b,c){var d=_toArray(b),e=d.slice(0);e.forEach(a);for(var f=0;f<e.length;f++)this._eventEmitter.on("".concat(e[f],"-changed"),c)},PropsModel.prototype._createChangeHandler=function(a,b,c){var d=_toArray(b),e=d.slice(0),f=this._createUtilizer(a,e,c);return this._onAny(function(){},e,function(){return f()}),f},PropsModel.prototype.createUtilizer=function(a,b){return this._createUtilizer(function(){},a,b)},PropsModel.prototype.onAny=function(a,b){return this._onAny(function(){},a,b)},PropsModel.prototype.createChangeHandler=function(a,b){return this._createChangeHandler(function(){},a,b)},PropsModel.prototype.set=function(){for(var a=arguments.length,b=Array(a),c=0;c<a;c++)b[c]=arguments[c];return this._set.apply(this,[function(){}].concat(b))},PropsModel.prototype.get=function(){for(var a=arguments.length,b=Array(a),c=0;c<a;c++)b[c]=arguments[c];return this._get.apply(this,[function(){}].concat(b))},PropsModel.prototype.toJSON=function(){return this._toJSON(function(){return!0})},PropsModel.prototype.installAccessors=function(){for(var a=arguments.length,b=Array(a),c=0;c<a;c++)b[c]=arguments[c];return this._installAccessors.apply(this,[function(){},function(){}].concat(b))};function propertyCheckerToValidator(a){return function(b){var c=a(b);if(c instanceof Error)throw c;else if(!c)throw new Error("Requested access to property '".concat(b,"' is not allowed"))}}PropsModel.prototype.createApi=function(a){var b=this,c=1<arguments.length&&arguments[1]!==void 0?arguments[1]:propertyCheckerToValidator(a),d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:c;return{get:function f(){for(var a=arguments.length,d=Array(a),e=0;e<a;e++)d[e]=arguments[e];return b._get.apply(b,[c].concat(d))},set:function f(){for(var a=arguments.length,c=Array(a),e=0;e<a;e++)c[e]=arguments[e];return b._set.apply(b,[d].concat(c))},createUtilizer:function f(){for(var a=arguments.length,d=Array(a),e=0;e<a;e++)d[e]=arguments[e];return b._createUtilizer.apply(b,[c].concat(d))},createChangeHandler:function f(){for(var a=arguments.length,d=Array(a),e=0;e<a;e++)d[e]=arguments[e];return b._createChangeHandler.apply(b,[c].concat(d))},installAccessors:function g(){for(var a=arguments.length,e=Array(a),f=0;f<a;f++)e[f]=arguments[f];return b._installAccessors.apply(b,[c,d].concat(e))},toJSON:function c(){return b._toJSON(a)}}};function propNameIsPublic(a){return!a.startsWith("_")}function assertPropNameIsPublic(a){if(!propNameIsPublic(a))throw new Error("Property is not publicly accessible: ".concat(a))}function createStandardWriteValidator(a){return function(b){if(a._props[b].derived)throw new Error("Write access to ".concat(b," is not allowed because the property is a derived property."))}}PropsModel.prototype.getStandardPublicApi=function(){var a=createStandardWriteValidator(this);return this.createApi(propNameIsPublic,assertPropNameIsPublic,function(b){assertPropNameIsPublic(b),a(b)})},PropsModel.prototype.getStandardPrivateApi=function(){return this.createApi(function(){return!0},function(){},createStandardWriteValidator(this))};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PropsModel = void 0;
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
/**
* Instances of this class are used to configure and manage a set of named properties.
* Properties can have their values set and retrieved, and they fire change events through
* the given [EventEmitter]{@link external:EventEmitter} when the values change.
*
* Each property has a unique name, defined by a string. Properties can either be _primary_ or
* _derived_. A **primary** property is one that you have to set a value for explicitly. A **derived**
* property is calcuated automatically from the values of other properties (either primary or derived).
* Derived properties are registered as change-listeners to all the properties they are calculated
* from, so that they are automatically updated when any of their dependencies change. Derived properties
* likewise fire property change events when their value changes.
*
* @extends PropsModelApi
*/
class PropsModel {
/**
* @param {external:EventEmitter} eventEmitter The event emitter on which property change events will be
* emitted and listened to.
*/
constructor(eventEmitter) {
this._eventEmitter = eventEmitter;
this._props = {};
}
/**
* Helper method that is used to fire a property change event. This should be called *after* the properties
* value has been updated. Also note that the standard [EventEmitter]{@link external:EventEmitter} fires events
* and triggers listeners synchronously, so this won't return until all listeners have acted. This could lead
* to a deep call stack if those listeners end up updating other properties, and so on.
*
* @private
* @param {string} propName The name of hte property which has changed.
* @param {*} newValue The new value of the property.
* @param {*} oldValue The previous value of the property.
*/
_firePropChangeEvent(propName, newValue, oldValue) {
this._eventEmitter.emit(`${propName}-changed`, propName, newValue, oldValue);
}
/**
* Define a primary property that the model will track. It's value is set to the `initialValue`, which counts as setting
* the value of the property, changing it from `undefined`, so a change event for the property is fired unless `didChange`
* returns a falsey value for the change.
*
* @param {string} propName The name of the property. An Error will be thrown if the property is already defined.
*
* @param {*} initialValue The value to set the property to.
*
* @param {valueValidator} [valueValidator] An optional validator function that will be used whenever the property is set.
* The default allows all values. **Note** that this is _not_ called for the initial value, it's up to you to ensure that
* the initial value is valid.
*
* @param {didChange} [didChange] An optional function that is called anytime the property value is set, to determine
* whether or not the old value and new value should be considered a change. A change event for the property is fired if and only
* if the function returns a truthy value. The default uses `newValue !== oldValue`. Note that the property value is changed
* regardless of what this function returns, it is only used to determine if the event should be fired.
*/
defineProp(propName, initialValue, valueValidator = NOOP, didChange = defaultDidChange) {
if (this._props[propName]) {
throw new Error(`Property already defined: ${propName}`);
}
this._props[propName] = {
value: initialValue,
derived: false,
valueValidator,
didChange
};
if (didChange(initialValue, undefined)) {
this._firePropChangeEvent(propName, initialValue, undefined);
}
return this;
}
/**
* Defines a derived property which is set and updated automatically based on the values other specified properties.
*
* @param {string} propName The name of the property to define. An error will be thrown if the property already exists.
* @param {Array<string>} dependsOn The list of property names that this derived property depends on. Note that
* derived properties can only depend on already existing properties, and the dependencies of a derived property cannot
* be changed once set, so it is not possible to create cycles of dependent properties.
* @param {function(...*):*} calculateValue A function that will be invoked as needed to calculate the value of this
* property. It is invoked immediately to set the initial value (unless `initialValue` is given), and invoked again
* anytime one of the specified properties fires a change event. It is invoked with the contemporary values of the
* named properties, each passed as a separate arg, in the order specified in `dependsOn`.
* @param {*} [initialValue] An optional initial value to use, _in place of_ calculating the value. This will
* be used unless it has a `typeof` equal to `'undefined'`.
* @param {didChange} [didChange] An optional function to determine if a new value for the property should be
* considered a change from its previous value. See the same parameter on [`defineProp`]{@link PropsModel#defineProp}.
*/
defineDerivedProp(propName, dependsOn = [], _calculateValue, initialValue, didChange = defaultDidChange) {
if (this._props[propName]) {
throw new Error(`Property already defined: ${propName}`);
}
const calculateValue = this.createUtilizer(dependsOn, _calculateValue);
const value = typeof initialValue === 'undefined' ? calculateValue() : initialValue;
this._props[propName] = {
value,
derived: true,
valueValidator: () => {},
didChange
};
this._onAny(() => {}, dependsOn, () => {
this.set(propName, calculateValue());
});
if (didChange(value, undefined)) {
this._firePropChangeEvent(propName, value, undefined);
}
return this;
}
/**
* Create a special derived property that produces a backed view of another property, called the base property.
* Like any derived property, changing the base property will cause a new value fo the view property to be calculated
* and set. Unlike a normal derived property, you can also set the value of the view property and it will be reflected
* in the base property.
*
* In otherwords, you're creating a circular dependency between the view prop and the base prop. We ensure this doesn't
* lead (directly) to an infinite loop by supressing the update of the view prop in response to consequent
* updating of the base prop. However, that means that view and base prop might get out of sync; you need to make sure
* your reduceBaseValue function produces a value for the base property that would yield the same view value.
*
* @param {string} viewName The name of the view-prop to define
* @param {string} viewOf The name of a property that this is a view of
* @param {function(B):V} calculateViewValue A function that calculates the value of the view prop from the value
* of the `viewOf` property.
* @param {function(V, B):B} reduceBaseValue A function which is called whenever the view property is get the updated
* value of the base property. It's invoked with the new value of the view prop and the current value of the base prop.
* @param {function(V, V):boolean} didChange An optional function used to determine if the view prop should be considered
* to be changed.
*/
definePropView(viewName, viewOf, calculateViewValue, reduceBaseValue, didChange = defaultDidChange) {
if (this._props[viewName]) {
throw new Error(`Property already defined: ${viewName}`);
}
const calculateValue = this.createUtilizer([viewOf], calculateViewValue);
const value = calculateValue();
this._props[viewName] = {
value,
derived: true,
valueValidator: () => {},
didChange
};
let triggered = false;
this._onAny(() => {}, [viewOf], () => {
if (!triggered) {
this.set(viewName, calculateValue());
}
});
this._onAny(() => {}, [viewName], (ignore, newViewValue, oldViewValue) => {
const newViewOfValue = reduceBaseValue(newViewValue, this.get(viewOf));
triggered = true;
this.set(viewOf, newViewOfValue);
triggered = false;
});
if (didChange(value, undefined)) {
this._firePropChangeEvent(viewName, value, undefined);
}
return this;
}
/**
* Set one or more properties. You won't typically call this directly, you would use it through
* the [set()]{@link PropsModelApi#set} method.
*
* @private
* @param {propValidator} propValidator Called to verify write access to each property attempting
* to be set.
* @param {...*} args There are two signatures available: provide the property name and
* value as two arguments, or provide an object whose property names and property values
* describe what properties you want to set, and how. See {@link PropsModelApi#set(1)}
* and {@link PropsModelApi#set(2)}.
*/
_set(propValidator, ...args) {
if (args.length === 1) {
Object.keys(args[0]).forEach(propName => {
if (!this._props[propName]) {
throw new Error(`No such property '${propName}'`);
}
});
Object.keys(args[0]).forEach(propValidator);
Object.entries(args[0]).forEach(([propName, value]) => {
this._props[propName].valueValidator(value);
});
const oldValues = [];
Object.entries(args[0]).forEach(([propName, value]) => {
oldValues.push(this._props[propName].value);
this._props[propName].value = value;
});
Object.entries(args[0]).forEach(([propName, value], idx) => {
const oldValue = oldValues[idx];
if (this._props[propName].didChange(value, oldValue)) {
this._firePropChangeEvent(propName, value, oldValue);
}
});
} else {
const propName = args[0],
value = args[1];
if (!this._props[propName]) {
throw new Error(`No such property '${propName}'`);
}
propValidator(propName);
this._props[propName].valueValidator(value);
const oldValue = this._props[propName].value;
this._props[propName].value = value;
if (this._props[propName].didChange(value, oldValue)) {
this._firePropChangeEvent(propName, value, oldValue);
}
}
}
/**
* Get the value of the named property. You won't typically call this directly, you would use it through
* the [set()]{@link PropsModelApi#get} method.
*
* @private
* @param {propValidator} propValidator Called to verify read access to the named property.
* @param {string} propName The name of the property to get the value of.
*
* @returns {*} The value of the requested property
* @throws {Error} If the named property does not exist.
* @throws {*} Anything thrown by the `propValidator` when invoked with the given `propName`.
*/
_get(propValidator, propName) {
propValidator(propName);
if (!this._props[propName]) {
throw new Error(`No such property '${propName}'`);
}
return this._props[propName].value;
}
/**
* Return an JSON-serializable object that represents properties tracked by this model and their
* values. You won't typically call this directly, you would use it through
* the [set()]{@link PropsModelApi#toJSON} method.
*
* The name of each property known to this model is checked against the given `propChecker`; if
* it returns a truthy value, the property will be included in the returned "JSON" object, otherwise
* it wil not be.
*
* Note that property values are passed through `JSON.stringify` and then `JSON.parse` before being
* attached to the returned object. This may or may not lead to a different instance than what
* is kept in the model, depending on how the object handles JSONification, which could leak a
* reference to a mutable shared object, possibly allowing write unintended modifications without
* access restriction or event firing.
*
* @private
* @param {propChecker} propChecker Called to determine which properties should be included
* in the returned object.
* @returns {*} An object representing a JSONable account of the permitted properties and their values.
*/
_toJSON(propChecker) {
return Object.entries(this._props).filter(([propName]) => propChecker(propName)).reduce((o, [propName, {
value
}]) => {
o[propName] = JSON.parse(JSON.stringify(value));
return o;
}, {});
}
/**
* XXX Left off documenting here.
* Adds accessor methods (getters and setters) fo the specified properties as methods on the given target object.
*/
_installAccessors(readValidator, writeValidator, target, propertyAccess) {
var _arr = Object.keys(propertyAccess);
for (var _i = 0; _i < _arr.length; _i++) {
let propName = _arr[_i];
const access = propertyAccess[propName];
if (!this._props[propName]) {
throw new Error(`Cannot create accessors for non-existant property '${propName}'`);
}
switch (access.toLowerCase()) {
case 'readonly':
readValidator(propName);
break;
case 'readwrite':
readValidator(propName);
writeValidator(propName);
break;
case 'none':
break;
default:
throw new Error(`Unknown access type '${access}' specified for property '${propName}'`);
}
}
var _arr2 = Object.keys(propertyAccess);
for (var _i2 = 0; _i2 < _arr2.length; _i2++) {
let propName = _arr2[_i2];
const access = propertyAccess[propName];
switch (access.toLowerCase()) {
case 'readonly':
const _createAccessorNames = createAccessorNames(propName, 'get'),
_createAccessorNames2 = _slicedToArray(_createAccessorNames, 1),
funcName = _createAccessorNames2[0];
const getter = {
[funcName]: () => {
return this._props[propName].value;
}
}[funcName];
target[funcName] = getter;
break;
case 'readwrite':
const _createAccessorNames3 = createAccessorNames(propName, 'get', 'set'),
_createAccessorNames4 = _slicedToArray(_createAccessorNames3, 2),
getterName = _createAccessorNames4[0],
setterName = _createAccessorNames4[1];
const funcs = {
[getterName]: () => {
return this._props[propName].value;
},
[setterName]: value => {
this._set(() => {}, propName, value);
}
};
target[getterName] = funcs[getterName];
target[setterName] = funcs[setterName];
break;
default:
break;
}
}
}
/**
* Create a function that will delegate to the given handler with the values of specified properties as the arguments.
* The returned function will fetch the values of the named properties and pass them in the order given as the first
* arguments to the given handler function. Any arguments passed to the returned function will be passed as additional
* arguments to handler.
*
* @param {Array<string>} propNames The array of property names you want to utilize
* @param {function} handler The function that the returned function will delegate to with the values of the specified
* properties.
*/
_createUtilizer(propValidator, [...propNames], handler) {
propNames.forEach(propValidator);
propNames.forEach(propName => {
if (!this._props[propName]) {
throw new Error(`Cannot create utilizer of unknown property '${propName}'`);
}
});
return (...args) => {
return handler(...propNames.map(propName => this._props[propName].value), ...args);
};
}
/**
* Register the given handler to be invoked any time any of the given properties fire a change event.
*
* The given handler is invoked with three arguments: propName, newValue, oldValue.
*/
_onAny(propValidator, [...propNames], handler) {
propNames.forEach(propValidator);
for (let i = 0; i < propNames.length; i++) {
this._eventEmitter.on(`${propNames[i]}-changed`, handler);
}
}
/**
* Register the given handler to be called with the values of all of the named properties anytime
* any one of those properties changes.
*
* This uses {@link #_createUtilizer} to create a no-argument function that will collect the
* values of the properties and delegate them to the given `handler`. The function thus prouced is
* registered as a change handler for the given properties, and is also returned from this function.
*/
_createChangeHandler(propValidator, [...respondsTo], handler) {
const callback = this._createUtilizer(propValidator, respondsTo, handler);
this._onAny(() => {}, respondsTo, callback);
return callback;
}
createUtilizer(propNames, handler) {
return this._createUtilizer(() => {}, propNames, handler);
}
onAny(propNames, handler) {
return this._onAny(() => {}, propNames, handler);
}
/**
* Like {@link #createUtilizer}, but it also registers the created utilizer function to be called anytime
* any of the specified properties change. The utilizer function is still returned.
*
* @param {Array<string>} respondsTo The array of property names to respond to
* @param {function} handler The handler to all when ay of the specified properties change
*/
createChangeHandler(respondsTo, handler) {
return this._createChangeHandler(() => {}, respondsTo, handler);
}
set(...args) {
return this._set(() => {}, ...args);
}
get(...args) {
return this._get(() => {}, ...args);
}
toJSON() {
return this._toJSON(() => true);
}
installAccessors(...args) {
return this._installAccessors(() => {}, () => {}, ...args);
}
/**
* Create an API object that provides limited access to this model defined by the given checkers and validators.
*
* @param {function(string):boolean|function(string):Error} readChecker A function to determine whether or not the API should have
* read access to a given property name. Invoked with a property name, it should return a non-Error truthy value if the API
* should have read access to the property, and either a falsey value or an Error object if not.
* @param {function(string):*} [readValidator] A function to enforce read access; it is invoked with a property name and should
* throw an Error if and only if the API should not have read access to the named property. The return value is ignored. If this
* argument is not provided, a default is derived from the `readChecker`.
* @param {function(string):*} [writeValidator=readValidator] A function to enforce write access, similar to the `readValidator`.
* If not given, the default is to use the `readValidator`.
*
* @returns {{get, set, createUtilizer, createChangeHandler, toJSON}}
*/
createApi(readChecker, readValidator = propertyCheckerToValidator(readChecker), writeValidator = readValidator) {
return {
get: (...args) => this._get(readValidator, ...args),
set: (...args) => this._set(writeValidator, ...args),
createUtilizer: (...args) => this._createUtilizer(readValidator, ...args),
createChangeHandler: (...args) => this._createChangeHandler(readValidator, ...args),
installAccessors: (...args) => this._installAccessors(readValidator, writeValidator, ...args),
toJSON: () => this._toJSON(readChecker)
};
}
/**
* Returns an API object that has read access to all public properties, and write access to all public
* properties that are not derived.
*
* Public properties are those whose name does not begin with an underscore
*
* @see #createApi
*/
getStandardPublicApi() {
const standardWriteValidator = createStandardWriteValidator(this);
return this.createApi(propNameIsPublic, assertPropNameIsPublic, propName => {
assertPropNameIsPublic(propName);
standardWriteValidator(propName);
});
}
/**
* Returns an API object that allows read access to all properties, and write access
* to all non-derived properties.
*
* This is typically the API used by the property owner itself, to ensure you aren't
* trying to write to derived properties, which is usually not recommended.
*/
getStandardPrivateApi() {
return this.createApi(() => true, () => {}, createStandardWriteValidator(this));
}
}
exports.PropsModel = PropsModel;
function NOOP() {}
function defaultDidChange(newValue, oldVaue) {
return newValue !== oldVaue;
}
function createAccessorNames(propName, ...prefixes) {
return prefixes.map(prefix => `${prefix}${propName.replace(/^./, c => c.toUpperCase())}`);
}
function propertyCheckerToValidator(checker) {
return propName => {
const checkResult = checker(propName);
if (checkResult instanceof Error) {
throw checkResult;
} else if (!checkResult) {
throw new Error(`Requested access to property '${propName}' is not allowed`);
}
};
}
function propNameIsPublic(propName) {
return !propName.startsWith('_');
}
function assertPropNameIsPublic(propName) {
if (!propNameIsPublic(propName)) {
throw new Error(`Property is not publicly accessible: ${propName}`);
}
}
function createStandardWriteValidator(propModel) {
return propName => {
if (propModel._props[propName].derived) {
throw new Error(`Write access to ${propName} is not allowed because the property is a derived property.`);
}
};
}
/**
* Handles emitting and subscribing to events. Used for handling property change events by the {@link PropsModel}.
* @external EventEmitter
* @type {Constructor}
* @see https://nodejs.org/api/events.html#events_class_eventemitter
*/
/**
* A function that can be defined for each primary property to determine whether or not the property can
* be set to a given value. The value is considered valid unless the validator throws.
*
* Any return value is ignored.
*
* @callback valueValidator
* @param {*} incomingValue The value we want to set the property to.
* @throws {*} If the function throws anything, the incoming value is considered invalid and the property
* value will not be set, nor will a property change event be fired for it.
*/
/**
* A function that is used to determine if a property should be considered to have changed, given the
* old and new values. This is used to determine whether or not property change event will be fired.
*
* This is useful if you have multiple valid ways to represent the same canonical value, and don't
* want to cfire a change event unnecessarily. For instance, if the value of the property is an object,
* then two different objects which have all the same contents _might_ be considered equivalent and
* not treated as a change.
*
* **NB**: Keep in mind that non-primitive property values can be changed out from under you by anyone
* who still has a reference to it. So even though an object or array might look the same as the current
* value when it's passed in, that doesn't mean it will remain the same.
*
* @callback didChange
* @param {*} newValue The new value of the property, to which it was changed.
* @param {*} prevValue The previous value of the property, from which it was changed.
* @return {boolean} Any truthy value will indicate that the property should be considered changed.
*/
/**
* A generic validator that is typically used to enforce access authorization for properties based
* on their names.
*
* @callback propValidator
* @param {string} propName The name of the property
* @throws {*} Throw an error if the property name is not valid for the appropriate task.
*/
/**
* A generic checker function that is typically used to determine access authorization
* for properties. Contrasted with a {@link propValidator}, this is not intended to make
* any assertions, i.e., it is not meant to throw access to the property is not allowed,
* but simply to return a falsey value in that case.
*
* @callback propChecker
* @param {string} propName the name of the property
* @returns {boolean} A truthy value if and only if the access should be granted to the named
* property, a falsey value otherwise.
*/
/**
* A common interface for manipulating and using (but not defining) properties.
* The {@link PropsModel} class implements this interface, it also provides methods for
* getting other implementations of this interface for various access limitations.
* See, for instance, {@link PropsModel#createApi}.
*
* @interface PropsModelApi
*/
/**
* Set a single property to a new value. The given value will be passed to the configured {@link valueValidator}
* for the property, _before_ the property is set; if the validator throws an error, the error will not be
* caught, and the property will not be updated.
*
* After the value is updated, its configured {@link didChange} function will be called and a change event
* will be fired unless `didChange` returns a falsey value.
*
* @method set(1)
* @inner
* @memberof PropsModelApi
* @param {string} propName The name of the property
* @param {*} value The new value.
*/
/**
* Atomically set multiple properties at once. See [`set(string, *)`]{@link PropsModelApi#set(1)} for general information.
* This variant sets all the properties specified as keys to the given `propValues` object, setting each
* to the corresponding value. Note that all properties and values are validated _before_ any property
* is changed. This includes ensuring that the property is accessible for the given API, that the property
* exists, and that the value is valid according to the property's {@link valueValidator}.
*
* Additionally, all properties are updated _before_ any property change events are fired. Events are fired
* individually for each property, in the order they iterate from `propVaues`, and subject to that properties
* {@link didChange} function.
*
* @method set(2)
* @inner
* @memberof PropsModelApi
* @param {object} propValues An object mapping property names to the values you want to set them to.
* All own-properties of the object are assumed to be property names you want to set.
*/
/**
* Get the value of the named property. Throws an error if the property does not exist or the
* API doesn't have read access to it.
*
* @method get
* @inner
* @memberof PropsModelApi
* @param {string} propName The name of the property to get
* @returns {*} The value of the named property.
*/
/**
* Create a "utilizer function" that makes use of the values of all the named properties.
*
* @method createUtilizer
* @inner
* @memberof PropsModelApi
* @param {Array<string>} propNames A list of property names that the utilizer will use.
* @param {function(...*):*} handler The function that the returned utilizer function will
* delegate to, invoked with the contemporary values of the named properties, each passed
* as an individual argument, in the same order they're given in `propNames`. Any arguments passed
* to the utlizer function will be also be passed, following the property values.
*
* @throws {Error} If any of the named properties either don't exist or aren't accessible to the
* API at the time this funciton is called.
*
* @returns {function(...args):*} Returns a "utilizer" function, which can be invoked to get the
* values of the properties named by `propNames` and pass them to the given `handler` function,
* along with any args passed to the utilizer function. The utilizer function delegates to
* the `handler` at that point, returning whatever value it returns.
*/
/**
* Register the given `handler` to be called anytime one of the properties specified by `propNames`
* fires a change event. The `handler` is invoked with the current values of all the specified
* properties, followed by the standard change-event listener arguments: propertyName, newValue, oldValue
* for the property that was changed. Note that there is _no aggregation_ of change events, so it
* something changes multiple properties at once, the handler will be invoked for each property change.
*
* This actually uses [createUtilizer()]{@link PropsModelApi~createUtilizer} to create and returns a utilizer function, after registering
* the utilizer for the change events.
*
* @method createChangeHandler
* @inner
* @memberof PropsModelApi
* @param {Array<string>} propNames A list of property names that the utilizer will use.
* @param {function(...*):*} handler The function that the returned utilizer function will
*
* @throws {Error} If any of the named properties either don't exist or aren't accessible to the
* API at the time this funciton is called.
*
* @returns {function(...args):*} Returns a "utilizer" function, which can be invoked to get the
* values of the properties named by `propNames` and pass them to the given `handler` function,
* along with any args passed to the utilizer function. The utilizer function delegates to
* the `handler` at that point, returning whatever value it returns.
*/
/**
* Creates accessor functions (getter and/or setters) for specific properties and attaches them
* as methods to the given `target` object. This is a usefull way to create a bean-type
* interface for your properties.
*
* Accessor function names (and the property names with which they attach to target) take the form
* "get${PropName}" for getters and "set${PropName}" for setters, where `${PropName}` is simply the
* properties name with the first character capitalized.
*
* @method installAccessors
* @inner
* @memberof PropsModelApi
* @param {object} target The object onto which the accessor methods will be attached as properties.
* @param {object} propertyAccess An object describing which properties to create accessors for, and
* what accessors to create. Each own-property of the object is the name of a property, and the corresponding
* property value should be one of `'readonly'`, `'readwrite'`, or `'none'`, to create a getter only,
* a getter and a setter, or no accessors, respectively. The `'none'` value is only to be explicit, if
* the property is not included in the object, no accessors will be created for it. Any other property
* values will cause an error, as will properties of this object which do not correspond to known property
* names or which correspond to properties the API doesn't have appropriate access to.
*/
/**
* Returns an object representing the properties and their current values that this API has
* read access to.
*
* @method toJSON
* @inner
* @memberof PropsModelApi
*/

18

package.json
{
"name": "props-model",
"version": "0.2.0",
"version": "0.3.0",
"description": "A model for properties including change events and derived properties",

@@ -46,4 +46,4 @@ "main": "dist/index.js",

"verify": "npm-run-all check test",
"compile": "if-env NODE_ENV=production && babel --presets minify --no-comments src -d dist || babel src -d dist",
"docs": "jsdoc --recurse --destination out/jsdocs --package package.json --configure jsdoc.conf.json \"src/**/*\"",
"compile": "babel src -d dist",
"docs": "jsdoc --recurse --readme README.md --destination out/jsdocs --configure jsdoc.conf.json --package package.json src/",
"build": "npm-run-all clean verify compile",

@@ -70,3 +70,2 @@ "prepublishOnly": "cross-env NODE_ENV=production npm run build"

"@babel/register": "7.0.0",
"babel-preset-minify": "0.5.0",
"chai": "4.2.0",

@@ -76,15 +75,14 @@ "cross-env": "5.2.0",

"jsdoc": "3.5.5",
"jsdoc-to-markdown": "4.0.1",
"mkdirp": "0.5.1",
"mocha": "5.2.0",
"npm-package-json-lint": "3.3.0",
"npm-package-json-lint": "3.4.1",
"npm-run-all": "4.1.5",
"nyc": "13.1.0",
"remark-cli": "5.0.0",
"nyc": "13.3.0",
"remark-cli": "6.0.1",
"rimraf": "2.6.2",
"sinon": "7.1.1",
"sinon": "7.2.0",
"sinon-chai": "3.3.0",
"snazzy": "7.1.1",
"snazzy": "8.0.0",
"standard": "12.0.1"
}
}

@@ -5,2 +5,17 @@ # props-model

[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
[![npm](https://img.shields.io/npm/v/props-model.svg)](https://libraries.io/github/mearns/props-model)
[![Stable Build](https://travis-ci.org/mearns/props-model.svg?branch=versions%2Fstable)](https://travis-ci.org/mearns/props-model)
[![node](https://img.shields.io/node/v/props-model.svg)](https://www.npmjs.com/package/props-model)
[![NpmLicense](https://img.shields.io/npm/l/props-model.svg)](https://spdx.org/licenses/MIT)
[![npm bundle size (minified)](https://img.shields.io/bundlephobia/min/props-model.svg)](https://www.npmjs.com/package/props-model)
[![Libraries.io for GitHub](https://img.shields.io/librariesio/github/mearns/props-model.svg)](https://libraries.io/github/mearns/props-model)
[![npm](https://img.shields.io/npm/dy/props-model.svg)](https://www.npmjs.com/package/props-model)
[![GitHub issues](https://img.shields.io/github/issues-raw/mearns/props-model.svg)](https://github.com/mearns/props-model/issues?q=is%3Aissue+is%3Aopen)
[![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/mearns/props-model.svg)](https://github.com/mearns/props-model/pulls?q=is%3Apr+is%3Aopen)
[![GitHub last commit](https://img.shields.io/github/last-commit/mearns/props-model.svg)](https://github.com/mearns/props-model/commits/)
## Overview

@@ -7,0 +22,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc