cozy-pouch-link 60.28.1 → 60.29.0

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.
@@ -184,6 +184,9 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
184
184
  _this.doctypes = doctypes;
185
185
  _this.doctypesReplicationOptions = doctypesReplicationOptions;
186
186
  _this.indexes = {};
187
+ /** @private Cache of per-drive logical-typed query engines, keyed by `dbDoctype::logicalDoctype`. */
188
+
189
+ _this._driveQueryEngines = new Map();
187
190
  _this.storage = new _localStorage.PouchLocalStorage(((_options$platform = options.platform) === null || _options$platform === void 0 ? void 0 : _options$platform.storage) || _platformWeb.platformWeb.storage);
188
191
  _this.initialSync = initialSync !== null && initialSync !== void 0 ? initialSync : false;
189
192
  _this.periodicSync = periodicSync !== null && periodicSync !== void 0 ? periodicSync : true;
@@ -201,14 +204,6 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
201
204
  _this.performanceApi = performanceApi || _cozyClient.defaultPerformanceApi;
202
205
  return _this;
203
206
  }
204
- /**
205
- * Return the PouchDB adapter name.
206
- * Should be IndexedDB for newest adapters.
207
- *
208
- * @param {import('./types').LocalStorage} localStorage Methods to access local storage
209
- * @returns {Promise<string>} The adapter name
210
- */
211
-
212
207
 
213
208
  (0, _createClass2.default)(PouchLink, [{
214
209
  key: "getReplicationURL",
@@ -458,10 +453,11 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
458
453
  queryEngine: this.queryEngine,
459
454
  client: this.client
460
455
  });
461
- _context3.next = 30;
456
+ this._driveQueryEngines = new Map();
457
+ _context3.next = 31;
462
458
  return this.pouches.init();
463
459
 
464
- case 30:
460
+ case 31:
465
461
  if (this.client && this.initialSync) {
466
462
  this.startReplication();
467
463
  }
@@ -471,7 +467,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
471
467
  category: 'CozyPouchLink'
472
468
  });
473
469
 
474
- case 32:
470
+ case 33:
475
471
  case "end":
476
472
  return _context3.stop();
477
473
  }
@@ -504,8 +500,9 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
504
500
  case 3:
505
501
  this.pouches = null;
506
502
  this.client = null;
503
+ this._driveQueryEngines = new Map();
507
504
 
508
- case 5:
505
+ case 6:
509
506
  case "end":
510
507
  return _context4.stop();
511
508
  }
@@ -743,11 +740,59 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
743
740
  value: function getSyncInfo(doctype) {
744
741
  return this.pouches.getSyncInfo(doctype);
745
742
  }
