ember-source
Version:
A JavaScript framework for creating ambitious web applications
119 lines (114 loc) • 6.09 kB
JavaScript
import { scheduleDestroyed, scheduleDestroy } from '../global-context/index.js';
import { debugToString as debugToString$1 } from '../util/index.js';
import { isDevelopingApp } from '@embroider/macros';
var DestroyingState = function (DestroyingState) {
return DestroyingState[DestroyingState.Live = 0] = "Live", DestroyingState[DestroyingState.Destroying = 1] = "Destroying", DestroyingState[DestroyingState.Destroyed = 2] = "Destroyed", DestroyingState;
}(DestroyingState || {});
let enableDestroyableTracking,
assertDestroyablesDestroyed,
DESTROYABLE_META = new WeakMap();
function push(collection, newItem) {
return null === collection ? newItem : Array.isArray(collection) ? (collection.push(newItem), collection) : [collection, newItem];
}
function iterate(collection, fn) {
Array.isArray(collection) ? collection.forEach(fn) : null !== collection && fn(collection);
}
function remove(collection, item, message) {
if (isDevelopingApp()) {
let collectionIsItem = collection === item,
collectionContainsItem = Array.isArray(collection) && -1 !== collection.indexOf(item);
if (!collectionIsItem && !collectionContainsItem) throw new Error(String(message));
}
if (Array.isArray(collection) && collection.length > 1) {
let index = collection.indexOf(item);
return collection.splice(index, 1), collection;
}
return null;
}
function getDestroyableMeta(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return void 0 === meta && (meta = {
parents: null,
children: null,
eagerDestructors: null,
destructors: null,
state: DestroyingState.Live
}, isDevelopingApp() && (meta.source = destroyable), DESTROYABLE_META.set(destroyable, meta)), meta;
}
function associateDestroyableChild(parent, child) {
if (isDevelopingApp() && isDestroying(parent)) throw new Error("Attempted to associate a destroyable child with an object that is already destroying or destroyed");
let parentMeta = getDestroyableMeta(parent),
childMeta = getDestroyableMeta(child);
return parentMeta.children = push(parentMeta.children, child), childMeta.parents = push(childMeta.parents, parent), child;
}
function registerDestructor(destroyable, destructor, eager = !1) {
if (isDevelopingApp() && isDestroying(destroyable)) throw new Error("Attempted to register a destructor with an object that is already destroying or destroyed");
let meta = getDestroyableMeta(destroyable),
destructorsKey = !0 === eager ? "eagerDestructors" : "destructors";
return meta[destructorsKey] = push(meta[destructorsKey], destructor), destructor;
}
function unregisterDestructor(destroyable, destructor, eager = !1) {
if (isDevelopingApp() && isDestroying(destroyable)) throw new Error("Attempted to unregister a destructor with an object that is already destroying or destroyed");
let meta = getDestroyableMeta(destroyable),
destructorsKey = !0 === eager ? "eagerDestructors" : "destructors";
meta[destructorsKey] = remove(meta[destructorsKey], destructor, isDevelopingApp() && "attempted to remove a destructor that was not registered with the destroyable");
}
////////////
function destroy(destroyable) {
let meta = getDestroyableMeta(destroyable);
if (meta.state >= DestroyingState.Destroying) return;
let {
parents: parents,
children: children,
eagerDestructors: eagerDestructors,
destructors: destructors
} = meta;
meta.state = DestroyingState.Destroying, iterate(children, destroy), iterate(eagerDestructors, destructor => destructor(destroyable)), iterate(destructors, destructor => scheduleDestroy(destroyable, destructor)), scheduleDestroyed(() => {
iterate(parents, parent => function (child, parent) {
let parentMeta = getDestroyableMeta(parent);
parentMeta.state === DestroyingState.Live && (parentMeta.children = remove(parentMeta.children, child, isDevelopingApp() && "attempted to remove child from parent, but the parent's children did not contain the child. This is likely a bug with destructors."));
}(destroyable, parent)), meta.state = DestroyingState.Destroyed;
});
}
function destroyChildren(destroyable) {
let {
children: children
} = getDestroyableMeta(destroyable);
iterate(children, destroy);
}
function _hasDestroyableChildren(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return void 0 !== meta && null !== meta.children;
}
function isDestroying(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return void 0 !== meta && meta.state >= DestroyingState.Destroying;
}
function isDestroyed(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return void 0 !== meta && meta.state >= DestroyingState.Destroyed;
}
////////////
if (isDevelopingApp()) {
let isTesting = !1;
enableDestroyableTracking = () => {
if (isTesting)
// Reset destroyable meta just in case, before throwing the error
throw DESTROYABLE_META = new WeakMap(), new Error("Attempted to start destroyable testing, but you did not end the previous destroyable test. Did you forget to call `assertDestroyablesDestroyed()`");
isTesting = !0, DESTROYABLE_META = new Map();
}, assertDestroyablesDestroyed = () => {
if (!isTesting) throw new Error("Attempted to assert destroyables destroyed, but you did not start a destroyable test. Did you forget to call `enableDestroyableTracking()`");
isTesting = !1;
let map = DESTROYABLE_META;
DESTROYABLE_META = new WeakMap();
let undestroyed = [];
if (map.forEach(meta => {
meta.state !== DestroyingState.Destroyed && undestroyed.push(meta.source);
}), undestroyed.length > 0) {
let objectsToString = undestroyed.map(debugToString$1).join("\n "),
error = new Error(`Some destroyables were not destroyed during this test:\n ${objectsToString}`);
throw error.destroyables = undestroyed, error;
}
};
}
export { _hasDestroyableChildren, assertDestroyablesDestroyed, associateDestroyableChild, destroy, destroyChildren, enableDestroyableTracking, isDestroyed, isDestroying, registerDestructor, unregisterDestructor };