vivisector
Advanced tools
Comparing version 1.5.1 to 1.6.0
@@ -29,4 +29,4 @@ | ||
/* global Reflect, Promise */ | ||
var _extendStatics = function extendStatics(d, b) { | ||
_extendStatics = Object.setPrototypeOf || { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || { | ||
__proto__: [] | ||
@@ -36,8 +36,6 @@ } instanceof Array && function (d, b) { | ||
} || function (d, b) { | ||
for (var p in b) { | ||
if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
} | ||
for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
}; | ||
return _extendStatics(d, b); | ||
return extendStatics(d, b); | ||
}; | ||
@@ -47,5 +45,4 @@ | ||
if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
_extendStatics(d, b); | ||
function __() { | ||
@@ -67,6 +64,8 @@ this.constructor = d; | ||
var unboundedSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundedSlice); | ||
var unboundSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundSlice); | ||
/** | ||
* @summary Shallow copy an object or array | ||
* | ||
* @internal | ||
*/ | ||
@@ -81,12 +80,12 @@ function shallowCopy(base) { | ||
var key = keys[i]; | ||
var desc = descriptors[key]; | ||
if (!desc.writable) { | ||
desc.writable = true; | ||
desc.configurable = true; | ||
var descriptor = descriptors[key]; | ||
if (!descriptor.writable) { | ||
descriptor.writable = true; | ||
descriptor.configurable = true; | ||
} | ||
if (desc.get || desc.set) { | ||
if (descriptor.get || descriptor.set) { | ||
descriptors[key] = { | ||
configurable: true, | ||
writable: !!desc.set, | ||
enumerable: desc.enumerable, | ||
writable: !!descriptor.set, | ||
enumerable: descriptor.enumerable, | ||
value: base[key] | ||
@@ -100,5 +99,6 @@ }; | ||
* @summary Define a non-configurable function property `value` with name `name` on a given object `context` | ||
* @param {VxState} context The object on which the property will be defined | ||
* @param {string} name The name of the property | ||
* @param {Function} value The value of the function property | ||
* @param name The name of the property | ||
* @param value The value of the function property | ||
* | ||
* @internal | ||
*/ | ||
@@ -116,10 +116,10 @@ function defineNonConfigurableProp(context, name, value) { | ||
* and whether a given property exists on that array's prototype | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function isArrayProto(target, prop) { | ||
return Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype) | ||
.includes(prop); | ||
return (Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype).includes(prop)); | ||
} | ||
@@ -129,5 +129,6 @@ /** | ||
* and whether it is out of bounds relative to a given target | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
@@ -138,3 +139,3 @@ function isArrayPropOutOfBounds(target, prop) { | ||
return false; | ||
return Array.isArray(target) && (maybeIdx > (target.length - 1)); | ||
return Array.isArray(target) && maybeIdx > target.length - 1; | ||
} | ||
@@ -144,2 +145,4 @@ | ||
* @summary Base implementation model for extended errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -160,2 +163,4 @@ var BaseVxError = /** @class */ (function (_super) { | ||
* Base implementation for errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -173,2 +178,4 @@ var VxError = /** @class */ (function (_super) { | ||
* @summary Exception metadata builder | ||
* | ||
* @internal | ||
*/ | ||
@@ -183,4 +190,3 @@ var VxException = /** @class */ (function () { | ||
* @summary Build an error object with the given exception metadata instance | ||
* @param {VxException} instance | ||
* @returns {VxError} | ||
* @param instance | ||
*/ | ||
@@ -192,3 +198,2 @@ VxException.create = function (instance) { | ||
* @summary Serialize the source metadata into a string | ||
* @returns {string} | ||
*/ | ||
@@ -199,10 +204,9 @@ VxException.prototype.serializeSource = function () { | ||
var _a = this.source, filename = _a.filename, lineno = _a.lineno; | ||
return "at " + filename + ", Ln " + lineno; | ||
return " at " + filename + ", Ln " + lineno; | ||
}; | ||
/** | ||
* @summary Serialize the exception metadata into a string | ||
* @returns {string} | ||
*/ | ||
VxException.prototype.serialize = function () { | ||
return this.reason + " " + this.serializeSource(); | ||
return "" + this.reason + this.serializeSource(); | ||
}; | ||
@@ -213,105 +217,51 @@ return VxException; | ||
/** | ||
* @summary Validate a provided event handler name, value | ||
* @param {string} eventName | ||
* @param {Function} handler | ||
* @see https://github.com/microsoft/TypeScript/issues/26916 | ||
*/ | ||
function validateEventHandler(eventName, handler) { | ||
if (!(eventName in this.handlerStore)) { | ||
/** | ||
* @summary Validate a provided event handler | ||
* @param handler | ||
* | ||
* @internal | ||
*/ | ||
function validateEventHandler(handler) { | ||
if (typeof handler !== 'function') { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
reason: 'The provided event handler must be a function' | ||
})); | ||
} | ||
else if (typeof handler !== 'function') { | ||
} | ||
/** | ||
* @summary Validate a provided event name | ||
* @param eventName | ||
* @param validEvents - a list of possible valid event names | ||
* | ||
* @internal | ||
*/ | ||
function validateEventName(eventName, validEvents) { | ||
if (typeof eventName !== 'string') { | ||
throw VxException.create(new VxException({ | ||
reason: 'The provided event handler must be a function' | ||
reason: "Event name must be a string" | ||
})); | ||
} | ||
if (!validEvents.includes(eventName)) { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
})); | ||
} | ||
} | ||
/** | ||
* @summary Evaluate whether `testValue` is a plain object | ||
* @param testValue | ||
* | ||
* @internal | ||
*/ | ||
var isObject = function (testValue) { | ||
return {}.toString.call(testValue) == '[object Object]'; | ||
}; | ||
var VX_EVENT_TYPE; | ||
(function (VX_EVENT_TYPE) { | ||
VX_EVENT_TYPE["ADD"] = "add"; | ||
VX_EVENT_TYPE["DEL"] = "del"; | ||
VX_EVENT_TYPE["SET"] = "set"; | ||
VX_EVENT_TYPE["BATCHED"] = "batched"; | ||
})(VX_EVENT_TYPE || (VX_EVENT_TYPE = {})); | ||
var VX_LISTENER_INTERNALS; | ||
(function (VX_LISTENER_INTERNALS) { | ||
VX_LISTENER_INTERNALS["ADD"] = "addEventListener"; | ||
VX_LISTENER_INTERNALS["REM"] = "removeEventListener"; | ||
})(VX_LISTENER_INTERNALS || (VX_LISTENER_INTERNALS = {})); | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* @summary Construct a done committal function | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
var _a; | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
* @property {VxEventHandlerStore} | ||
*/ | ||
this.handlerStore = (_a = {}, | ||
_a[VX_EVENT_TYPE.ADD] = new Set(), | ||
_a[VX_EVENT_TYPE.DEL] = new Set(), | ||
_a[VX_EVENT_TYPE.SET] = new Set(), | ||
_a[VX_EVENT_TYPE.BATCHED] = new Set(), | ||
_a); | ||
this.internals = __spreadArray([], Object.values(VX_LISTENER_INTERNALS)); | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param {string|symbol} prop The property presently being accessed | ||
* @returns {boolean} | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `addEventListener`, `removeEventListener` on the proxied object | ||
* @param context The context (i.e. `this` instance) of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineListeners = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.ADD, function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventHandler.call(_this, eventName, handler); | ||
_this.handlerStore[eventName] | ||
.add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.REM, function (eventName, handler) { | ||
validateEventHandler.call(_this, eventName, handler); | ||
var handlers = _this.handlerStore[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param {object} event An object containing data about the event | ||
* @param {object} context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, context, done) { | ||
this.handlerStore[event.type] | ||
.forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler.call(context, event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
var DoneFunctionBuilder = function (ret) { | ||
@@ -325,4 +275,10 @@ var done = function (commit) { | ||
var eventedArrayPrototypeResolver = function (target, prop) { | ||
var _this = this; | ||
/** | ||
* @summary Define evented analogs on a proxied array prototype | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function eventedArrayPrototypeResolver(context, target, prop) { | ||
var ogMethod = target[prop]; | ||
@@ -338,8 +294,10 @@ return function () { | ||
var ret = nextState.shift(); | ||
var done = DoneFunctionBuilder(function () { return Array.prototype.shift.call(target); }); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
var done = DoneFunctionBuilder(function () { | ||
return Array.prototype.shift.call(target); | ||
}); | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -352,7 +310,7 @@ } | ||
}); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
context.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -365,10 +323,10 @@ } | ||
var _a; | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args)); | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args, false)); | ||
}); | ||
ret = nextState.unshift.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
} | ||
@@ -380,7 +338,7 @@ return ret; | ||
nextState.reverse(); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState; | ||
@@ -391,7 +349,7 @@ } | ||
nextState.push.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: args.length > 1 ? VX_EVENT_TYPE.BATCHED : VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: args.length > 1 ? 'batched' : 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState.length; | ||
@@ -403,7 +361,7 @@ } | ||
ogMethod.apply(nextState, [arg]); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
prevState = shallowCopy(target); | ||
@@ -420,18 +378,12 @@ }; | ||
}; | ||
}; | ||
} | ||
var batchedMethods = [ | ||
'shift', | ||
'unshift', | ||
'push', | ||
'reverse', | ||
'sort', | ||
'pop' | ||
]; | ||
var batchedMethods = ['shift', 'unshift', 'push', 'reverse', 'sort', 'pop']; | ||
/** | ||
* @summary Construct a base proxy handler with an implicit context | ||
* @returns {ProxyHandler<VxState>} Base proxy handler | ||
* @returns {ProxyHandler<?>} Base proxy handler | ||
* | ||
* @internal | ||
*/ | ||
function RootHandlerFactory() { | ||
var _this = this; | ||
function RootHandlerFactory(base) { | ||
var rootHandler = { | ||
@@ -447,3 +399,3 @@ get: function (target, prop, recv) { | ||
if (Array.isArray(target) && batchedMethods.includes(prop)) { | ||
return eventedArrayPrototypeResolver.call(_this, target, prop); | ||
return eventedArrayPrototypeResolver(base, target, prop); | ||
} | ||
@@ -460,3 +412,3 @@ // we use reflection to mitigate violation of Proxy invariants, as described in the specification here: | ||
set: function (target, prop, value) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -469,15 +421,16 @@ } | ||
var ret = Reflect.set(nextState, prop, value); | ||
var done = DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); }); | ||
if (!(prop in prevState) || isArrayPropOutOfBounds(prevState, prop)) { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
base.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
else { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.SET, | ||
base.raiseEvent({ | ||
type: 'set', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
@@ -487,3 +440,3 @@ return ret; | ||
deleteProperty: function (target, prop) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -493,9 +446,12 @@ } | ||
var ret = true; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (Array.isArray(nextState)) { | ||
nextState.splice(Number(prop), 1); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
var numericProp_1 = Number(prop); | ||
nextState.splice(numericProp_1, 1); | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return target.splice(Number(prop), 1); })); | ||
}, DoneFunctionBuilder(function () { return target.splice(numericProp_1, 1); })); | ||
return ret; | ||
@@ -505,9 +461,10 @@ } | ||
Reflect.deleteProperty(nextState, prop); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
}, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
return ret; | ||
} | ||
// istanbul ignore next | ||
return ret; | ||
@@ -519,13 +476,99 @@ } | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
*/ | ||
this.observers = { | ||
add: new Set(), | ||
del: new Set(), | ||
set: new Set(), | ||
batched: new Set() | ||
}; | ||
this.internals = ['subscribe', 'unsubscribe']; | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param prop The property presently being accessed | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
// see -> https://github.com/microsoft/TypeScript/issues/26255 | ||
// btw, this is where TypeScript's bizarre `Array.prototype.includes` approach could screw us over | ||
// if I followed TS' way, I may only pass a string to `includes`, which means if we receive a symbol, we'll return false | ||
// which would propagate to the proxy and lead to a invariant violation | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `subscribe`, `unsubscribe` on the proxied object | ||
* @param context The context of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineSubscribers = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, 'subscribe', function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
validateEventHandler(handler); | ||
_this.observers[eventName].add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, 'unsubscribe', function (eventName, handler) { | ||
// both validators are tested via the public API | ||
// istanbul ignore next | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
// istanbul ignore next | ||
validateEventHandler(handler); | ||
var handlers = _this.observers[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param event An object containing data about the event | ||
* @param context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, done) { | ||
this.observers[event.type].forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler(event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
/** | ||
* @summary Create proxies | ||
* | ||
* @internal | ||
*/ | ||
var ProxiedObservableFactory = /** @class */ (function (_super) { | ||
__extends(ProxiedObservableFactory, _super); | ||
function ProxiedObservableFactory() { | ||
var _this = _super.call(this) || this; | ||
_this.rootHandler = RootHandlerFactory | ||
.call(_this); | ||
return _this; | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
ProxiedObservableFactory.prototype.create = function (initialState) { | ||
/** | ||
* @summary Root Proxy handler; injects event broadcasts into get|set|delete traps | ||
*/ | ||
ProxiedObservableFactory.create = function (initialState) { | ||
var excisedInitialState = shallowCopy(initialState); | ||
return this.defineListeners(new Proxy(excisedInitialState, this.rootHandler)); | ||
var instance = new ProxiedObservableFactory(); | ||
return instance.defineSubscribers(new Proxy(excisedInitialState, RootHandlerFactory(instance))); | ||
}; | ||
@@ -535,5 +578,19 @@ return ProxiedObservableFactory; | ||
var vivisect = function (initialState) { return new ProxiedObservableFactory() | ||
.create(initialState); }; | ||
/** | ||
* @summary 'Vivisect' an object - render the object evented by | ||
* proxying it through a subscribable interface | ||
* | ||
* @param initialState | ||
* | ||
* @public | ||
*/ | ||
var vivisect = function (initialState) { | ||
if (isObject(initialState) || Array.isArray(initialState)) { | ||
return ProxiedObservableFactory.create(initialState); | ||
} | ||
throw VxException.create(new VxException({ | ||
reason: 'invalid initial state type' | ||
})); | ||
}; | ||
exports.vivisect = vivisect; |
@@ -1,43 +0,34 @@ | ||
declare enum VX_EVENT_TYPE { | ||
ADD = "add", | ||
DEL = "del", | ||
SET = "set", | ||
BATCHED = "batched" | ||
} | ||
declare enum VX_LISTENER_INTERNALS { | ||
ADD = "addEventListener", | ||
REM = "removeEventListener" | ||
} | ||
interface VxEventedObject { | ||
readonly [VX_LISTENER_INTERNALS.ADD]: VxEventRegistrar; | ||
readonly [VX_LISTENER_INTERNALS.REM]: VxEventRegistrar; | ||
[k: string]: any; | ||
} | ||
interface DoneFunction { | ||
declare type ISubscriptionEvent = 'add' | 'del' | 'set' | 'batched'; | ||
declare type IVivisectorApi<S> = { | ||
readonly subscribe: ISubscription<S>; | ||
readonly unsubscribe: ISubscription<S>; | ||
} & S; | ||
interface IDoneFunction { | ||
(commit: boolean): void; | ||
} | ||
interface VxAddEventListenerOpts { | ||
interface ISubscriptionOpts { | ||
alwaysCommit?: boolean; | ||
} | ||
interface VxEventRegistrar { | ||
(eventName: VX_EVENT_TYPE, handler: VxEventHandler, opts?: VxAddEventListenerOpts): VxEventedObject; | ||
interface ISubscription<S> { | ||
(eventName: ISubscriptionEvent, handler: ISubscriptionCallback<S>, opts?: ISubscriptionOpts): IVivisectorApi<S>; | ||
} | ||
declare type VxState = object | any[]; | ||
declare type VxEvent<T extends VxState> = { | ||
type: VX_EVENT_TYPE; | ||
prevState: T; | ||
nextState: T; | ||
}; | ||
declare type VxEventHandler = (e: VxEvent<VxState>, done: DoneFunction) => void; | ||
declare global { | ||
interface Array<T> { | ||
includes<U extends (T extends U ? unknown : never)>(el: U, idx?: number): boolean; | ||
} | ||
interface ISubscriptionCallback<S> { | ||
(e: ISubscriptionEventMetadata<S>, done: IDoneFunction): void; | ||
} | ||
interface Vivisector { | ||
(initialState: VxState): VxEventedObject; | ||
interface ISubscriptionEventMetadata<S> { | ||
type: ISubscriptionEvent; | ||
prevState: S; | ||
nextState: S; | ||
} | ||
declare const vivisect: Vivisector; | ||
/** | ||
* @summary 'Vivisect' an object - render the object evented by | ||
* proxying it through a subscribable interface | ||
* | ||
* @param initialState | ||
* | ||
* @public | ||
*/ | ||
declare const vivisect: <S>(initialState: S) => IVivisectorApi<S>; | ||
export { vivisect }; |
@@ -25,4 +25,4 @@ | ||
/* global Reflect, Promise */ | ||
var _extendStatics = function extendStatics(d, b) { | ||
_extendStatics = Object.setPrototypeOf || { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || { | ||
__proto__: [] | ||
@@ -32,8 +32,6 @@ } instanceof Array && function (d, b) { | ||
} || function (d, b) { | ||
for (var p in b) { | ||
if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
} | ||
for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
}; | ||
return _extendStatics(d, b); | ||
return extendStatics(d, b); | ||
}; | ||
@@ -43,5 +41,4 @@ | ||
if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
_extendStatics(d, b); | ||
function __() { | ||
@@ -63,6 +60,8 @@ this.constructor = d; | ||
var unboundedSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundedSlice); | ||
var unboundSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundSlice); | ||
/** | ||
* @summary Shallow copy an object or array | ||
* | ||
* @internal | ||
*/ | ||
@@ -77,12 +76,12 @@ function shallowCopy(base) { | ||
var key = keys[i]; | ||
var desc = descriptors[key]; | ||
if (!desc.writable) { | ||
desc.writable = true; | ||
desc.configurable = true; | ||
var descriptor = descriptors[key]; | ||
if (!descriptor.writable) { | ||
descriptor.writable = true; | ||
descriptor.configurable = true; | ||
} | ||
if (desc.get || desc.set) { | ||
if (descriptor.get || descriptor.set) { | ||
descriptors[key] = { | ||
configurable: true, | ||
writable: !!desc.set, | ||
enumerable: desc.enumerable, | ||
writable: !!descriptor.set, | ||
enumerable: descriptor.enumerable, | ||
value: base[key] | ||
@@ -96,5 +95,6 @@ }; | ||
* @summary Define a non-configurable function property `value` with name `name` on a given object `context` | ||
* @param {VxState} context The object on which the property will be defined | ||
* @param {string} name The name of the property | ||
* @param {Function} value The value of the function property | ||
* @param name The name of the property | ||
* @param value The value of the function property | ||
* | ||
* @internal | ||
*/ | ||
@@ -112,10 +112,10 @@ function defineNonConfigurableProp(context, name, value) { | ||
* and whether a given property exists on that array's prototype | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function isArrayProto(target, prop) { | ||
return Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype) | ||
.includes(prop); | ||
return (Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype).includes(prop)); | ||
} | ||
@@ -125,5 +125,6 @@ /** | ||
* and whether it is out of bounds relative to a given target | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
@@ -134,3 +135,3 @@ function isArrayPropOutOfBounds(target, prop) { | ||
return false; | ||
return Array.isArray(target) && (maybeIdx > (target.length - 1)); | ||
return Array.isArray(target) && maybeIdx > target.length - 1; | ||
} | ||
@@ -140,2 +141,4 @@ | ||
* @summary Base implementation model for extended errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -156,2 +159,4 @@ var BaseVxError = /** @class */ (function (_super) { | ||
* Base implementation for errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -169,2 +174,4 @@ var VxError = /** @class */ (function (_super) { | ||
* @summary Exception metadata builder | ||
* | ||
* @internal | ||
*/ | ||
@@ -179,4 +186,3 @@ var VxException = /** @class */ (function () { | ||
* @summary Build an error object with the given exception metadata instance | ||
* @param {VxException} instance | ||
* @returns {VxError} | ||
* @param instance | ||
*/ | ||
@@ -188,3 +194,2 @@ VxException.create = function (instance) { | ||
* @summary Serialize the source metadata into a string | ||
* @returns {string} | ||
*/ | ||
@@ -195,10 +200,9 @@ VxException.prototype.serializeSource = function () { | ||
var _a = this.source, filename = _a.filename, lineno = _a.lineno; | ||
return "at " + filename + ", Ln " + lineno; | ||
return " at " + filename + ", Ln " + lineno; | ||
}; | ||
/** | ||
* @summary Serialize the exception metadata into a string | ||
* @returns {string} | ||
*/ | ||
VxException.prototype.serialize = function () { | ||
return this.reason + " " + this.serializeSource(); | ||
return "" + this.reason + this.serializeSource(); | ||
}; | ||
@@ -209,105 +213,51 @@ return VxException; | ||
/** | ||
* @summary Validate a provided event handler name, value | ||
* @param {string} eventName | ||
* @param {Function} handler | ||
* @see https://github.com/microsoft/TypeScript/issues/26916 | ||
*/ | ||
function validateEventHandler(eventName, handler) { | ||
if (!(eventName in this.handlerStore)) { | ||
/** | ||
* @summary Validate a provided event handler | ||
* @param handler | ||
* | ||
* @internal | ||
*/ | ||
function validateEventHandler(handler) { | ||
if (typeof handler !== 'function') { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
reason: 'The provided event handler must be a function' | ||
})); | ||
} | ||
else if (typeof handler !== 'function') { | ||
} | ||
/** | ||
* @summary Validate a provided event name | ||
* @param eventName | ||
* @param validEvents - a list of possible valid event names | ||
* | ||
* @internal | ||
*/ | ||
function validateEventName(eventName, validEvents) { | ||
if (typeof eventName !== 'string') { | ||
throw VxException.create(new VxException({ | ||
reason: 'The provided event handler must be a function' | ||
reason: "Event name must be a string" | ||
})); | ||
} | ||
if (!validEvents.includes(eventName)) { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
})); | ||
} | ||
} | ||
/** | ||
* @summary Evaluate whether `testValue` is a plain object | ||
* @param testValue | ||
* | ||
* @internal | ||
*/ | ||
var isObject = function (testValue) { | ||
return {}.toString.call(testValue) == '[object Object]'; | ||
}; | ||
var VX_EVENT_TYPE; | ||
(function (VX_EVENT_TYPE) { | ||
VX_EVENT_TYPE["ADD"] = "add"; | ||
VX_EVENT_TYPE["DEL"] = "del"; | ||
VX_EVENT_TYPE["SET"] = "set"; | ||
VX_EVENT_TYPE["BATCHED"] = "batched"; | ||
})(VX_EVENT_TYPE || (VX_EVENT_TYPE = {})); | ||
var VX_LISTENER_INTERNALS; | ||
(function (VX_LISTENER_INTERNALS) { | ||
VX_LISTENER_INTERNALS["ADD"] = "addEventListener"; | ||
VX_LISTENER_INTERNALS["REM"] = "removeEventListener"; | ||
})(VX_LISTENER_INTERNALS || (VX_LISTENER_INTERNALS = {})); | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* @summary Construct a done committal function | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
var _a; | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
* @property {VxEventHandlerStore} | ||
*/ | ||
this.handlerStore = (_a = {}, | ||
_a[VX_EVENT_TYPE.ADD] = new Set(), | ||
_a[VX_EVENT_TYPE.DEL] = new Set(), | ||
_a[VX_EVENT_TYPE.SET] = new Set(), | ||
_a[VX_EVENT_TYPE.BATCHED] = new Set(), | ||
_a); | ||
this.internals = __spreadArray([], Object.values(VX_LISTENER_INTERNALS)); | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param {string|symbol} prop The property presently being accessed | ||
* @returns {boolean} | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `addEventListener`, `removeEventListener` on the proxied object | ||
* @param context The context (i.e. `this` instance) of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineListeners = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.ADD, function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventHandler.call(_this, eventName, handler); | ||
_this.handlerStore[eventName] | ||
.add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.REM, function (eventName, handler) { | ||
validateEventHandler.call(_this, eventName, handler); | ||
var handlers = _this.handlerStore[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param {object} event An object containing data about the event | ||
* @param {object} context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, context, done) { | ||
this.handlerStore[event.type] | ||
.forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler.call(context, event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
var DoneFunctionBuilder = function (ret) { | ||
@@ -321,4 +271,10 @@ var done = function (commit) { | ||
var eventedArrayPrototypeResolver = function (target, prop) { | ||
var _this = this; | ||
/** | ||
* @summary Define evented analogs on a proxied array prototype | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function eventedArrayPrototypeResolver(context, target, prop) { | ||
var ogMethod = target[prop]; | ||
@@ -334,8 +290,10 @@ return function () { | ||
var ret = nextState.shift(); | ||
var done = DoneFunctionBuilder(function () { return Array.prototype.shift.call(target); }); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
var done = DoneFunctionBuilder(function () { | ||
return Array.prototype.shift.call(target); | ||
}); | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -348,7 +306,7 @@ } | ||
}); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
context.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -361,10 +319,10 @@ } | ||
var _a; | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args)); | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args, false)); | ||
}); | ||
ret = nextState.unshift.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
} | ||
@@ -376,7 +334,7 @@ return ret; | ||
nextState.reverse(); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState; | ||
@@ -387,7 +345,7 @@ } | ||
nextState.push.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: args.length > 1 ? VX_EVENT_TYPE.BATCHED : VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: args.length > 1 ? 'batched' : 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState.length; | ||
@@ -399,7 +357,7 @@ } | ||
ogMethod.apply(nextState, [arg]); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
prevState = shallowCopy(target); | ||
@@ -416,18 +374,12 @@ }; | ||
}; | ||
}; | ||
} | ||
var batchedMethods = [ | ||
'shift', | ||
'unshift', | ||
'push', | ||
'reverse', | ||
'sort', | ||
'pop' | ||
]; | ||
var batchedMethods = ['shift', 'unshift', 'push', 'reverse', 'sort', 'pop']; | ||
/** | ||
* @summary Construct a base proxy handler with an implicit context | ||
* @returns {ProxyHandler<VxState>} Base proxy handler | ||
* @returns {ProxyHandler<?>} Base proxy handler | ||
* | ||
* @internal | ||
*/ | ||
function RootHandlerFactory() { | ||
var _this = this; | ||
function RootHandlerFactory(base) { | ||
var rootHandler = { | ||
@@ -443,3 +395,3 @@ get: function (target, prop, recv) { | ||
if (Array.isArray(target) && batchedMethods.includes(prop)) { | ||
return eventedArrayPrototypeResolver.call(_this, target, prop); | ||
return eventedArrayPrototypeResolver(base, target, prop); | ||
} | ||
@@ -456,3 +408,3 @@ // we use reflection to mitigate violation of Proxy invariants, as described in the specification here: | ||
set: function (target, prop, value) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -465,15 +417,16 @@ } | ||
var ret = Reflect.set(nextState, prop, value); | ||
var done = DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); }); | ||
if (!(prop in prevState) || isArrayPropOutOfBounds(prevState, prop)) { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
base.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
else { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.SET, | ||
base.raiseEvent({ | ||
type: 'set', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
@@ -483,3 +436,3 @@ return ret; | ||
deleteProperty: function (target, prop) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -489,9 +442,12 @@ } | ||
var ret = true; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (Array.isArray(nextState)) { | ||
nextState.splice(Number(prop), 1); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
var numericProp_1 = Number(prop); | ||
nextState.splice(numericProp_1, 1); | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return target.splice(Number(prop), 1); })); | ||
}, DoneFunctionBuilder(function () { return target.splice(numericProp_1, 1); })); | ||
return ret; | ||
@@ -501,9 +457,10 @@ } | ||
Reflect.deleteProperty(nextState, prop); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
}, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
return ret; | ||
} | ||
// istanbul ignore next | ||
return ret; | ||
@@ -515,13 +472,99 @@ } | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
*/ | ||
this.observers = { | ||
add: new Set(), | ||
del: new Set(), | ||
set: new Set(), | ||
batched: new Set() | ||
}; | ||
this.internals = ['subscribe', 'unsubscribe']; | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param prop The property presently being accessed | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
// see -> https://github.com/microsoft/TypeScript/issues/26255 | ||
// btw, this is where TypeScript's bizarre `Array.prototype.includes` approach could screw us over | ||
// if I followed TS' way, I may only pass a string to `includes`, which means if we receive a symbol, we'll return false | ||
// which would propagate to the proxy and lead to a invariant violation | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `subscribe`, `unsubscribe` on the proxied object | ||
* @param context The context of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineSubscribers = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, 'subscribe', function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
validateEventHandler(handler); | ||
_this.observers[eventName].add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, 'unsubscribe', function (eventName, handler) { | ||
// both validators are tested via the public API | ||
// istanbul ignore next | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
// istanbul ignore next | ||
validateEventHandler(handler); | ||
var handlers = _this.observers[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param event An object containing data about the event | ||
* @param context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, done) { | ||
this.observers[event.type].forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler(event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
/** | ||
* @summary Create proxies | ||
* | ||
* @internal | ||
*/ | ||
var ProxiedObservableFactory = /** @class */ (function (_super) { | ||
__extends(ProxiedObservableFactory, _super); | ||
function ProxiedObservableFactory() { | ||
var _this = _super.call(this) || this; | ||
_this.rootHandler = RootHandlerFactory | ||
.call(_this); | ||
return _this; | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
ProxiedObservableFactory.prototype.create = function (initialState) { | ||
/** | ||
* @summary Root Proxy handler; injects event broadcasts into get|set|delete traps | ||
*/ | ||
ProxiedObservableFactory.create = function (initialState) { | ||
var excisedInitialState = shallowCopy(initialState); | ||
return this.defineListeners(new Proxy(excisedInitialState, this.rootHandler)); | ||
var instance = new ProxiedObservableFactory(); | ||
return instance.defineSubscribers(new Proxy(excisedInitialState, RootHandlerFactory(instance))); | ||
}; | ||
@@ -531,5 +574,19 @@ return ProxiedObservableFactory; | ||
var vivisect = function (initialState) { return new ProxiedObservableFactory() | ||
.create(initialState); }; | ||
/** | ||
* @summary 'Vivisect' an object - render the object evented by | ||
* proxying it through a subscribable interface | ||
* | ||
* @param initialState | ||
* | ||
* @public | ||
*/ | ||
var vivisect = function (initialState) { | ||
if (isObject(initialState) || Array.isArray(initialState)) { | ||
return ProxiedObservableFactory.create(initialState); | ||
} | ||
throw VxException.create(new VxException({ | ||
reason: 'invalid initial state type' | ||
})); | ||
}; | ||
export { vivisect }; |
@@ -31,4 +31,4 @@ | ||
/* global Reflect, Promise */ | ||
var _extendStatics = function extendStatics(d, b) { | ||
_extendStatics = Object.setPrototypeOf || { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || { | ||
__proto__: [] | ||
@@ -38,8 +38,6 @@ } instanceof Array && function (d, b) { | ||
} || function (d, b) { | ||
for (var p in b) { | ||
if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
} | ||
for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; | ||
}; | ||
return _extendStatics(d, b); | ||
return extendStatics(d, b); | ||
}; | ||
@@ -49,5 +47,4 @@ | ||
if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
_extendStatics(d, b); | ||
function __() { | ||
@@ -69,6 +66,8 @@ this.constructor = d; | ||
var unboundedSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundedSlice); | ||
var unboundSlice = Array.prototype.slice; | ||
var slice = Function.prototype.call.bind(unboundSlice); | ||
/** | ||
* @summary Shallow copy an object or array | ||
* | ||
* @internal | ||
*/ | ||
@@ -83,12 +82,12 @@ function shallowCopy(base) { | ||
var key = keys[i]; | ||
var desc = descriptors[key]; | ||
if (!desc.writable) { | ||
desc.writable = true; | ||
desc.configurable = true; | ||
var descriptor = descriptors[key]; | ||
if (!descriptor.writable) { | ||
descriptor.writable = true; | ||
descriptor.configurable = true; | ||
} | ||
if (desc.get || desc.set) { | ||
if (descriptor.get || descriptor.set) { | ||
descriptors[key] = { | ||
configurable: true, | ||
writable: !!desc.set, | ||
enumerable: desc.enumerable, | ||
writable: !!descriptor.set, | ||
enumerable: descriptor.enumerable, | ||
value: base[key] | ||
@@ -102,5 +101,6 @@ }; | ||
* @summary Define a non-configurable function property `value` with name `name` on a given object `context` | ||
* @param {VxState} context The object on which the property will be defined | ||
* @param {string} name The name of the property | ||
* @param {Function} value The value of the function property | ||
* @param name The name of the property | ||
* @param value The value of the function property | ||
* | ||
* @internal | ||
*/ | ||
@@ -118,10 +118,10 @@ function defineNonConfigurableProp(context, name, value) { | ||
* and whether a given property exists on that array's prototype | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function isArrayProto(target, prop) { | ||
return Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype) | ||
.includes(prop); | ||
return (Array.isArray(target) && | ||
Object.getOwnPropertyNames(Array.prototype).includes(prop)); | ||
} | ||
@@ -131,5 +131,6 @@ /** | ||
* and whether it is out of bounds relative to a given target | ||
* @param {VxState} target | ||
* @param {string|symbol} prop | ||
* @returns {boolean} | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
@@ -140,3 +141,3 @@ function isArrayPropOutOfBounds(target, prop) { | ||
return false; | ||
return Array.isArray(target) && (maybeIdx > (target.length - 1)); | ||
return Array.isArray(target) && maybeIdx > target.length - 1; | ||
} | ||
@@ -146,2 +147,4 @@ | ||
* @summary Base implementation model for extended errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -162,2 +165,4 @@ var BaseVxError = /** @class */ (function (_super) { | ||
* Base implementation for errors | ||
* | ||
* @internal | ||
*/ | ||
@@ -175,2 +180,4 @@ var VxError = /** @class */ (function (_super) { | ||
* @summary Exception metadata builder | ||
* | ||
* @internal | ||
*/ | ||
@@ -185,4 +192,3 @@ var VxException = /** @class */ (function () { | ||
* @summary Build an error object with the given exception metadata instance | ||
* @param {VxException} instance | ||
* @returns {VxError} | ||
* @param instance | ||
*/ | ||
@@ -194,3 +200,2 @@ VxException.create = function (instance) { | ||
* @summary Serialize the source metadata into a string | ||
* @returns {string} | ||
*/ | ||
@@ -201,10 +206,9 @@ VxException.prototype.serializeSource = function () { | ||
var _a = this.source, filename = _a.filename, lineno = _a.lineno; | ||
return "at " + filename + ", Ln " + lineno; | ||
return " at " + filename + ", Ln " + lineno; | ||
}; | ||
/** | ||
* @summary Serialize the exception metadata into a string | ||
* @returns {string} | ||
*/ | ||
VxException.prototype.serialize = function () { | ||
return this.reason + " " + this.serializeSource(); | ||
return "" + this.reason + this.serializeSource(); | ||
}; | ||
@@ -215,105 +219,51 @@ return VxException; | ||
/** | ||
* @summary Validate a provided event handler name, value | ||
* @param {string} eventName | ||
* @param {Function} handler | ||
* @see https://github.com/microsoft/TypeScript/issues/26916 | ||
*/ | ||
function validateEventHandler(eventName, handler) { | ||
if (!(eventName in this.handlerStore)) { | ||
/** | ||
* @summary Validate a provided event handler | ||
* @param handler | ||
* | ||
* @internal | ||
*/ | ||
function validateEventHandler(handler) { | ||
if (typeof handler !== 'function') { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
reason: 'The provided event handler must be a function' | ||
})); | ||
} | ||
else if (typeof handler !== 'function') { | ||
} | ||
/** | ||
* @summary Validate a provided event name | ||
* @param eventName | ||
* @param validEvents - a list of possible valid event names | ||
* | ||
* @internal | ||
*/ | ||
function validateEventName(eventName, validEvents) { | ||
if (typeof eventName !== 'string') { | ||
throw VxException.create(new VxException({ | ||
reason: 'The provided event handler must be a function' | ||
reason: "Event name must be a string" | ||
})); | ||
} | ||
if (!validEvents.includes(eventName)) { | ||
throw VxException.create(new VxException({ | ||
reason: "An unknown event name '" + eventName + "' was provided; there are no subscribable events matching this identifier" | ||
})); | ||
} | ||
} | ||
/** | ||
* @summary Evaluate whether `testValue` is a plain object | ||
* @param testValue | ||
* | ||
* @internal | ||
*/ | ||
var isObject = function (testValue) { | ||
return {}.toString.call(testValue) == '[object Object]'; | ||
}; | ||
var VX_EVENT_TYPE; | ||
(function (VX_EVENT_TYPE) { | ||
VX_EVENT_TYPE["ADD"] = "add"; | ||
VX_EVENT_TYPE["DEL"] = "del"; | ||
VX_EVENT_TYPE["SET"] = "set"; | ||
VX_EVENT_TYPE["BATCHED"] = "batched"; | ||
})(VX_EVENT_TYPE || (VX_EVENT_TYPE = {})); | ||
var VX_LISTENER_INTERNALS; | ||
(function (VX_LISTENER_INTERNALS) { | ||
VX_LISTENER_INTERNALS["ADD"] = "addEventListener"; | ||
VX_LISTENER_INTERNALS["REM"] = "removeEventListener"; | ||
})(VX_LISTENER_INTERNALS || (VX_LISTENER_INTERNALS = {})); | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* @summary Construct a done committal function | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
var _a; | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
* @property {VxEventHandlerStore} | ||
*/ | ||
this.handlerStore = (_a = {}, | ||
_a[VX_EVENT_TYPE.ADD] = new Set(), | ||
_a[VX_EVENT_TYPE.DEL] = new Set(), | ||
_a[VX_EVENT_TYPE.SET] = new Set(), | ||
_a[VX_EVENT_TYPE.BATCHED] = new Set(), | ||
_a); | ||
this.internals = __spreadArray([], Object.values(VX_LISTENER_INTERNALS)); | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param {string|symbol} prop The property presently being accessed | ||
* @returns {boolean} | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `addEventListener`, `removeEventListener` on the proxied object | ||
* @param context The context (i.e. `this` instance) of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineListeners = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.ADD, function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventHandler.call(_this, eventName, handler); | ||
_this.handlerStore[eventName] | ||
.add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, VX_LISTENER_INTERNALS.REM, function (eventName, handler) { | ||
validateEventHandler.call(_this, eventName, handler); | ||
var handlers = _this.handlerStore[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param {object} event An object containing data about the event | ||
* @param {object} context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, context, done) { | ||
this.handlerStore[event.type] | ||
.forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler.call(context, event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
var DoneFunctionBuilder = function (ret) { | ||
@@ -327,4 +277,10 @@ var done = function (commit) { | ||
var eventedArrayPrototypeResolver = function (target, prop) { | ||
var _this = this; | ||
/** | ||
* @summary Define evented analogs on a proxied array prototype | ||
* @param target | ||
* @param prop | ||
* | ||
* @internal | ||
*/ | ||
function eventedArrayPrototypeResolver(context, target, prop) { | ||
var ogMethod = target[prop]; | ||
@@ -340,8 +296,10 @@ return function () { | ||
var ret = nextState.shift(); | ||
var done = DoneFunctionBuilder(function () { return Array.prototype.shift.call(target); }); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
var done = DoneFunctionBuilder(function () { | ||
return Array.prototype.shift.call(target); | ||
}); | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -354,7 +312,7 @@ } | ||
}); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
context.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return ret; | ||
@@ -367,10 +325,10 @@ } | ||
var _a; | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args)); | ||
return (_a = Array.prototype.unshift).call.apply(_a, __spreadArray([target], args, false)); | ||
}); | ||
ret = nextState.unshift.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
} | ||
@@ -382,7 +340,7 @@ return ret; | ||
nextState.reverse(); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.BATCHED, | ||
context.raiseEvent({ | ||
type: 'batched', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState; | ||
@@ -393,7 +351,7 @@ } | ||
nextState.push.apply(nextState, args); | ||
_this.raiseEvent({ | ||
type: args.length > 1 ? VX_EVENT_TYPE.BATCHED : VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: args.length > 1 ? 'batched' : 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
return nextState.length; | ||
@@ -405,7 +363,7 @@ } | ||
ogMethod.apply(nextState, [arg]); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
context.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, done); | ||
}, done); | ||
prevState = shallowCopy(target); | ||
@@ -422,18 +380,12 @@ }; | ||
}; | ||
}; | ||
} | ||
var batchedMethods = [ | ||
'shift', | ||
'unshift', | ||
'push', | ||
'reverse', | ||
'sort', | ||
'pop' | ||
]; | ||
var batchedMethods = ['shift', 'unshift', 'push', 'reverse', 'sort', 'pop']; | ||
/** | ||
* @summary Construct a base proxy handler with an implicit context | ||
* @returns {ProxyHandler<VxState>} Base proxy handler | ||
* @returns {ProxyHandler<?>} Base proxy handler | ||
* | ||
* @internal | ||
*/ | ||
function RootHandlerFactory() { | ||
var _this = this; | ||
function RootHandlerFactory(base) { | ||
var rootHandler = { | ||
@@ -449,3 +401,3 @@ get: function (target, prop, recv) { | ||
if (Array.isArray(target) && batchedMethods.includes(prop)) { | ||
return eventedArrayPrototypeResolver.call(_this, target, prop); | ||
return eventedArrayPrototypeResolver(base, target, prop); | ||
} | ||
@@ -462,3 +414,3 @@ // we use reflection to mitigate violation of Proxy invariants, as described in the specification here: | ||
set: function (target, prop, value) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -471,15 +423,16 @@ } | ||
var ret = Reflect.set(nextState, prop, value); | ||
var done = DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); }); | ||
if (!(prop in prevState) || isArrayPropOutOfBounds(prevState, prop)) { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.ADD, | ||
base.raiseEvent({ | ||
type: 'add', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
else { | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.SET, | ||
base.raiseEvent({ | ||
type: 'set', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.set(target, prop, value); })); | ||
}, done); | ||
} | ||
@@ -489,3 +442,3 @@ return ret; | ||
deleteProperty: function (target, prop) { | ||
if (!_this.isConfigurableProp(prop)) { | ||
if (!base.isConfigurableProp(prop)) { | ||
return false; | ||
@@ -495,9 +448,12 @@ } | ||
var ret = true; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (Array.isArray(nextState)) { | ||
nextState.splice(Number(prop), 1); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
var numericProp_1 = Number(prop); | ||
nextState.splice(numericProp_1, 1); | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return target.splice(Number(prop), 1); })); | ||
}, DoneFunctionBuilder(function () { return target.splice(numericProp_1, 1); })); | ||
return ret; | ||
@@ -507,9 +463,10 @@ } | ||
Reflect.deleteProperty(nextState, prop); | ||
_this.raiseEvent({ | ||
type: VX_EVENT_TYPE.DEL, | ||
base.raiseEvent({ | ||
type: 'del', | ||
prevState: prevState, | ||
nextState: nextState | ||
}, _this, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
}, DoneFunctionBuilder(function () { return Reflect.deleteProperty(target, prop); })); | ||
return ret; | ||
} | ||
// istanbul ignore next | ||
return ret; | ||
@@ -521,13 +478,99 @@ } | ||
/** | ||
* Implements base state and shared functionality for a Vivisector observable | ||
* @class BaseObservableFactory | ||
* | ||
* @internal | ||
*/ | ||
var BaseObservableFactory = /** @class */ (function () { | ||
function BaseObservableFactory() { | ||
/** | ||
* Event-correlated handlers; below are defaults | ||
*/ | ||
this.observers = { | ||
add: new Set(), | ||
del: new Set(), | ||
set: new Set(), | ||
batched: new Set() | ||
}; | ||
this.internals = ['subscribe', 'unsubscribe']; | ||
} | ||
/** | ||
* @summary Evaluates whether the given property is marked as non-configurable | ||
* @param prop The property presently being accessed | ||
*/ | ||
BaseObservableFactory.prototype.isConfigurableProp = function (prop) { | ||
// see -> https://github.com/microsoft/TypeScript/issues/26255 | ||
// btw, this is where TypeScript's bizarre `Array.prototype.includes` approach could screw us over | ||
// if I followed TS' way, I may only pass a string to `includes`, which means if we receive a symbol, we'll return false | ||
// which would propagate to the proxy and lead to a invariant violation | ||
return !this.internals.includes(prop); | ||
}; | ||
/** | ||
* @summary Programmatically define `subscribe`, `unsubscribe` on the proxied object | ||
* @param context The context of the target object on which the | ||
* aforementioned listeners will be defined | ||
*/ | ||
BaseObservableFactory.prototype.defineSubscribers = function (context) { | ||
var _this = this; | ||
defineNonConfigurableProp(context, 'subscribe', function (eventName, handler, _a) { | ||
var _b = _a === void 0 ? {} : _a, _c = _b.alwaysCommit, alwaysCommit = _c === void 0 ? false : _c; | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
validateEventHandler(handler); | ||
_this.observers[eventName].add({ handler: handler, alwaysCommit: alwaysCommit }); | ||
return context; | ||
}); | ||
defineNonConfigurableProp(context, 'unsubscribe', function (eventName, handler) { | ||
// both validators are tested via the public API | ||
// istanbul ignore next | ||
validateEventName(eventName, Object.keys(_this.observers)); | ||
// istanbul ignore next | ||
validateEventHandler(handler); | ||
var handlers = _this.observers[eventName]; | ||
handlers.forEach(function (ref) { | ||
if (handler === ref.handler) { | ||
handlers["delete"](ref); | ||
} | ||
}); | ||
return context; | ||
}); | ||
return context; | ||
}; | ||
/** | ||
* @summary Serially invokes each handler of the given event type | ||
* @param event An object containing data about the event | ||
* @param context The `this` value on which to call each instance | ||
*/ | ||
BaseObservableFactory.prototype.raiseEvent = function (event, done) { | ||
this.observers[event.type].forEach(function (_a) { | ||
var handler = _a.handler, alwaysCommit = _a.alwaysCommit; | ||
var finalDoneFunction = done; | ||
// tested via public API | ||
// istanbul ignore next | ||
if (alwaysCommit) { | ||
done(true); | ||
finalDoneFunction = function () { }; | ||
} | ||
handler(event, finalDoneFunction); | ||
}); | ||
}; | ||
return BaseObservableFactory; | ||
}()); | ||
/** | ||
* @summary Create proxies | ||
* | ||
* @internal | ||
*/ | ||
var ProxiedObservableFactory = /** @class */ (function (_super) { | ||
__extends(ProxiedObservableFactory, _super); | ||
function ProxiedObservableFactory() { | ||
var _this = _super.call(this) || this; | ||
_this.rootHandler = RootHandlerFactory | ||
.call(_this); | ||
return _this; | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
ProxiedObservableFactory.prototype.create = function (initialState) { | ||
/** | ||
* @summary Root Proxy handler; injects event broadcasts into get|set|delete traps | ||
*/ | ||
ProxiedObservableFactory.create = function (initialState) { | ||
var excisedInitialState = shallowCopy(initialState); | ||
return this.defineListeners(new Proxy(excisedInitialState, this.rootHandler)); | ||
var instance = new ProxiedObservableFactory(); | ||
return instance.defineSubscribers(new Proxy(excisedInitialState, RootHandlerFactory(instance))); | ||
}; | ||
@@ -537,4 +580,18 @@ return ProxiedObservableFactory; | ||
var vivisect = function (initialState) { return new ProxiedObservableFactory() | ||
.create(initialState); }; | ||
/** | ||
* @summary 'Vivisect' an object - render the object evented by | ||
* proxying it through a subscribable interface | ||
* | ||
* @param initialState | ||
* | ||
* @public | ||
*/ | ||
var vivisect = function (initialState) { | ||
if (isObject(initialState) || Array.isArray(initialState)) { | ||
return ProxiedObservableFactory.create(initialState); | ||
} | ||
throw VxException.create(new VxException({ | ||
reason: 'invalid initial state type' | ||
})); | ||
}; | ||
@@ -541,0 +598,0 @@ exports.vivisect = vivisect; |
@@ -21,2 +21,2 @@ /** | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},t(e,r)};function r(e,r){if("function"!=typeof r&&null!==r)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");function n(){this.constructor=e}t(e,r),e.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}function n(e,t,r){if(r||2===arguments.length)for(var n,i=0,o=t.length;i<o;i++)!n&&i in t||(n||(n=Array.prototype.slice.call(t,0,i)),n[i]=t[i]);return e.concat(n||Array.prototype.slice.call(t))}var i=Array.prototype.slice,o=Function.prototype.call.bind(i);function a(e){if(Array.isArray(e))return o(e);for(var t=Object.getOwnPropertyDescriptors(e),r=Reflect.ownKeys(t),n=0;n<r.length;n++){var i=r[n],a=t[i];a.writable||(a.writable=!0,a.configurable=!0),(a.get||a.set)&&(t[i]={configurable:!0,writable:!!a.set,enumerable:a.enumerable,value:e[i]})}return Object.create(Object.getPrototypeOf(e),t)}function u(e,t,r){Object.defineProperty(e,t,{configurable:!1,enumerable:!1,writable:!1,value:r})}var c,s,f=function(e){function t(r){var n=e.call(this,r)||this;return Object.setPrototypeOf(n,t.prototype),n}return r(t,e),t}(function(e){function t(r){var n=e.call(this,r)||this;return Object.setPrototypeOf(n,t.prototype),n}return r(t,e),t}(Error)),l=function(){function e(e){var t=e.reason,r=e.source;this.reason=t,this.source=r}return e.create=function(e){return new f(e.serialize())},e.prototype.serializeSource=function(){if(!this.source)return"";var e=this.source;return"at "+e.filename+", Ln "+e.lineno},e.prototype.serialize=function(){return this.reason+" "+this.serializeSource()},e}();function p(e,t){if(!(e in this.handlerStore))throw l.create(new l({reason:"An unknown event name '"+e+"' was provided; there are no subscribable events matching this identifier"}));if("function"!=typeof t)throw l.create(new l({reason:"The provided event handler must be a function"}))}!function(e){e.ADD="add",e.DEL="del",e.SET="set",e.BATCHED="batched"}(c||(c={})),function(e){e.ADD="addEventListener",e.REM="removeEventListener"}(s||(s={}));var y=function(){function e(){var e;this.handlerStore=((e={})[c.ADD]=new Set,e[c.DEL]=new Set,e[c.SET]=new Set,e[c.BATCHED]=new Set,e),this.internals=n([],Object.values(s))}return e.prototype.isConfigurableProp=function(e){return!this.internals.includes(e)},e.prototype.defineListeners=function(e){var t=this;return u(e,s.ADD,(function(r,n,i){var o=(void 0===i?{}:i).alwaysCommit,a=void 0!==o&&o;return p.call(t,r,n),t.handlerStore[r].add({handler:n,alwaysCommit:a}),e})),u(e,s.REM,(function(r,n){p.call(t,r,n);var i=t.handlerStore[r];return i.forEach((function(e){n===e.handler&&i.delete(e)})),e})),e},e.prototype.raiseEvent=function(e,t,r){this.handlerStore[e.type].forEach((function(n){var i=n.handler,o=n.alwaysCommit,a=r;o&&(r(!0),a=function(){}),i.call(t,e,a)}))},e}(),v=function(e){return function(t){if(t)return e()}},h=function(e,t){var r=this,i=e[t];return function(){for(var o=[],u=0;u<arguments.length;u++)o[u]=arguments[u];var s=a(e),f=a(e);if("shift"==t){var l=s.shift(),p=v((function(){return Array.prototype.shift.call(e)}));return r.raiseEvent({type:c.BATCHED,prevState:f,nextState:s},r,p),l}if("pop"==t){l=s.pop(),p=v((function(){e.length=e.length-1}));return r.raiseEvent({type:c.DEL,prevState:f,nextState:s},r,p),l}if("unshift"==t){l=e.length;if(o.length){p=v((function(){var t;return(t=Array.prototype.unshift).call.apply(t,n([e],o))}));l=s.unshift.apply(s,o),r.raiseEvent({type:c.BATCHED,prevState:f,nextState:s},r,p)}return l}if("reverse"==t){p=v((function(){return e.reverse()}));return s.reverse(),r.raiseEvent({type:c.BATCHED,prevState:f,nextState:s},r,p),s}if("push"==t){p=v((function(){return e.push.apply(e,o)}));return s.push.apply(s,o),r.raiseEvent({type:o.length>1?c.BATCHED:c.ADD,prevState:f,nextState:s},r,p),s.length}if(o.length){for(var y=function(t){var n=v((function(){return i.apply(e,[t])}));i.apply(s,[t]),r.raiseEvent({type:c.ADD,prevState:f,nextState:s},r,n),f=a(e)},h=0,d=o;h<d.length;h++){var b=d[h];y(b)}return s}return i.apply(e,o)}},d=["shift","unshift","push","reverse","sort","pop"];function b(){var e=this,t={get:function(r,n,i){if(Array.isArray(r)&&d.includes(n))return h.call(e,r,n);var o=Reflect.get(r,n,i);return"object"==typeof o?new Proxy(o,t):o},set:function(t,r,n){if(!e.isConfigurableProp(r))return!1;if(function(e,t){return Array.isArray(e)&&Object.getOwnPropertyNames(Array.prototype).includes(t)}(t,r))return Reflect.set(t,r,n);var i=[a(t),a(t)],o=i[0],u=i[1],s=Reflect.set(u,r,n);return!(r in o)||function(e,t){var r=Number(t);return!!Number.isNaN(r)&&Array.isArray(e)&&r>e.length-1}(o,r)?e.raiseEvent({type:c.ADD,prevState:o,nextState:u},e,v((function(){return Reflect.set(t,r,n)}))):e.raiseEvent({type:c.SET,prevState:o,nextState:u},e,v((function(){return Reflect.set(t,r,n)}))),s},deleteProperty:function(t,r){if(!e.isConfigurableProp(r))return!1;var n=[a(t),a(t)],i=n[0],o=n[1],u=!0;return Array.isArray(o)?(o.splice(Number(r),1),e.raiseEvent({type:c.DEL,prevState:i,nextState:o},e,v((function(){return t.splice(Number(r),1)}))),u):r in i?(Reflect.deleteProperty(o,r),e.raiseEvent({type:c.DEL,prevState:i,nextState:o},e,v((function(){return Reflect.deleteProperty(t,r)}))),u):u}};return t}var S=function(e){function t(){var t=e.call(this)||this;return t.rootHandler=b.call(t),t}return r(t,e),t.prototype.create=function(e){var t=a(e);return this.defineListeners(new Proxy(t,this.rootHandler))},t}(y);e.vivisect=function(e){return(new S).create(e)},Object.defineProperty(e,"__esModule",{value:!0})})); | ||
***************************************************************************** */var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},t(e,r)};function r(e,r){if("function"!=typeof r&&null!==r)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");function n(){this.constructor=e}t(e,r),e.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}function n(e,t,r){if(r||2===arguments.length)for(var n,i=0,o=t.length;i<o;i++)!n&&i in t||(n||(n=Array.prototype.slice.call(t,0,i)),n[i]=t[i]);return e.concat(n||Array.prototype.slice.call(t))}var i=Array.prototype.slice,o=Function.prototype.call.bind(i);function a(e){if(Array.isArray(e))return o(e);for(var t=Object.getOwnPropertyDescriptors(e),r=Reflect.ownKeys(t),n=0;n<r.length;n++){var i=r[n],a=t[i];a.writable||(a.writable=!0,a.configurable=!0),(a.get||a.set)&&(t[i]={configurable:!0,writable:!!a.set,enumerable:a.enumerable,value:e[i]})}return Object.create(Object.getPrototypeOf(e),t)}function u(e,t,r){Object.defineProperty(e,t,{configurable:!1,enumerable:!1,writable:!1,value:r})}var s=function(e){function t(r){var n=e.call(this,r)||this;return Object.setPrototypeOf(n,t.prototype),n}return r(t,e),t}(function(e){function t(r){var n=e.call(this,r)||this;return Object.setPrototypeOf(n,t.prototype),n}return r(t,e),t}(Error)),c=function(){function e(e){var t=e.reason,r=e.source;this.reason=t,this.source=r}return e.create=function(e){return new s(e.serialize())},e.prototype.serializeSource=function(){if(!this.source)return"";var e=this.source;return" at "+e.filename+", Ln "+e.lineno},e.prototype.serialize=function(){return""+this.reason+this.serializeSource()},e}();function f(e){if("function"!=typeof e)throw c.create(new c({reason:"The provided event handler must be a function"}))}function p(e,t){if("string"!=typeof e)throw c.create(new c({reason:"Event name must be a string"}));if(!t.includes(e))throw c.create(new c({reason:"An unknown event name '"+e+"' was provided; there are no subscribable events matching this identifier"}))}var l=function(e){return function(t){if(t)return e()}};var y=["shift","unshift","push","reverse","sort","pop"];function v(e){var t={get:function(r,i,o){if(Array.isArray(r)&&y.includes(i))return function(e,t,r){var i=t[r];return function(){for(var o=[],u=0;u<arguments.length;u++)o[u]=arguments[u];var s=a(t),c=a(t);if("shift"==r){var f=s.shift(),p=l((function(){return Array.prototype.shift.call(t)}));return e.raiseEvent({type:"batched",prevState:c,nextState:s},p),f}if("pop"==r)return f=s.pop(),p=l((function(){t.length=t.length-1})),e.raiseEvent({type:"del",prevState:c,nextState:s},p),f;if("unshift"==r)return f=t.length,o.length&&(p=l((function(){var e;return(e=Array.prototype.unshift).call.apply(e,n([t],o,!1))})),f=s.unshift.apply(s,o),e.raiseEvent({type:"batched",prevState:c,nextState:s},p)),f;if("reverse"==r)return p=l((function(){return t.reverse()})),s.reverse(),e.raiseEvent({type:"batched",prevState:c,nextState:s},p),s;if("push"==r)return p=l((function(){return t.push.apply(t,o)})),s.push.apply(s,o),e.raiseEvent({type:o.length>1?"batched":"add",prevState:c,nextState:s},p),s.length;if(o.length){for(var y=function(r){var n=l((function(){return i.apply(t,[r])}));i.apply(s,[r]),e.raiseEvent({type:"add",prevState:c,nextState:s},n),c=a(t)},v=0,h=o;v<h.length;v++){y(h[v])}return s}return i.apply(t,o)}}(e,r,i);var u=Reflect.get(r,i,o);return"object"==typeof u?new Proxy(u,t):u},set:function(t,r,n){if(!e.isConfigurableProp(r))return!1;if(function(e,t){return Array.isArray(e)&&Object.getOwnPropertyNames(Array.prototype).includes(t)}(t,r))return Reflect.set(t,r,n);var i=[a(t),a(t)],o=i[0],u=i[1],s=Reflect.set(u,r,n),c=l((function(){return Reflect.set(t,r,n)}));return!(r in o)||function(e,t){var r=Number(t);return!!Number.isNaN(r)&&Array.isArray(e)&&r>e.length-1}(o,r)?e.raiseEvent({type:"add",prevState:o,nextState:u},c):e.raiseEvent({type:"set",prevState:o,nextState:u},c),s},deleteProperty:function(t,r){if(!e.isConfigurableProp(r))return!1;var n=[a(t),a(t)],i=n[0],o=n[1],u=!0;if(Array.isArray(o)){var s=Number(r);return o.splice(s,1),e.raiseEvent({type:"del",prevState:i,nextState:o},l((function(){return t.splice(s,1)}))),u}return r in i?(Reflect.deleteProperty(o,r),e.raiseEvent({type:"del",prevState:i,nextState:o},l((function(){return Reflect.deleteProperty(t,r)}))),u):u}};return t}var h=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return r(t,e),t.create=function(e){var r=a(e),n=new t;return n.defineSubscribers(new Proxy(r,v(n)))},t}(function(){function e(){this.observers={add:new Set,del:new Set,set:new Set,batched:new Set},this.internals=["subscribe","unsubscribe"]}return e.prototype.isConfigurableProp=function(e){return!this.internals.includes(e)},e.prototype.defineSubscribers=function(e){var t=this;return u(e,"subscribe",(function(r,n,i){var o=(void 0===i?{}:i).alwaysCommit,a=void 0!==o&&o;return p(r,Object.keys(t.observers)),f(n),t.observers[r].add({handler:n,alwaysCommit:a}),e})),u(e,"unsubscribe",(function(r,n){p(r,Object.keys(t.observers)),f(n);var i=t.observers[r];return i.forEach((function(e){n===e.handler&&i.delete(e)})),e})),e},e.prototype.raiseEvent=function(e,t){this.observers[e.type].forEach((function(r){var n=r.handler,i=r.alwaysCommit,o=t;i&&(t(!0),o=function(){}),n(e,o)}))},e}());e.vivisect=function(e){if(t=e,"[object Object]"=={}.toString.call(t)||Array.isArray(e))return h.create(e);var t;throw c.create(new c({reason:"invalid initial state type"}))},Object.defineProperty(e,"__esModule",{value:!0})})); |
{ | ||
"name": "vivisector", | ||
"version": "1.5.1", | ||
"version": "1.6.0", | ||
"description": "subscribe to any object and commit or revert state mutations", | ||
@@ -33,5 +33,4 @@ "keywords": [ | ||
"scripts": { | ||
"coverage": "jest --coverage && pnpm clean", | ||
"lint": "eslint --ext .js,.ts,.json --no-fix .", | ||
"test": "jest --bail --passWithNoTests __tests__", | ||
"test": "jest --bail --coverage __tests__", | ||
"test:watch": "jest --watch", | ||
@@ -41,3 +40,2 @@ "prebuild": "pnpm clean", | ||
"clean": "rimraf coverage dist .build", | ||
"coveralls": "jest --coverage && cat ./coverage/lcov.info | coveralls && pnpm clean", | ||
"prerelease": "npm pack && tar -xvzf *.tgz && rimraf package *.tgz", | ||
@@ -52,3 +50,3 @@ "semantic-release": "semantic-release", | ||
"lint-staged": { | ||
"lib/**/*.ts": [ | ||
"src/**/*.ts": [ | ||
"pnpm lint", | ||
@@ -62,2 +60,3 @@ "pnpm test" | ||
"@babel/preset-env": "7.15.4", | ||
"@babel/preset-typescript": "7.15.0", | ||
"@commitlint/cli": "^13.1.0", | ||
@@ -71,6 +70,5 @@ "@commitlint/config-conventional": "^13.1.0", | ||
"@types/jest": "27.0.1", | ||
"babel-jest": "27.1.0", | ||
"coveralls": "^3.1.1", | ||
"cz-conventional-changelog": "^3.3.0", | ||
"eslint": "7.32.0", | ||
"eslint-config-prettier": "8.3.0", | ||
"eslint-plugin-jest": "24.4.0", | ||
@@ -81,2 +79,3 @@ "husky": "7.0.2", | ||
"lint-staged": "11.1.2", | ||
"prettier": "2.4.1", | ||
"rimraf": "^3.0.2", | ||
@@ -87,5 +86,4 @@ "rollup": "2.56.3", | ||
"semantic-release": "^17.4.7", | ||
"ts-jest": "27.0.5", | ||
"tslib": "2.3.1", | ||
"typescript": "4.3.5" | ||
"typescript": "4.4.4" | ||
}, | ||
@@ -92,0 +90,0 @@ "config": { |
![Vivisector Logo](/docs/vx.png) | ||
# Vivisector | Subscribe to Any Object ✂️ | ||
# Vivisector | Convert any object into an evented, reactive state machine ✂️ | ||
@@ -39,3 +39,3 @@ [![Build Status](https://travis-ci.com/MatthewZito/vivisector.svg?branch=master)](https://travis-ci.com/MatthewZito/vivisector) | ||
}) | ||
.addEventListener('set', ({ prevState, nextState, done }) => { | ||
.subscribe('set', ({ prevState, nextState, done }) => { | ||
if (!isValidEmail(nextState.email)) { | ||
@@ -121,3 +121,3 @@ emitErrorMessage(); | ||
// every time an item is added to `users`, we want to invoke `logAdditions` | ||
users.addEventListener('add', logAdditions, { alwaysCommit: true }); | ||
users.subscribe('add', logAdditions, { alwaysCommit: true }); | ||
@@ -140,12 +140,12 @@ // let's bring someone fictional into the mix | ||
Event handlers are registered by calling `addEventListener`. This method will exist on every `vivisected` object: | ||
Event handlers are registered by calling `subscribe`. This method will exist on every `vivisected` object: | ||
```js | ||
users.addEventListener(eventType, eventHandler, options); | ||
users.subscribe(eventType, eventHandler, options); | ||
``` | ||
And when we're done, we can remove the handler by passing a reference to it into the `removeEventListener` method: | ||
And when we're done, we can remove the handler by passing a reference to it into the `unsubscribe` method: | ||
```js | ||
users.removeEventListener(eventType, eventHandlerRef); | ||
users.unsubscribe(eventType, eventHandlerRef); | ||
``` | ||
@@ -170,4 +170,2 @@ | ||
**Type (TypeScript only)** `VX_LISTENER_INTERNALS.ADD` | ||
**Note:** Operations such as `Array.prototype.push` are considered `batched` events if provided more than a single argument | ||
@@ -188,4 +186,2 @@ | ||
**Type (TypeScript only)** `VX_LISTENER_INTERNALS.SET` | ||
#### del | ||
@@ -204,4 +200,2 @@ | ||
**Type (TypeScript only)** `VX_LISTENER_INTERNALS.DEL` | ||
#### batched | ||
@@ -220,4 +214,2 @@ | ||
**Type (TypeScript only)** `VX_LISTENER_INTERNALS.BATCHED` | ||
### <a name="methods"></a> Methods | ||
@@ -227,3 +219,3 @@ | ||
#### addEventListener (eventName: VX_EVENT_TYPE, handler: VxEventHandler, { alwaysCommit = false }: { alwaysCommit?: boolean }): VxEventedObject | ||
#### subscribe (eventName: ISubscriptionEvent, handler: ISubscriptionCallback, opts?: ISubscriptionOpts) => IVivisectorApi | ||
@@ -248,3 +240,3 @@ Bind the callback `handler` to fire whenever an event of `eventName` has been triggered. | ||
const languages = vivisect(['C', 'Go']).addEventListener('add', logMsg); | ||
const languages = vivisect(['C', 'Go']).subscribe('add', logMsg); | ||
@@ -255,3 +247,3 @@ languages.push('JavaScript'); | ||
#### removeEventListener (eventName: VX_EVENT_TYPE, handler: VxEventHandler): void | ||
#### unsubscribe (eventName: ISubscriptionEvent, handler: ISubscriptionCallback, opts?: ISubscriptionOpts) => IVivisectorApi | ||
@@ -272,4 +264,4 @@ Remove an existing callback from the respective event-type to which it has been registered. | ||
const queens = vivisect(['RuPaul', 'Alaska']) | ||
.addEventListener('add', logMsg, { alwaysCommit: true }) | ||
.removeEventListener('add', logMsg); | ||
.subscribe('add', logMsg, { alwaysCommit: true }) | ||
.unsubscribe('add', logMsg); | ||
@@ -276,0 +268,0 @@ queens.push('Bianca Del Rio'); |
82152
1707
281