743
+ /**
744
+ * Returns the registered physical doctype for a given logical doctype and query options.
745
+ *
746
+ * @param {any} logicalDoctype - The logical doctype
747
+ * @param {object} [options] - Optional query options
748
+ */
749
+
750
+ }, {
751
+ key: "getDbDoctype",
752
+ value: function getDbDoctype(logicalDoctype, options) {
753
+ var _this4 = this;
754
+
755
+ var driveId = options === null || options === void 0 ? void 0 : options.driveId;
756
+ if (!driveId) return logicalDoctype;
757
+ var match = Object.keys(this.doctypesReplicationOptions || {}).find(function (d) {
758
+ var _this4$doctypesReplic;
759
+
760
+ return ((_this4$doctypesReplic = _this4.doctypesReplicationOptions[d]) === null || _this4$doctypesReplic === void 0 ? void 0 : _this4$doctypesReplic.driveId) === driveId;
761
+ });
762
+ return match || logicalDoctype;
763
+ }
764
+ /**
765
+ * @param {any} doctype - The doctype
766
+ * @param {object} [options] - Optional query options
767
+ */
768
+
746
769
  }, {
747
770
  key: "getQueryEngineFromDoctype",
748
- value: function getQueryEngineFromDoctype(doctype) {
749
- var dbName = (0, _utils.getDatabaseName)((0, _utils.getPrefix)(this.client.stackClient.uri), doctype);
750
- return this.pouches.getQueryEngine(dbName, doctype);
771
+ value: function getQueryEngineFromDoctype(doctype, options) {
772
+ var logicalDoctype = doctype;
773
+ var dbDoctype = this.getDbDoctype(doctype, options);
774
+ var dbName = (0, _utils.getDatabaseName)((0, _utils.getPrefix)(this.client.stackClient.uri), dbDoctype);
775
+
776
+ if (logicalDoctype !== dbDoctype) {
777
+ // Separate cache keyed by dbDoctype::logicalDoctype so a drive DB is never stamped with the wrong _type.
778
+ var cacheKey = "".concat(dbDoctype, "::").concat(logicalDoctype);
779
+
780
+ var engine = this._driveQueryEngines.get(cacheKey);
781
+
782
+ if (!engine) {
783
+ var QueryEngine =
784
+ /** @type {typeof PouchDBQueryEngine} */
785
+ this.queryEngine || _pouchdb.default;
786
+ engine = new QueryEngine(this.pouches, logicalDoctype);
787
+ engine.openDB(dbName);
788
+
789
+ this._driveQueryEngines.set(cacheKey, engine);
790
+ }
791
+
792
+ return engine;
793
+ }
794
+
795
+ return this.pouches.getQueryEngine(dbName, logicalDoctype);
751
796
  }
752
797
  }, {
753
798
  key: "getPouch",
@@ -757,12 +802,12 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
757
802
  }
758
803
  }, {
759
804
  key: "supportsOperation",
760
- value: function supportsOperation(operation) {
805
+ value: function supportsOperation(operation, options) {
761
806
  if (this.options.readOnly && operation.mutationType) {
762
807
  return false;
763
808
  }
764
809
 
765
- var impactedDoctype = (0, _cozyClient.getDoctypeFromOperation)(operation); // If the Pouch is configured only to replicate from the remote,
810
+ var impactedDoctype = this.getDbDoctype((0, _cozyClient.getDoctypeFromOperation)(operation), options); // If the Pouch is configured only to replicate from the remote,
766
811
  // we don't want to apply the mutation on it, but to forward
767
812
  // to the next link
768
813
 
@@ -776,6 +821,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
776
821
  var result,
777
822
  forward,
778
823
  doctype,
824
+ dbDoctype,
779
825
  _args6 = arguments;
780
826
  return _regenerator.default.wrap(function _callee6$(_context6) {
781
827
  while (1) {
@@ -784,7 +830,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
784
830
  result = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : null;
785
831
  forward = _args6.length > 3 && _args6[3] !== undefined ? _args6[3] : doNothing;
786
832
 
787
- if (!(options !== null && options !== void 0 && options.forceStack)) {
833
+ if (!((0, _cozyClient.resolveForceLink)(options) === 'stack')) {
788
834
  _context6.next = 4;
789
835
  break;
790
836
  }
@@ -793,50 +839,51 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
793
839
 
794
840
  case 4:
795
841
  doctype = (0, _cozyClient.getDoctypeFromOperation)(operation);
842
+ dbDoctype = this.getDbDoctype(doctype, options);
796
843
 
797
844
  if (this.pouches) {
798
- _context6.next = 8;
845
+ _context6.next = 9;
799
846
  break;
800
847
  }
801
848
 
802
849
  if (process.env.NODE_ENV !== 'production') {
803
- _logger.default.info("Tried to access local ".concat(doctype, " but Cozy Pouch is not initialized yet. Forwarding the operation to next link"));
850
+ _logger.default.info("Tried to access local ".concat(dbDoctype, " but Cozy Pouch is not initialized yet. Forwarding the operation to next link"));
804
851
  }
805
852
 
806
853
  return _context6.abrupt("return", forward(operation, options));
807
854
 
808
- case 8:
809
- if (!(this.pouches.getSyncStatus(doctype) === 'not_synced')) {
810
- _context6.next = 11;
855
+ case 9:
856
+ if (!(this.pouches.getSyncStatus(dbDoctype) === 'not_synced')) {
857
+ _context6.next = 12;
811
858
  break;
812
859
  }
813
860
 
814
861
  // The doctype is not locally synced and thus cannot be requested: forward to next link
815
862
  if (process.env.NODE_ENV !== 'production') {
816
- _logger.default.info("Tried to access local ".concat(doctype, " but Cozy Pouch is not synced yet. Forwarding the operation to next link"));
863
+ _logger.default.info("Tried to access local ".concat(dbDoctype, " but Cozy Pouch is not synced yet. Forwarding the operation to next link"));
817
864
  }
818
865
 
819
866
  return _context6.abrupt("return", forward(operation, options));
820
867
 
821
- case 11:
822
- _context6.next = 13;
823
- return this.needsToWaitWarmup(doctype);
868
+ case 12:
869
+ _context6.next = 14;
870
+ return this.needsToWaitWarmup(dbDoctype);
824
871
 
825
- case 13:
872
+ case 14:
826
873
  if (!_context6.sent) {
827
- _context6.next = 16;
874
+ _context6.next = 17;
828
875
  break;
829
876
  }
830
877
 
831
878
  if (process.env.NODE_ENV !== 'production') {
832
- _logger.default.info("Tried to access local ".concat(doctype, " but not warmuped yet. Forwarding the operation to next link"));
879
+ _logger.default.info("Tried to access local ".concat(dbDoctype, " but not warmuped yet. Forwarding the operation to next link"));
833
880
  }
834
881
 
835
882
  return _context6.abrupt("return", forward(operation, options));
836
883
 
837
- case 16:
838
- if (this.supportsOperation(operation)) {
839
- _context6.next = 19;
884
+ case 17:
885
+ if (this.supportsOperation(operation, options)) {
886
+ _context6.next = 20;
840
887
  break;
841
888
  }
842
889
 
@@ -846,18 +893,18 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
846
893
 
847
894
  return _context6.abrupt("return", forward(operation, options));
848
895
 
849
- case 19:
896
+ case 20:
850
897
  if (!operation.mutationType) {
851
- _context6.next = 23;
898
+ _context6.next = 24;
852
899
  break;
853
900
  }
854
901
 
855
902
  return _context6.abrupt("return", this.executeMutation(operation, options, result, forward));
856
903
 
857
- case 23:
858
- return _context6.abrupt("return", this.executeQuery(operation));
859
-
860
904
  case 24:
905
+ return _context6.abrupt("return", this.executeQuery(operation, options));
906
+
907
+ case 25:
861
908
  case "end":
862
909
  return _context6.stop();
863
910
  }
@@ -1008,33 +1055,37 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1008
1055
  resp = _context9.sent;
1009
1056
 
1010
1057
  if (!(!(resp !== null && resp !== void 0 && resp.data) || Object.keys(resp === null || resp === void 0 ? void 0 : resp.data).length < 1)) {
1011
- _context9.next = 15;
1058
+ _context9.next = 17;
1012
1059
  break;
1013
1060
  }
1014
1061
 
1015
- return _context9.abrupt("return", pouch.put(sanitizedDoc));
1062
+ _context9.next = 16;
1063
+ return pouch.put(sanitizedDoc);
1064
+
1065
+ case 16:
1066
+ return _context9.abrupt("return", _context9.sent);
1016
1067
 
1017
- case 15:
1068
+ case 17:
1018
1069
  oldDoc = (0, jsonapi.sanitizeJsonApi)(resp.data);
1019
1070
 
1020
1071
  if (!(0, _helpers2.areDocsEqual)(oldDoc, sanitizedDoc)) {
1021
- _context9.next = 18;
1072
+ _context9.next = 20;
1022
1073
  break;
1023
1074
  }
1024
1075
 
1025
1076
  return _context9.abrupt("return");
1026
1077
 
1027
- case 18:
1078
+ case 20:
1028
1079
  sanitizedDoc._rev = oldDoc._rev;
1029
- _context9.next = 21;
1080
+ _context9.next = 23;
1030
1081
  return pouch.put(sanitizedDoc);
1031
1082
 
1032
- case 21:
1033
- _context9.next = 27;
1083
+ case 23:
1084
+ _context9.next = 29;
1034
1085
  break;
1035
1086
 
1036
- case 23:
1037
- _context9.prev = 23;
1087
+ case 25:
1088
+ _context9.prev = 25;
1038
1089
  _context9.t0 = _context9["catch"](3);
1039
1090
 
1040
1091
  _logger.default.error("PersistCozyData failed: with ".concat(_context9.t0)); // Do nothing on catch, to avoid throwing a read query
@@ -1042,18 +1093,18 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1042
1093
 
1043
1094
  return _context9.abrupt("return", null);
1044
1095
 
1045
- case 27:
1096
+ case 29:
1046
1097
  this.performanceApi.measure({
1047
1098
  markName: markName,
1048
1099
  category: 'CozyPouchLink'
1049
1100
  });
1050
1101
 
1051
- case 28:
1102
+ case 30:
1052
1103
  case "end":
1053
1104
  return _context9.stop();
1054
1105
  }
1055
1106
  }
1056
- }, _callee9, this, [[3, 23]]);
1107
+ }, _callee9, this, [[3, 25]]);
1057
1108
  }));
