From 3717a66d28458ec06b2f4854759a6f7fd02e12eb Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 18 Jun 2024 17:27:46 +0200
Subject: [PATCH 01/10] fix(pouch-link): Make CozyPouchLink use native partial
indexes
When we implemented CozyPouchLink, PouchDB did not support partial
indexes and so we implemented `mergePartialIndexInSelector` to adapt
the query and simulate partial filters
Today, when using PouchDB 8 then partial indexes are supported
So we don't need `mergePartialIndexInSelector` anymore and we want to
use the native implementation instead
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 41 ++++++++-----------
packages/cozy-pouch-link/src/mango.js | 37 +++++++++--------
packages/cozy-pouch-link/src/mango.spec.js | 2 +-
3 files changed, 38 insertions(+), 42 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 816e916246..23bfca261a 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -450,23 +450,15 @@ class PouchLink extends CozyLink {
return Boolean(this.indexes[name])
}
- // This merge is necessary because PouchDB does not support partial indexes
- mergePartialIndexInSelector(selector, partialFilter) {
- if (partialFilter) {
- logger.info(
- `PouchLink: The query contains a partial index but PouchDB does not support it. ` +
- `Hence, the partial index definition is used in the selector for in-memory evaluation, ` +
- `which might impact expected performances. If this support is important in your use-case, ` +
- `please let us know or help us contribute to PouchDB!`
- )
- return { ...selector, ...partialFilter }
- }
- return selector
- }
-
async ensureIndex(doctype, query) {
- const fields = query.indexedFields || getIndexFields(query)
- const name = getIndexNameFromFields(fields)
+ let { indexedFields, partialFilter } = query
+ const fields = indexedFields || getIndexFields(query)
+ const partialFilterFields = partialFilter
+ ? getIndexFields({ selector: {}, partialFilter })
+ : null
+ const name = getIndexNameFromFields(fields, {
+ partialFilterFields
+ })
const absName = `${doctype}/${name}`
const db = this.pouches.getPouch(doctype)
if (this.indexes[absName]) {
@@ -474,7 +466,10 @@ class PouchLink extends CozyLink {
} else {
const index = await db.createIndex({
index: {
- fields
+ fields,
+ ddoc: name,
+ name,
+ partial_filter_selector: partialFilter
}
})
this.indexes[absName] = index
@@ -495,11 +490,6 @@ class PouchLink extends CozyLink {
partialFilter
}) {
const db = this.getPouch(doctype)
- // The partial index is not supported by PouchDB, so we ensure the selector includes it
- const mergedSelector = this.mergePartialIndexInSelector(
- selector,
- partialFilter
- )
let res, withRows
if (id) {
res = await db.get(id)
@@ -509,14 +499,14 @@ class PouchLink extends CozyLink {
res = withoutDesignDocuments(res)
res.total_rows = null // pouch indicates the total number of docs in res.total_rows, even though we use "keys". Setting it to null avoids cozy-client thinking there are more docs to fetch.
withRows = true
- } else if (!mergedSelector && !fields && !sort) {
+ } else if (!selector && !partialFilter && !fields && !sort) {
res = await allDocs(db, { include_docs: true, limit })
res = withoutDesignDocuments(res)
withRows = true
} else {
const findOpts = {
sort,
- selector: mergedSelector,
+ selector,
// same selector as Document Collection. We force _id.
// Fix https://github.com/cozy/cozy-client/issues/985
fields: fields ? [...fields, '_id', '_type', 'class'] : undefined,
@@ -525,7 +515,8 @@ class PouchLink extends CozyLink {
}
const index = await this.ensureIndex(doctype, {
...findOpts,
- indexedFields
+ indexedFields,
+ partialFilter
})
findOpts.use_index = index.id
res = await find(db, findOpts)
diff --git a/packages/cozy-pouch-link/src/mango.js b/packages/cozy-pouch-link/src/mango.js
index 482bde9716..508651c109 100644
--- a/packages/cozy-pouch-link/src/mango.js
+++ b/packages/cozy-pouch-link/src/mango.js
@@ -1,18 +1,13 @@
-import flatten from 'lodash/flatten'
-import isObject from 'lodash/isObject'
+import head from 'lodash/head'
-export const getIndexNameFromFields = fields => {
- return `by_${fields.join('_and_')}`
-}
-
-const getSortKeys = sort => {
- if (Array.isArray(sort)) {
- return flatten(sort.map(x => Object.keys(x)))
- } else if (isObject(sort)) {
- return Object.keys(sort)
- } else {
- throw new Error('Get sort key can only be called on Arrays or Objects')
- }
+export const getIndexNameFromFields = (
+ fields,
+ { partialFilterFields } = {}
+) => {
+ const indexName = `by_${fields.join('_and_')}`
+ return partialFilterFields
+ ? `${indexName}_filter_${partialFilterFields.join('_and_')}`
+ : indexName
}
/**
@@ -25,6 +20,16 @@ const getSortKeys = sort => {
* @returns {Array} - Fields to index
*/
const defaultSelector = { _id: { $gt: null } }
-export const getIndexFields = ({ selector = defaultSelector, sort = {} }) => {
- return Array.from(new Set([...Object.keys(selector), ...getSortKeys(sort)]))
+export const getIndexFields = ({
+ selector = defaultSelector,
+ sort = [],
+ partialFilter
+}) => {
+ return Array.from(
+ new Set([
+ ...sort.map(sortOption => head(Object.keys(sortOption))),
+ ...(selector ? Object.keys(selector) : []),
+ ...(partialFilter ? Object.keys(partialFilter) : [])
+ ])
+ )
}
diff --git a/packages/cozy-pouch-link/src/mango.spec.js b/packages/cozy-pouch-link/src/mango.spec.js
index 86f778f919..8579ff1010 100644
--- a/packages/cozy-pouch-link/src/mango.spec.js
+++ b/packages/cozy-pouch-link/src/mango.spec.js
@@ -6,6 +6,6 @@ describe('mango utils', () => {
it('should be able to get the fields from the selector', () => {
const query = Q('io.cozy.rockets').sortBy([{ label: true }, { _id: true }])
const fields = getIndexFields(query)
- expect(fields).toEqual(['_id', 'label'])
+ expect(fields).toEqual(['label', '_id'])
})
})
From e45f9214d1d3165992386248729800ce5e581183 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Wed, 19 Jun 2024 15:16:32 +0200
Subject: [PATCH 02/10] refactor: Homogenize index creation in
CozyPouchLink/DocumentCollection
`DocumentCollection.handleMissingIndex()` and
`CozyPouchLink.ensureIndex()` have nearly identical logic with only a
few changes
To emphasize those changes, we want to homogenize the code logic
between those two methods so their structure will be similar enough to
be compared
partial index tests
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 89 +++++++++++++++----
.../cozy-pouch-link/src/CozyPouchLink.spec.js | 50 +++++------
packages/cozy-pouch-link/src/mango.js | 14 +--
packages/cozy-pouch-link/src/types.js | 35 ++++++++
.../src/DocumentCollection.js | 6 +-
5 files changed, 142 insertions(+), 52 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 23bfca261a..3dd4f10ff7 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -450,30 +450,83 @@ class PouchLink extends CozyLink {
return Boolean(this.indexes[name])
}
- async ensureIndex(doctype, query) {
- let { indexedFields, partialFilter } = query
- const fields = indexedFields || getIndexFields(query)
+ /**
+ * Create the PouchDB index if not existing
+ *
+ * @param {Array} fields - Fields to index
+ * @param {object} indexOption - Options for the index
+ * @param {object} [indexOption.partialFilter] - partialFilter
+ * @param {string} [indexOption.indexName] - indexName
+ * @param {string} [indexOption.doctype] - doctype
+ * @returns {Promise}
+ */
+ async createIndex(fields, { partialFilter, indexName, doctype } = {}) {
+ const absName = `${doctype}/${indexName}`
+ const db = this.pouches.getPouch(doctype)
+
+ const index = await db.createIndex({
+ index: {
+ fields,
+ ddoc: indexName,
+ indexName,
+ partial_filter_selector: partialFilter
+ }
+ })
+ this.indexes[absName] = index
+ return index
+ }
+
+ /**
+ * Retrieve the PouchDB index if exist, undefined otherwise
+ *
+ * @param {string} doctype - The query's doctype
+ * @param {import('./types').MangoQueryOptions} options - The find options
+ * @param {string} indexName - The index name
+ * @returns {import('./types').PouchDbIndex | undefined}
+ */
+ findExistingIndex(doctype, options, indexName) {
+ const absName = `${doctype}/${indexName}`
+ return this.indexes[absName]
+ }
+
+ /**
+ * Handle index creation if it is missing.
+ *
+ * When an index is missing, we first check if there is one with a different
+ * name but the same definition. If there is none, we create the new index.
+ *
+ * /!\ Warning: this method is similar to DocumentCollection.handleMissingIndex()
+ * If you edit this method, please check if the change is also needed in DocumentCollection
+ *
+ * @param {string} doctype The mango selector
+ * @param {import('./types').MangoQueryOptions} options The find options
+ * @returns {Promise} index
+ * @private
+ */
+ async ensureIndex(doctype, options) {
+ let { indexedFields, partialFilter } = options
+
+ let fields = indexedFields
+ if (!indexedFields) {
+ fields = getIndexFields(options)
+ }
const partialFilterFields = partialFilter
? getIndexFields({ selector: {}, partialFilter })
: null
- const name = getIndexNameFromFields(fields, {
+
+ const indexName = getIndexNameFromFields(fields, {
partialFilterFields
})
- const absName = `${doctype}/${name}`
- const db = this.pouches.getPouch(doctype)
- if (this.indexes[absName]) {
- return this.indexes[absName]
- } else {
- const index = await db.createIndex({
- index: {
- fields,
- ddoc: name,
- name,
- partial_filter_selector: partialFilter
- }
+
+ const existingIndex = this.findExistingIndex(doctype, options, indexName)
+ if (!existingIndex) {
+ return await this.createIndex(indexedFields, {
+ partialFilter,
+ indexName,
+ doctype
})
- this.indexes[absName] = index
- return index
+ } else {
+ return existingIndex
}
}
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
index 48e78804eb..9d3151e5e3 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
@@ -296,28 +296,6 @@ describe('CozyPouchLink', () => {
})
})
- it('should merge selector and partial filter definitions', () => {
- const selector = { _id: { $gt: null } }
- expect(link.mergePartialIndexInSelector(selector, {})).toEqual(selector)
-
- const partialFilter = {
- trashed: {
- $exists: false
- }
- }
- const expectedMergedSelector = {
- _id: {
- $gt: null
- },
- trashed: {
- $exists: false
- }
- }
- expect(link.mergePartialIndexInSelector(selector, partialFilter)).toEqual(
- expectedMergedSelector
- )
- })
-
it("should add _id in the selected fields since CozyClient' store needs it", async () => {
find.mockReturnValue({ docs: [TODO_3, TODO_4] })
await setup()
@@ -589,16 +567,31 @@ describe('CozyPouchLink', () => {
.where({})
.sortBy([{ name: 'asc' }])
await link.request(query)
- expect(spy).toHaveBeenCalledWith({ index: { fields: ['name'] } })
+ expect(spy).toHaveBeenCalledWith({
+ index: {
+ ddoc: 'by_name',
+ fields: ['name'],
+ indexName: 'by_name',
+ partial_filter_selector: undefined
+ }
+ })
})
it('uses indexFields if provided', async () => {
spy = jest.spyOn(PouchDB.prototype, 'createIndex').mockReturnValue({})
await setup()
- link.ensureIndex(TODO_DOCTYPE, {
+ await link.ensureIndex(TODO_DOCTYPE, {
indexedFields: ['myIndex']
})
- expect(spy).toHaveBeenCalledWith({ index: { fields: ['myIndex'] } })
+ expect(spy).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalledWith({
+ index: {
+ ddoc: 'by_myIndex',
+ fields: ['myIndex'],
+ indexName: 'by_myIndex',
+ partial_filter_selector: undefined
+ }
+ })
})
it('uses the specified index', async () => {
@@ -615,9 +608,14 @@ describe('CozyPouchLink', () => {
})
const params = {
sort: undefined,
- selector: {},
+ selector: {
+ myIndex2: {
+ $gt: null
+ }
+ },
fields: undefined,
limit: undefined,
+ partialFilter: undefined,
skip: undefined
}
diff --git a/packages/cozy-pouch-link/src/mango.js b/packages/cozy-pouch-link/src/mango.js
index 508651c109..fc2261d2cf 100644
--- a/packages/cozy-pouch-link/src/mango.js
+++ b/packages/cozy-pouch-link/src/mango.js
@@ -16,15 +16,17 @@ export const getIndexNameFromFields = (
* query to work
*
* @private
- * @param {object} options - Mango query options
+ * @param {import('./types').MangoQueryOptions} options - Mango query options
* @returns {Array} - Fields to index
*/
const defaultSelector = { _id: { $gt: null } }
-export const getIndexFields = ({
- selector = defaultSelector,
- sort = [],
- partialFilter
-}) => {
+export const getIndexFields = (
+ /** @type {import('./types').MangoQueryOptions} */ {
+ selector = defaultSelector,
+ sort = [],
+ partialFilter
+ }
+) => {
return Array.from(
new Set([
...sort.map(sortOption => head(Object.keys(sortOption))),
diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js
index 91318c2863..7f324e805f 100644
--- a/packages/cozy-pouch-link/src/types.js
+++ b/packages/cozy-pouch-link/src/types.js
@@ -35,4 +35,39 @@
* @property {function(): Promise} isOnline Method that check if the app is connected to internet
*/
+/**
+ * @typedef {Object} MangoPartialFilter
+ */
+
+/**
+ * @typedef {object} MangoSelector
+ */
+
+/**
+ * @typedef {Array} MangoSort
+ */
+
+/**
+ * @typedef {object} MangoQueryOptions
+ * @property {MangoSelector} [selector] Selector
+ * @property {MangoSort} [sort] The sorting parameters
+ * @property {Array} [fields] The fields to return
+ * @property {Array} [partialFilterFields] The partial filter fields
+ * @property {number|null} [limit] For pagination, the number of results to return
+ * @property {number|null} [skip] For skip-based pagination, the number of referenced files to skip
+ * @property {string|null} [indexId] The _id of the CouchDB index to use for this request
+ * @property {string|null} [bookmark] For bookmark-based pagination, the document _id to start from
+ * @property {Array} [indexedFields]
+ * @property {string} [use_index] Name of the index to use
+ * @property {boolean} [execution_stats] If true, we request the stats from Couch
+ * @property {MangoPartialFilter|null} [partialFilter] An optional partial filter
+ */
+
+/**
+ * @typedef {object} PouchDbIndex
+ * @property {string} id - The ddoc's id
+ * @property {string} name - The ddoc's name
+ * @property {'exists'|'created'} result - If the index has been created or if it already exists
+ */
+
export default {}
diff --git a/packages/cozy-stack-client/src/DocumentCollection.js b/packages/cozy-stack-client/src/DocumentCollection.js
index dd31b5607f..8edca0cbe5 100644
--- a/packages/cozy-stack-client/src/DocumentCollection.js
+++ b/packages/cozy-stack-client/src/DocumentCollection.js
@@ -227,6 +227,9 @@ class DocumentCollection {
* name but the same definition. If yes, it means we found an old unamed
* index, so we migrate it. If there is none, we create the new index.
*
+ * /!\ Warning: this method is similar to CozyPouchLink.ensureIndex()
+ * If you edit this method, please check if the change is also needed in CozyPouchLink
+ *
* @param {object} selector The mango selector
* @param {MangoQueryOptions} options The find options
* @private
@@ -238,10 +241,9 @@ class DocumentCollection {
indexedFields = getIndexFields({ sort: options.sort, selector })
}
- const existingIndex = await this.findExistingIndex(selector, options)
-
const indexName = getIndexNameFromFields(indexedFields, partialFilter)
+ const existingIndex = await this.findExistingIndex(selector, options)
if (!existingIndex) {
await this.createIndex(indexedFields, {
partialFilter,
From b8d54356af9e2d6fe37ad11084d30ad1f315944a Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Mon, 15 Jul 2024 16:14:27 +0200
Subject: [PATCH 03/10] refactor: Remove unnecessary var
By introducing this variable, we used a different value for generating
the index name and for the index creation process
We want to use the same value
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 3dd4f10ff7..69b3b7a834 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -506,15 +506,14 @@ class PouchLink extends CozyLink {
async ensureIndex(doctype, options) {
let { indexedFields, partialFilter } = options
- let fields = indexedFields
if (!indexedFields) {
- fields = getIndexFields(options)
+ indexedFields = getIndexFields(options)
}
const partialFilterFields = partialFilter
? getIndexFields({ selector: {}, partialFilter })
: null
- const indexName = getIndexNameFromFields(fields, {
+ const indexName = getIndexNameFromFields(indexedFields, {
partialFilterFields
})
From d4d0f2cb6b6243196e27df77fe4533b46e656a4d Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 30 Jul 2024 18:27:16 +0200
Subject: [PATCH 04/10] feat(pouch-link): Handle missing selector predicates
for indexed values
With CouchDB, it is possible to make a mango query on an index without
having any predicate on an indexed field in the selector
It is not possible with PouchDB that requires to have any indexed
fields to be in the selector
We automatically handle that to avoid breaking existing queries
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 69b3b7a834..76048ff76d 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -556,6 +556,15 @@ class PouchLink extends CozyLink {
res = withoutDesignDocuments(res)
withRows = true
} else {
+ if (indexedFields) {
+ for (const indexedField of indexedFields) {
+ if (!Object.keys(selector).includes(indexedField)) {
+ selector[indexedField] = {
+ $gt: null
+ }
+ }
+ }
+ }
const findOpts = {
sort,
selector,
From 54d82fa8551920a1ad45afa0c7f26f68f9355b40 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 30 Jul 2024 18:29:03 +0200
Subject: [PATCH 05/10] feat(pouch-link): Support empty selector for PouchDB
With CouchDB, it is possible to provide an empty selector
It is however not possible with PouchDB that will throw an error
So we force a selector on the `_id` for such cases
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 76048ff76d..b0fcdb8bbd 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -556,18 +556,28 @@ class PouchLink extends CozyLink {
res = withoutDesignDocuments(res)
withRows = true
} else {
+ let findSelector = selector
+ const shouldAddId = !findSelector
+ if (shouldAddId) {
+ findSelector = {}
+ }
if (indexedFields) {
for (const indexedField of indexedFields) {
- if (!Object.keys(selector).includes(indexedField)) {
- selector[indexedField] = {
+ if (!Object.keys(findSelector).includes(indexedField)) {
+ findSelector[indexedField] = {
$gt: null
}
}
}
}
+ if (shouldAddId) {
+ findSelector['_id'] = {
+ $gt: null
+ }
+ }
const findOpts = {
sort,
- selector,
+ selector: findSelector,
// same selector as Document Collection. We force _id.
// Fix https://github.com/cozy/cozy-client/issues/985
fields: fields ? [...fields, '_id', '_type', 'class'] : undefined,
From 6ee574421b4590cf71036de11b08403c229cac04 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Mon, 22 Jul 2024 19:36:36 +0200
Subject: [PATCH 06/10] feat(pouch-link): Apply new index naming algorithm on
CozyPouchLink
In #1495 we improved index naming for CozyStackClient
This commit applies the same algorithm to CozyPouchLink
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 7 +--
packages/cozy-pouch-link/src/mango.js | 61 ++++++++++++++++---
packages/cozy-stack-client/src/mangoIndex.js | 6 ++
3 files changed, 61 insertions(+), 13 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index b0fcdb8bbd..511ef8f24f 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -509,13 +509,8 @@ class PouchLink extends CozyLink {
if (!indexedFields) {
indexedFields = getIndexFields(options)
}
- const partialFilterFields = partialFilter
- ? getIndexFields({ selector: {}, partialFilter })
- : null
- const indexName = getIndexNameFromFields(indexedFields, {
- partialFilterFields
- })
+ const indexName = getIndexNameFromFields(indexedFields, partialFilter)
const existingIndex = this.findExistingIndex(doctype, options, indexName)
if (!existingIndex) {
diff --git a/packages/cozy-pouch-link/src/mango.js b/packages/cozy-pouch-link/src/mango.js
index fc2261d2cf..e38fc7cb21 100644
--- a/packages/cozy-pouch-link/src/mango.js
+++ b/packages/cozy-pouch-link/src/mango.js
@@ -1,13 +1,60 @@
import head from 'lodash/head'
-export const getIndexNameFromFields = (
- fields,
- { partialFilterFields } = {}
-) => {
+/**
+ * Process a partial filter to generate a string key
+ *
+ * /!\ Warning: this method is similar to cozy-stack-client mangoIndex.makeKeyFromPartialFilter()
+ * If you edit this method, please check if the change is also needed in mangoIndex file
+ *
+ * @param {object} condition - An object representing the partial filter or a sub-condition of the partial filter
+ * @returns {string} - The string key of the processed partial filter
+ */
+export const makeKeyFromPartialFilter = condition => {
+ if (typeof condition !== 'object' || condition === null) {
+ return String(condition)
+ }
+
+ const conditions = Object.entries(condition).map(([key, value]) => {
+ if (
+ Array.isArray(value) &&
+ value.every(subObj => typeof subObj === 'string')
+ ) {
+ return `${key}_(${value.join('_')})`
+ } else if (Array.isArray(value)) {
+ return `(${value
+ .map(subCondition => `${makeKeyFromPartialFilter(subCondition)}`)
+ .join(`)_${key}_(`)})`
+ } else if (typeof value === 'object') {
+ return `${key}_${makeKeyFromPartialFilter(value)}`
+ } else {
+ return `${key}_${value}`
+ }
+ })
+
+ return conditions.join(')_and_(')
+}
+
+/**
+ * Name an index, based on its indexed fields and partial filter.
+ *
+ * It follows this naming convention:
+ * `by_{indexed_field1}_and_{indexed_field2}_filter_({partial_filter.key1}_{partial_filter.value1})_and_({partial_filter.key2}_{partial_filter.value2})`
+ *
+ * /!\ Warning: this method is similar to cozy-stack-client mangoIndex.getIndexNameFromFields()
+ * If you edit this method, please check if the change is also needed in mangoIndex file
+ *
+ * @param {Array} fields - The indexed fields
+ * @param {object} [partialFilter] - The partial filter
+ * @returns {string} The index name, built from the fields
+ */
+export const getIndexNameFromFields = (fields, partialFilter) => {
const indexName = `by_${fields.join('_and_')}`
- return partialFilterFields
- ? `${indexName}_filter_${partialFilterFields.join('_and_')}`
- : indexName
+
+ if (partialFilter) {
+ return `${indexName}_filter_(${makeKeyFromPartialFilter(partialFilter)})`
+ }
+
+ return indexName
}
/**
diff --git a/packages/cozy-stack-client/src/mangoIndex.js b/packages/cozy-stack-client/src/mangoIndex.js
index 38d662c6cb..2963915dd9 100644
--- a/packages/cozy-stack-client/src/mangoIndex.js
+++ b/packages/cozy-stack-client/src/mangoIndex.js
@@ -50,6 +50,9 @@ export const normalizeDesignDoc = designDoc => {
/**
* Process a partial filter to generate a string key
*
+ * /!\ Warning: this method is similar to cozy-pouch-link mango.makeKeyFromPartialFilter()
+ * If you edit this method, please check if the change is also needed in mango file
+ *
* @param {object} condition - An object representing the partial filter or a sub-condition of the partial filter
* @returns {string} - The string key of the processed partial filter
*/
@@ -84,6 +87,9 @@ export const makeKeyFromPartialFilter = condition => {
* It follows this naming convention:
* `by_{indexed_field1}_and_{indexed_field2}_filter_({partial_filter.key1}_{partial_filter.value1})_and_({partial_filter.key2}_{partial_filter.value2})`
*
+ * /!\ Warning: this method is similar to cozy-pouch-link mango.getIndexNameFromFields()
+ * If you edit this method, please check if the change is also needed in mango file
+ *
* @param {Array} fields - The indexed fields
* @param {object} [partialFilter] - The partial filter
* @returns {string} The index name, built from the fields
From 458c64fad17e80beade71f4c8f23217cd9c9e020 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 18 Jun 2024 17:29:22 +0200
Subject: [PATCH 07/10] fix(pouch-link): Add relationships to jsonapi's
normalizeDoc
When served from the cozy-stack, JSON:API results contain a
`relationships.referenced_by` item
When served from the local pouch, `referenced_by` is on the document's
root so we have to move it back into `relationships` attribute
---
packages/cozy-pouch-link/src/CozyPouchLink.spec.js | 5 ++++-
packages/cozy-pouch-link/src/jsonapi.js | 8 +++++++-
packages/cozy-pouch-link/src/jsonapi.spec.js | 5 ++++-
3 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
index 9d3151e5e3..3aab87189f 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js
@@ -454,7 +454,10 @@ describe('CozyPouchLink', () => {
cozyFromPouch: true,
done: false,
id: '1',
- label: 'Buy bread'
+ label: 'Buy bread',
+ relationships: {
+ referenced_by: undefined
+ }
}
]
})
diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js
index 5ff0a18f58..8b2c69a253 100644
--- a/packages/cozy-pouch-link/src/jsonapi.js
+++ b/packages/cozy-pouch-link/src/jsonapi.js
@@ -1,6 +1,8 @@
export const normalizeDoc = (doc, doctype) => {
const id = doc._id || doc.id
+ const { relationships, referenced_by } = doc
+
// PouchDB sends back .rev attribute but we do not want to
// keep it on the server. It is potentially higher than the
// _rev.
@@ -11,7 +13,11 @@ export const normalizeDoc = (doc, doctype) => {
_id: id,
_rev,
_type: doctype,
- cozyFromPouch: true
+ cozyFromPouch: true,
+ relationships: {
+ ...relationships,
+ referenced_by
+ }
}
if (normalizedDoc.rev) {
delete normalizedDoc.rev
diff --git a/packages/cozy-pouch-link/src/jsonapi.spec.js b/packages/cozy-pouch-link/src/jsonapi.spec.js
index 86bfd067cc..9dcafd01e8 100644
--- a/packages/cozy-pouch-link/src/jsonapi.spec.js
+++ b/packages/cozy-pouch-link/src/jsonapi.spec.js
@@ -45,7 +45,10 @@ describe('doc normalization', () => {
_type: 'io.cozy.contacts',
cozyFromPouch: true,
firstName: 'Bobba',
- lastName: 'Fett'
+ lastName: 'Fett',
+ relationships: {
+ referenced_by: undefined
+ }
})
})
})
From 40e7c993aafcb52afd3ca18dd4aae6bab989e398 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Mon, 15 Jul 2024 16:00:22 +0200
Subject: [PATCH 08/10] feat(pouch-link): Add links to jsonapi's normalizeDoc
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 11 +++---
packages/cozy-pouch-link/src/jsonapi.js | 36 ++++++++++++++++---
2 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index 511ef8f24f..a534333d2e 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -54,8 +54,8 @@ export const isExpiredTokenError = pouchError => {
return expiredTokenError.test(pouchError.error)
}
-const normalizeAll = (docs, doctype) => {
- return docs.map(doc => jsonapi.normalizeDoc(doc, doctype))
+const normalizeAll = client => (docs, doctype) => {
+ return docs.map(doc => jsonapi.normalizeDoc(doc, doctype, client))
}
/**
@@ -239,7 +239,7 @@ class PouchLink extends CozyLink {
* Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done
*/
handleOnSync(doctypeUpdates) {
- const normalizedData = mapValues(doctypeUpdates, normalizeAll)
+ const normalizedData = mapValues(doctypeUpdates, normalizeAll(this.client))
if (this.client) {
this.client.setData(normalizedData)
}
@@ -590,7 +590,7 @@ class PouchLink extends CozyLink {
res.limit = limit
withRows = true
}
- return jsonapi.fromPouchResult(res, withRows, doctype)
+ return jsonapi.fromPouchResult(res, withRows, doctype, this.client)
}
async executeMutation(mutation, result, forward) {
@@ -618,7 +618,8 @@ class PouchLink extends CozyLink {
return jsonapi.fromPouchResult(
pouchRes,
false,
- getDoctypeFromOperation(mutation)
+ getDoctypeFromOperation(mutation),
+ this.client
)
}
diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js
index 8b2c69a253..990155786b 100644
--- a/packages/cozy-pouch-link/src/jsonapi.js
+++ b/packages/cozy-pouch-link/src/jsonapi.js
@@ -1,4 +1,6 @@
-export const normalizeDoc = (doc, doctype) => {
+import { generateWebLink } from 'cozy-client'
+
+export const normalizeDoc = (doc, doctype, client) => {
const id = doc._id || doc.id
const { relationships, referenced_by } = doc
@@ -22,12 +24,36 @@ export const normalizeDoc = (doc, doctype) => {
if (normalizedDoc.rev) {
delete normalizedDoc.rev
}
+
+ normalizeLinks(normalizedDoc, doctype, client)
+
return normalizedDoc
}
+const normalizeLinks = (docRef, doctype, client) => {
+ if (doctype !== 'io.cozy.apps') {
+ return
+ }
+
+ const webLink = generateWebLink({
+ cozyUrl: client.getStackClient().uri,
+ slug: docRef.slug,
+ subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested',
+ pathname: '',
+ hash: '',
+ searchParams: []
+ })
+
+ docRef.links = {
+ self: `/apps/${docRef.slug}`,
+ related: webLink,
+ icon: `/apps/${docRef.slug}/icon/${docRef.version}`
+ }
+}
+
const filterDeletedDocumentsFromRows = doc => !!doc
-export const fromPouchResult = (res, withRows, doctype) => {
+export const fromPouchResult = (res, withRows, doctype, client) => {
// Sometimes, queries are transformed by Collections and they call a dedicated
// cozy-stack route. When this is the case, we want to be able to replicate the same
// query from cozy-pouch-link. It is not possible as-is because the received data
@@ -49,7 +75,7 @@ export const fromPouchResult = (res, withRows, doctype) => {
const offset = res.offset || 0
return {
- data: docs.map(doc => normalizeDoc(doc, doctype)),
+ data: docs.map(doc => normalizeDoc(doc, doctype, client)),
meta: { count: docs.length },
skip: offset,
next: offset + docs.length < res.total_rows || docs.length >= res.limit
@@ -57,8 +83,8 @@ export const fromPouchResult = (res, withRows, doctype) => {
} else {
return {
data: Array.isArray(res)
- ? res.map(doc => normalizeDoc(doc, doctype))
- : normalizeDoc(res, doctype)
+ ? res.map(doc => normalizeDoc(doc, doctype, client))
+ : normalizeDoc(res, doctype, client)
}
}
}
From bbded3b41188b22b98ab805d17e5787192151b9b Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 16 Jul 2024 10:54:33 +0200
Subject: [PATCH 09/10] feat(pouch-link): Allow to ignore warmup
With previous changes, we now expect the CozyPouchLink to be the last
link of the chain, so forwarding the query would result to an exception
thrown
This mean we cannot forward the query when warmup queries are not
finished yet
So we want to allow CozyPouchLink to ignore the verification step and
process the query independently of the warmup queries status
To allow this we introduce the `ignoreWarmup` parameter. When set to
`true` the CozyPouchLink will process the query even if warmup is not
finished yet
---
packages/cozy-pouch-link/src/CozyPouchLink.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js
index a534333d2e..201abcfe49 100644
--- a/packages/cozy-pouch-link/src/CozyPouchLink.js
+++ b/packages/cozy-pouch-link/src/CozyPouchLink.js
@@ -83,7 +83,7 @@ class PouchLink extends CozyLink {
constructor(opts) {
const options = defaults({}, opts, DEFAULT_OPTIONS)
super(options)
- const { doctypes, doctypesReplicationOptions } = options
+ const { doctypes, doctypesReplicationOptions, ignoreWarmup } = options
this.options = options
if (!doctypes) {
throw new Error(
@@ -96,6 +96,7 @@ class PouchLink extends CozyLink {
this.storage = new PouchLocalStorage(
options.platform?.storage || platformWeb.storage
)
+ this.ignoreWarmup = ignoreWarmup
/** @type {Record} - Stores replication states per doctype */
this.replicationStatus = this.replicationStatus || {}
@@ -361,7 +362,7 @@ class PouchLink extends CozyLink {
return forward(operation)
}
- if (await this.needsToWaitWarmup(doctype)) {
+ if (!this.ignoreWarmup && (await this.needsToWaitWarmup(doctype))) {
if (process.env.NODE_ENV !== 'production') {
logger.info(
`Tried to access local ${doctype} but not warmuped yet. Forwarding the operation to next link`
From 05ce44d6c07fb480935fc076e51dd1d2909e2198 Mon Sep 17 00:00:00 2001
From: Ldoppea
Date: Tue, 30 Jul 2024 18:39:13 +0200
Subject: [PATCH 10/10] docs: Update types and documentation
---
docs/api/cozy-pouch-link/classes/PouchLink.md | 150 ++++++++++--------
docs/api/cozy-stack-client.md | 10 ++
.../cozy-pouch-link/types/CozyPouchLink.d.ts | 42 ++++-
packages/cozy-pouch-link/types/jsonapi.d.ts | 4 +-
packages/cozy-pouch-link/types/mango.d.ts | 12 +-
packages/cozy-pouch-link/types/types.d.ts | 64 ++++++++
6 files changed, 203 insertions(+), 79 deletions(-)
diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md
index 48d8512c61..fa89c7c635 100644
--- a/docs/api/cozy-pouch-link/classes/PouchLink.md
+++ b/docs/api/cozy-pouch-link/classes/PouchLink.md
@@ -46,7 +46,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L136)
+[CozyPouchLink.js:137](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L137)
***
@@ -70,6 +70,16 @@ CozyLink.constructor
***
+### ignoreWarmup
+
+• **ignoreWarmup**: `any`
+
+*Defined in*
+
+[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99)
+
+***
+
### indexes
• **indexes**: `Object`
@@ -96,7 +106,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:206](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L206)
+[CozyPouchLink.js:207](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L207)
***
@@ -106,7 +116,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:101](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L101)
+[CozyPouchLink.js:102](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L102)
***
@@ -136,7 +146,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:607](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L607)
+[CozyPouchLink.js:666](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L666)
***
@@ -156,39 +166,45 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:568](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L568)
+[CozyPouchLink.js:627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L627)
***
-### dbMethod
+### createIndex
-▸ **dbMethod**(`method`, `mutation`): `Promise`<`any`>
+▸ **createIndex**(`fields`, `indexOption?`): `Promise`<`PouchDbIndex`>
+
+Create the PouchDB index if not existing
*Parameters*
-| Name | Type |
-| :------ | :------ |
-| `method` | `any` |
-| `mutation` | `any` |
+| Name | Type | Description |
+| :------ | :------ | :------ |
+| `fields` | `any`\[] | Fields to index |
+| `indexOption` | `Object` | Options for the index |
+| `indexOption.doctype` | `string` | - |
+| `indexOption.indexName` | `string` | - |
+| `indexOption.partialFilter` | `any` | - |
*Returns*
-`Promise`<`any`>
+`Promise`<`PouchDbIndex`>
*Defined in*
-[CozyPouchLink.js:611](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L611)
+[CozyPouchLink.js:464](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L464)
***
-### deleteDocument
+### dbMethod
-▸ **deleteDocument**(`mutation`): `Promise`<`any`>
+▸ **dbMethod**(`method`, `mutation`): `Promise`<`any`>
*Parameters*
| Name | Type |
| :------ | :------ |
+| `method` | `any` |
| `mutation` | `any` |
*Returns*
@@ -197,20 +213,19 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:596](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L596)
+[CozyPouchLink.js:670](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L670)
***
-### ensureIndex
+### deleteDocument
-▸ **ensureIndex**(`doctype`, `query`): `Promise`<`any`>
+▸ **deleteDocument**(`mutation`): `Promise`<`any`>
*Parameters*
| Name | Type |
| :------ | :------ |
-| `doctype` | `any` |
-| `query` | `any` |
+| `mutation` | `any` |
*Returns*
@@ -218,7 +233,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:467](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L467)
+[CozyPouchLink.js:655](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L655)
***
@@ -240,7 +255,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:539](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L539)
+[CozyPouchLink.js:597](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L597)
***
@@ -260,7 +275,31 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:485](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L485)
+[CozyPouchLink.js:528](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L528)
+
+***
+
+### findExistingIndex
+
+▸ **findExistingIndex**(`doctype`, `options`, `indexName`): `PouchDbIndex`
+
+Retrieve the PouchDB index if exist, undefined otherwise
+
+*Parameters*
+
+| Name | Type | Description |
+| :------ | :------ | :------ |
+| `doctype` | `string` | The query's doctype |
+| `options` | `MangoQueryOptions` | The find options |
+| `indexName` | `string` | The index name |
+
+*Returns*
+
+`PouchDbIndex`
+
+*Defined in*
+
+[CozyPouchLink.js:488](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L488)
***
@@ -280,7 +319,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:323](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L323)
+[CozyPouchLink.js:324](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L324)
***
@@ -300,7 +339,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:116](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L116)
+[CozyPouchLink.js:117](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L117)
***
@@ -320,7 +359,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:319](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L319)
+[CozyPouchLink.js:320](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L320)
***
@@ -340,7 +379,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L260)
+[CozyPouchLink.js:261](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L261)
***
@@ -360,7 +399,7 @@ CozyLink.constructor
*Defined in*
-[CozyPouchLink.js:255](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L255)
+[CozyPouchLink.js:256](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L256)
***
@@ -386,7 +425,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done
*Defined in*
-[CozyPouchLink.js:241](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L241)
+[CozyPouchLink.js:242](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L242)
***
@@ -406,28 +445,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done
*Defined in*
-[CozyPouchLink.js:449](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L449)
-
-***
-
-### mergePartialIndexInSelector
-
-▸ **mergePartialIndexInSelector**(`selector`, `partialFilter`): `any`
-
-*Parameters*
-
-| Name | Type |
-| :------ | :------ |
-| `selector` | `any` |
-| `partialFilter` | `any` |
-
-*Returns*
-
-`any`
-
-*Defined in*
-
-[CozyPouchLink.js:454](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L454)
+[CozyPouchLink.js:450](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L450)
***
@@ -457,7 +475,7 @@ Migrate the current adapter
*Defined in*
-[CozyPouchLink.js:150](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L150)
+[CozyPouchLink.js:151](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L151)
***
@@ -482,7 +500,7 @@ the need to wait for the warmup
*Defined in*
-[CozyPouchLink.js:435](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L435)
+[CozyPouchLink.js:436](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L436)
***
@@ -496,7 +514,7 @@ the need to wait for the warmup
*Defined in*
-[CozyPouchLink.js:169](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L169)
+[CozyPouchLink.js:170](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L170)
***
@@ -516,7 +534,7 @@ the need to wait for the warmup
*Defined in*
-[CozyPouchLink.js:299](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L299)
+[CozyPouchLink.js:300](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L300)
***
@@ -541,7 +559,7 @@ CozyLink.persistData
*Defined in*
-[CozyPouchLink.js:390](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L390)
+[CozyPouchLink.js:391](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L391)
***
@@ -561,7 +579,7 @@ CozyLink.persistData
*Defined in*
-[CozyPouchLink.js:135](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L135)
+[CozyPouchLink.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L136)
***
@@ -587,7 +605,7 @@ CozyLink.request
*Defined in*
-[CozyPouchLink.js:342](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L342)
+[CozyPouchLink.js:343](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L343)
***
@@ -601,7 +619,7 @@ CozyLink.request
*Defined in*
-[CozyPouchLink.js:225](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L225)
+[CozyPouchLink.js:226](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L226)
***
@@ -620,7 +638,7 @@ Emits pouchlink:sync:start event when the replication begins
*Defined in*
-[CozyPouchLink.js:274](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L274)
+[CozyPouchLink.js:275](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L275)
***
@@ -639,7 +657,7 @@ Emits pouchlink:sync:stop event
*Defined in*
-[CozyPouchLink.js:291](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L291)
+[CozyPouchLink.js:292](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L292)
***
@@ -659,7 +677,7 @@ Emits pouchlink:sync:stop event
*Defined in*
-[CozyPouchLink.js:327](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L327)
+[CozyPouchLink.js:328](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L328)
***
@@ -673,7 +691,7 @@ Emits pouchlink:sync:stop event
*Defined in*
-[CozyPouchLink.js:633](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L633)
+[CozyPouchLink.js:692](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L692)
***
@@ -693,7 +711,7 @@ Emits pouchlink:sync:stop event
*Defined in*
-[CozyPouchLink.js:573](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L573)
+[CozyPouchLink.js:632](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L632)
***
@@ -713,7 +731,7 @@ Emits pouchlink:sync:stop event
*Defined in*
-[CozyPouchLink.js:578](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L578)
+[CozyPouchLink.js:637](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L637)
***
@@ -738,4 +756,4 @@ The adapter name
*Defined in*
-[CozyPouchLink.js:111](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L111)
+[CozyPouchLink.js:112](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L112)
diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md
index 0faf81fadf..23db3115c9 100644
--- a/docs/api/cozy-stack-client.md
+++ b/docs/api/cozy-stack-client.md
@@ -85,11 +85,15 @@ Serves to dedupe equal queries requested at the same time
makeKeyFromPartialFilter ⇒ string
Process a partial filter to generate a string key
+/!\ Warning: this method is similar to cozy-pouch-link mango.makeKeyFromPartialFilter()
+If you edit this method, please check if the change is also needed in mango file
getIndexNameFromFields ⇒ string
Name an index, based on its indexed fields and partial filter.
It follows this naming convention:
by_{indexed_field1}_and_{indexed_field2}_filter_({partial_filter.key1}_{partial_filter.value1})_and_({partial_filter.key2}_{partial_filter.value2})
+/!\ Warning: this method is similar to cozy-pouch-link mango.getIndexNameFromFields()
+If you edit this method, please check if the change is also needed in mango file
transformSort ⇒ MangoSort
Transform sort into Array
@@ -2175,6 +2179,9 @@ Get the list of illegal characters in the file name
## makeKeyFromPartialFilter ⇒ string
Process a partial filter to generate a string key
+/!\ Warning: this method is similar to cozy-pouch-link mango.makeKeyFromPartialFilter()
+If you edit this method, please check if the change is also needed in mango file
+
**Kind**: global constant
**Returns**: string
- - The string key of the processed partial filter
@@ -2190,6 +2197,9 @@ Name an index, based on its indexed fields and partial filter.
It follows this naming convention:
`by_{indexed_field1}_and_{indexed_field2}_filter_({partial_filter.key1}_{partial_filter.value1})_and_({partial_filter.key2}_{partial_filter.value2})`
+/!\ Warning: this method is similar to cozy-pouch-link mango.getIndexNameFromFields()
+If you edit this method, please check if the change is also needed in mango file
+
**Kind**: global constant
**Returns**: string
- The index name, built from the fields
diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts
index 92f829f75e..373e26f8ff 100644
--- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts
+++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts
@@ -49,6 +49,7 @@ declare class PouchLink extends CozyLink {
doctypesReplicationOptions: any[];
indexes: {};
storage: PouchLocalStorage;
+ ignoreWarmup: any;
/** @type {Record} - Stores replication states per doctype */
replicationStatus: Record;
getReplicationURL(doctype: any): string;
@@ -140,8 +141,45 @@ declare class PouchLink extends CozyLink {
*/
needsToWaitWarmup(doctype: string): Promise;
hasIndex(name: any): boolean;
- mergePartialIndexInSelector(selector: any, partialFilter: any): any;
- ensureIndex(doctype: any, query: any): Promise;
+ /**
+ * Create the PouchDB index if not existing
+ *
+ * @param {Array} fields - Fields to index
+ * @param {object} indexOption - Options for the index
+ * @param {object} [indexOption.partialFilter] - partialFilter
+ * @param {string} [indexOption.indexName] - indexName
+ * @param {string} [indexOption.doctype] - doctype
+ * @returns {Promise}
+ */
+ createIndex(fields: any[], { partialFilter, indexName, doctype }?: {
+ partialFilter: object;
+ indexName: string;
+ doctype: string;
+ }): Promise;
+ /**
+ * Retrieve the PouchDB index if exist, undefined otherwise
+ *
+ * @param {string} doctype - The query's doctype
+ * @param {import('./types').MangoQueryOptions} options - The find options
+ * @param {string} indexName - The index name
+ * @returns {import('./types').PouchDbIndex | undefined}
+ */
+ findExistingIndex(doctype: string, options: import('./types').MangoQueryOptions, indexName: string): import('./types').PouchDbIndex | undefined;
+ /**
+ * Handle index creation if it is missing.
+ *
+ * When an index is missing, we first check if there is one with a different
+ * name but the same definition. If there is none, we create the new index.
+ *
+ * /!\ Warning: this method is similar to DocumentCollection.handleMissingIndex()
+ * If you edit this method, please check if the change is also needed in DocumentCollection
+ *
+ * @param {string} doctype The mango selector
+ * @param {import('./types').MangoQueryOptions} options The find options
+ * @returns {Promise} index
+ * @private
+ */
+ private ensureIndex;
executeQuery({ doctype, selector, sort, fields, limit, id, ids, skip, indexedFields, partialFilter }: {
doctype: any;
selector: any;
diff --git a/packages/cozy-pouch-link/types/jsonapi.d.ts b/packages/cozy-pouch-link/types/jsonapi.d.ts
index bdd3d3ed37..6dcd082731 100644
--- a/packages/cozy-pouch-link/types/jsonapi.d.ts
+++ b/packages/cozy-pouch-link/types/jsonapi.d.ts
@@ -1,5 +1,5 @@
-export function normalizeDoc(doc: any, doctype: any): any;
-export function fromPouchResult(res: any, withRows: any, doctype: any): {
+export function normalizeDoc(doc: any, doctype: any, client: any): any;
+export function fromPouchResult(res: any, withRows: any, doctype: any, client: any): {
data: any;
meta?: undefined;
skip?: undefined;
diff --git a/packages/cozy-pouch-link/types/mango.d.ts b/packages/cozy-pouch-link/types/mango.d.ts
index 0604d9ea92..8c44479026 100644
--- a/packages/cozy-pouch-link/types/mango.d.ts
+++ b/packages/cozy-pouch-link/types/mango.d.ts
@@ -1,9 +1,3 @@
-export function getIndexNameFromFields(fields: any): string;
-export function getIndexFields({ selector, sort }: {
- selector?: {
- _id: {
- $gt: any;
- };
- };
- sort?: {};
-}): string[];
+export function makeKeyFromPartialFilter(condition: object): string;
+export function getIndexNameFromFields(fields: Array, partialFilter?: object): string;
+export function getIndexFields({ selector, sort, partialFilter }: import('./types').MangoQueryOptions): string[];
diff --git a/packages/cozy-pouch-link/types/types.d.ts b/packages/cozy-pouch-link/types/types.d.ts
index 99e46385e6..3322d56501 100644
--- a/packages/cozy-pouch-link/types/types.d.ts
+++ b/packages/cozy-pouch-link/types/types.d.ts
@@ -38,3 +38,67 @@ export type LinkPlatform = {
*/
isOnline: () => Promise;
};
+export type MangoPartialFilter = any;
+export type MangoSelector = any;
+export type MangoSort = any[];
+export type MangoQueryOptions = {
+ /**
+ * Selector
+ */
+ selector?: MangoSelector;
+ /**
+ * The sorting parameters
+ */
+ sort?: MangoSort;
+ /**
+ * The fields to return
+ */
+ fields?: Array;
+ /**
+ * The partial filter fields
+ */
+ partialFilterFields?: Array;
+ /**
+ * For pagination, the number of results to return
+ */
+ limit?: number | null;
+ /**
+ * For skip-based pagination, the number of referenced files to skip
+ */
+ skip?: number | null;
+ /**
+ * The _id of the CouchDB index to use for this request
+ */
+ indexId?: string | null;
+ /**
+ * For bookmark-based pagination, the document _id to start from
+ */
+ bookmark?: string | null;
+ indexedFields?: Array;
+ /**
+ * Name of the index to use
+ */
+ use_index?: string;
+ /**
+ * If true, we request the stats from Couch
+ */
+ execution_stats?: boolean;
+ /**
+ * An optional partial filter
+ */
+ partialFilter?: MangoPartialFilter | null;
+};
+export type PouchDbIndex = {
+ /**
+ * - The ddoc's id
+ */
+ id: string;
+ /**
+ * - The ddoc's name
+ */
+ name: string;
+ /**
+ * - If the index has been created or if it already exists
+ */
+ result: 'exists' | 'created';
+};