patella
Version:
Patella is a library for reactive programming in JavaScript, inspired by Hyperactiv and Vue.js.
292 lines (252 loc) • 8.12 kB
JavaScript
// Global object/function references
var _Object = Object;
var _Object_hasOwnProperty = _Object.hasOwnProperty;
var _Array_isArray = Array.isArray;
var _Error = Error;
var _TypeError = TypeError;
/** Reference to global Object.defineProperty */
var defineProperty = _Object.defineProperty;
/**
* Checks if an object has a specified property as its own property (ignores prototype properties and `__proto__`)
* @param object Object to check
* @param key Property key
* @returns Does `object` have the property `key`?
*/
function hasOwnProperty(object, key) {
return key !== "__proto__" && _Object_hasOwnProperty.call(object, key);
}
/**
* Creates an ECMAScript 6 Symbol, falling back to a simple string in environments that do not support Symbols
* @param description Symbol description
* @returns Symbol object or string
* @function
*/
/* c8 ignore start */
/* istanbul ignore next */
var createSymbol =
typeof Symbol === "function"
? Symbol
: function (description) {
return "__" + description;
};
/* c8 ignore stop */
/** Hint property to indicate if an object has been observed */
var HINT_OBSERVE = createSymbol("observe");
/** Hint property to indicate if a function has been disposed */
var HINT_DISPOSE = createSymbol("dispose");
/** Hint property that contains a function's dependency disposal callbacks */
var HINT_DEPENDS = createSymbol("depends");
/**
* Defines a hint property on an object
* @param object Object to define property on
* @param hint Property key
* @param {*} [value] Property value, property will be made non-configurable if this is unset (`undefined`)
*/
function defineHint(object, hint, value) {
defineProperty(object, hint, {
value: value,
configurable: value !== void 0,
enumerable: false,
writable: false
});
}
/**
* Checks if a value is a normal object, ignores functions and arrays
* @param value Value to check
* @returns Is `value` a normal object?
*/
function isObject(value) {
return value !== null && typeof value === "object" && !_Array_isArray(value);
}
/**
* Checks if a value is a function
* @param value Value to check
* @return Is `value` a function?
*/
function isFunction(value) {
return typeof value === "function";
}
/** Error message printed when an argument is of an incorrect type (not a normal object) */
var MESSAGE_NOT_OBJECT = "Argument 'object' is not an object";
/** Error message printed when an argument is of an incorrect type (not a function) */
var MESSAGE_NOT_FUNCTION = "Argument 'func' is not a function";
/**
* Throws an error message
* @param message Message to construct the error with
* @param generic Should the more generic `Error` be thrown instead of `TypeError`?
*/
function throwError(message, generic) {
throw new (generic ? _Error : _TypeError)(message);
}
/** Maximum queue length */
var MAX_QUEUE = 2000;
/** Is the queue being executed? */
var computedLock = false;
/** Queue of computed functions to be called */
var computedQueue = [];
/** Current index into `computedQueue` */
var computedI = 0;
/**
* Throws an error indicating that the computed queue has overflowed
*/
function computedOverflow() {
var message = "Computed queue overflow! Last 10 functions in the queue:";
var length = computedQueue.length;
for (var i = length - 11; i < length; i++) {
var func = computedQueue[i];
message +=
"\n"
+ (i + 1)
+ ": "
+ (func.name || "anonymous");
}
throwError(message, true);
}
/**
* Attempts to add a function to the computed queue, then attempts to lock and execute the computed queue
* @param func Function to queue
*/
function computedNotify(func) {
if (hasOwnProperty(func, HINT_DISPOSE)) return;
// Only add to the queue if not already pending execution
if (computedQueue.lastIndexOf(func) >= computedI) return;
computedQueue.push(func);
// Make sure that the function in question has a depends hint
if (!hasOwnProperty(func, HINT_DEPENDS)) {
defineHint(func, HINT_DEPENDS, []);
}
// Attempt to lock and execute the queue
if (!computedLock) {
computedLock = true;
try {
for (; computedI < computedQueue.length; computedI++) {
// Indirectly call the function to avoid leaking `computedQueue` as `this`
(0, computedQueue[computedI])();
if (computedI > MAX_QUEUE) /* @__NOINLINE */ computedOverflow();
}
} finally {
computedLock = false;
computedQueue = [];
computedI = 0;
}
}
}
/** See lib/patella.d.ts */
function computed(func) {
if (!isFunction(func)) {
throwError(MESSAGE_NOT_FUNCTION);
}
computedNotify(func);
return func;
}
/**
* Generates a property descriptor for a reactive property
* @param value Initial property value
* @returns Property descriptor object
*/
function reactiveProperty(value) {
if (isObject(value)) reactiveObserve(value);
// List of computed functions that depend on this property
var depends = [];
/**
* Remove a computed function from this reactive property
* @param func Computed function to remove
*/
function dependsRemove(func) {
var i = depends.lastIndexOf(func);
if (i >= 0) depends.splice(i, 1);
}
return {
get: function() {
// Add the current executing computed function to this reactive property's dependencies
var func = computedQueue[computedI];
if (func) {
var i = depends.lastIndexOf(func);
if (i < 0) {
// Add them to our dependencies
depends.push(func);
// Add us to their dependants
func[HINT_DEPENDS].push(dependsRemove);
}
}
return value;
},
set: function(newValue) {
if (isObject(newValue)) reactiveObserve(newValue);
value = newValue;
// Notify all dependencies
for (var i = 0; i < depends.length; i++) {
computedNotify(depends[i]);
}
}
};
}
/**
* Observes an object by making all of its enumerable properties reactive
* @param object Object to observe
*/
function reactiveObserve(object) {
if (hasOwnProperty(object, HINT_OBSERVE)) return;
defineHint(object, HINT_OBSERVE);
for (var key in object) {
if (hasOwnProperty(object, key)) {
try {
defineProperty(object, key, reactiveProperty(object[key]));
} catch (err) {}
}
}
}
/** See lib/patella.d.ts */
function observe(object) {
if (!isObject(object) && !isFunction(object)) {
throwError(MESSAGE_NOT_OBJECT);
}
reactiveObserve(object);
return object;
}
/** See lib/patella.d.ts */
function ignore(object) {
if (!isObject(object) && !isFunction(object)) {
throwError(MESSAGE_NOT_OBJECT);
}
if (!hasOwnProperty(object, HINT_OBSERVE)) {
defineHint(object, HINT_OBSERVE);
}
return object;
}
/** See lib/patella.d.ts */
function dispose(func, clean) {
if (func == null) {
func = computedQueue[computedI];
if (!func) {
throwError("Tried to dispose of current computed function while not running a computed function", true);
}
} else if (!isFunction(func)) {
throwError(MESSAGE_NOT_FUNCTION);
}
// Only execute if the function has not been disposed yet
if (!hasOwnProperty(func, HINT_DISPOSE)) {
// Only define disposed property if we aren't cleaning
if (!clean) defineHint(func, HINT_DISPOSE);
// Remove from dependant reactive objects
var depends = func[HINT_DEPENDS];
if (depends) {
defineHint(func, HINT_DEPENDS, clean ? [] : void 0);
for (var i = 0; i < depends.length; i++) {
depends[i](func);
}
}
// Remove from the queue if locked and pending execution
if (computedLock) { // Not required, but saves a `lastIndexOf` call on an empty array for like 6 bytes
var i = computedQueue.lastIndexOf(func);
if (i > computedI) computedQueue.splice(i, 1);
}
}
// Only return the function if it was specified as an argument
if (!computedLock) return func;
}
exports.computed = computed;
exports.dispose = dispose;
exports.ignore = ignore;
exports.observe = observe;
//# sourceMappingURL=patella.cjs.js.map