UNPKG

patella

Version:

Patella is a library for reactive programming in JavaScript, inspired by Hyperactiv and Vue.js.

292 lines (252 loc) 8.12 kB
// 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