Skip to content

Commit

Permalink
refactor: split up code to make more sense, closes #103
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed May 30, 2024
1 parent 3055070 commit cb52f7b
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 194 deletions.
28 changes: 17 additions & 11 deletions packages/core/src/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,16 @@ import type {
WhereFilterOp,
WriteLock,
} from '@magnetarjs/types'
import { actionNameTypeMap } from '@magnetarjs/types'
import { merge, mergeAndConcat } from 'merge-anything'
import {
executeSetupModulePerStore,
getCountFromDataStore,
getDataFromDataStore,
proxify,
} from './helpers/moduleHelpers'
import {
handleActionPerStore,
HandleActionSharedParams,
} from './moduleActions/handleActionPerStore'
import { HandleFetchPerStoreParams, handleFetchPerStore } from './moduleActions/handleFetchPerStore'
import { handleStreamPerStore } from './moduleActions/handleStreamPerStore'
import { HandleWritePerStoreParams, handleWritePerStore } from './moduleActions/handleWritePerStore'

export function createCollectionWithContext(
collectionPath: string,
Expand All @@ -55,7 +52,16 @@ export function createCollectionWithContext(
return docFn(`${path}/${docId}`, merge(moduleConfig, _moduleConfig))
}) as any

const sharedParams: HandleActionSharedParams = {
const writeParams: HandleWritePerStoreParams = {
collectionPath,
_docId: undefined,
moduleConfig,
globalConfig,
writeLockMap,
docFn,
collectionFn,
}
const fetchParams: HandleFetchPerStoreParams = {
collectionPath,
_docId: undefined,
moduleConfig,
Expand All @@ -66,11 +72,11 @@ export function createCollectionWithContext(
collectionFn,
setLastFetched: fetchMeta.set,
}
const insert = handleActionPerStore(sharedParams, 'insert', actionNameTypeMap.insert) as MagnetarInsertAction //prettier-ignore
const _delete = handleActionPerStore(sharedParams, 'delete', actionNameTypeMap.delete) as MagnetarDeleteAction //prettier-ignore
const fetch = handleActionPerStore(sharedParams, 'fetch', actionNameTypeMap.fetch) as MagnetarFetchAction<Record<string, unknown>, 'collection'> //prettier-ignore
const fetchCount = handleActionPerStore(sharedParams, 'fetchCount', actionNameTypeMap.fetchCount) as MagnetarFetchCountAction // prettier-ignore
const stream = handleStreamPerStore([collectionPath, undefined], moduleConfig, globalConfig, actionNameTypeMap.stream, streaming, cacheStream, writeLockMap) // prettier-ignore
const insert = handleWritePerStore(writeParams, 'insert') as MagnetarInsertAction //prettier-ignore
const _delete = handleWritePerStore(writeParams, 'delete') as MagnetarDeleteAction //prettier-ignore
const fetch = handleFetchPerStore(fetchParams, 'fetch') as MagnetarFetchAction<Record<string, unknown>, 'collection'> //prettier-ignore
const fetchCount = handleFetchPerStore(fetchParams, 'fetchCount') as MagnetarFetchCountAction // prettier-ignore
const stream = handleStreamPerStore([collectionPath, undefined], moduleConfig, globalConfig, 'write', streaming, cacheStream, writeLockMap) // prettier-ignore

const actions = { stream, fetch, fetchCount, insert, delete: _delete }

Expand Down
33 changes: 19 additions & 14 deletions packages/core/src/Doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,15 @@ import type {
ModuleConfig,
WriteLock,
} from '@magnetarjs/types'
import { actionNameTypeMap } from '@magnetarjs/types'
import {
executeSetupModulePerStore,
getDataFromDataStore,
getExistsFromDataStore,
proxify,
} from './helpers/moduleHelpers'
import {
handleActionPerStore,
HandleActionSharedParams,
} from './moduleActions/handleActionPerStore'
import { HandleFetchPerStoreParams, handleFetchPerStore } from './moduleActions/handleFetchPerStore'
import { handleStreamPerStore } from './moduleActions/handleStreamPerStore'
import { HandleWritePerStoreParams, handleWritePerStore } from './moduleActions/handleWritePerStore'

export function createDocWithContext(
[collectionPath, docId]: [string, string],
Expand All @@ -47,7 +44,15 @@ export function createDocWithContext(
return collectionFn(`${path}/${collectionId}`, _moduleConfig)
}

const sharedParams: HandleActionSharedParams = {
const writeParams: HandleWritePerStoreParams = {
collectionPath,
_docId: docId,
moduleConfig,
globalConfig,
writeLockMap,
docFn,
}
const fetchParams: HandleFetchPerStoreParams = {
collectionPath,
_docId: docId,
moduleConfig,
Expand All @@ -57,14 +62,14 @@ export function createDocWithContext(
docFn,
}
const actions = {
insert: (handleActionPerStore(sharedParams, 'insert', actionNameTypeMap.insert) as MagnetarInsertAction), // prettier-ignore
merge: (handleActionPerStore(sharedParams, 'merge', actionNameTypeMap.merge) as MagnetarWriteAction), // prettier-ignore
assign: (handleActionPerStore(sharedParams, 'assign', actionNameTypeMap.assign) as MagnetarWriteAction), // prettier-ignore
replace: (handleActionPerStore(sharedParams, 'replace', actionNameTypeMap.replace) as MagnetarWriteAction), // prettier-ignore
deleteProp: (handleActionPerStore(sharedParams, 'deleteProp', actionNameTypeMap.deleteProp) as MagnetarDeletePropAction), // prettier-ignore
delete: (handleActionPerStore(sharedParams, 'delete', actionNameTypeMap.delete) as MagnetarDeleteAction), // prettier-ignore
fetch: (handleActionPerStore(sharedParams, 'fetch', actionNameTypeMap.fetch) as MagnetarFetchAction<Record<string, unknown>, 'doc'>), // prettier-ignore
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, actionNameTypeMap.stream, streaming, cacheStream, writeLockMap), // prettier-ignore
insert: (handleWritePerStore(writeParams, 'insert') as MagnetarInsertAction), // prettier-ignore
merge: (handleWritePerStore(writeParams, 'merge') as MagnetarWriteAction), // prettier-ignore
assign: (handleWritePerStore(writeParams, 'assign') as MagnetarWriteAction), // prettier-ignore
replace: (handleWritePerStore(writeParams, 'replace') as MagnetarWriteAction), // prettier-ignore
deleteProp: (handleWritePerStore(writeParams, 'deleteProp') as MagnetarDeletePropAction), // prettier-ignore
delete: (handleWritePerStore(writeParams, 'delete') as MagnetarDeleteAction), // prettier-ignore
fetch: (handleFetchPerStore(fetchParams, 'fetch') as MagnetarFetchAction<Record<string, unknown>, 'doc'>), // prettier-ignore
stream: handleStreamPerStore([collectionPath, docId], moduleConfig, globalConfig, 'write', streaming, cacheStream, writeLockMap), // prettier-ignore
}

// Every store will have its 'setupModule' function executed
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Magnetar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import type {
WriteLock,
} from '@magnetarjs/types'
import {
MODULE_IDENTIFIER_SPLIT,
getPathFilterIdentifier,
getPathWhereOrderByIdentifier,
MODULE_IDENTIFIER_SPLIT,
} from '@magnetarjs/types'
import { mapGetOrSet } from 'getorset-anything'
import { isString } from 'is-what'
Expand Down Expand Up @@ -95,7 +95,7 @@ export function Magnetar(magnetarConfig: GlobalConfig): MagnetarInstance {
const fetchPromises = mapGetOrSet(
fetchPromiseMap,
pathFilterIdentifier,
(): FetchPromises => new Map()
(): FetchPromises => ({ fetch: new Map(), fetchCount: new Map() })
)
// Create the FetchMeta helpers for this module
const pathWhereOrderByIdentifier = getPathWhereOrderByIdentifier(modulePath, moduleConfig)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
ActionConfig,
ActionName,
ActionTernary,
ActionType,
CollectionFn,
DocFn,
DoOnFetch,
Expand All @@ -12,19 +11,14 @@ import type {
FetchPromises,
FetchResponse,
GlobalConfig,
MagnetarDeleteAction,
MagnetarDeletePropAction,
MagnetarFetchAction,
MagnetarFetchCountAction,
MagnetarInsertAction,
MagnetarWriteAction,
ModuleConfig,
OnAddedFn,
SyncBatch,
WriteLock,
} from '@magnetarjs/types'
import { mapGetOrSet } from 'getorset-anything'
import { isBoolean, isFullArray, isFullString, isPromise } from 'is-what'
import { isBoolean, isPromise } from 'is-what'
import { getEventNameFnsMap } from '../helpers/eventHelpers'
import { executeOnFns } from '../helpers/executeOnFns'
import { getModifyPayloadFnsMap } from '../helpers/modifyPayload'
Expand All @@ -40,7 +34,7 @@ import {
import { throwIfNoFnsToExecute } from '../helpers/throwFns'
import { handleAction } from './handleAction'

export type HandleActionSharedParams = {
export type HandleFetchPerStoreParams = {
collectionPath: string
_docId: string | undefined
moduleConfig: ModuleConfig
Expand All @@ -52,64 +46,31 @@ export type HandleActionSharedParams = {
setLastFetched?: (payload: FetchMetaDataCollection) => void
}

export function handleActionPerStore<TActionName extends Exclude<ActionName, 'stream'>>(
sharedParams: HandleActionSharedParams,
actionName: TActionName,
actionType: ActionType
): ActionTernary<TActionName>
export function handleActionPerStore(
sharedParams: HandleActionSharedParams,
actionName: Exclude<ActionName, 'stream'>,
actionType: ActionType
):
| MagnetarFetchCountAction
| MagnetarFetchAction<any>
| MagnetarWriteAction<any>
| MagnetarInsertAction<any>
| MagnetarDeleteAction
| MagnetarDeletePropAction<any> {
export function handleFetchPerStore<
TActionName extends Extract<ActionName, 'fetch' | 'fetchCount'>
>(sharedParams: HandleFetchPerStoreParams, actionName: TActionName): ActionTernary<TActionName>
export function handleFetchPerStore(
sharedParams: HandleFetchPerStoreParams,
actionName: Extract<ActionName, 'fetch' | 'fetchCount'>
): MagnetarFetchCountAction | MagnetarFetchAction<any> {
const { collectionPath, _docId, moduleConfig, globalConfig, fetchPromises, writeLockMap, docFn, collectionFn, setLastFetched } = sharedParams // prettier-ignore

// returns the action the dev can call with myModule.insert() etc.
return function (payload?: any, actionConfig: ActionConfig = {}): Promise<any> {
// first of all, check if the same fetch call was just made or not, if so return the same fetch promise early
const fetchPromiseKey = JSON.stringify(payload)
const foundFetchPromise = fetchPromises.get(fetchPromiseKey)
const foundFetchPromise = fetchPromises[actionName].get(fetchPromiseKey)
// return the same fetch promise early if it's not yet resolved
if (actionName === 'fetch' && isPromise(foundFetchPromise)) return foundFetchPromise

// set up and/or reset te writeLock for write actions
const writeLockId = _docId ? `${collectionPath}/${_docId}` : collectionPath
const writeLock = mapGetOrSet(writeLockMap, writeLockId, (): WriteLock => {
return { promise: null, resolve: () => {}, countdown: null }
})

if (actionName !== 'fetch' && actionName !== 'fetchCount') {
// we need to create a promise we'll resolve later to prevent any incoming docs from being written to the local state during this time
if (writeLock.promise === null) {
writeLock.promise = new Promise<void>((resolve) => {
writeLock.resolve = () => {
resolve()
writeLock.resolve = () => {}
writeLock.promise = null
if (writeLock.countdown !== null) {
clearTimeout(writeLock.countdown)
writeLock.countdown = null
}
}
})
}
if (writeLock.promise !== null && writeLock.countdown !== null) {
// there already is a promise, let's just stop the countdown, we'll start it again at the end of all the store actions
clearTimeout(writeLock.countdown)
writeLock.countdown = null
}
}
const writeLock = writeLockMap.get(writeLockId)

// eslint-disable-next-line no-async-promise-executor
const actionPromise = new Promise<any>(async (resolve, reject) => {
let docId = _docId
let modulePath = [collectionPath, docId].filter(Boolean).join('/')
const docId = _docId
const modulePath = [collectionPath, docId].filter(Boolean).join('/')
/**
* Are we forcing to check in with the DB or can we be satisfied with only optimistic fetch?
*/
Expand All @@ -118,7 +79,7 @@ export function handleActionPerStore(
force || (docId ? docFn(modulePath, moduleConfig).exists !== true : false)
// we need to await any writeLock _before_ fetching, to prevent grabbing outdated data
if (actionName === 'fetch' && willForceFetch) {
await writeLock.promise
await writeLock?.promise
if (!_docId) {
// we need to await all promises of all docs in this collection...
const collectionWriteMaps = getCollectionWriteLocks(collectionPath, writeLockMap)
Expand Down Expand Up @@ -147,9 +108,9 @@ export function handleActionPerStore(
const storesToExecute: string[] =
actionConfig.executionOrder ||
(moduleConfig.executionOrder || {})[actionName] ||
(moduleConfig.executionOrder || {})[actionType] ||
(moduleConfig.executionOrder || {})['read'] ||
(globalConfig.executionOrder || {})[actionName] ||
(globalConfig.executionOrder || {})[actionType] ||
(globalConfig.executionOrder || {})['read'] ||
[]
throwIfNoFnsToExecute(storesToExecute)
// update the payload
Expand Down Expand Up @@ -228,51 +189,14 @@ export function handleActionPerStore(

// 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,
actionConfig,
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
}
}
// we must update the `exists` prop for fetch calls
if (actionName === 'fetch' && docId) {
doOnFetchFns.forEach((fn) => fn(undefined, 'error'))
}
if (actionName !== 'fetch' && actionName !== 'fetchCount') {
writeLock.resolve()
}
// now we must throw the error
throw resultFromPlugin
}

// special handling for 'insert' (resultFromPlugin will always be `string | [string, SyncBatch]`)
if (actionName === 'insert') {
// 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) {
if (isFullString(resultFromPlugin)) {
docId = resultFromPlugin
}
if (isFullArray(resultFromPlugin) && isFullString(resultFromPlugin[0])) {
docId = resultFromPlugin[0]
}
modulePath = [collectionPath, docId].filter(Boolean).join('/')
}
}

// special handling for 'fetch' (resultFromPlugin will always be `FetchResponse | OnAddedFn`)
if (actionName === 'fetch') {
if (isDoOnFetch(resultFromPlugin)) {
Expand Down Expand Up @@ -319,42 +243,30 @@ export function handleActionPerStore(
}
// all the stores resolved their actions

// return fetchCount
fetchPromises[actionName].delete(fetchPromiseKey)

if (actionName === 'fetchCount') {
// return the fetchCount
resolve(fetchCount)
return
}

// start the writeLock countdown
if (actionName !== 'fetch' && !writeLock.countdown) {
writeLock.countdown = setTimeout(writeLock.resolve, 5000)
}

// 'insert' always returns a DocInstance, unless the "abort" action was called, then the modulePath might still be a collection:
if (actionName === 'insert' && docId) {
resolve(docFn(modulePath, moduleConfig))
return
}

// anything that's executed from a "doc" module:
if (docId || !collectionFn) {
// return the module's up-to-date data
resolve(docFn(modulePath, moduleConfig).data)
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey)
return
}

// all other actions triggered on collections ('fetch' is the only possibility left)
// return the collectionFetchResult
resolve(collectionFetchResult)
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey)
} catch (error) {
reject(error)
if (actionName === 'fetch') fetchPromises.delete(fetchPromiseKey)
fetchPromises[actionName].delete(fetchPromiseKey)
}
})

if (actionName === 'fetch') {
fetchPromises.set(fetchPromiseKey, actionPromise)
}
fetchPromises[actionName].set(fetchPromiseKey, actionPromise)

return actionPromise
}
Expand Down
Loading

0 comments on commit cb52f7b

Please sign in to comment.