/
fetchLocal.js
85 lines (77 loc) 路 2.87 KB
/
fetchLocal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// @flow
import {
// $FlowFixMe
promiseAllObject,
map,
reduce,
values,
pipe,
any,
identity,
} from 'rambdax'
import { unnest, allPromises } from '../../utils/fp'
import type { Database, Collection, Model } from '../..'
import * as Q from '../../QueryDescription'
import { columnName } from '../../Schema'
import type { SyncTableChangeSet, SyncDatabaseChangeSet } from '../index'
import { ensureActionsEnabled } from './helpers'
export type SyncLocalChanges = $Exact<{ changes: SyncDatabaseChangeSet, affectedRecords: Model[] }>
const notSyncedQuery = Q.where(columnName('_status'), Q.notEq('synced'))
// TODO: It would be best to omit _status, _changed fields, since they're not necessary for the server
// but this complicates markLocalChangesAsDone, since we don't have the exact copy to compare if record changed
// TODO: It would probably also be good to only send to server locally changed fields, not full records
const rawsForStatus = (status, records) =>
reduce(
(raws, record) => (record._raw._status === status ? raws.concat({ ...record._raw }) : raws),
[],
records,
)
async function fetchLocalChangesForCollection<T: Model>(
collection: Collection<T>,
): Promise<[SyncTableChangeSet, T[]]> {
const changedRecords = await collection.query(notSyncedQuery).fetch()
const changeSet = {
created: rawsForStatus('created', changedRecords),
updated: rawsForStatus('updated', changedRecords),
deleted: await collection.database.adapter.getDeletedRecords(collection.table),
}
return [changeSet, changedRecords]
}
const extractChanges = map(([changeSet]) => changeSet)
const extractAllAffectedRecords = pipe(
values,
map(([, records]) => records),
unnest,
)
export default function fetchLocalChanges(db: Database): Promise<SyncLocalChanges> {
ensureActionsEnabled(db)
return db.action(async () => {
const changes = await promiseAllObject(
map(
fetchLocalChangesForCollection,
// $FlowFixMe
db.collections.map,
),
)
// TODO: deep-freeze changes object (in dev mode only) to detect mutations (user bug)
return {
// $FlowFixMe
changes: extractChanges(changes),
affectedRecords: extractAllAffectedRecords(changes),
}
}, 'sync-fetchLocalChanges')
}
export function hasUnsyncedChanges(db: Database): Promise<boolean> {
ensureActionsEnabled(db)
// action is necessary to ensure other code doesn't make changes under our nose
return db.action(async () => {
const collections = values(db.collections.map)
const hasUnsynced = async collection => {
const changes = await collection.query(notSyncedQuery).fetchCount()
const deleted = await db.adapter.getDeletedRecords(collection.table)
return changes + deleted.length > 0
}
const unsyncedFlags = await allPromises(hasUnsynced, collections)
return any(identity, unsyncedFlags)
}, 'sync-hasUnsyncedChanges')
}