@magnetarjs/core
Advanced tools
Comparing version 0.0.19 to 0.0.20
@@ -325,3 +325,3 @@ 'use strict'; | ||
function handleActionPerStore([collectionPath, _docId], moduleConfig, globalConfig, actionName, actionType, docFn, // actions executed on a "doc" will always return `doc()` | ||
function handleActionPerStore([collectionPath, _docId], moduleConfig, globalConfig, actionName, actionType, fetchPromises, docFn, // actions executed on a "doc" will always return `doc()` | ||
collectionFn // actions executed on a "collection" will return `collection()` or `doc()` | ||
@@ -331,114 +331,137 @@ ) { | ||
return function (payload, actionConfig = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let docId = _docId; | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError; | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap(globalConfig.modifyPayloadOn, moduleConfig.modifyPayloadOn, actionConfig.modifyPayloadOn); | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap(globalConfig.modifyReadResponseOn, moduleConfig.modifyReadResponseOn, actionConfig.modifyReadResponseOn); | ||
const eventNameFnsMap = getEventNameFnsMap(globalConfig.on, moduleConfig.on, actionConfig.on); | ||
const storesToExecute = actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[]; | ||
throwIfNoFnsToExecute(storesToExecute); | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId); | ||
} | ||
let stopExecution = false; | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert = true) { | ||
stopExecution = trueOrRevert; | ||
} | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns = modifyReadResponseMap.added; | ||
// handle and await each action in sequence | ||
let resultFromPlugin; | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) | ||
break; | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName]; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName); | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: yield handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
stopExecutionAfterAction, | ||
storeName, | ||
}); | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i); | ||
storesToRevert.reverse(); | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert); | ||
yield pluginRevertAction({ | ||
payload, | ||
const fetchPromiseKey = JSON.stringify(payload); | ||
const foundFetchPromise = fetchPromises.get(fetchPromiseKey); | ||
if (actionName === 'fetch' && isWhat.isPromise(foundFetchPromise)) | ||
return foundFetchPromise; | ||
// eslint-disable-next-line no-async-promise-executor | ||
const actionPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
let docId = _docId; | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError; | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap(globalConfig.modifyPayloadOn, moduleConfig.modifyPayloadOn, actionConfig.modifyPayloadOn); | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap(globalConfig.modifyReadResponseOn, moduleConfig.modifyReadResponseOn, actionConfig.modifyReadResponseOn); | ||
const eventNameFnsMap = getEventNameFnsMap(globalConfig.on, moduleConfig.on, actionConfig.on); | ||
const storesToExecute = actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[]; | ||
throwIfNoFnsToExecute(storesToExecute); | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId); | ||
} | ||
let stopExecution = false; | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert = true) { | ||
stopExecution = trueOrRevert; | ||
} | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns = modifyReadResponseMap.added; | ||
// handle and await each action in sequence | ||
let resultFromPlugin; | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) | ||
break; | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName]; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName); | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: yield handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
stopExecutionAfterAction, | ||
storeName, | ||
}); | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
yield fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }); // prettier-ignore | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i); | ||
storesToRevert.reverse(); | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert); | ||
yield pluginRevertAction({ | ||
payload, | ||
collectionPath, | ||
docId, | ||
pluginModuleConfig, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
}); | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
yield fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }); // prettier-ignore | ||
} | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin; | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin; | ||
} | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isWhat.isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin; | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isWhat.isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin; | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
} | ||
} | ||
} | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin); | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]); | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin); | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]); | ||
} | ||
} | ||
} | ||
} | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
resolve(docFn(modulePath)); | ||
return; | ||
} | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) { | ||
resolve(docFn(modulePath, moduleConfig)); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
return; | ||
} | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
resolve(collectionFn(modulePath, moduleConfig)); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
} | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
return docFn(modulePath); | ||
catch (error) { | ||
reject(error); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
} | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) | ||
return docFn(modulePath, moduleConfig); | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
return collectionFn(modulePath, moduleConfig); | ||
}); | ||
})); | ||
if (actionName === 'fetch') { | ||
fetchPromises.set(fetchPromiseKey, actionPromise); | ||
} | ||
return actionPromise; | ||
}; | ||
@@ -488,7 +511,7 @@ } | ||
function handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionType, streams) { | ||
function handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionType, streamPromiseInfo) { | ||
// returns the action the dev can call with myModule.insert() etc. | ||
return function (payload, actionConfig = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { openStreams, openStreamPromises, findStreamPromise } = streams; | ||
const { openStreams, openStreamPromises, findStreamPromise } = streamPromiseInfo; | ||
const foundStreamPromise = findStreamPromise(payload); | ||
@@ -584,4 +607,5 @@ if (isWhat.isPromise(foundStreamPromise)) | ||
function createCollectionWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams; | ||
function createCollectionWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises; // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const id = collectionPath.split('/').slice(-1)[0]; | ||
@@ -592,6 +616,6 @@ const path = collectionPath; | ||
}; | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn, collectionFn); //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn, collectionFn); //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn, collectionFn); //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams); // prettier-ignore | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo); // prettier-ignore | ||
const actions = { stream, fetch, insert, delete: _delete }; | ||
@@ -628,4 +652,5 @@ // Every store will have its 'setupModule' function executed | ||
function createDocWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams; | ||
function createDocWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises; // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const id = docId; | ||
@@ -637,10 +662,10 @@ const path = [collectionPath, docId].join('/'); | ||
const actions = { | ||
insert: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn), | ||
merge: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, docFn), | ||
assign: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, docFn), | ||
replace: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, docFn), | ||
deleteProp: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, docFn), | ||
delete: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn), | ||
fetch: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn), | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams), // prettier-ignore | ||
insert: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn), | ||
merge: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, fetchPromises, docFn), | ||
assign: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, fetchPromises, docFn), | ||
replace: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, fetchPromises, docFn), | ||
deleteProp: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, fetchPromises, docFn), | ||
delete: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn), | ||
fetch: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn), | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo), // prettier-ignore | ||
}; | ||
@@ -706,2 +731,6 @@ // Every store will have its 'setupModule' function executed | ||
const streamPromiseMap = new Map(); // apply type upon get/set | ||
/** | ||
* the global storage for fetch promises | ||
*/ | ||
const fetchPromiseMap = new Map(); // apply type upon get/set | ||
function getModuleInstance(modulePath, moduleConfig = {}, moduleType, docFn, collectionFn) { | ||
@@ -717,3 +746,3 @@ throwIfInvalidModulePath(modulePath, moduleType); | ||
// else create and cache a new instance | ||
// first create the streamCloseFnMap and streamPromiseMap for this module | ||
// create the streamCloseFnMap and streamPromiseMap for this module | ||
if (!streamCloseFnMap.has(modulePath)) { | ||
@@ -725,11 +754,24 @@ streamCloseFnMap.set(modulePath, new Map()); | ||
} | ||
// create the fetchPromiseMap for this module | ||
if (!fetchPromiseMap.has(modulePath)) { | ||
fetchPromiseMap.set(modulePath, new Map()); | ||
} | ||
// grab the open stream utils | ||
const openStreams = streamCloseFnMap.get(modulePath); | ||
const findStream = (streamPayload) => findMapValueForKey(openStreams, streamPayload); | ||
// grab the fetch promise utils | ||
const fetchPromises = fetchPromiseMap.get(modulePath); | ||
const openStreamPromises = streamPromiseMap.get(modulePath); | ||
const findStreamPromise = (streamPayload) => findMapValueForKey(openStreamPromises, streamPayload); | ||
const streams = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const streamAndFetchPromises = { | ||
openStreams, | ||
findStream, | ||
openStreamPromises, | ||
findStreamPromise, | ||
fetchPromises, | ||
}; | ||
// then create the module instance | ||
const createInstanceWithContext = moduleType === 'doc' ? createDocWithContext : createCollectionWithContext; | ||
// @ts-ignore | ||
const moduleInstance = createInstanceWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams); | ||
const moduleInstance = createInstanceWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises); | ||
moduleMap.set(moduleIdentifier, moduleInstance); | ||
@@ -736,0 +778,0 @@ return moduleInstance; |
@@ -321,3 +321,3 @@ import { merge, mergeAndConcat } from 'merge-anything'; | ||
function handleActionPerStore([collectionPath, _docId], moduleConfig, globalConfig, actionName, actionType, docFn, // actions executed on a "doc" will always return `doc()` | ||
function handleActionPerStore([collectionPath, _docId], moduleConfig, globalConfig, actionName, actionType, fetchPromises, docFn, // actions executed on a "doc" will always return `doc()` | ||
collectionFn // actions executed on a "collection" will return `collection()` or `doc()` | ||
@@ -327,114 +327,137 @@ ) { | ||
return function (payload, actionConfig = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let docId = _docId; | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError; | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap(globalConfig.modifyPayloadOn, moduleConfig.modifyPayloadOn, actionConfig.modifyPayloadOn); | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap(globalConfig.modifyReadResponseOn, moduleConfig.modifyReadResponseOn, actionConfig.modifyReadResponseOn); | ||
const eventNameFnsMap = getEventNameFnsMap(globalConfig.on, moduleConfig.on, actionConfig.on); | ||
const storesToExecute = actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[]; | ||
throwIfNoFnsToExecute(storesToExecute); | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId); | ||
} | ||
let stopExecution = false; | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert = true) { | ||
stopExecution = trueOrRevert; | ||
} | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns = modifyReadResponseMap.added; | ||
// handle and await each action in sequence | ||
let resultFromPlugin; | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) | ||
break; | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName]; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName); | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: yield handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
stopExecutionAfterAction, | ||
storeName, | ||
}); | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i); | ||
storesToRevert.reverse(); | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert); | ||
yield pluginRevertAction({ | ||
payload, | ||
const fetchPromiseKey = JSON.stringify(payload); | ||
const foundFetchPromise = fetchPromises.get(fetchPromiseKey); | ||
if (actionName === 'fetch' && isPromise(foundFetchPromise)) | ||
return foundFetchPromise; | ||
// eslint-disable-next-line no-async-promise-executor | ||
const actionPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
let docId = _docId; | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError; | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap(globalConfig.modifyPayloadOn, moduleConfig.modifyPayloadOn, actionConfig.modifyPayloadOn); | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap(globalConfig.modifyReadResponseOn, moduleConfig.modifyReadResponseOn, actionConfig.modifyReadResponseOn); | ||
const eventNameFnsMap = getEventNameFnsMap(globalConfig.on, moduleConfig.on, actionConfig.on); | ||
const storesToExecute = actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[]; | ||
throwIfNoFnsToExecute(storesToExecute); | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId); | ||
} | ||
let stopExecution = false; | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert = true) { | ||
stopExecution = trueOrRevert; | ||
} | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns = modifyReadResponseMap.added; | ||
// handle and await each action in sequence | ||
let resultFromPlugin; | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) | ||
break; | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName]; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName); | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: yield handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
stopExecutionAfterAction, | ||
storeName, | ||
}); | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
yield fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }); // prettier-ignore | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i); | ||
storesToRevert.reverse(); | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert; | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert); | ||
yield pluginRevertAction({ | ||
payload, | ||
collectionPath, | ||
docId, | ||
pluginModuleConfig, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
}); | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
yield fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }); // prettier-ignore | ||
} | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin; | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin; | ||
} | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin; | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin; | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/'); | ||
} | ||
} | ||
} | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin); | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]); | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin); | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]); | ||
} | ||
} | ||
} | ||
} | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
resolve(docFn(modulePath)); | ||
return; | ||
} | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) { | ||
resolve(docFn(modulePath, moduleConfig)); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
return; | ||
} | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
resolve(collectionFn(modulePath, moduleConfig)); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
} | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
return docFn(modulePath); | ||
catch (error) { | ||
reject(error); | ||
if (actionName === 'fetch') | ||
fetchPromises.delete(fetchPromiseKey); | ||
} | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) | ||
return docFn(modulePath, moduleConfig); | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
return collectionFn(modulePath, moduleConfig); | ||
}); | ||
})); | ||
if (actionName === 'fetch') { | ||
fetchPromises.set(fetchPromiseKey, actionPromise); | ||
} | ||
return actionPromise; | ||
}; | ||
@@ -484,7 +507,7 @@ } | ||
function handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionType, streams) { | ||
function handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionType, streamPromiseInfo) { | ||
// returns the action the dev can call with myModule.insert() etc. | ||
return function (payload, actionConfig = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { openStreams, openStreamPromises, findStreamPromise } = streams; | ||
const { openStreams, openStreamPromises, findStreamPromise } = streamPromiseInfo; | ||
const foundStreamPromise = findStreamPromise(payload); | ||
@@ -580,4 +603,5 @@ if (isPromise(foundStreamPromise)) | ||
function createCollectionWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams; | ||
function createCollectionWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises; // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const id = collectionPath.split('/').slice(-1)[0]; | ||
@@ -588,6 +612,6 @@ const path = collectionPath; | ||
}; | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn, collectionFn); //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn, collectionFn); //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn, collectionFn); //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams); // prettier-ignore | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn, collectionFn); //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo); // prettier-ignore | ||
const actions = { stream, fetch, insert, delete: _delete }; | ||
@@ -624,4 +648,5 @@ // Every store will have its 'setupModule' function executed | ||
function createDocWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams; | ||
function createDocWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises) { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises; // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const id = docId; | ||
@@ -633,10 +658,10 @@ const path = [collectionPath, docId].join('/'); | ||
const actions = { | ||
insert: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn), | ||
merge: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, docFn), | ||
assign: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, docFn), | ||
replace: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, docFn), | ||
deleteProp: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, docFn), | ||
delete: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn), | ||
fetch: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn), | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams), // prettier-ignore | ||
insert: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn), | ||
merge: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, fetchPromises, docFn), | ||
assign: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, fetchPromises, docFn), | ||
replace: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, fetchPromises, docFn), | ||
deleteProp: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, fetchPromises, docFn), | ||
delete: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn), | ||
fetch: handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn), | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo), // prettier-ignore | ||
}; | ||
@@ -702,2 +727,6 @@ // Every store will have its 'setupModule' function executed | ||
const streamPromiseMap = new Map(); // apply type upon get/set | ||
/** | ||
* the global storage for fetch promises | ||
*/ | ||
const fetchPromiseMap = new Map(); // apply type upon get/set | ||
function getModuleInstance(modulePath, moduleConfig = {}, moduleType, docFn, collectionFn) { | ||
@@ -713,3 +742,3 @@ throwIfInvalidModulePath(modulePath, moduleType); | ||
// else create and cache a new instance | ||
// first create the streamCloseFnMap and streamPromiseMap for this module | ||
// create the streamCloseFnMap and streamPromiseMap for this module | ||
if (!streamCloseFnMap.has(modulePath)) { | ||
@@ -721,11 +750,24 @@ streamCloseFnMap.set(modulePath, new Map()); | ||
} | ||
// create the fetchPromiseMap for this module | ||
if (!fetchPromiseMap.has(modulePath)) { | ||
fetchPromiseMap.set(modulePath, new Map()); | ||
} | ||
// grab the open stream utils | ||
const openStreams = streamCloseFnMap.get(modulePath); | ||
const findStream = (streamPayload) => findMapValueForKey(openStreams, streamPayload); | ||
// grab the fetch promise utils | ||
const fetchPromises = fetchPromiseMap.get(modulePath); | ||
const openStreamPromises = streamPromiseMap.get(modulePath); | ||
const findStreamPromise = (streamPayload) => findMapValueForKey(openStreamPromises, streamPayload); | ||
const streams = { openStreams, findStream, openStreamPromises, findStreamPromise }; | ||
const streamAndFetchPromises = { | ||
openStreams, | ||
findStream, | ||
openStreamPromises, | ||
findStreamPromise, | ||
fetchPromises, | ||
}; | ||
// then create the module instance | ||
const createInstanceWithContext = moduleType === 'doc' ? createDocWithContext : createCollectionWithContext; | ||
// @ts-ignore | ||
const moduleInstance = createInstanceWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streams); | ||
const moduleInstance = createInstanceWithContext([collectionPath, docId], moduleConfig, globalConfig, docFn, collectionFn, streamAndFetchPromises); | ||
moduleMap.set(moduleIdentifier, moduleInstance); | ||
@@ -732,0 +774,0 @@ return moduleInstance; |
{ | ||
"name": "@magnetarjs/core", | ||
"version": "0.0.19", | ||
"version": "0.0.20", | ||
"sideEffects": false, | ||
@@ -8,3 +8,3 @@ "description": "Magnetar core library.", | ||
"module": "dist/index.esm.js", | ||
"types": "types/index.d.ts", | ||
"types": "dist/types/index.d.ts", | ||
"scripts": { | ||
@@ -15,3 +15,3 @@ "build": "npm run rollup", | ||
"test--only": "ava --match='*only:*'", | ||
"rollup": "rimraf dist && rimraf types && rollup -c build.js" | ||
"rollup": "rimraf dist && rollup -c build.js" | ||
}, | ||
@@ -28,3 +28,3 @@ "author": "Luca Ban - Mesqueeb", | ||
"devDependencies": { | ||
"@magnetarjs/test-utils": "^0.0.10", | ||
"@magnetarjs/test-utils": "^0.0.11", | ||
"ava": "^3.15.0" | ||
@@ -73,3 +73,3 @@ }, | ||
}, | ||
"gitHead": "0894958dbdebbe4ec1adeb418d4d76050bd5c51d" | ||
"gitHead": "ad27bdb9bb5fba289ffdb3abe4c94e1b94072160" | ||
} |
@@ -13,2 +13,3 @@ import { O } from 'ts-toolbelt' | ||
MagnetarDeleteAction, | ||
FetchPromises, | ||
} from './types/actions' | ||
@@ -67,3 +68,3 @@ import { actionNameTypeMap } from './types/actionsInternal' | ||
collectionFn: CollectionFn<DocDataType>, | ||
streams: { | ||
streamAndFetchPromises: { | ||
openStreams: OpenStreams | ||
@@ -73,5 +74,7 @@ findStream: FindStream | ||
findStreamPromise: FindStreamPromise | ||
fetchPromises: FetchPromises | ||
} | ||
): CollectionInstance<DocDataType> { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise } | ||
const id = collectionPath.split('/').slice(-1)[0] | ||
@@ -84,6 +87,6 @@ const path = collectionPath | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn, collectionFn) as MagnetarInsertAction<DocDataType> //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn, collectionFn) as MagnetarDeleteAction<DocDataType> //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn, collectionFn) as MagnetarFetchAction<DocDataType, 'collection'> //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams) // prettier-ignore | ||
const insert = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn, collectionFn) as MagnetarInsertAction<DocDataType> //prettier-ignore | ||
const _delete = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn, collectionFn) as MagnetarDeleteAction<DocDataType> //prettier-ignore | ||
const fetch = handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn, collectionFn) as MagnetarFetchAction<DocDataType, 'collection'> //prettier-ignore | ||
const stream = handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo) // prettier-ignore | ||
@@ -90,0 +93,0 @@ const actions = { stream, fetch, insert, delete: _delete } |
@@ -13,2 +13,3 @@ import { O } from 'ts-toolbelt' | ||
FindStreamPromise, | ||
FetchPromises, | ||
} from './types/actions' | ||
@@ -26,3 +27,3 @@ import { actionNameTypeMap } from './types/actionsInternal' | ||
*/ | ||
data: DocDataType | ||
data: DocDataType | undefined | ||
/** | ||
@@ -70,3 +71,3 @@ * `collection` is available on every document for chaining | ||
collectionFn: CollectionFn, | ||
streams: { | ||
streamAndFetchPromises: { | ||
openStreams: OpenStreams | ||
@@ -76,5 +77,7 @@ findStream: FindStream | ||
findStreamPromise: FindStreamPromise | ||
fetchPromises: FetchPromises | ||
} | ||
): DocInstance<DocDataType> { | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise } = streams | ||
const { openStreams, findStream, openStreamPromises, findStreamPromise, fetchPromises } = streamAndFetchPromises // prettier-ignore | ||
const streamPromiseInfo = { openStreams, findStream, openStreamPromises, findStreamPromise } | ||
const id = docId | ||
@@ -88,10 +91,10 @@ const path = [collectionPath, docId].join('/') | ||
const actions = { | ||
insert: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, docFn) as MagnetarInsertAction<DocDataType>), // prettier-ignore | ||
merge: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
assign: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
replace: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
deleteProp: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, docFn) as MagnetarDeletePropAction<DocDataType>), // prettier-ignore | ||
delete: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, docFn) as MagnetarDeleteAction<DocDataType>), // prettier-ignore | ||
fetch: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, docFn) as MagnetarFetchAction<DocDataType, 'doc'>), // prettier-ignore | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streams), // prettier-ignore | ||
insert: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'insert', actionNameTypeMap.insert, fetchPromises, docFn) as MagnetarInsertAction<DocDataType>), // prettier-ignore | ||
merge: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'merge', actionNameTypeMap.merge, fetchPromises, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
assign: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'assign', actionNameTypeMap.assign, fetchPromises, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
replace: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'replace', actionNameTypeMap.replace, fetchPromises, docFn) as MagnetarWriteAction<DocDataType>), // prettier-ignore | ||
deleteProp: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'deleteProp', actionNameTypeMap.deleteProp, fetchPromises, docFn) as MagnetarDeletePropAction<DocDataType>), // prettier-ignore | ||
delete: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'delete', actionNameTypeMap.delete, fetchPromises, docFn) as MagnetarDeleteAction<DocDataType>), // prettier-ignore | ||
fetch: (handleActionPerStore([collectionPath, docId], moduleConfig, globalConfig, 'fetch', actionNameTypeMap.fetch, fetchPromises, docFn) as MagnetarFetchAction<DocDataType, 'doc'>), // prettier-ignore | ||
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streamPromiseInfo), // prettier-ignore | ||
} | ||
@@ -98,0 +101,0 @@ |
@@ -6,3 +6,9 @@ import { O } from 'ts-toolbelt' | ||
import { createDocWithContext, DocInstance } from './Doc' | ||
import { OpenStreams, OpenStreamPromises, FindStream, FindStreamPromise } from './types/actions' | ||
import { | ||
OpenStreams, | ||
OpenStreamPromises, | ||
FindStream, | ||
FindStreamPromise, | ||
FetchPromises, | ||
} from './types/actions' | ||
import { throwIfInvalidModulePath } from './helpers/throwFns' | ||
@@ -76,2 +82,6 @@ import { getCollectionPathDocIdEntry } from './helpers/pathHelpers' | ||
const streamPromiseMap: Map<string, OpenStreamPromises> = new Map() // apply type upon get/set | ||
/** | ||
* the global storage for fetch promises | ||
*/ | ||
const fetchPromiseMap: Map<string, FetchPromises> = new Map() // apply type upon get/set | ||
@@ -93,3 +103,4 @@ function getModuleInstance( | ||
// else create and cache a new instance | ||
// first create the streamCloseFnMap and streamPromiseMap for this module | ||
// create the streamCloseFnMap and streamPromiseMap for this module | ||
if (!streamCloseFnMap.has(modulePath)) { | ||
@@ -101,9 +112,25 @@ streamCloseFnMap.set(modulePath, new Map()) | ||
} | ||
// create the fetchPromiseMap for this module | ||
if (!fetchPromiseMap.has(modulePath)) { | ||
fetchPromiseMap.set(modulePath, new Map()) | ||
} | ||
// grab the open stream utils | ||
const openStreams = streamCloseFnMap.get(modulePath) as OpenStreams | ||
const findStream: FindStream = (streamPayload: any) => | ||
findMapValueForKey(openStreams, streamPayload) | ||
// grab the fetch promise utils | ||
const fetchPromises = fetchPromiseMap.get(modulePath) as FetchPromises | ||
const openStreamPromises = streamPromiseMap.get(modulePath) as OpenStreamPromises | ||
const findStreamPromise: FindStreamPromise = (streamPayload: any) => | ||
findMapValueForKey(openStreamPromises, streamPayload) | ||
const streams = { openStreams, findStream, openStreamPromises, findStreamPromise } | ||
const streamAndFetchPromises = { | ||
openStreams, | ||
findStream, | ||
openStreamPromises, | ||
findStreamPromise, | ||
fetchPromises, | ||
} | ||
// then create the module instance | ||
@@ -119,3 +146,3 @@ const createInstanceWithContext = | ||
collectionFn, | ||
streams | ||
streamAndFetchPromises | ||
) | ||
@@ -122,0 +149,0 @@ moduleMap.set(moduleIdentifier, moduleInstance) |
@@ -0,3 +1,4 @@ | ||
/* eslint-disable no-inner-declarations */ | ||
import { O } from 'ts-toolbelt' | ||
import { isFullString } from 'is-what' | ||
import { isFullString, isPromise } from 'is-what' | ||
import { handleAction } from './handleAction' | ||
@@ -13,2 +14,3 @@ import { getEventNameFnsMap } from '../types/events' | ||
ActionName, | ||
FetchPromises, | ||
} from '../types/actions' | ||
@@ -33,2 +35,3 @@ import { ActionType, ActionTernary } from '../types/actionsInternal' | ||
actionType: ActionType, | ||
fetchPromises: FetchPromises, | ||
docFn: DocFn, // actions executed on a "doc" will always return `doc()` | ||
@@ -44,2 +47,3 @@ collectionFn?: CollectionFn // actions executed on a "collection" will return `collection()` or `doc()` | ||
actionType: ActionType, | ||
fetchPromises: FetchPromises, | ||
docFn: DocFn, // actions executed on a "doc" will always return `doc()` | ||
@@ -54,134 +58,162 @@ collectionFn?: CollectionFn // actions executed on a "collection" will return `collection()` or `doc()` | ||
// returns the action the dev can call with myModule.insert() etc. | ||
return async function ( | ||
return function ( | ||
payload?: any, | ||
actionConfig: ActionConfig = {} | ||
): Promise<DocInstance | CollectionInstance> { | ||
let docId = _docId | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/') | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap( | ||
globalConfig.modifyPayloadOn, | ||
moduleConfig.modifyPayloadOn, | ||
actionConfig.modifyPayloadOn | ||
) | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap( | ||
globalConfig.modifyReadResponseOn, | ||
moduleConfig.modifyReadResponseOn, | ||
actionConfig.modifyReadResponseOn | ||
) | ||
const eventNameFnsMap = getEventNameFnsMap(globalConfig.on, moduleConfig.on, actionConfig.on) | ||
const storesToExecute: string[] = | ||
actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[] | ||
throwIfNoFnsToExecute(storesToExecute) | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId) | ||
} | ||
const fetchPromiseKey = JSON.stringify(payload) | ||
const foundFetchPromise = fetchPromises.get(fetchPromiseKey) | ||
if (actionName === 'fetch' && isPromise(foundFetchPromise)) return foundFetchPromise | ||
// create the abort mechanism | ||
type StopExecution = boolean | 'revert' | ||
let stopExecution = false as StopExecution | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert: StopExecution = true): void { | ||
stopExecution = trueOrRevert | ||
} | ||
// eslint-disable-next-line no-async-promise-executor | ||
const actionPromise = new Promise<DocInstance | CollectionInstance>(async (resolve, reject) => { | ||
try { | ||
let docId = _docId | ||
let modulePath = [collectionPath, docId].filter(Boolean).join('/') | ||
// get all the config needed to perform this action | ||
const onError = actionConfig.onError || moduleConfig.onError || globalConfig.onError | ||
const modifyPayloadFnsMap = getModifyPayloadFnsMap( | ||
globalConfig.modifyPayloadOn, | ||
moduleConfig.modifyPayloadOn, | ||
actionConfig.modifyPayloadOn | ||
) | ||
const modifyReadResponseMap = getModifyReadResponseFnsMap( | ||
globalConfig.modifyReadResponseOn, | ||
moduleConfig.modifyReadResponseOn, | ||
actionConfig.modifyReadResponseOn | ||
) | ||
const eventNameFnsMap = getEventNameFnsMap( | ||
globalConfig.on, | ||
moduleConfig.on, | ||
actionConfig.on | ||
) | ||
const storesToExecute: string[] = | ||
actionConfig.executionOrder || | ||
(moduleConfig.executionOrder || {})[actionName] || | ||
(moduleConfig.executionOrder || {})[actionType] || | ||
(globalConfig.executionOrder || {})[actionName] || | ||
(globalConfig.executionOrder || {})[actionType] || | ||
[] | ||
throwIfNoFnsToExecute(storesToExecute) | ||
// update the payload | ||
for (const modifyFn of modifyPayloadFnsMap[actionName]) { | ||
payload = modifyFn(payload, docId) | ||
} | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns: DoOnFetch[] = modifyReadResponseMap.added | ||
// create the abort mechanism | ||
type StopExecution = boolean | 'revert' | ||
let stopExecution = false as StopExecution | ||
/** | ||
* The abort mechanism for the entire store chain. When executed in handleAction() it won't go to the next store in executionOrder. | ||
*/ | ||
function stopExecutionAfterAction(trueOrRevert: StopExecution = true): void { | ||
stopExecution = trueOrRevert | ||
} | ||
// handle and await each action in sequence | ||
let resultFromPlugin: void | string | FetchResponse | OnAddedFn | any | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) break | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName] | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName) | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: await handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, // should always use the payload as passed originally for clarity | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
stopExecutionAfterAction, | ||
storeName, | ||
}) | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i) | ||
storesToRevert.reverse() | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert) | ||
await pluginRevertAction({ | ||
payload, | ||
collectionPath, | ||
docId, | ||
pluginModuleConfig, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
}) | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
await fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }) // prettier-ignore | ||
/** | ||
* each each time a store returns a `FetchResponse` then all `doOnFetchFns` need to be executed | ||
*/ | ||
const doOnFetchFns: DoOnFetch[] = modifyReadResponseMap.added | ||
// handle and await each action in sequence | ||
let resultFromPlugin: void | string | FetchResponse | OnAddedFn | any | ||
for (const [i, storeName] of storesToExecute.entries()) { | ||
// a previous iteration stopped the execution: | ||
if (stopExecution === true) break | ||
// find the action on the plugin | ||
const pluginAction = globalConfig.stores[storeName].actions[actionName] | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeName) | ||
// the plugin action | ||
resultFromPlugin = !pluginAction | ||
? resultFromPlugin | ||
: await handleAction({ | ||
collectionPath, | ||
docId, | ||
modulePath, | ||
pluginModuleConfig, | ||
pluginAction, | ||
payload, // should always use the payload as passed originally for clarity | ||
eventNameFnsMap, | ||
onError, | ||
actionName, | ||
stopExecutionAfterAction, | ||
storeName, | ||
}) | ||
// handle reverting. stopExecution might have been modified by `handleAction` | ||
if (stopExecution === 'revert') { | ||
const storesToRevert = storesToExecute.slice(0, i) | ||
storesToRevert.reverse() | ||
for (const storeToRevert of storesToRevert) { | ||
const pluginRevertAction = globalConfig.stores[storeToRevert].revert | ||
const pluginModuleConfig = getPluginModuleConfig(moduleConfig, storeToRevert) | ||
await pluginRevertAction({ | ||
payload, | ||
collectionPath, | ||
docId, | ||
pluginModuleConfig, | ||
actionName, | ||
error: resultFromPlugin, // in this case the result is the error | ||
}) | ||
// revert eventFns, handle and await each eventFn in sequence | ||
for (const fn of eventNameFnsMap.revert) { | ||
await fn({ payload, result: resultFromPlugin, actionName, storeName, collectionPath, docId, path: modulePath, pluginModuleConfig }) // prettier-ignore | ||
} | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin | ||
} | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/') | ||
} | ||
} | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin) | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]) | ||
} | ||
} | ||
} | ||
} | ||
// now we must throw the error | ||
throw resultFromPlugin | ||
} | ||
// special handling for 'insert' (resultFromPlugin will always be `string`) | ||
if (actionName === 'insert' && isFullString(resultFromPlugin)) { | ||
// update the modulePath if a doc with random ID was inserted in a collection | ||
// if this is the case the result will be a string - the randomly genererated ID | ||
if (!docId) { | ||
docId = resultFromPlugin | ||
modulePath = [collectionPath, docId].filter(Boolean).join('/') | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
resolve(docFn(modulePath)) | ||
return | ||
} | ||
} | ||
// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`) | ||
if (actionName === 'fetch') { | ||
if (isDoOnFetch(resultFromPlugin)) { | ||
doOnFetchFns.push(resultFromPlugin) | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) { | ||
resolve(docFn(modulePath, moduleConfig)) | ||
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey) | ||
return | ||
} | ||
if (isFetchResponse(resultFromPlugin)) { | ||
for (const docRetrieved of resultFromPlugin.docs) { | ||
executeOnFns(doOnFetchFns, docRetrieved.data, [docRetrieved]) | ||
} | ||
} | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
resolve(collectionFn(modulePath, moduleConfig)) | ||
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey) | ||
} catch (error) { | ||
reject(error) | ||
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey) | ||
} | ||
} | ||
}) | ||
// anything that's executed from a "collection" module: | ||
// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection: | ||
if (actionName === 'insert' && docId) { | ||
// we do not pass the `moduleConfig`, because it's the moduleConfig of the "collection" in this case | ||
return docFn(modulePath) | ||
if (actionName === 'fetch') { | ||
fetchPromises.set(fetchPromiseKey, actionPromise) | ||
} | ||
// anything that's executed from a "doc" module: | ||
if (docId || !collectionFn) return docFn(modulePath, moduleConfig) | ||
// all other actions triggered on collections ('fetch' is the only possibility left) | ||
// should return the collection: | ||
return collectionFn(modulePath, moduleConfig) | ||
return actionPromise | ||
} | ||
} |
@@ -27,3 +27,3 @@ import { O } from 'ts-toolbelt' | ||
actionType: ActionType, | ||
streams: { | ||
streamPromiseInfo: { | ||
openStreams: OpenStreams | ||
@@ -37,3 +37,3 @@ findStream: FindStream | ||
return async function (payload?: any, actionConfig: ActionConfig = {}): Promise<void> { | ||
const { openStreams, openStreamPromises, findStreamPromise } = streams | ||
const { openStreams, openStreamPromises, findStreamPromise } = streamPromiseInfo | ||
const foundStreamPromise = findStreamPromise(payload) | ||
@@ -40,0 +40,0 @@ if (isPromise(foundStreamPromise)) return foundStreamPromise |
@@ -104,1 +104,6 @@ import { O } from 'ts-toolbelt' | ||
export type FindStreamPromise = (streamPayload?: any) => Promise<void> | undefined | ||
/** | ||
* All fetch promises with the payload passed to `fetch(payload)` as key (JSON.stringify) and the "fetch promise" as value. In case `fetch()` had no payload, use `undefined` | ||
*/ | ||
export type FetchPromises = Map<string, Promise<any>> |
export type StoreName = string | ||
export type DocMetadata = { | ||
data: Record<string, any> | ||
data: Record<string, any> | undefined | ||
id: string | ||
@@ -6,0 +6,0 @@ /** |
@@ -44,3 +44,3 @@ import test from 'ava' | ||
const prop = 'age' | ||
t.deepEqual(trainerModule.data.age, 10) | ||
t.deepEqual(trainerModule.data?.age, 10) | ||
@@ -51,3 +51,2 @@ // create references on beforehand | ||
const magnetarCollectionData = magnetar.collection('app-data').data // prettier-ignore | ||
console.log(`magnetarCollectionData → `, magnetarCollectionData) | ||
@@ -62,7 +61,7 @@ try { | ||
// check data of references created on beforehand | ||
t.deepEqual(magnetarDoc.data[prop], undefined) | ||
t.deepEqual(magnetarCollectionDoc.data[prop], undefined) | ||
t.deepEqual(magnetarDoc.data?.[prop], undefined) | ||
t.deepEqual(magnetarCollectionDoc.data?.[prop], undefined) | ||
t.deepEqual(magnetarCollectionData.get('trainer')?.[prop], undefined) | ||
// check data of references executed on | ||
t.deepEqual(trainerModule.data[prop], undefined) | ||
t.deepEqual(trainerModule.data?.[prop], undefined) | ||
}) | ||
@@ -110,3 +109,3 @@ | ||
test('read: get (collection)', async (t) => { | ||
test('read: fetch (collection)', async (t) => { | ||
// 'fetch' resolves once all stores have given a response with data | ||
@@ -128,3 +127,3 @@ const { pokedexModule } = createMagnetarInstance() | ||
test('read: get (document)', async (t) => { | ||
test('read: fetch (document)', async (t) => { | ||
// get resolves once all stores have given a response with data | ||
@@ -131,0 +130,0 @@ const { trainerModule } = createMagnetarInstance() |
@@ -5,3 +5,3 @@ { | ||
"declaration": true, | ||
"declarationDir": "./types/", | ||
"declarationDir": "./dist/types/", | ||
// This must be specified if "paths" is set | ||
@@ -8,0 +8,0 @@ "baseUrl": "." |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
260500
5864