diff --git a/scripts/secretariat.js b/scripts/secretariat.js index 1eef4b5..a66376e 100644 --- a/scripts/secretariat.js +++ b/scripts/secretariat.js @@ -11,173 +11,132 @@ import logging from "/gui/scripts/logging.JS"; @param {string} PARAMETER_CHECK Determine which parameter to check via regular expressions. @return {object} the data */ -export async function read(DATA_NAME, CLOUD = 0, PARAMETER_TEST = null) { - // Initialize the selected pref data. - let DATA = {}, - DATA_ALL = {}, - DATA_RETURNED = {}; +export async function read(DATA_NAME, CLOUD = 0) { + // Initialize the selected pref data. + let DATA = {}, + DATA_ALL = {}, + DATA_RETURNED = {}; - // Convert the entered prefname to an array if it is not one. - if (!(typeof DATA_NAME).includes(`object`)) { - // Avoid null - if ((typeof DATA_NAME).includes(`str`) ? DATA_NAME.trim() : DATA_NAME) { - // Syntax of splitting is by commas. - DATA_NAME = String(DATA_NAME).trim().split(","); - } - } + // Convert the entered prefname to an array if it is not one. + if (!(typeof DATA_NAME).includes(`object`)) { + // Avoid null + if ((typeof DATA_NAME).includes(`str`) ? DATA_NAME.trim() : DATA_NAME) { + // Syntax of splitting is by commas. + DATA_NAME = String(DATA_NAME).trim().split(","); + } + } - /* - Get all dataset. + /* + Get all dataset. - @param {number} SOURCE the data source - */ - async function read_database(SOURCE = -1) { - let data = {}; - let data_returned; + @param {number} SOURCE the data source + */ + async function read_database(SOURCE = -1) { + let data = {}; + let data_returned; - async function read_database_local() { - return new Promise((resolve, reject) => { - chrome.storage.local.get(null, function (result) { - if (chrome.runtime.lastError) { - // Something went wrong - reject(new Error(chrome.runtime.lastError)); - } else { - // If the key exists, return the value - resolve(result); - } - }); - }); - } + async function read_database_local() { + return new Promise((resolve, reject) => { + chrome.storage.local.get(null, function (result) { + if (chrome.runtime.lastError) { + // Something went wrong + reject(new Error(chrome.runtime.lastError)); + } else { + // If the key exists, return the value + resolve(result); + } + }); + }); + } - async function read_database_sync() { - return new Promise((resolve, reject) => { - chrome.storage.sync.get(null, function (result) { - if (chrome.runtime.lastError) { - // Something went wrong - reject(new Error(chrome.runtime.lastError)); - } else { - // If the key exists, return the value - resolve(result); - } - }); - }); - } + async function read_database_sync() { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(null, function (result) { + if (chrome.runtime.lastError) { + // Something went wrong + reject(new Error(chrome.runtime.lastError)); + } else { + // If the key exists, return the value + resolve(result); + } + }); + }); + } - // Return the data. - if (SOURCE > 0) { - data_returned = read_database_sync(); - } else { - data_returned = read_database_local(); - } + // Return the data. + if (SOURCE > 0) { + data_returned = read_database_sync(); + } else { + data_returned = read_database_local(); + } - return data_returned; - } + return data_returned; + } - /* Recursively find through each data, returning either that value or null when the object is not found. + /* Recursively find through each data, returning either that value or null when the object is not found. - @param {dictionary} DATA_ALL the data - @param {object} DATA_PATH the path of the data - @param {object} PARAMETER_TEST what among the value to test - @return {object} the data - */ - function find_data(DATA_ALL, DATA_PATH, PARAMETER_TEST) { - let DATA_SELECTED = DATA_ALL; + @param {dictionary} DATA_ALL the data + @param {object} DATA_PATH the path of the data + @return {object} the data + */ + function find_data(DATA_ALL, DATA_PATH) { + let DATA = DATA_ALL; - // Pull the data out. - if ( - DATA_ALL && DATA_PATH && DATA_PATH != null ? DATA_PATH.length > 0 : false - ) { - let DATA_PATH_SELECTED = String(DATA_PATH.shift()).trim(); + // Pull the data out. + if ( + DATA_ALL && DATA_PATH && DATA_PATH != null ? DATA_PATH.length > 0 : false + ) { + let DATA_PATH_SELECTED = String(DATA_PATH.shift()).trim(); - // Get the selected data. - DATA_SELECTED = DATA_ALL[DATA_PATH_SELECTED]; + // Get the selected data. + DATA = DATA_ALL[DATA_PATH_SELECTED]; - // must run if there is actually a parameter to test - if ( - DATA_PATH.length > 0 || - ((PARAMETER_TEST != null ? PARAMETER_TEST.length > 0 : false) - ? PARAMETER_TEST[`field`] - : false) - ) { - // Recursively run to make use of the existing data. - DATA_SELECTED = find_data(DATA_SELECTED, DATA_PATH, PARAMETER_TEST); - } - } else if ( - PARAMETER_TEST - ? !!PARAMETER_TEST[`field`] && !!PARAMETER_TEST[`test value`] - : false - ) { - let QUALIFIED = false; - let DATA_SELECTED_KEYS = Object.keys(DATA_SELECTED); + // must run if there is actually a parameter to test + if (DATA_PATH.length > 0) { + // Recursively run to make use of the existing data. + DATA = find_data(DATA, DATA_PATH); + } + } else { + return null; + } - // Perform a sequential search. - for ( - let DATA_SELECTED_KEY_INDEX = 0; - DATA_SELECTED_KEY_INDEX < DATA_SELECTED_KEYS.length && !QUALIFIED; - DATA_SELECTED_KEY_INDEX++ - ) { - PARAMETER_TEST[`value`] = - DATA_SELECTED[DATA_SELECTED_KEYS[DATA_SELECTED_KEY_INDEX]][ - PARAMETER_TEST[`field`] - ]; - if (PARAMETER_TEST[`value`]) { - QUALIFIED = - new RegExp(String(PARAMETER_TEST[`value`])).test( - PARAMETER_TEST[`test value`], - ) || PARAMETER_TEST[`test value`].includes(PARAMETER_TEST[`value`]); - } + // Now return the data. + return DATA; + } - if (QUALIFIED) { - DATA_SELECTED = - DATA_SELECTED[DATA_SELECTED_KEYS[DATA_SELECTED_KEY_INDEX]]; - break; - } - } + // Read data from local and sync storage (asynchronous operations) + try { + if (CLOUD <= 0) { + [DATA_ALL[`local`]] = await Promise.all([read_database(-1)]); + } + if (CLOUD >= 0) { + [DATA_ALL[`sync`]] = await Promise.all([read_database(1)]); + } + } catch ({ name, message }) { + logging.error(name, message); + } - if (!QUALIFIED) { - DATA_SELECTED = null; - } - } else { - return null; - } + // Let's get through everything and then determine which one has… + Object.keys(DATA_ALL).forEach((DATA_SOURCE) => { + if (DATA_ALL[DATA_SOURCE]) { + DATA[DATA_SOURCE] = DATA_NAME + ? find_data(DATA_ALL[DATA_SOURCE], DATA_NAME) + : DATA_ALL[DATA_SOURCE]; + } + }); - // Now return the data. - return DATA_SELECTED; - } + // Now return the data. + DATA_RETURNED[`source`] = + CLOUD != 0 + ? CLOUD > 0 + ? `sync` + : `local` + : (DATA[`sync`] ? DATA[`sync`].length <= 0 : DATA[`sync`]) + ? `sync` + : `local`; + DATA_RETURNED[`value`] = DATA[DATA_RETURNED[`source`]]; - // Read data from local and sync storage (asynchronous operations) - try { - if (CLOUD <= 0) { - [DATA_ALL[`local`]] = await Promise.all([read_database(-1)]); - } - if (CLOUD >= 0) { - [DATA_ALL[`sync`]] = await Promise.all([read_database(1)]); - } - } catch ({ name, message }) { - logging.error(name, message); - } - - // Let's get through everything and then determine which one has… - Object.keys(DATA_ALL).forEach((DATA_SOURCE) => { - if (DATA_ALL[DATA_SOURCE]) { - DATA[DATA_SOURCE] = DATA_NAME - ? find_data(DATA_ALL[DATA_SOURCE], DATA_NAME, PARAMETER_TEST) - : DATA_ALL[DATA_SOURCE]; - } - }); - - // Now return the data. - DATA_RETURNED[`source`] = - CLOUD != 0 - ? CLOUD > 0 - ? `sync` - : `local` - : (DATA[`sync`] ? DATA[`sync`].length <= 0 : DATA[`sync`]) - ? `sync` - : `local`; - DATA_RETURNED[`value`] = DATA[DATA_RETURNED[`source`]]; - - return DATA_RETURNED[`value`]; + return DATA_RETURNED[`value`]; } /* More enhanced searching. @@ -187,67 +146,81 @@ export async function read(DATA_NAME, CLOUD = 0, PARAMETER_TEST = null) { @param {Array} ADDITIONAL_PLACES additional places to search @return {Array} the results */ -export function search(SOURCE, TERM, VALUE, ADDITIONAL_PLACES, STRICT = false) { - let DATA = read(SOURCE); - let RESULTS; +export async function search(SOURCE, TERM, ADDITIONAL_PLACES, STRICT = false) { + let DATA = await read(SOURCE); + let RESULTS; - if (DATA) { - RESULTS = {}; + if (DATA) { + RESULTS = {}; - if (TERM) { - // Sequentially search through the data, first by key. - for (let DATA_NAME in Object.keys(DATA)) { - if (STRICT) { - if ( - STRICT - ? DATA_NAME == TERM - : DATA_NAME.includes(TERM) || TERM.includes(DATA_NAME) - ) { - RESULTS[`DATA_NAME`] = DATA[DATA_NAME]; - } - } - } + if (TERM) { + // Sequentially search through the data, first by key. + let key_number = {"total": (Object.keys(DATA)).length, "current": 0}; + + while (key_number[`current`] < key_number[`total`]) { + let DATA_NAME = (Object.keys(DATA))[key_number[`current`]] + + if ( + STRICT + ? DATA_NAME == TERM + : (DATA_NAME.includes(TERM) || TERM.includes(DATA_NAME)) + ) { + RESULTS[DATA_NAME] = DATA[DATA_NAME]; + } + + key_number[`current`]++; + } + + // Then, get the additional places. + if ( + (ADDITIONAL_PLACES != null ? Array.isArray(ADDITIONAL_PLACES) : false) + ? ADDITIONAL_PLACES.length > 0 + : false + ) { + for (let PARAMETER_PRIORITY_NUMBER = 0; PARAMETER_PRIORITY_NUMBER < ADDITIONAL_PLACES.length; PARAMETER_PRIORITY_NUMBER++) { + // Recursively search + RESULTS = Object.assign({}, RESULTS, search(SOURCE, TERM, ADDITIONAL_PLACES[PARAMETER_PRIORITY_NUMBER], STRICT)); + }; + } + } else if (((typeof ADDITIONAL_PLACES).includes(`str`) && (ADDITIONAL_PLACES)) ? ADDITIONAL_PLACES.trim() : false) { + // Perform a sequential search on the data. + if ((typeof DATA).includes(`obj`) && !Array.isArray(DATA) && SOURCE != null) { + let VALUE = {}; + VALUE[`test`] = TERM; + + for (let DICTIONARY_INDEX = 0; DICTIONARY_INDEX < (Object.keys(DATA)).length; DICTIONARY_INDEX) { + VALUE[`parent`] = DATA[(Object.keys(DATA))[DICTIONARY_INDEX]]; + + if (((typeof VALUE[`parent`]).includes(`obj`) && !Array.isArray(VALUE[`parent`]) && VALUE[`parent`] != null) ? (Object.keys(VALUE[`parent`])).length > 0 : false ) { + VALUE[`current`] = (VALUE[`parent`])[`test`]; + } + + if (VALUE[`current`]) { + // Add the data. + RESULTS[(Object.keys(DATA))[DICTIONARY_INDEX]] = DATA; + } + }; + } else { + for (let ELEMENT_INDEX = 0; ELEMENT_INDEX < DATA.length; ELEMENT_INDEX++) { + if ( + ((STRICT || (typeof DATA[ELEMENT_INDEX]).includes(`num`)) && DATA[ELEMENT_INDEX] == TERM) || + ((!STRICT && !((typeof DATA[ELEMENT_INDEX]).includes(`num`))) + ? (TERM.includes(DATA[ELEMENT_INDEX]) || DATA[ELEMENT_INDEX].includes(TERM) || + (typeof(DATA[ELEMENT_INDEX])).includes(`str`) + ? new RegExp(DATA[ELEMENT_INDEX]).test(TERM) + : false + ) : false + ) + ) { + RESULTS[SOURCE] = DATA; + break; + } + } + } + } + } - // Then, get the additional places. - if ( - (ADDITIONAL_PLACES != null ? Array.isArray(ADDITIONAL_PLACES) : false) - ? ADDITIONAL_PLACES.length > 0 - : false - ) { - for (let FIELD_NAME in ADDITIONAL_PLACES) { - let RESULT = read(SOURCE, 0, { - field: FIELD_NAME, - "test value": TERM, - }); - if (RESULT) { - RESULTS = Object.assign( - {}, - RESULTS, - search(SOURCE, null, RESULT, null, true), - ); - } - } - } - } else if (VALUE) { - for (let ENTRY in (typeof SOURCE).includes(`obj`) && - !Array.isArray(SOURCE) && - SOURCE != null - ? Object.keys(SOURCE) - : SOURCE) { - if ( - (typeof SOURCE).includes(`obj`) && !Array.isArray(SOURCE) - ? SOURCE[ENTRY] == VALUE - : false - ) { - RESULTS[ENTRY] = VALUE; - } else if (SOURCE[ENTRY] == VALUE) { - RESULTS[SOURCE.indexOf(ENTRY)] = VALUE; - } - } - } - } - - return RESULTS; + return RESULTS; } /* Write the data on the selected prefname. @@ -257,74 +230,74 @@ export function search(SOURCE, TERM, VALUE, ADDITIONAL_PLACES, STRICT = false) { @param {int} CLOUD store in the cloud; otherwise set to automatic */ export function write(PATH, DATA, CLOUD = -1) { - let DATA_INJECTED = {}; + let DATA_INJECTED = {}; - /* Forcibly write the data to chrome database + /* Forcibly write the data to chrome database - @param {object} DATA the data - @param {number} CLOUD the storage - */ - function write_database(DATA, CLOUD = 0) { - // If CLOUD is set to 0, it should automatically determine where the previous source of data was taken from. + @param {object} DATA the data + @param {number} CLOUD the storage + */ + function write_database(DATA, CLOUD = 0) { + // If CLOUD is set to 0, it should automatically determine where the previous source of data was taken from. - if (CLOUD > 0) { - chrome.storage.sync.set(DATA); - } else if (CLOUD < 0) { - chrome.storage.local.set(DATA); - } - } + if (CLOUD > 0) { + chrome.storage.sync.set(DATA); + } else if (CLOUD < 0) { + chrome.storage.local.set(DATA); + } + } - /* Appropriately nest and merge the data. + /* Appropriately nest and merge the data. - @param {object} EXISTING the original data - @param {object} PATH the subpath - @param {object} VALUE the value - @return {object} the updated data - */ - function nest(EXISTING, SUBPATH, VALUE) { - let DATABASE = EXISTING; + @param {object} EXISTING the original data + @param {object} PATH the subpath + @param {object} VALUE the value + @return {object} the updated data + */ + function nest(EXISTING, SUBPATH, VALUE) { + let DATABASE = EXISTING; - // Get the current path. - let PATH = {}; - PATH[`current`] = String(SUBPATH.shift()).trim(); - PATH[`target`] = SUBPATH; + // Get the current path. + let PATH = {}; + PATH[`current`] = String(SUBPATH.shift()).trim(); + PATH[`target`] = SUBPATH; - if (PATH[`target`].length > 0) { - if (DATABASE[PATH[`current`]] == null) { - DATABASE[PATH[`current`]] = {}; - } - DATABASE[PATH[`current`]] = nest( - DATABASE[PATH[`current`]], - PATH[`target`], - VALUE, - ); - } else { - DATABASE[PATH[`current`]] = VALUE; - } - // Return the value. - return DATABASE; - } + if (PATH[`target`].length > 0) { + if (DATABASE[PATH[`current`]] == null) { + DATABASE[PATH[`current`]] = {}; + } + DATABASE[PATH[`current`]] = nest( + DATABASE[PATH[`current`]], + PATH[`target`], + VALUE, + ); + } else { + DATABASE[PATH[`current`]] = VALUE; + } + // Return the value. + return DATABASE; + } - read(null, CLOUD).then((DATA_ALL) => { - // handle empty collected data. - if (!DATA_ALL) { - DATA_ALL = {}; - } + read(null, CLOUD).then((DATA_ALL) => { + // handle empty collected data. + if (!DATA_ALL) { + DATA_ALL = {}; + } - let DATA_NAME = PATH; + let DATA_NAME = PATH; - // Convert the entered prefname to an array if it is not one. - if (!(typeof SUBPATH).includes(`object`)) { - // Split what is not an object. - DATA_NAME = String(PATH).trim().split(","); - } + // Convert the entered prefname to an array if it is not one. + if (!(typeof SUBPATH).includes(`object`)) { + // Split what is not an object. + DATA_NAME = String(PATH).trim().split(","); + } - // Merge! - DATA_INJECTED = nest(DATA_ALL, DATA_NAME, DATA); + // Merge! + DATA_INJECTED = nest(DATA_ALL, DATA_NAME, DATA); - // Write! - write_database(DATA_INJECTED, CLOUD); - }); + // Write! + write_database(DATA_INJECTED, CLOUD); + }); } /* Dangerous: Resets all data or a domain's data. @@ -335,58 +308,58 @@ export function write(PATH, DATA, CLOUD = -1) { @return {boolean} the user's confirmation */ export function forget(preference, subpreference, CLOUD = 0) { - let forget_action = false; + let forget_action = false; - (async () => { - // Import alerts module. - let alerts = (await import(chrome.runtime.getURL(`gui/scripts/alerts.js`)))[ - `alerts` - ]; + (async () => { + // Import alerts module. + let alerts = (await import(chrome.runtime.getURL(`gui/scripts/alerts.js`)))[ + `alerts` + ]; - // Confirm the action. - let forget_action = alerts.confirm_action(); + // Confirm the action. + let forget_action = alerts.confirm_action(); - if (forget_action) { - if (preference) { - if (subpreference) { - // Get the data. - data = read(preference, CLOUD); + if (forget_action) { + if (preference) { + if (subpreference) { + // Get the data. + data = read(preference, CLOUD); - // Should only run when existent - if (data[subpreference]) { - delete data[subpreference]; - write([preference, subpreference], data, CLOUD); - } - } else { - // Remove that particular data. - if (CLOUD <= 0) { - chrome.storage.local.get(null, (data) => { - delete data[preference]; + // Should only run when existent + if (data[subpreference]) { + delete data[subpreference]; + write([preference, subpreference], data, CLOUD); + } + } else { + // Remove that particular data. + if (CLOUD <= 0) { + chrome.storage.local.get(null, (data) => { + delete data[preference]; - chrome.storage.local.set(data, (result) => {}); - }); - } - if (CLOUD >= 0) { - chrome.storage.sync.get(null, (data) => { - delete data[preference]; + chrome.storage.local.set(data, (result) => {}); + }); + } + if (CLOUD >= 0) { + chrome.storage.sync.get(null, (data) => { + delete data[preference]; - chrome.storage.sync.set(data, (result) => {}); - }); - } - } - } else { - // Clear the data storage. - if (CLOUD >= 0) { - chrome.storage.sync.clear(); - } - if (CLOUD <= 0) { - chrome.storage.local.clear(); - } - } - } - })(); + chrome.storage.sync.set(data, (result) => {}); + }); + } + } + } else { + // Clear the data storage. + if (CLOUD >= 0) { + chrome.storage.sync.clear(); + } + if (CLOUD <= 0) { + chrome.storage.local.clear(); + } + } + } + })(); - return forget_action; + return forget_action; } /* Initialize the storage. @@ -394,71 +367,71 @@ export function forget(preference, subpreference, CLOUD = 0) { @param {dictionary} data this build's managed data */ export function init(data) { - let PREFERENCES_ALL = {}; - PREFERENCES_ALL[`build`] = data; + let PREFERENCES_ALL = {}; + PREFERENCES_ALL[`build`] = data; - // Read all data. - chrome.storage.managed.get(null, function (DATA_MANAGED) { - PREFERENCES_ALL[`managed`] = DATA_MANAGED; - }); + // Read all data. + chrome.storage.managed.get(null, function (DATA_MANAGED) { + PREFERENCES_ALL[`managed`] = DATA_MANAGED; + }); - chrome.storage.local.get(null, function (DATA_LOCAL) { - PREFERENCES_ALL[`local`] = DATA_LOCAL; - }); + chrome.storage.local.get(null, function (DATA_LOCAL) { + PREFERENCES_ALL[`local`] = DATA_LOCAL; + }); - chrome.storage.sync.get(null, function (DATA_SYNC) { - PREFERENCES_ALL[`sync`] = DATA_SYNC; - }); + chrome.storage.sync.get(null, function (DATA_SYNC) { + PREFERENCES_ALL[`sync`] = DATA_SYNC; + }); - // Merge data. - // Managed > Synchronized > Imported > Local + // Merge data. + // Managed > Synchronized > Imported > Local - if (PREFERENCES_ALL[`managed`]) { - Object.keys(PREFERENCES_ALL[`managed`]).forEach((item) => { - let PREFERENCE = { name: item, existing: false }; + if (PREFERENCES_ALL[`managed`]) { + Object.keys(PREFERENCES_ALL[`managed`]).forEach((item) => { + let PREFERENCE = { name: item, existing: false }; - if (PREFERENCES_ALL[`sync`]) { - PREFERENCE[`existing`] = PREFERENCES_ALL[`sync`].hasOwnProperty( - PREFERENCE[`name`], - ); - } + if (PREFERENCES_ALL[`sync`]) { + PREFERENCE[`existing`] = PREFERENCES_ALL[`sync`].hasOwnProperty( + PREFERENCE[`name`], + ); + } - if (!PREFERENCE[`existing`]) { - // Do not allow synchronized data to interfere with managed data. - forget(PREFERENCE[`name`]); - write( - PREFERENCE[`name`], - PREFERENCES_ALL[`managed`][PREFERENCE[`name`]], - ); - } - }); - } + if (!PREFERENCE[`existing`]) { + // Do not allow synchronized data to interfere with managed data. + forget(PREFERENCE[`name`]); + write( + PREFERENCE[`name`], + PREFERENCES_ALL[`managed`][PREFERENCE[`name`]], + ); + } + }); + } - // Import build data - if (PREFERENCES_ALL[`build`]) { - Object.keys(PREFERENCES_ALL[`build`]).forEach((item) => { - let PREFERENCE = { name: item, existing: false }; + // Import build data + if (PREFERENCES_ALL[`build`]) { + Object.keys(PREFERENCES_ALL[`build`]).forEach((item) => { + let PREFERENCE = { name: item, existing: false }; - PREFERENCE[`existing`] = - (PREFERENCES_ALL[`sync`] - ? PREFERENCES_ALL[`sync`].hasOwnProperty(PREFERENCE[`name`]) - : false) || - (PREFERENCES_ALL[`managed`] - ? PREFERENCES_ALL[`managed`].hasOwnProperty(PREFERENCE[`name`]) - : false) || - (PREFERENCES_ALL[`local`] - ? PREFERENCES_ALL[`local`].hasOwnProperty(PREFERENCE[`local`]) - : false); + PREFERENCE[`existing`] = + (PREFERENCES_ALL[`sync`] + ? PREFERENCES_ALL[`sync`].hasOwnProperty(PREFERENCE[`name`]) + : false) || + (PREFERENCES_ALL[`managed`] + ? PREFERENCES_ALL[`managed`].hasOwnProperty(PREFERENCE[`name`]) + : false) || + (PREFERENCES_ALL[`local`] + ? PREFERENCES_ALL[`local`].hasOwnProperty(PREFERENCE[`local`]) + : false); - if (!PREFERENCE[`existing`]) { - write( - PREFERENCE[`name`], - PREFERENCES_ALL[`build`][PREFERENCE[`name`]], - -1, - ); - } - }); - } + if (!PREFERENCE[`existing`]) { + write( + PREFERENCE[`name`], + PREFERENCES_ALL[`build`][PREFERENCE[`name`]], + -1, + ); + } + }); + } } /* @@ -467,7 +440,7 @@ Run a script when the browser storage has been changed. @param {object} reaction the function to run */ export function observe(reaction) { - chrome.storage.onChanged.addListener((changes, namespace) => { - reaction(changes, namespace); - }); + chrome.storage.onChanged.addListener((changes, namespace) => { + reaction(changes, namespace); + }); }