1058
1109
 
1059
1110
  function persistCozyData(_x9) {
@@ -1112,17 +1163,22 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1112
1163
  value: function hasIndex(name) {
1113
1164
  return Boolean(this.indexes[name]);
1114
1165
  }
1166
+ /**
1167
+ * @param {object} operation - The query operation
1168
+ * @param {object} [options] - Optional query options
1169
+ */
1170
+
1115
1171
  }, {
1116
1172
  key: "executeQuery",
1117
1173
  value: function () {
1118
- var _executeQuery = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee11(_ref5) {
1174
+ var _executeQuery = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee11(_ref5, options) {
1119
1175
  var doctype, selector, sort, fields, limit, id, ids, skip, indexedFields, partialFilter, sharingId, engine, res, findSelector, findOpts;
1120
1176
  return _regenerator.default.wrap(function _callee11$(_context11) {
1121
1177
  while (1) {
1122
1178
  switch (_context11.prev = _context11.next) {
1123
1179
  case 0:
1124
1180
  doctype = _ref5.doctype, selector = _ref5.selector, sort = _ref5.sort, fields = _ref5.fields, limit = _ref5.limit, id = _ref5.id, ids = _ref5.ids, skip = _ref5.skip, indexedFields = _ref5.indexedFields, partialFilter = _ref5.partialFilter, sharingId = _ref5.sharingId;
1125
- engine = this.getQueryEngineFromDoctype(doctype);
1181
+ engine = this.getQueryEngineFromDoctype(doctype, options);
1126
1182
 
1127
1183
  if (!id) {
1128
1184
  _context11.next = 8;
@@ -1214,7 +1270,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1214
1270
  }, _callee11, this);
1215
1271
  }));
1216
1272
 
1217
- function executeQuery(_x11) {
1273
+ function executeQuery(_x11, _x12) {
1218
1274
  return _executeQuery.apply(this, arguments);
1219
1275
  }
1220
1276
 
@@ -1314,7 +1370,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1314
1370
  }, _callee12, this);
1315
1371
  }));
1316
1372
 
