@recordtimelabel/core 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/package.json +1 -1
- package/src/index.js +124 -32
package/README.md
CHANGED
|
@@ -17,11 +17,13 @@ During local development an app can consume a sibling checkout with:
|
|
|
17
17
|
"@recordtimelabel/core": "file:../recordtimelabel-core"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The current release
|
|
20
|
+
For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The current published release is:
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
|
-
"@recordtimelabel/core": "0.1.
|
|
23
|
+
"@recordtimelabel/core": "0.1.9"
|
|
24
24
|
```
|
|
25
|
+
|
|
26
|
+
If this checkout's `package.json` is ahead of the published version, publish the new package before updating consumers to that version.
|
|
25
27
|
|
|
26
28
|
## Public API
|
|
27
29
|
|
|
@@ -32,8 +34,8 @@ For release builds, consume a fixed npm package, git tag, or private registry ve
|
|
|
32
34
|
- `applyRecordTimeLabelOperation(state, operation)`
|
|
33
35
|
- `createSyncEngine({ storageAdapter, cloudAdapter, clientId, clock, logger })`
|
|
34
36
|
- `createRecordTimeLabelController({ storageAdapter, cloudAdapter, clientId, settingKeys, clock, logger })`
|
|
35
|
-
- `buildRecordTimeLabelSyncPayload({ data, previousCloudData, pendingOps, syncMode, clientId, now })`
|
|
36
|
-
- `buildSyncPayloadWithOperations({ data, previousCloudData, pendingOps, syncMode, clientId, now })`
|
|
37
|
+
- `buildRecordTimeLabelSyncPayload({ data, previousCloudData, pendingOps, syncMode, clientId, now, groupOrderNormalizer })`
|
|
38
|
+
- `buildSyncPayloadWithOperations({ data, previousCloudData, pendingOps, syncMode, clientId, now, groupOrderNormalizer })`
|
|
37
39
|
- `flushPendingOperations({ operations, storageAdapter, cloudAdapter, clientId, now })`
|
|
38
40
|
- `mergeRemoteStateIntoLocal({ localData, remoteData, pendingOps, settingKeys, clientId, now })`
|
|
39
41
|
- `buildLocalStateFromStorage(data, options)`
|
|
@@ -41,6 +43,7 @@ For release builds, consume a fixed npm package, git tag, or private registry ve
|
|
|
41
43
|
- `buildFirestoreV1UserPatch(state, options)`
|
|
42
44
|
- `buildFirestoreV2LogicalPaths(userId)`
|
|
43
45
|
- `buildFirestoreV2DocumentsFromState(state, options)`
|
|
46
|
+
- `buildFirestoreV2DocumentChangeSet(previousDocuments, nextDocuments, options)`
|
|
44
47
|
- `buildStateFromFirestoreV2Documents(documents, options)`
|
|
45
48
|
- `hasMeaningfulRecordTimeLabelCloudState(data, options)`
|
|
46
49
|
- `buildRecordTimeLabelContentFingerprint(data)`
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const REQUIRED_FOLDERS = [
|
|
|
5
5
|
{ id: DEFAULT_FOLDER_ID, name: 'Uncategorized' }
|
|
6
6
|
];
|
|
7
7
|
|
|
8
|
-
export const RECORD_TIMELABEL_CORE_VERSION = '0.1.
|
|
8
|
+
export const RECORD_TIMELABEL_CORE_VERSION = '0.1.9';
|
|
9
9
|
|
|
10
10
|
export const OPERATION_TYPES = Object.freeze({
|
|
11
11
|
RECORD_CREATE: 'record.create',
|
|
@@ -1274,6 +1274,94 @@ export const buildFirestoreV2DocumentsFromState = (state = {}, options = {}) =>
|
|
|
1274
1274
|
};
|
|
1275
1275
|
};
|
|
1276
1276
|
|
|
1277
|
+
const canonicalizeFirestoreV2DocumentValue = (value) => {
|
|
1278
|
+
if (Array.isArray(value)) {
|
|
1279
|
+
return value.map((entry) => canonicalizeFirestoreV2DocumentValue(entry));
|
|
1280
|
+
}
|
|
1281
|
+
if (value instanceof Date) {
|
|
1282
|
+
return value.toISOString();
|
|
1283
|
+
}
|
|
1284
|
+
if (!value || typeof value !== 'object') {
|
|
1285
|
+
return value;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
return Object.keys(value)
|
|
1289
|
+
.sort()
|
|
1290
|
+
.reduce((result, key) => {
|
|
1291
|
+
result[key] = canonicalizeFirestoreV2DocumentValue(value[key]);
|
|
1292
|
+
return result;
|
|
1293
|
+
}, {});
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const areFirestoreV2DocumentValuesEqual = (left, right) => (
|
|
1297
|
+
JSON.stringify(canonicalizeFirestoreV2DocumentValue(left)) ===
|
|
1298
|
+
JSON.stringify(canonicalizeFirestoreV2DocumentValue(right))
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
const buildFirestoreV2CollectionChangeSet = (previousCollection, nextCollection, allowDeletes) => {
|
|
1302
|
+
const previous = previousCollection && typeof previousCollection === 'object'
|
|
1303
|
+
? previousCollection
|
|
1304
|
+
: {};
|
|
1305
|
+
const next = nextCollection && typeof nextCollection === 'object'
|
|
1306
|
+
? nextCollection
|
|
1307
|
+
: {};
|
|
1308
|
+
const upserts = {};
|
|
1309
|
+
|
|
1310
|
+
Object.keys(next).sort().forEach((documentId) => {
|
|
1311
|
+
if (!Object.prototype.hasOwnProperty.call(previous, documentId) ||
|
|
1312
|
+
!areFirestoreV2DocumentValuesEqual(previous[documentId], next[documentId])) {
|
|
1313
|
+
upserts[documentId] = next[documentId];
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
const deleteIds = allowDeletes
|
|
1318
|
+
? Object.keys(previous).filter((documentId) => (
|
|
1319
|
+
!Object.prototype.hasOwnProperty.call(next, documentId)
|
|
1320
|
+
)).sort()
|
|
1321
|
+
: [];
|
|
1322
|
+
|
|
1323
|
+
return { upserts, deleteIds };
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
export const buildFirestoreV2DocumentChangeSet = (
|
|
1327
|
+
previousDocuments = {},
|
|
1328
|
+
nextDocuments = {},
|
|
1329
|
+
options = {}
|
|
1330
|
+
) => {
|
|
1331
|
+
const allowDeletes = options.allowDeletes === true;
|
|
1332
|
+
const previousRoot = previousDocuments?.root;
|
|
1333
|
+
const nextRoot = nextDocuments?.root;
|
|
1334
|
+
const rootUpsert = nextRoot && !areFirestoreV2DocumentValuesEqual(previousRoot, nextRoot)
|
|
1335
|
+
? nextRoot
|
|
1336
|
+
: null;
|
|
1337
|
+
const records = buildFirestoreV2CollectionChangeSet(
|
|
1338
|
+
previousDocuments?.records,
|
|
1339
|
+
nextDocuments?.records,
|
|
1340
|
+
allowDeletes
|
|
1341
|
+
);
|
|
1342
|
+
const folders = buildFirestoreV2CollectionChangeSet(
|
|
1343
|
+
previousDocuments?.folders,
|
|
1344
|
+
nextDocuments?.folders,
|
|
1345
|
+
allowDeletes
|
|
1346
|
+
);
|
|
1347
|
+
const ops = buildFirestoreV2CollectionChangeSet(
|
|
1348
|
+
previousDocuments?.ops,
|
|
1349
|
+
nextDocuments?.ops,
|
|
1350
|
+
allowDeletes
|
|
1351
|
+
);
|
|
1352
|
+
const hasCollectionChanges = [records, folders, ops].some((changeSet) => (
|
|
1353
|
+
Object.keys(changeSet.upserts).length > 0 || changeSet.deleteIds.length > 0
|
|
1354
|
+
));
|
|
1355
|
+
|
|
1356
|
+
return {
|
|
1357
|
+
hasChanges: Boolean(rootUpsert) || hasCollectionChanges,
|
|
1358
|
+
root: { upsert: rootUpsert },
|
|
1359
|
+
records,
|
|
1360
|
+
folders,
|
|
1361
|
+
ops
|
|
1362
|
+
};
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1277
1365
|
const removeV2Metadata = (data = {}) => {
|
|
1278
1366
|
const { schemaVersion, folderId, ...rest } = data || {};
|
|
1279
1367
|
return rest;
|
|
@@ -1502,14 +1590,14 @@ const resolveMergedOrder = ({
|
|
|
1502
1590
|
return mergeOrderArrays(normalizer, remoteData?.[stateKey], localData?.[stateKey]);
|
|
1503
1591
|
};
|
|
1504
1592
|
|
|
1505
|
-
const buildSyncStateInput = (data = {}, operationTime) => ({
|
|
1506
|
-
folders: data?.folders || [],
|
|
1507
|
-
records: data?.records || {},
|
|
1508
|
-
settings: data?.settings || {},
|
|
1509
|
-
groupOrder: data?.groupOrder || [],
|
|
1510
|
-
folderOrder: data?.folderOrder || [],
|
|
1511
|
-
expandedGroups: data?.expandedGroups || [],
|
|
1512
|
-
rtlSyncMeta: data?.rtlSyncMeta || {},
|
|
1593
|
+
const buildSyncStateInput = (data = {}, operationTime, options = {}) => ({
|
|
1594
|
+
folders: data?.folders || [],
|
|
1595
|
+
records: data?.records || {},
|
|
1596
|
+
settings: data?.settings || {},
|
|
1597
|
+
groupOrder: normalizeGroupOrderForOption(data?.groupOrder || [], options.groupOrderNormalizer),
|
|
1598
|
+
folderOrder: data?.folderOrder || [],
|
|
1599
|
+
expandedGroups: data?.expandedGroups || [],
|
|
1600
|
+
rtlSyncMeta: data?.rtlSyncMeta || {},
|
|
1513
1601
|
deletedRecordTombstones: data?.deletedRecordTombstones || {},
|
|
1514
1602
|
deletedFolderTombstones: data?.deletedFolderTombstones || {},
|
|
1515
1603
|
lastModified: operationTime
|
|
@@ -1518,23 +1606,24 @@ const buildSyncStateInput = (data = {}, operationTime) => ({
|
|
|
1518
1606
|
export const buildRecordTimeLabelSyncPayload = ({
|
|
1519
1607
|
data,
|
|
1520
1608
|
previousCloudData,
|
|
1521
|
-
pendingOps = [],
|
|
1522
|
-
syncMode = RECORD_TIMELABEL_SYNC_MODES.FULL,
|
|
1523
|
-
clientId = 'recordtimelabel-client',
|
|
1524
|
-
now = Date.now()
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1609
|
+
pendingOps = [],
|
|
1610
|
+
syncMode = RECORD_TIMELABEL_SYNC_MODES.FULL,
|
|
1611
|
+
clientId = 'recordtimelabel-client',
|
|
1612
|
+
now = Date.now(),
|
|
1613
|
+
groupOrderNormalizer
|
|
1614
|
+
} = {}) => {
|
|
1615
|
+
const operationTime = toFiniteTimestamp(data?.lastModified) || now;
|
|
1616
|
+
const payloadSource = syncMode === RECORD_TIMELABEL_SYNC_MODES.MERGE
|
|
1617
|
+
? mergeLocalRemote({
|
|
1618
|
+
localState: buildSyncStateInput(data || {}, operationTime, { groupOrderNormalizer }),
|
|
1619
|
+
remoteState: previousCloudData || {},
|
|
1620
|
+
pendingOps,
|
|
1621
|
+
clientId,
|
|
1622
|
+
now
|
|
1623
|
+
}).state
|
|
1624
|
+
: buildSyncStateInput(data || {}, operationTime, { groupOrderNormalizer });
|
|
1625
|
+
const sanitizedRecords = removeRecordSyncFlags(payloadSource.records || {});
|
|
1626
|
+
const deletionTombstones = buildDeletionTombstones({
|
|
1538
1627
|
previousState: previousCloudData,
|
|
1539
1628
|
nextRecords: sanitizedRecords,
|
|
1540
1629
|
nextFolders: payloadSource.folders || [],
|
|
@@ -1544,11 +1633,12 @@ export const buildRecordTimeLabelSyncPayload = ({
|
|
|
1544
1633
|
clientId
|
|
1545
1634
|
});
|
|
1546
1635
|
|
|
1547
|
-
return normalizeState({
|
|
1548
|
-
...payloadSource,
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1636
|
+
return normalizeState({
|
|
1637
|
+
...payloadSource,
|
|
1638
|
+
groupOrder: normalizeGroupOrderForOption(payloadSource.groupOrder, groupOrderNormalizer),
|
|
1639
|
+
records: sanitizedRecords,
|
|
1640
|
+
deletedRecordTombstones: deletionTombstones.deletedRecordTombstones,
|
|
1641
|
+
deletedFolderTombstones: deletionTombstones.deletedFolderTombstones,
|
|
1552
1642
|
lastModified: operationTime
|
|
1553
1643
|
});
|
|
1554
1644
|
};
|
|
@@ -1569,7 +1659,8 @@ export const buildSyncPayloadWithOperations = ({
|
|
|
1569
1659
|
pendingOps: [],
|
|
1570
1660
|
syncMode,
|
|
1571
1661
|
clientId,
|
|
1572
|
-
now
|
|
1662
|
+
now,
|
|
1663
|
+
groupOrderNormalizer
|
|
1573
1664
|
});
|
|
1574
1665
|
|
|
1575
1666
|
operations.forEach((operation) => {
|
|
@@ -2122,6 +2213,7 @@ export default {
|
|
|
2122
2213
|
buildFirestoreV1UserPatch,
|
|
2123
2214
|
buildFirestoreV2LogicalPaths,
|
|
2124
2215
|
buildFirestoreV2DocumentsFromState,
|
|
2216
|
+
buildFirestoreV2DocumentChangeSet,
|
|
2125
2217
|
buildStateFromFirestoreV2Documents,
|
|
2126
2218
|
flushPendingOperations,
|
|
2127
2219
|
mergeRemoteStateIntoLocal,
|