cozy-pouch-link 57.4.0 → 57.6.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.
Files changed (43) hide show
  1. package/dist/CozyPouchLink.js +221 -469
  2. package/dist/CozyPouchLink.spec.js +6 -147
  3. package/dist/PouchManager.js +43 -8
  4. package/dist/PouchManager.spec.js +21 -12
  5. package/dist/__mocks__/@op-engineering/op-sqlite.js +11 -0
  6. package/dist/db/dbInterface.js +190 -0
  7. package/dist/db/helpers.js +106 -0
  8. package/dist/db/pouchdb/getDocs.js +157 -0
  9. package/dist/db/pouchdb/getDocs.spec.js +63 -0
  10. package/dist/db/pouchdb/pouchdb.js +264 -0
  11. package/dist/db/pouchdb/pouchdb.spec.js +151 -0
  12. package/dist/db/sqlite/sql.js +418 -0
  13. package/dist/db/sqlite/sql.spec.js +363 -0
  14. package/dist/db/sqlite/sqliteDb.js +319 -0
  15. package/dist/errors.js +17 -2
  16. package/dist/helpers.js +21 -147
  17. package/dist/helpers.spec.js +1 -98
  18. package/dist/index.js +9 -1
  19. package/dist/jsonapi.js +49 -10
  20. package/dist/jsonapi.spec.js +105 -32
  21. package/dist/mango.js +146 -3
  22. package/dist/migrations/pouchdb.js +32 -0
  23. package/dist/replicateOnce.js +25 -23
  24. package/dist/types.js +5 -0
  25. package/dist/utils.js +33 -3
  26. package/package.json +4 -3
  27. package/types/CozyPouchLink.d.ts +4 -60
  28. package/types/PouchManager.d.ts +6 -1
  29. package/types/__mocks__/@op-engineering/op-sqlite.d.ts +1 -0
  30. package/types/db/dbInterface.d.ts +117 -0
  31. package/types/db/helpers.d.ts +3 -0
  32. package/types/db/pouchdb/getDocs.d.ts +18 -0
  33. package/types/db/pouchdb/pouchdb.d.ts +8 -0
  34. package/types/db/sqlite/sql.d.ts +45 -0
  35. package/types/db/sqlite/sqliteDb.d.ts +7 -0
  36. package/types/errors.d.ts +2 -0
  37. package/types/helpers.d.ts +1 -4
  38. package/types/index.d.ts +1 -0
  39. package/types/jsonapi.d.ts +2 -0
  40. package/types/mango.d.ts +19 -1
  41. package/types/migrations/pouchdb.d.ts +1 -0
  42. package/types/types.d.ts +2 -0
  43. package/types/utils.d.ts +3 -0
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.getDocs = exports.getDocsAndNormalize = void 0;
9
+
10
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
+
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
+
14
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
15
+
16
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
17
+
18
+ var _helpers = _interopRequireDefault(require("../../helpers"));
19
+
20
+ var _jsonapi = require("../../jsonapi");
21
+
22
+ 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; }
23
+
24
+ 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; }
25
+
26
+ var isAdapterBugged = _helpers.default.isAdapterBugged,
27
+ LIMIT_BUG = _helpers.default.LIMIT_BUG;
28
+
29
+ var getDocsAndNormalize = /*#__PURE__*/function () {
30
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref) {
31
+ var client, doctype, db, queryFunc, _ref$queryParams, queryParams, _ref$withRows, withRows, results, jsonResult;
32
+
33
+ return _regenerator.default.wrap(function _callee$(_context) {
34
+ while (1) {
35
+ switch (_context.prev = _context.next) {
36
+ case 0:
37
+ client = _ref.client, doctype = _ref.doctype, db = _ref.db, queryFunc = _ref.queryFunc, _ref$queryParams = _ref.queryParams, queryParams = _ref$queryParams === void 0 ? {} : _ref$queryParams, _ref$withRows = _ref.withRows, withRows = _ref$withRows === void 0 ? true : _ref$withRows;
38
+ _context.next = 3;
39
+ return getDocs(db, queryFunc, queryParams);
40
+
41
+ case 3:
42
+ results = _context.sent;
43
+ jsonResult = (0, _jsonapi.fromPouchResult)({
44
+ res: results,
45
+ withRows: withRows,
46
+ doctype: doctype,
47
+ client: client
48
+ });
49
+ return _context.abrupt("return", jsonResult);
50
+
51
+ case 6:
52
+ case "end":
53
+ return _context.stop();
54
+ }
55
+ }
56
+ }, _callee);
57
+ }));
58
+
59
+ return function getDocsAndNormalize(_x) {
60
+ return _ref2.apply(this, arguments);
61
+ };
62
+ }();
63
+
64
+ exports.getDocsAndNormalize = getDocsAndNormalize;
65
+
66
+ var getDocs = /*#__PURE__*/function () {
67
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(db, fct) {
68
+ var queryParams,
69
+ limit,
70
+ field,
71
+ data,
72
+ next,
73
+ _args2 = arguments;
74
+ return _regenerator.default.wrap(function _callee2$(_context2) {
75
+ while (1) {
76
+ switch (_context2.prev = _context2.next) {
77
+ case 0:
78
+ queryParams = _args2.length > 2 && _args2[2] !== undefined ? _args2[2] : {};
79
+ // allDocs return an error when limit is null
80
+ if (queryParams.limit === null) delete queryParams.limit;
81
+ limit = queryParams.limit;
82
+ field = fct === 'allDocs' ? 'rows' : 'docs';
83
+
84
+ if (isAdapterBugged(db.adapter)) {
85
+ // FIXME: to remove? This was used to deal with an old adapter that is probably no longer used
86
+ // But it might be worth checking the issue does not exist anymore
87
+ if (limit === undefined || limit > LIMIT_BUG) {
88
+ queryParams.limit = LIMIT_BUG;
89
+ queryParams.skip = queryParams.skip || 0;
90
+ }
91
+ }
92
+
93
+ if (!(fct === 'get')) {
94
+ _context2.next = 9;
95
+ break;
96
+ }
97
+
98
+ if (queryParams.id) {
99
+ _context2.next = 8;
100
+ break;
101
+ }
102
+
103
+ return _context2.abrupt("return", null);
104
+
105
+ case 8:
106
+ return _context2.abrupt("return", db.get(queryParams.id));
107
+
108
+ case 9:
109
+ _context2.next = 11;
110
+ return db[fct](queryParams);
111
+
112
+ case 11:
113
+ data = _context2.sent;
114
+
115
+ if (!(data[field].length === queryParams.limit)) {
116
+ _context2.next = 20;
117
+ break;
118
+ }
119
+
120
+ queryParams.skip = (queryParams.skip ? queryParams.skip : 0) + queryParams.limit;
121
+ queryParams.limit = limit ? limit - queryParams.limit : undefined;
122
+
123
+ if (!(queryParams.limit > 0 || queryParams.limit === undefined)) {
124
+ _context2.next = 20;
125
+ break;
126
+ }
127
+
128
+ _context2.next = 18;
129
+ return getDocs(db, fct, queryParams);
130
+
131
+ case 18:
132
+ next = _context2.sent;
133
+ return _context2.abrupt("return", _objectSpread(_objectSpread({}, data), {}, (0, _defineProperty2.default)({}, field, [].concat((0, _toConsumableArray2.default)(data[field]), (0, _toConsumableArray2.default)(next[field])))));
134
+
135
+ case 20:
136
+ if (queryParams.keys) {
137
+ // Special case for getByIds queries: Pouch returns the total number of rows in the database,
138
+ // which will make cozy-client believes there are more results to fetch
139
+ data.total_rows = data.rows.length;
140
+ }
141
+
142
+ return _context2.abrupt("return", data);
143
+
144
+ case 22:
145
+ case "end":
146
+ return _context2.stop();
147
+ }
148
+ }
149
+ }, _callee2);
150
+ }));
151
+
152
+ return function getDocs(_x2, _x3) {
153
+ return _ref3.apply(this, arguments);
154
+ };
155
+ }();
156
+
157
+ exports.getDocs = getDocs;
@@ -0,0 +1,63 @@
1
+ import { getDocs } from './getDocs'
2
+
3
+ import PouchDB from 'pouchdb-browser'
4
+ import PouchDBFind from 'pouchdb-find'
5
+ import adapter from 'pouchdb-adapter-memory'
6
+ PouchDB.plugin(PouchDBFind)
7
+ PouchDB.plugin(adapter)
8
+
9
+ const insertData = async (db, number) => {
10
+ const docs = []
11
+ for (let i = 0; i < number; i++) {
12
+ docs.push({ _id: `doc${i}`, status: true })
13
+ }
14
+ await db.bulkDocs(docs)
15
+ }
16
+
17
+ describe('getDocs', () => {
18
+ let db, options
19
+
20
+ beforeEach(async () => {
21
+ db = new PouchDB('test', { adapter: 'memory' })
22
+ options = { selector: { status: { $eq: true } } }
23
+ await db.createIndex({ index: { fields: ['status'] } })
24
+ await insertData(db, 105)
25
+ })
26
+
27
+ afterEach(async () => {
28
+ await db.destroy()
29
+ })
30
+
31
+ it('should get all docs', async () => {
32
+ const resp = await getDocs(db, 'allDocs', options)
33
+ expect(resp.rows).toHaveLength(106) // include design doc
34
+ })
35
+
36
+ it('should get 20 docs with allDocs', async () => {
37
+ const resp = await getDocs(db, 'allDocs', { limit: 20 })
38
+ expect(resp.rows).toHaveLength(20)
39
+ })
40
+
41
+ it('should find docs with default limit', async () => {
42
+ const resp = await getDocs(db, 'find', options)
43
+ expect(resp.docs).toHaveLength(105) // does not include design doc
44
+ })
45
+
46
+ it('should get 20 docs with find', async () => {
47
+ const resp = await getDocs(db, 'find', { ...options, limit: 20 })
48
+ expect(resp.docs).toHaveLength(20)
49
+ })
50
+
51
+ it('should get a single doc by id', async () => {
52
+ const resp = await getDocs(db, 'get', { ...options, id: 'doc0' })
53
+ expect(resp._id).toEqual('doc0')
54
+ })
55
+
56
+ it('should get a multiple docs by ids', async () => {
57
+ const resp = await getDocs(db, 'allDocs', {
58
+ ...options,
59
+ keys: ['doc0', 'doc1', 'doc2']
60
+ })
61
+ expect(resp.rows).toHaveLength(3)
62
+ })
63
+ })
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
+
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
+
14
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
15
+
16
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
17
+
18
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
19
+
20
+ var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
21
+
22
+ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
23
+
24
+ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
25
+
26
+ var _mango = require("../../mango");
27
+
28
+ var _dbInterface = _interopRequireDefault(require("../dbInterface"));
29
+
30
+ var _errors = require("../../errors");
31
+
32
+ var _getDocs = require("./getDocs");
33
+
34
+ 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; }
35
+
36
+ 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; }
37
+
38
+ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
39
+
40
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
41
+
42
+ var PouchDBQueryEngine = /*#__PURE__*/function (_DatabaseQueryEngine) {
43
+ (0, _inherits2.default)(PouchDBQueryEngine, _DatabaseQueryEngine);
44
+
45
+ var _super = _createSuper(PouchDBQueryEngine);
46
+
47
+ function PouchDBQueryEngine(pouchManager, doctype) {
48
+ var _this;
49
+
50
+ (0, _classCallCheck2.default)(this, PouchDBQueryEngine);
51
+ _this = _super.call(this);
52
+ _this.pouchManager = pouchManager;
53
+ _this.client = pouchManager === null || pouchManager === void 0 ? void 0 : pouchManager.client;
54
+ _this.doctype = doctype;
55
+ _this.db = null;
56
+ return _this;
57
+ }
58
+
59
+ (0, _createClass2.default)(PouchDBQueryEngine, [{
60
+ key: "openDB",
61
+ value: function openDB(dbName) {
62
+ this.db = this.pouchManager.getPouch(dbName);
63
+ return this.db;
64
+ }
65
+ }, {
66
+ key: "allDocs",
67
+ value: function () {
68
+ var _allDocs = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(options) {
69
+ return _regenerator.default.wrap(function _callee$(_context) {
70
+ while (1) {
71
+ switch (_context.prev = _context.next) {
72
+ case 0:
73
+ return _context.abrupt("return", (0, _getDocs.getDocsAndNormalize)({
74
+ client: this.client,
75
+ db: this.db,
76
+ doctype: this.doctype,
77
+ queryFunc: 'allDocs',
78
+ queryParams: _objectSpread({
79
+ include_docs: true
80
+ }, options)
81
+ }));
82
+
83
+ case 1:
84
+ case "end":
85
+ return _context.stop();
86
+ }
87
+ }
88
+ }, _callee, this);
89
+ }));
90
+
91
+ function allDocs(_x) {
92
+ return _allDocs.apply(this, arguments);
93
+ }
94
+
95
+ return allDocs;
96
+ }()
97
+ }, {
98
+ key: "getById",
99
+ value: function () {
100
+ var _getById = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(id) {
101
+ return _regenerator.default.wrap(function _callee2$(_context2) {
102
+ while (1) {
103
+ switch (_context2.prev = _context2.next) {
104
+ case 0:
105
+ return _context2.abrupt("return", (0, _getDocs.getDocsAndNormalize)({
106
+ client: this.client,
107
+ db: this.db,
108
+ doctype: this.doctype,
109
+ queryFunc: 'get',
110
+ queryParams: {
111
+ id: id
112
+ },
113
+ withRows: false
114
+ }));
115
+
116
+ case 1:
117
+ case "end":
118
+ return _context2.stop();
119
+ }
120
+ }
121
+ }, _callee2, this);
122
+ }));
123
+
124
+ function getById(_x2) {
125
+ return _getById.apply(this, arguments);
126
+ }
127
+
128
+ return getById;
129
+ }()
130
+ }, {
131
+ key: "getByIds",
132
+ value: function () {
133
+ var _getByIds = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(ids) {
134
+ return _regenerator.default.wrap(function _callee3$(_context3) {
135
+ while (1) {
136
+ switch (_context3.prev = _context3.next) {
137
+ case 0:
138
+ return _context3.abrupt("return", (0, _getDocs.getDocsAndNormalize)({
139
+ client: this.client,
140
+ db: this.db,
141
+ doctype: this.doctype,
142
+ queryFunc: 'allDocs',
143
+ queryParams: {
144
+ include_docs: true,
145
+ keys: ids
146
+ }
147
+ }));
148
+
149
+ case 1:
150
+ case "end":
151
+ return _context3.stop();
152
+ }
153
+ }
154
+ }, _callee3, this);
155
+ }));
156
+
157
+ function getByIds(_x3) {
158
+ return _getByIds.apply(this, arguments);
159
+ }
160
+
161
+ return getByIds;
162
+ }()
163
+ }, {
164
+ key: "find",
165
+ value: function () {
166
+ var _find = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(options) {
167
+ var selector, sort, partialFilter, doctype, indexedFields, indexName, findOpts, res;
168
+ return _regenerator.default.wrap(function _callee4$(_context4) {
169
+ while (1) {
170
+ switch (_context4.prev = _context4.next) {
171
+ case 0:
172
+ selector = options.selector, sort = options.sort, partialFilter = options.partialFilter, doctype = options.doctype;
173
+ indexedFields = options.indexedFields;
174
+ indexedFields = (0, _mango.getIndexFields)({
175
+ indexedFields: indexedFields,
176
+ selector: selector,
177
+ sort: sort,
178
+ partialFilter: partialFilter
179
+ });
180
+ indexName = (0, _mango.getIndexName)({
181
+ selector: selector,
182
+ sort: sort,
183
+ partialFilter: partialFilter,
184
+ indexedFields: indexedFields
185
+ });
186
+ findOpts = _objectSpread({
187
+ selector: selector,
188
+ sort: sort,
189
+ partialFilter: partialFilter,
190
+ doctype: doctype,
191
+ use_index: indexName
192
+ }, options);
193
+ findOpts.indexedFields = indexedFields;
194
+ _context4.prev = 6;
195
+ _context4.next = 9;
196
+ return (0, _getDocs.getDocsAndNormalize)({
197
+ client: this.client,
198
+ db: this.db,
199
+ doctype: this.doctype,
200
+ queryFunc: 'find',
201
+ queryParams: findOpts
202
+ });
203
+
204
+ case 9:
205
+ res = _context4.sent;
206
+ _context4.next = 23;
207
+ break;
208
+
209
+ case 12:
210
+ _context4.prev = 12;
211
+ _context4.t0 = _context4["catch"](6);
212
+
213
+ if (!(0, _errors.isMissingPouchDBIndexError)(_context4.t0)) {
214
+ _context4.next = 22;
215
+ break;
216
+ }
217
+
218
+ _context4.next = 17;
219
+ return (0, _mango.createIndex)(this.db, indexedFields, {
220
+ indexName: indexName,
221
+ doctype: doctype,
222
+ partialFilter: partialFilter
223
+ });
224
+
225
+ case 17:
226
+ _context4.next = 19;
227
+ return (0, _getDocs.getDocsAndNormalize)({
228
+ client: this.client,
229
+ db: this.db,
230
+ doctype: this.doctype,
231
+ queryFunc: 'find',
232
+ queryParams: findOpts
233
+ });
234
+
235
+ case 19:
236
+ res = _context4.sent;
237
+ _context4.next = 23;
238
+ break;
239
+
240
+ case 22:
241
+ throw _context4.t0;
242
+
243
+ case 23:
244
+ return _context4.abrupt("return", res);
245
+
246
+ case 24:
247
+ case "end":
248
+ return _context4.stop();
249
+ }
250
+ }
251
+ }, _callee4, this, [[6, 12]]);
252
+ }));
253
+
254
+ function find(_x4) {
255
+ return _find.apply(this, arguments);
256
+ }
257
+
258
+ return find;
259
+ }()
260
+ }]);
261
+ return PouchDBQueryEngine;
262
+ }(_dbInterface.default);
263
+
264
+ exports.default = PouchDBQueryEngine;
@@ -0,0 +1,151 @@
1
+ import PouchDBQuery from './pouchdb'
2
+ import { createIndex } from '../../mango'
3
+ import { getDocsAndNormalize } from './getDocs'
4
+ import { Q } from 'cozy-client'
5
+
6
+ jest.mock('../../mango', () => ({
7
+ ...jest.requireActual('../../mango'),
8
+ createIndex: jest.fn()
9
+ }))
10
+
11
+ jest.mock('./getDocs', () => ({
12
+ getDocsAndNormalize: jest.fn()
13
+ }))
14
+
15
+ describe('PouchDBQuery find()', () => {
16
+ let pouchQE
17
+
18
+ beforeEach(() => {
19
+ pouchQE = new PouchDBQuery()
20
+ pouchQE.client = {}
21
+ pouchQE.db = {}
22
+ pouchQE.doctype = 'io.cozy.test'
23
+
24
+ jest.clearAllMocks()
25
+ })
26
+
27
+ it('should use provided indexedFields', async () => {
28
+ const options = {
29
+ selector: { name: 'John' },
30
+ sort: [{ name: 'asc' }],
31
+ indexedFields: ['name'],
32
+ doctype: 'io.cozy.test'
33
+ }
34
+
35
+ getDocsAndNormalize.mockResolvedValue([{ _id: '1', name: 'John' }])
36
+
37
+ const result = await pouchQE.find(options)
38
+
39
+ expect(getDocsAndNormalize).toHaveBeenCalledWith({
40
+ client: pouchQE.client,
41
+ db: pouchQE.db,
42
+ doctype: pouchQE.doctype,
43
+ queryFunc: 'find',
44
+ queryParams: expect.objectContaining({
45
+ selector: options.selector,
46
+ sort: options.sort,
47
+ partialFilter: options.partialFilter,
48
+ indexedFields: ['name'],
49
+ use_index: 'by_name'
50
+ })
51
+ })
52
+ expect(result).toEqual([{ _id: '1', name: 'John' }])
53
+ })
54
+
55
+ it('should use the index attribute from the sort', async () => {
56
+ const query = Q('io.cozy.todos')
57
+ .where({})
58
+ .sortBy([{ name: 'asc' }])
59
+ await pouchQE.find(query)
60
+ expect(getDocsAndNormalize).toHaveBeenCalledWith({
61
+ client: pouchQE.client,
62
+ db: pouchQE.db,
63
+ doctype: pouchQE.doctype,
64
+ queryFunc: 'find',
65
+ queryParams: expect.objectContaining({
66
+ sort: query.sort,
67
+ indexedFields: ['name'],
68
+ use_index: 'by_name'
69
+ })
70
+ })
71
+ })
72
+
73
+ it('should handle partialIndex', async () => {
74
+ const query = Q('io.cozy.todos')
75
+ .indexFields(['name'])
76
+ .partialIndex({ name: { $exists: true } })
77
+ await pouchQE.find(query)
78
+ expect(getDocsAndNormalize).toHaveBeenCalledWith({
79
+ client: pouchQE.client,
80
+ db: pouchQE.db,
81
+ doctype: pouchQE.doctype,
82
+ queryFunc: 'find',
83
+ queryParams: expect.objectContaining({
84
+ partialFilter: { name: { $exists: true } },
85
+ indexedFields: ['name'],
86
+ use_index: 'by_name_filter_(name_$exists_true)'
87
+ })
88
+ })
89
+ })
90
+
91
+ it('should handle complex index name for partial filters ', async () => {
92
+ const query = Q('io.cozy.todos')
93
+ .indexFields(['name'])
94
+ .partialIndex({
95
+ $and: [{ name: { $exists: true } }, { date: { $gt: null } }],
96
+ $or: [{ count: { $eq: '1' } }, { count: { $eq: '2' } }]
97
+ })
98
+ await pouchQE.find(query)
99
+ expect(getDocsAndNormalize).toHaveBeenCalledWith({
100
+ client: pouchQE.client,
101
+ db: pouchQE.db,
102
+ doctype: pouchQE.doctype,
103
+ queryFunc: 'find',
104
+ queryParams: expect.objectContaining({
105
+ partialFilter: {
106
+ $and: [{ name: { $exists: true } }, { date: { $gt: null } }],
107
+ $or: [{ count: { $eq: '1' } }, { count: { $eq: '2' } }]
108
+ },
109
+ indexedFields: ['name'],
110
+ use_index:
111
+ 'by_name_filter_((name_$exists_true)_$and_(date_$gt_null))_and_((count_$eq_1)_$or_(count_$eq_2))'
112
+ })
113
+ })
114
+ })
115
+
116
+ it('should create index and retry query if missing index error occurs', async () => {
117
+ const options = {
118
+ selector: { age: { $gt: 18 } },
119
+ sort: [{ age: 'desc' }],
120
+ indexedFields: ['age'],
121
+ doctype: 'io.cozy.test'
122
+ }
123
+
124
+ const mockError = new Error('no index')
125
+
126
+ getDocsAndNormalize
127
+ .mockRejectedValueOnce(mockError)
128
+ .mockResolvedValueOnce([{ _id: '2', age: 20 }])
129
+
130
+ const result = await pouchQE.find(options)
131
+
132
+ expect(createIndex).toHaveBeenCalledWith(pouchQE.db, ['age'], {
133
+ indexName: 'by_age',
134
+ doctype: 'io.cozy.test'
135
+ })
136
+
137
+ expect(getDocsAndNormalize).toHaveBeenCalledTimes(2)
138
+ expect(result).toEqual([{ _id: '2', age: 20 }])
139
+ })
140
+
141
+ it('should throw error if non-index related error occurs', async () => {
142
+ const options = { selector: { status: 'active' } }
143
+ const mockError = new Error('generic error')
144
+
145
+ getDocsAndNormalize.mockRejectedValue(mockError)
146
+
147
+ await expect(pouchQE.find(options)).rejects.toThrow('generic error')
148
+
149
+ expect(createIndex).not.toHaveBeenCalled()
150
+ })
151
+ })