UNPKG

8.12 kBJavaScriptView Raw
1// Global object/function references
2var _Object = Object;
3var _Object_hasOwnProperty = _Object.hasOwnProperty;
4var _Array_isArray = Array.isArray;
5var _Error = Error;
6var _TypeError = TypeError;
7
8/** Reference to global Object.defineProperty */
9var defineProperty = _Object.defineProperty;
10
11/**
12 * Checks if an object has a specified property as its own property (ignores prototype properties and `__proto__`)
13 * @param object Object to check
14 * @param key Property key
15 * @returns Does `object` have the property `key`?
16 */
17function hasOwnProperty(object, key) {
18 return key !== "__proto__" && _Object_hasOwnProperty.call(object, key);
19}
20
21/**
22 * Creates an ECMAScript 6 Symbol, falling back to a simple string in environments that do not support Symbols
23 * @param description Symbol description
24 * @returns Symbol object or string
25 * @function
26 */
27/* c8 ignore start */
28/* istanbul ignore next */
29var createSymbol =
30 typeof Symbol === "function"
31 ? Symbol
32 : function (description) {
33 return "__" + description;
34 };
35/* c8 ignore stop */
36
37/** Hint property to indicate if an object has been observed */
38var HINT_OBSERVE = createSymbol("observe");
39/** Hint property to indicate if a function has been disposed */
40var HINT_DISPOSE = createSymbol("dispose");
41/** Hint property that contains a function's dependency disposal callbacks */
42var HINT_DEPENDS = createSymbol("depends");
43
44/**
45 * Defines a hint property on an object
46 * @param object Object to define property on
47 * @param hint Property key
48 * @param {*} [value] Property value, property will be made non-configurable if this is unset (`undefined`)
49 */
50function defineHint(object, hint, value) {
51 defineProperty(object, hint, {
52 value: value,
53 configurable: value !== void 0,
54 enumerable: false,
55 writable: false
56 });
57}
58
59/**
60 * Checks if a value is a normal object, ignores functions and arrays
61 * @param value Value to check
62 * @returns Is `value` a normal object?
63 */
64function isObject(value) {
65 return value !== null && typeof value === "object" && !_Array_isArray(value);
66}
67
68/**
69 * Checks if a value is a function
70 * @param value Value to check
71 * @return Is `value` a function?
72 */
73function isFunction(value) {
74 return typeof value === "function";
75}
76
77/** Error message printed when an argument is of an incorrect type (not a normal object) */
78var MESSAGE_NOT_OBJECT = "Argument 'object' is not an object";
79/** Error message printed when an argument is of an incorrect type (not a function) */
80var MESSAGE_NOT_FUNCTION = "Argument 'func' is not a function";
81
82/**
83 * Throws an error message
84 * @param message Message to construct the error with
85 * @param generic Should the more generic `Error` be thrown instead of `TypeError`?
86 */
87function throwError(message, generic) {
88 throw new (generic ? _Error : _TypeError)(message);
89}
90
91/** Maximum queue length */
92var MAX_QUEUE = 2000;
93
94/** Is the queue being executed? */
95var computedLock = false;
96/** Queue of computed functions to be called */
97var computedQueue = [];
98/** Current index into `computedQueue` */
99var computedI = 0;
100
101/**
102 * Throws an error indicating that the computed queue has overflowed
103 */
104function computedOverflow() {
105 var message = "Computed queue overflow! Last 10 functions in the queue:";
106
107 var length = computedQueue.length;
108 for (var i = length - 11; i < length; i++) {
109 var func = computedQueue[i];
110 message +=
111 "\n"
112 + (i + 1)
113 + ": "
114 + (func.name || "anonymous");
115 }
116
117 throwError(message, true);
118}
119
120/**
121 * Attempts to add a function to the computed queue, then attempts to lock and execute the computed queue
122 * @param func Function to queue
123 */
124function computedNotify(func) {
125 if (hasOwnProperty(func, HINT_DISPOSE)) return;
126
127 // Only add to the queue if not already pending execution
128 if (computedQueue.lastIndexOf(func) >= computedI) return;
129 computedQueue.push(func);
130
131 // Make sure that the function in question has a depends hint
132 if (!hasOwnProperty(func, HINT_DEPENDS)) {
133 defineHint(func, HINT_DEPENDS, []);
134 }
135
136 // Attempt to lock and execute the queue
137 if (!computedLock) {
138 computedLock = true;
139
140 try {
141 for (; computedI < computedQueue.length; computedI++) {
142 // Indirectly call the function to avoid leaking `computedQueue` as `this`
143 (0, computedQueue[computedI])();
144 if (computedI > MAX_QUEUE) /* @__NOINLINE */ computedOverflow();
145 }
146 } finally {
147 computedLock = false;
148 computedQueue = [];
149 computedI = 0;
150 }
151 }
152}
153
154/** See lib/patella.d.ts */
155function computed(func) {
156 if (!isFunction(func)) {
157 throwError(MESSAGE_NOT_FUNCTION);
158 }
159
160 computedNotify(func);
161 return func;
162}
163
164/**
165 * Generates a property descriptor for a reactive property
166 * @param value Initial property value
167 * @returns Property descriptor object
168 */
169function reactiveProperty(value) {
170 if (isObject(value)) reactiveObserve(value);
171
172 // List of computed functions that depend on this property
173 var depends = [];
174 /**
175 * Remove a computed function from this reactive property
176 * @param func Computed function to remove
177 */
178 function dependsRemove(func) {
179 var i = depends.lastIndexOf(func);
180 if (i >= 0) depends.splice(i, 1);
181 }
182
183 return {
184 get: function() {
185 // Add the current executing computed function to this reactive property's dependencies
186 var func = computedQueue[computedI];
187 if (func) {
188 var i = depends.lastIndexOf(func);
189 if (i < 0) {
190 // Add them to our dependencies
191 depends.push(func);
192 // Add us to their dependants
193 func[HINT_DEPENDS].push(dependsRemove);
194 }
195 }
196
197 return value;
198 },
199 set: function(newValue) {
200 if (isObject(newValue)) reactiveObserve(newValue);
201 value = newValue;
202
203 // Notify all dependencies
204 for (var i = 0; i < depends.length; i++) {
205 computedNotify(depends[i]);
206 }
207 }
208 };
209}
210
211/**
212 * Observes an object by making all of its enumerable properties reactive
213 * @param object Object to observe
214 */
215function reactiveObserve(object) {
216 if (hasOwnProperty(object, HINT_OBSERVE)) return;
217 defineHint(object, HINT_OBSERVE);
218
219 for (var key in object) {
220 if (hasOwnProperty(object, key)) {
221 try {
222 defineProperty(object, key, reactiveProperty(object[key]));
223 } catch (err) {}
224 }
225 }
226}
227
228/** See lib/patella.d.ts */
229function observe(object) {
230 if (!isObject(object) && !isFunction(object)) {
231 throwError(MESSAGE_NOT_OBJECT);
232 }
233
234 reactiveObserve(object);
235 return object;
236}
237
238/** See lib/patella.d.ts */
239function ignore(object) {
240 if (!isObject(object) && !isFunction(object)) {
241 throwError(MESSAGE_NOT_OBJECT);
242 }
243
244 if (!hasOwnProperty(object, HINT_OBSERVE)) {
245 defineHint(object, HINT_OBSERVE);
246 }
247
248 return object;
249}
250
251/** See lib/patella.d.ts */
252function dispose(func, clean) {
253 if (func == null) {
254 func = computedQueue[computedI];
255 if (!func) {
256 throwError("Tried to dispose of current computed function while not running a computed function", true);
257 }
258 } else if (!isFunction(func)) {
259 throwError(MESSAGE_NOT_FUNCTION);
260 }
261
262 // Only execute if the function has not been disposed yet
263 if (!hasOwnProperty(func, HINT_DISPOSE)) {
264 // Only define disposed property if we aren't cleaning
265 if (!clean) defineHint(func, HINT_DISPOSE);
266
267 // Remove from dependant reactive objects
268 var depends = func[HINT_DEPENDS];
269 if (depends) {
270 defineHint(func, HINT_DEPENDS, clean ? [] : void 0);
271 for (var i = 0; i < depends.length; i++) {
272 depends[i](func);
273 }
274 }
275
276 // Remove from the queue if locked and pending execution
277 if (computedLock) { // Not required, but saves a `lastIndexOf` call on an empty array for like 6 bytes
278 var i = computedQueue.lastIndexOf(func);
279 if (i > computedI) computedQueue.splice(i, 1);
280 }
281 }
282
283 // Only return the function if it was specified as an argument
284 if (!computedLock) return func;
285}
286
287exports.computed = computed;
288exports.dispose = dispose;
289exports.ignore = ignore;
290exports.observe = observe;
291//# sourceMappingURL=patella.cjs.js.map