1317
- function executeMutation(_x12, _x13, _x14, _x15) {
1373
+ function executeMutation(_x13, _x14, _x15, _x16) {
1318
1374
  return _executeMutation.apply(this, arguments);
1319
1375
  }
1320
1376
 
@@ -1344,7 +1400,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1344
1400
  }, _callee13, this);
1345
1401
  }));
1346
1402
 
1347
- function createDocument(_x16) {
1403
+ function createDocument(_x17) {
1348
1404
  return _createDocument.apply(this, arguments);
1349
1405
  }
1350
1406
 
@@ -1368,7 +1424,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1368
1424
  }, _callee14, this);
1369
1425
  }));
1370
1426
 
1371
- function createDocuments(_x17) {
1427
+ function createDocuments(_x18) {
1372
1428
  return _createDocuments.apply(this, arguments);
1373
1429
  }
1374
1430
 
@@ -1398,7 +1454,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1398
1454
  }, _callee15, this);
1399
1455
  }));
1400
1456
 
1401
- function updateDocument(_x18) {
1457
+ function updateDocument(_x19) {
1402
1458
  return _updateDocument.apply(this, arguments);
1403
1459
  }
1404
1460
 
@@ -1422,7 +1478,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1422
1478
  }, _callee16, this);
1423
1479
  }));
1424
1480
 
1425
- function updateDocuments(_x19) {
1481
+ function updateDocuments(_x20) {
1426
1482
  return _updateDocuments.apply(this, arguments);
1427
1483
  }
1428
1484
 
@@ -1457,7 +1513,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1457
1513
  }, _callee17, this);
1458
1514
  }));
1459
1515
 
1460
- function deleteDocument(_x20) {
1516
+ function deleteDocument(_x21) {
1461
1517
  return _deleteDocument.apply(this, arguments);
1462
1518
  }
1463
1519
 
@@ -1490,7 +1546,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1490
1546
  }, _callee18, this);
1491
1547
  }));
1492
1548
 
1493
- function deleteDocuments(_x21) {
1549
+ function deleteDocuments(_x22) {
1494
1550
  return _deleteDocuments.apply(this, arguments);
1495
1551
  }
1496
1552
 
@@ -1538,7 +1594,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1538
1594
  }, _callee19, this);
1539
1595
  }));
1540
1596
 
1541
- function bulkMutation(_x22) {
1597
+ function bulkMutation(_x23) {
1542
1598
  return _bulkMutation.apply(this, arguments);
1543
1599
  }
1544
1600
 
@@ -1562,7 +1618,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1562
1618
  }, _callee20);
1563
1619
  }));
1564
1620
 
1565
- function addReferencesTo(_x23) {
1621
+ function addReferencesTo(_x24) {
1566
1622
  return _addReferencesTo.apply(this, arguments);
1567
1623
  }
1568
1624
 
@@ -1641,7 +1697,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1641
1697
  }, _callee21, this, [[4, 22]]);
1642
1698
  }));
1643
1699
 
1644
- function dbMethod(_x24, _x25) {
1700
+ function dbMethod(_x25, _x26) {
1645
1701
  return _dbMethod.apply(this, arguments);
1646
1702
  }
1647
1703
 
@@ -1738,7 +1794,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1738
1794
  }, _callee23, this);
1739
1795
  }));
1740
1796
 
1741
- function addDoctype(_x26, _x27, _x28) {
1797
+ function addDoctype(_x27, _x28, _x29) {
1742
1798
  return _addDoctype.apply(this, arguments);
1743
1799
  }
1744
1800
 
@@ -1774,7 +1830,7 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1774
1830
  }, _callee24, this);
1775
1831
  }));
1776
1832
 
1777
- function removeDoctype(_x29) {
1833
+ function removeDoctype(_x30) {
1778
1834
  return _removeDoctype.apply(this, arguments);
1779
1835
  }
1780
1836
 
@@ -1783,14 +1839,27 @@ var PouchLink = /*#__PURE__*/function (_CozyLink) {
1783
1839
  }, {
1784
1840
  key: "getSharedDriveDoctypes",
1785
1841
  value: function getSharedDriveDoctypes() {
1786
- var _this4 = this;
1842
+ var _this5 = this;
1787
1843
 
1788
1844
  return this.doctypes.filter(function (doctype) {
1789
- var _this4$doctypesReplic;
1845
+ var _this5$doctypesReplic;
1790
1846
 
1791
- return (_this4$doctypesReplic = _this4.doctypesReplicationOptions[doctype]) === null || _this4$doctypesReplic === void 0 ? void 0 : _this4$doctypesReplic.driveId;
1847
+ return (_this5$doctypesReplic = _this5.doctypesReplicationOptions[doctype]) === null || _this5$doctypesReplic === void 0 ? void 0 : _this5$doctypesReplic.driveId;
1792
1848
  });
1793
1849
  }
1850
+ }, {
1851
+ key: "name",
1852
+ get: function get() {
1853
+ return 'pouch';
1854
+ }
1855
+ /**
1856
+ * Return the PouchDB adapter name.
1857
+ * Should be IndexedDB for newest adapters.
1858
+ *
1859
+ * @param {import('./types').LocalStorage} localStorage Methods to access local storage
1860
+ * @returns {Promise<string>} The adapter name
1861
+ */
1862
+
1794
1863
  }]);
