@worldbrain/storex-sync
Version:
Offline-first syncing between multiple databases using Storex
139 lines (125 loc) • 5.13 kB
text/typescript
import { getObjectWithoutPk, getObjectPk } from '../utils'
import { ClientSyncLogEntry } from '../client-sync-log/types'
import { StorageRegistry, OperationBatch } from '@worldbrain/storex';
export type ExecuteAndLog = (originalOperation, logEntries : ClientSyncLogEntry[]) => Promise<any>
export interface OperationProcessorArgs {
next : {process: ({operation}) => any},
operation : any[],
executeAndLog : ExecuteAndLog,
getNow : () => number,
storageRegistry : StorageRegistry
includeCollections : Set<string>
}
export type OperationProcessor = (args : OperationProcessorArgs) => Promise<any>
export type OperationProcessorMap = {[operation : string] : OperationProcessor}
export const DEFAULT_OPERATION_PROCESSORS : OperationProcessorMap = {
createObject: _processCreateObject,
updateObject: _processUpdateObject,
updateObjects: _processUpdateObjects,
executeBatch: _processExecuteBatch,
}
async function _processCreateObject({next, operation, executeAndLog, getNow, includeCollections, storageRegistry} : OperationProcessorArgs) {
const [collection, value] = operation.slice(1)
if (!includeCollections.has(collection)) {
return next.process({operation})
}
const result = await executeAndLog(
{placeholder: 'object', operation: 'createObject', collection, args: value},
[_logEntryForCreateObject({collection, value, getNow, storageRegistry}) as ClientSyncLogEntry]
)
const object = result.info.object.object
return {object}
}
function _logEntryForCreateObject(
{collection, value, getNow, storageRegistry} :
{collection : string, value, getNow : () => number, storageRegistry : StorageRegistry}
) : ClientSyncLogEntry {
return {
collection,
createdOn: getNow(),
needsIntegration: false,
sharedOn: null,
operation: 'create',
pk: getObjectPk(value, collection, storageRegistry),
value: getObjectWithoutPk(value, collection, storageRegistry)
}
}
async function _processUpdateObject({next, operation, executeAndLog, getNow, includeCollections, storageRegistry} : OperationProcessorArgs) {
const [collection, where, updates] = operation.slice(1)
if (!includeCollections.has(collection)) {
return next.process({operation})
}
const pk = getObjectPk(where, collection, storageRegistry)
const logEntries : ClientSyncLogEntry[] = []
for (const [fieldName, newValue] of Object.entries(updates)) {
logEntries.push({
createdOn: getNow(),
sharedOn: null,
needsIntegration: false,
field: fieldName,
collection,
operation: 'modify',
pk: pk,
value: newValue
})
}
await executeAndLog(
{placeholder: 'object', operation: 'updateObjects', collection, where, updates},
logEntries,
)
}
async function _processUpdateObjects({next, operation, executeAndLog, getNow, includeCollections, storageRegistry} : OperationProcessorArgs) {
const [collection, where, updates] = operation.slice(1)
if (!includeCollections.has(collection)) {
return next.process({operation})
}
const logEntries : ClientSyncLogEntry[] = await _logEntriesForUpdateObjects({
next, collection, where, updates, getNow, storageRegistry
})
await executeAndLog(
{placeholder: 'update', operation: 'updateObjects', collection, where, updates},
logEntries,
)
}
async function _logEntriesForUpdateObjects(
{next, collection, where, updates, getNow, storageRegistry} :
{next : {process: ({operation}) => any}, collection : string, where, updates, getNow : () => number, storageRegistry : StorageRegistry}
) {
const affected = await next.process({operation: ['findObjects', collection, where]})
const logEntries : ClientSyncLogEntry[] = []
for (const object of affected) {
const pk = getObjectPk(object, collection, storageRegistry)
for (const [fieldName, newValue] of Object.entries(updates)) {
logEntries.push({
createdOn: getNow(),
sharedOn: null,
needsIntegration: false,
field: fieldName,
collection,
operation: 'modify',
pk: pk,
value: newValue
})
}
}
return logEntries
}
async function _processExecuteBatch({next, operation, executeAndLog, getNow, includeCollections, storageRegistry} : OperationProcessorArgs) {
const batch : OperationBatch = operation[1]
const logEntries : ClientSyncLogEntry[] = []
for (const step of batch) {
if (!includeCollections.has(step.collection)) {
continue
}
if (step.operation === 'createObject') {
logEntries.push(_logEntryForCreateObject({collection: step.collection, value: step.args, getNow, storageRegistry}))
}
}
if (!logEntries) {
return next.process({ operation })
}
return executeAndLog(
batch,
logEntries,
)
}