1795
1864
  return PouchLink;
1796
1865
  }(_cozyClient.CozyLink);
@@ -78,6 +78,38 @@ describe('CozyPouchLink', () => {
78
78
  expect(url).toBe('http://user:token@cozy.tools:8080/data/io.cozy.todos')
79
79
  })
80
80
 
81
+ it('has name === "pouch"', async () => {
82
+ await setup()
83
+ expect(link.name).toBe('pouch')
84
+ })
85
+
86
+ it('handles a forceLink:"dataproxy" query (regression: Plan A drive capability)', async () => {
87
+ await setup()
88
+ link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced')
89
+ const forwardFn = jest.fn()
90
+ await link.request(
91
+ Q(TODO_DOCTYPE),
92
+ { forceLink: 'dataproxy' },
93
+ null,
94
+ forwardFn
95
+ )
96
+ expect(forwardFn).not.toHaveBeenCalled()
97
+ })
98
+
99
+ it('forwards a forceLink:"stack" query', async () => {
100
+ await setup()
101
+ const forwardFn = jest.fn()
102
+ await link.request(Q(TODO_DOCTYPE), { forceLink: 'stack' }, null, forwardFn)
103
+ expect(forwardFn).toHaveBeenCalled()
104
+ })
105
+
106
+ it('forwards a legacy forceStack:true query', async () => {
107
+ await setup()
108
+ const forwardFn = jest.fn()
109
+ await link.request(Q(TODO_DOCTYPE), { forceStack: true }, null, forwardFn)
110
+ expect(forwardFn).toHaveBeenCalled()
111
+ })
112
+
81
113
  describe('request handling', () => {
82
114
  const query1 = () => ({
83
115
  definition: () => Q(TODO_DOCTYPE).limitBy(100),
@@ -683,6 +715,180 @@ describe('CozyPouchLink', () => {
683
715
  })
684
716
  })
685
717
 
718
+ describe('driveId support', () => {
719
+ it('maps a driveId option to its registered drive doctype', () => {
720
+ const driveLink = new CozyPouchLink({
721
+ doctypes: ['io.cozy.files', 'io.cozy.files.shareddrives-abc'],
722
+ doctypesReplicationOptions: {
723
+ 'io.cozy.files.shareddrives-abc': { driveId: 'abc' }
724
+ }
725
+ })
726
+ expect(driveLink.getDbDoctype('io.cozy.files', { driveId: 'abc' })).toBe(
727
+ 'io.cozy.files.shareddrives-abc'
728
+ )
729
+ expect(driveLink.getDbDoctype('io.cozy.files', {})).toBe('io.cozy.files')
730
+ expect(driveLink.getDbDoctype('io.cozy.files', { driveId: 'nope' })).toBe(
731
+ 'io.cozy.files'
732
+ )
733
+ })
734
+
735
+ it('reads the drive database but types docs as io.cozy.files', async () => {
736
+ const FILES_DOCTYPE = 'io.cozy.files'
737
+ const DRIVE_DOCTYPE = 'io.cozy.files.shareddrives-abc'
738
+
739
+ const driveLink = new CozyPouchLink({
740
+ doctypes: [FILES_DOCTYPE, DRIVE_DOCTYPE],
741
+ doctypesReplicationOptions: {
742
+ [DRIVE_DOCTYPE]: { driveId: 'abc' }
743
+ }
744
+ })
745
+ const driveClient = new CozyClient({
746
+ ...mockClient,
747
+ links: [driveLink],
748
+ warningForCustomHandlers: false,
749
+ schema: {}
750
+ })
751
+ driveClient.emit = jest.fn()
752
+ await driveLink.onLogin()
753
+ driveClient.setData = jest.fn()
754
+
755
+ await driveLink.getPouch(FILES_DOCTYPE).put({ _id: 'own1' })
756
+ await driveLink.getPouch(DRIVE_DOCTYPE).put({ _id: 'drive1' })
757
+
758
+ driveLink.pouches.getSyncStatus = jest.fn().mockReturnValue('synced')
759
+
760
+ const res = await driveLink.request(Q(FILES_DOCTYPE).getById('drive1'), {
761
+ driveId: 'abc'
762
+ })
763
+ expect(res.data._id).toBe('drive1')
764
+ expect(res.data._type).toBe(FILES_DOCTYPE)
765
+
766
+ await driveLink.reset()
767
+ })
768
+
769
+ it('reads the drive database through the find path and types docs as io.cozy.files', async () => {
770
+ const FILES_DOCTYPE = 'io.cozy.files'
771
+ const DRIVE_DOCTYPE = 'io.cozy.files.shareddrives-abc'
772
+
773
+ const driveLink = new CozyPouchLink({
774
+ doctypes: [FILES_DOCTYPE, DRIVE_DOCTYPE],
775
+ doctypesReplicationOptions: {
776
+ [DRIVE_DOCTYPE]: { driveId: 'abc' }
777
+ }
778
+ })
779
+ const driveClient = new CozyClient({
780
+ ...mockClient,
781
+ links: [driveLink],
782
+ warningForCustomHandlers: false,
783
+ schema: {}
784
+ })
785
+ driveClient.emit = jest.fn()
786
+ await driveLink.onLogin()
787
+ driveClient.setData = jest.fn()
788
+
789
+ await driveLink
790
+ .getPouch(FILES_DOCTYPE)
791
+ .put({ _id: 'own1', updated_at: '2024-01-01' })
792
+ await driveLink
793
+ .getPouch(DRIVE_DOCTYPE)
794
+ .put({ _id: 'drive1', updated_at: '2024-06-01' })
795
+
796
+ driveLink.pouches.getSyncStatus = jest.fn().mockReturnValue('synced')
797
+
798
+ const res = await driveLink.request(
799
+ Q(FILES_DOCTYPE)
800
+ .where({ updated_at: { $gt: null } })
801
+ .indexFields(['updated_at']),
802
+ { driveId: 'abc' }
803
+ )
804
+
805
+ expect(res.data.some(d => d._id === 'drive1')).toBe(true)
806
+ expect(res.data.some(d => d._id === 'own1')).toBe(false)
807
+ expect(res.data.every(d => d._type === FILES_DOCTYPE)).toBe(true)
808
+
809
+ await driveLink.reset()
810
+ })
811
+
812
+ it('supportsOperation is true for io.cozy.files with driveId when drive doctype is managed', async () => {
813
+ const FILES_DOCTYPE = 'io.cozy.files'
814
+ const DRIVE_DOCTYPE = 'io.cozy.files.shareddrives-abc'
815
+
816
+ const driveLink = new CozyPouchLink({
817
+ doctypes: [FILES_DOCTYPE, DRIVE_DOCTYPE],
818
+ doctypesReplicationOptions: {
819
+ [DRIVE_DOCTYPE]: { driveId: 'abc' }
820
+ }
821
+ })
822
+ const driveClient = new CozyClient({
823
+ ...mockClient,
824
+ links: [driveLink],
825
+ warningForCustomHandlers: false,
826
+ schema: {}
827
+ })
828
+ driveClient.emit = jest.fn()
829
+ await driveLink.onLogin()
830
+
831
+ expect(driveLink.supportsOperation({ doctype: FILES_DOCTYPE })).toBe(true)
832
+
833
+ await driveLink.reset()
834
+ })
835
+
836
+ it('request uses drive doctype sync state, not io.cozy.files sync state', async () => {
837
+ const FILES_DOCTYPE = 'io.cozy.files'
838
+ const DRIVE_DOCTYPE = 'io.cozy.files.shareddrives-abc'
839
+
840
+ const driveLink = new CozyPouchLink({
841
+ doctypes: [FILES_DOCTYPE, DRIVE_DOCTYPE],
842
+ doctypesReplicationOptions: {
843
+ [DRIVE_DOCTYPE]: { driveId: 'abc' }
844
+ }
845
+ })
846
+ const driveClient = new CozyClient({
847
+ ...mockClient,
848
+ links: [driveLink],
849
+ warningForCustomHandlers: false,
850
+ schema: {}
851
+ })
852
+ driveClient.emit = jest.fn()
853
+ await driveLink.onLogin()
854
+
855
+ await driveLink.getPouch(DRIVE_DOCTYPE).put({ _id: 'drive1' })
856
+
857
+ // Drive doctype synced, io.cozy.files not synced: drive query must be handled locally
858
+ driveLink.pouches.getSyncStatus = jest.fn(doctype => {
859
+ if (doctype === DRIVE_DOCTYPE) return 'synced'
860
+ return 'not_synced'
861
+ })
862
+
863
+ const forwardSpy = jest.fn()
864
+ const res = await driveLink.request(
865
+ Q(FILES_DOCTYPE).getById('drive1'),
866
+ { driveId: 'abc' },
867
+ null,
868
+ forwardSpy
869
+ )
870
+ expect(forwardSpy).not.toHaveBeenCalled()
871
+ expect(res.data._id).toBe('drive1')
872
+
873
+ // Inverse: drive doctype not synced, io.cozy.files synced: drive query must be forwarded
874
+ driveLink.pouches.getSyncStatus = jest.fn(doctype => {
875
+ if (doctype === DRIVE_DOCTYPE) return 'not_synced'
876
+ return 'synced'
877
+ })
878
+
879
+ const forwardSpy2 = jest.fn().mockResolvedValue({ data: null })
880
+ await driveLink.request(
881
+ Q(FILES_DOCTYPE).getById('drive1'),
882
+ { driveId: 'abc' },
883
+ null,
884
+ forwardSpy2
885
+ )
886
+ expect(forwardSpy2).toHaveBeenCalled()
887
+
888
+ await driveLink.reset()
889
+ })
890
+ })
891
+
686
892
  describe('deleteDocument', () => {
687
893
  it('should add _rev and _deleted prop to a deleted document', async () => {
688
894
  await setup()
package/dist/files.js CHANGED
@@ -9,31 +9,45 @@ exports.queryFileById = exports.TYPE_FILE = exports.TYPE_DIRECTORY = void 0;
9
9
 
10
10
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
11
 
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
+
12
14
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
15
 
14
16
  var _cozyClient = require("cozy-client");
15
17
 
18
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
19
+
20
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
21
+
16
22
  var defaultFetchPolicy = _cozyClient.fetchPolicies.olderThan(5 * 60 * 1000); // 5 min
17
23
 
18
24
 
19
25
  var TYPE_DIRECTORY = 'directory';
20
26
  exports.TYPE_DIRECTORY = TYPE_DIRECTORY;
21
27
  var TYPE_FILE = 'file';
28
+ /**
29
+ * @param {object} client - The cozy client
30
+ * @param {string} id - The file id
31
+ * @param {string} [driveId] - Optional shared-drive id to read the file from the drive scope
32
+ */
33
+
22
34
  exports.TYPE_FILE = TYPE_FILE;
23
35
 
24
36
  var queryFileById = /*#__PURE__*/function () {
25
- var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, id) {
37
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, id, driveId) {
26
38
  var definition, options;
27
39
  return _regenerator.default.wrap(function _callee$(_context) {
28
40
  while (1) {
29
41
  switch (_context.prev = _context.next) {
30
42
  case 0:
31
43
  definition = (0, _cozyClient.Q)('io.cozy.files').getById(id);
32
- options = {
33
- as: "io.cozy.files/".concat(id),
44
+ options = _objectSpread({
45
+ as: driveId ? "io.cozy.files/drive/".concat(driveId, "/").concat(id) : "io.cozy.files/".concat(id),
34
46
  fetchPolicy: defaultFetchPolicy,
35
47
  singleDocData: true
36
- };
48
+ }, driveId ? {
49
+ driveId: driveId
50
+ } : {});
37
51
  return _context.abrupt("return", client.fetchQueryAndGetFromState({
38
52
  definition: definition,
39
53
  options: options
@@ -47,7 +61,7 @@ var queryFileById = /*#__PURE__*/function () {
47
61
  }, _callee);
48
62
  }));
49
63
 
50
- return function queryFileById(_x, _x2) {
64
+ return function queryFileById(_x, _x2, _x3) {
51
65
  return _ref.apply(this, arguments);
52
66
  };
53
67
  }();
@@ -22,4 +22,36 @@ describe('queryFileById', () => {
22
22
  }
23
23
  })
24
24
  })
25
+
26
+ it('with a driveId, builds a drive-scoped query with driveId and per-drive cache key, no forceLink', async () => {
27
+ const expectedResult = { data: { _id: 'dir-123', path: '/Drive/Folder' } }
28
+ const client = {
29
+ fetchQueryAndGetFromState: jest.fn().mockResolvedValue(expectedResult)
30
+ }
31
+
32
+ const res = await queryFileById(client, 'dir-123', 'drive-abc')
33
+
34
+ expect(res).toBe(expectedResult)
35
+ expect(client.fetchQueryAndGetFromState).toHaveBeenCalledTimes(1)
36
+ const calledOptions =
37
+ client.fetchQueryAndGetFromState.mock.calls[0][0].options
38
+ expect(calledOptions.as).toBe('io.cozy.files/drive/drive-abc/dir-123')
39
+ expect(calledOptions.driveId).toBe('drive-abc')
40
+ expect(calledOptions).not.toHaveProperty('forceLink')
41
+ expect(calledOptions.singleDocData).toBe(true)
42
+ })
43
+
44
+ it('without a driveId, options are byte-for-byte identical to the legacy call', async () => {
45
+ const client = {
46
+ fetchQueryAndGetFromState: jest.fn().mockResolvedValue({ data: null })
47
+ }
48
+
49
+ await queryFileById(client, 'some-id', undefined)
50
+
51
+ const calledOptions =
52
+ client.fetchQueryAndGetFromState.mock.calls[0][0].options
53
+ expect(calledOptions).not.toHaveProperty('driveId')
54
+ expect(calledOptions).not.toHaveProperty('forceLink')
55
+ expect(calledOptions.as).toBe('io.cozy.files/some-id')
56
+ })
25
57
  })
package/dist/jsonapi.js CHANGED
@@ -243,7 +243,7 @@ var computeFileFullpath = /*#__PURE__*/function () {
243
243
 
244
244
  case 25:
245
245
  _context.next = 27;
246
- return (0, _files.queryFileById)(client, file.dir_id);
246
+ return (0, _files.queryFileById)(client, file.dir_id, file.driveId);
247
247
 
248
248
  case 27:
249
249
  result = _context.sent;
@@ -750,4 +750,47 @@ describe('computeFileFullpath', () => {
750
750
  expect(res).toBe(file)
751
751
  expect(res.path).toBeUndefined()
752
752
  })
753
+
754
+ it('passes file.driveId to queryFileById when the file carries a driveId', async () => {
755
+ const driveParentDir = {
756
+ _id: 'drive-dir-1',
757
+ _type: 'io.cozy.files',
758
+ type: 'directory',
759
+ path: '/Drive/Folder'
760
+ }
761
+ queryFileById.mockResolvedValue({ data: driveParentDir })
762
+ const driveFile = {
763
+ _id: 'drive-file-1',
764
+ _type: 'io.cozy.files',
765
+ type: 'file',
766
+ dir_id: 'drive-dir-1',
767
+ name: 'report.pdf',
768
+ driveId: 'drive-abc'
769
+ }
770
+
771
+ const res = await computeFileFullpath(client, driveFile)
772
+
773
+ expect(queryFileById).toHaveBeenCalledWith(
774
+ client,
775
+ 'drive-dir-1',
776
+ 'drive-abc'
777
+ )
778
+ expect(res.path).toBe('/Drive/Folder/report.pdf')
779
+ })
780
+
781
+ it('passes undefined driveId to queryFileById for own (non-drive) files', async () => {
782
+ queryFileById.mockResolvedValue({ data: dir })
783
+ const ownFile = {
784
+ _id: 'own-file-1',
785
+ _type: 'io.cozy.files',
786
+ type: 'file',
787
+ dir_id: '123',
788
+ name: 'own.txt'
789
+ // no driveId field
790
+ }
791
+
792
+ await computeFileFullpath(client, ownFile)
793
+
794
+ expect(queryFileById).toHaveBeenCalledWith(client, '123', undefined)
795
+ })
753
796
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-pouch-link",
3
- "version": "60.28.1",
3
+ "version": "60.29.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/linagora/cozy-client.git"
14
14
  },
15
15
  "dependencies": {
16
- "cozy-client": "^60.28.0",
16
+ "cozy-client": "^60.29.0",
17
17
  "pouchdb-browser": "^7.2.2",
18
18
  "pouchdb-find": "^7.2.2"
19
19
  },
@@ -41,5 +41,5 @@
41
41
  "typecheck": "tsc -p tsconfig.json"
42
42
  },
43
43
  "sideEffects": false,
44
- "gitHead": "f067e3cd364d60f3b3fb7a450f11119220f4d39c"
44
+ "gitHead": "0e624ae6b3ba93c2618ba9d26f4d4f48945bf5c8"
45
45
  }
@@ -89,6 +89,8 @@ declare class PouchLink extends CozyLink {
89
89
  doctypes: string[];
90
90
  doctypesReplicationOptions: Record<string, any>;
91
91
  indexes: {};
92
+ /** @private Cache of per-drive logical-typed query engines, keyed by `dbDoctype::logicalDoctype`. */
93
+ private _driveQueryEngines;
92
94
  storage: PouchLocalStorage;
93
95
  initialSync: boolean;
94
96
  periodicSync: boolean;
@@ -197,9 +199,20 @@ declare class PouchLink extends CozyLink {
197
199
  public stopReplication(): void;
198
200
  onSyncError(error: any): Promise<void>;
199
201
  getSyncInfo(doctype: any): import("./types").SyncInfo;
200
- getQueryEngineFromDoctype(doctype: any): any;
202
+ /**
203
+ * Returns the registered physical doctype for a given logical doctype and query options.
204
+ *
205
+ * @param {any} logicalDoctype - The logical doctype
206
+ * @param {object} [options] - Optional query options
207
+ */
208
+ getDbDoctype(logicalDoctype: any, options?: object): any;
209
+ /**
210
+ * @param {any} doctype - The doctype
211
+ * @param {object} [options] - Optional query options
212
+ */
213
+ getQueryEngineFromDoctype(doctype: any, options?: object): any;
201
214
  getPouch(doctype: any): any;
202
- supportsOperation(operation: any): boolean;
215
+ supportsOperation(operation: any, options: any): boolean;
203
216
  /**
204
217
  * Get PouchDB changes
205
218
  * See https://pouchdb.com/api.html#changes
@@ -227,19 +240,11 @@ declare class PouchLink extends CozyLink {
227
240
  */
228
241
  needsToWaitWarmup(doctype: string): Promise<boolean>;
229
242
  hasIndex(name: any): boolean;
230
- executeQuery({ doctype, selector, sort, fields, limit, id, ids, skip, indexedFields, partialFilter, sharingId }: {
231
- doctype: any;
232
- selector: any;
233
- sort: any;
234
- fields: any;
235
- limit: any;
236
- id: any;
237
- ids: any;
238
- skip: any;
239
- indexedFields: any;
240
- partialFilter: any;
241
- sharingId: any;
242
- }): Promise<any>;
243
+ /**
244
+ * @param {object} operation - The query operation
245
+ * @param {object} [options] - Optional query options
246
+ */
247
+ executeQuery({ doctype, selector, sort, fields, limit, id, ids, skip, indexedFields, partialFilter, sharingId }: object, options?: object): Promise<any>;
243
248
  executeMutation(mutation: any, options: any, result: any, forward: any): Promise<any>;
244
249
  createDocument(mutation: any): Promise<any>;
245
250
  createDocuments(mutation: any): Promise<any[]>;
package/types/files.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export const TYPE_DIRECTORY: "directory";
2
2
  export const TYPE_FILE: "file";
3
- export function queryFileById(client: any, id: any): Promise<any>;
3
+ export function queryFileById(client: object, id: string, driveId?: string): Promise<any>;