From e2f492906861e01f394b33890d6b51113c9516cb Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 16:54:39 +0200 Subject: [PATCH 01/84] fix: Clean types folder before running `types` script When calling `yarn types` all cozy-client's types are generated inside of the `cozy-client/types` folder However, there is no mechanism to remove types when their corresponding class is deleted To fix this, we want to clean the types folder before regenerating types so we ensure no fantom type would persist --- package.json | 3 ++- packages/cozy-client/types/devtools/Flags.d.ts | 2 -- .../cozy-client/types/models/doctypes/index.d.ts | 2 -- .../types/models/doctypes/locales/index.d.ts | 3 --- .../types/models/document/emojiCountry.d.ts | 1 - packages/cozy-client/types/node.d.ts | 15 --------------- .../cozy-client/types/queries/referencedBy.d.ts | 7 ------- 7 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 packages/cozy-client/types/devtools/Flags.d.ts delete mode 100644 packages/cozy-client/types/models/doctypes/index.d.ts delete mode 100644 packages/cozy-client/types/models/doctypes/locales/index.d.ts delete mode 100644 packages/cozy-client/types/models/document/emojiCountry.d.ts delete mode 100644 packages/cozy-client/types/node.d.ts delete mode 100644 packages/cozy-client/types/queries/referencedBy.d.ts diff --git a/package.json b/package.json index 76b46dcb98..a278049068 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,10 @@ "build": "lerna run build --parallel", "commitmsg": "commitlint -e $GIT_PARAMS", "clean": "rm -rf packages/*/dist", + "cleanTypes": "rm -rf packages/*/types", "docs": "node scripts/docs.js && yarn docs:cozy-client", "docs:cozy-client": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-client/tsconfig.json packages/cozy-client/src/index.js --out docs/api/cozy-client --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-client/", - "types": "cd packages/cozy-client && yarn typecheck" + "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck" }, "commitlint": { "extends": [ diff --git a/packages/cozy-client/types/devtools/Flags.d.ts b/packages/cozy-client/types/devtools/Flags.d.ts deleted file mode 100644 index 976ab4b7de..0000000000 --- a/packages/cozy-client/types/devtools/Flags.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export default Flags; -declare function Flags(): JSX.Element; diff --git a/packages/cozy-client/types/models/doctypes/index.d.ts b/packages/cozy-client/types/models/doctypes/index.d.ts deleted file mode 100644 index 2b36a603cd..0000000000 --- a/packages/cozy-client/types/models/doctypes/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { locales }; -import * as locales from "./locales"; diff --git a/packages/cozy-client/types/models/doctypes/locales/index.d.ts b/packages/cozy-client/types/models/doctypes/locales/index.d.ts deleted file mode 100644 index 617393d197..0000000000 --- a/packages/cozy-client/types/models/doctypes/locales/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import fr from "./fr.json"; -import en from "./en.json"; -export { fr, en }; diff --git a/packages/cozy-client/types/models/document/emojiCountry.d.ts b/packages/cozy-client/types/models/document/emojiCountry.d.ts deleted file mode 100644 index b9365092ea..0000000000 --- a/packages/cozy-client/types/models/document/emojiCountry.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function getEmojiByCountry(countryCode: string): string; diff --git a/packages/cozy-client/types/node.d.ts b/packages/cozy-client/types/node.d.ts deleted file mode 100644 index d168480c4f..0000000000 --- a/packages/cozy-client/types/node.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export { default } from "./CozyClient"; -export { default as CozyLink } from "./CozyLink"; -export { default as StackLink } from "./StackLink"; -export { default as compose } from "lodash/flow"; -export { cancelable } from "./utils"; -export { getQueryFromState } from "./store"; -export { default as Registry } from "./registry"; -export * from "./mock"; -export * from "./cli"; -import * as manifest from "./manifest"; -import * as models from "./models"; -export { manifest, models }; -export { QueryDefinition, Mutations, MutationTypes, getDoctypeFromOperation, Q } from "./queries/dsl"; -export { Association, HasMany, HasOne, HasOneInPlace, HasManyInPlace, HasManyTriggers } from "./associations"; -export { dehydrate, generateWebLink } from "./helpers"; diff --git a/packages/cozy-client/types/queries/referencedBy.d.ts b/packages/cozy-client/types/queries/referencedBy.d.ts deleted file mode 100644 index 543e83598f..0000000000 --- a/packages/cozy-client/types/queries/referencedBy.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function isReferencedBy(file: IOCozyFile, referencedBy: Doctype): boolean; -export function isReferencedById(file: IOCozyFile, referencedBy: Doctype, referencedId: string): boolean; -export function getReferencedBy(file: IOCozyFile, referencedBy: Doctype): Reference[]; -export function getReferencedById(file: IOCozyFile, referencedBy: Doctype, referencedId: string): Reference[]; -import { IOCozyFile } from "../types"; -import { Doctype } from "../types"; -import { Reference } from "../types"; From b6c523a053d640d5372dc71d4c81c2845b12e1ea Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:01:33 +0200 Subject: [PATCH 02/84] feat: Add types.d.ts generation on cozy-pouch-link through TSC We will start to use PouchLink in cozy-flagship-app (that uses typescript) and so we want to enable typing for this project This commit adds types generation using TSC and fixes all TSC errors --- package.json | 1 + packages/cozy-pouch-link/package.json | 8 +- packages/cozy-pouch-link/src/CozyPouchLink.js | 9 +- packages/cozy-pouch-link/src/PouchManager.js | 16 +- packages/cozy-pouch-link/src/mango.js | 2 + .../cozy-pouch-link/src/migrations/adapter.js | 2 +- packages/cozy-pouch-link/src/remote.js | 4 +- .../cozy-pouch-link/src/startReplication.js | 11 +- packages/cozy-pouch-link/src/types.js | 14 ++ packages/cozy-pouch-link/tsconfig.json | 19 +++ .../cozy-pouch-link/types/AccessToken.d.ts | 16 ++ .../cozy-pouch-link/types/CozyPouchLink.d.ts | 161 ++++++++++++++++++ .../cozy-pouch-link/types/PouchManager.d.ts | 64 +++++++ .../types/__tests__/fixtures.d.ts | 48 ++++++ .../types/__tests__/mocks.d.ts | 4 + packages/cozy-pouch-link/types/helpers.d.ts | 11 ++ packages/cozy-pouch-link/types/index.d.ts | 1 + packages/cozy-pouch-link/types/jsonapi.d.ts | 14 ++ .../cozy-pouch-link/types/localStorage.d.ts | 28 +++ packages/cozy-pouch-link/types/logger.d.ts | 2 + packages/cozy-pouch-link/types/loop.d.ts | 60 +++++++ packages/cozy-pouch-link/types/mango.d.ts | 9 + .../types/migrations/adapter.d.ts | 18 ++ packages/cozy-pouch-link/types/remote.d.ts | 2 + .../types/startReplication.d.ts | 7 + packages/cozy-pouch-link/types/types.d.ts | 10 ++ packages/cozy-pouch-link/types/utils.d.ts | 2 + 27 files changed, 527 insertions(+), 16 deletions(-) create mode 100644 packages/cozy-pouch-link/src/types.js create mode 100644 packages/cozy-pouch-link/tsconfig.json create mode 100644 packages/cozy-pouch-link/types/AccessToken.d.ts create mode 100644 packages/cozy-pouch-link/types/CozyPouchLink.d.ts create mode 100644 packages/cozy-pouch-link/types/PouchManager.d.ts create mode 100644 packages/cozy-pouch-link/types/__tests__/fixtures.d.ts create mode 100644 packages/cozy-pouch-link/types/__tests__/mocks.d.ts create mode 100644 packages/cozy-pouch-link/types/helpers.d.ts create mode 100644 packages/cozy-pouch-link/types/index.d.ts create mode 100644 packages/cozy-pouch-link/types/jsonapi.d.ts create mode 100644 packages/cozy-pouch-link/types/localStorage.d.ts create mode 100644 packages/cozy-pouch-link/types/logger.d.ts create mode 100644 packages/cozy-pouch-link/types/loop.d.ts create mode 100644 packages/cozy-pouch-link/types/mango.d.ts create mode 100644 packages/cozy-pouch-link/types/migrations/adapter.d.ts create mode 100644 packages/cozy-pouch-link/types/remote.d.ts create mode 100644 packages/cozy-pouch-link/types/startReplication.d.ts create mode 100644 packages/cozy-pouch-link/types/types.d.ts create mode 100644 packages/cozy-pouch-link/types/utils.d.ts diff --git a/package.json b/package.json index a278049068..913ea539f9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "docs": "node scripts/docs.js && yarn docs:cozy-client", "docs:cozy-client": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-client/tsconfig.json packages/cozy-client/src/index.js --out docs/api/cozy-client --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-client/", "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck" + "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck && cd ../cozy-pouch-link && yarn typecheck" }, "commitlint": { "extends": [ diff --git a/packages/cozy-pouch-link/package.json b/packages/cozy-pouch-link/package.json index 143801e3bc..c482624f1b 100644 --- a/packages/cozy-pouch-link/package.json +++ b/packages/cozy-pouch-link/package.json @@ -3,7 +3,9 @@ "version": "48.25.0", "license": "MIT", "main": "dist/index.js", + "types": "types/index.d.ts", "files": [ + "types", "dist" ], "repository": { @@ -23,7 +25,8 @@ "parcel": "1.12.4", "pouchdb-adapter-memory": "7.2.2", "react": "16.14.0", - "react-dom": "16.14.0" + "react-dom": "16.14.0", + "typescript": "4.1.5" }, "peerDependencies": { "@cozy/minilog": "1.0.0", @@ -32,7 +35,8 @@ "scripts": { "build": "../../bin/build", "watch": "yarn run build --watch", - "prepublishOnly": "yarn run build" + "prepublishOnly": "yarn run build", + "typecheck": "tsc -p tsconfig.json" }, "sideEffects": false } diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 373ff776f9..cc4dd3f181 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -52,7 +52,7 @@ export const getReplicationURL = (uri, token, doctype) => { return `${authenticatedURL}/data/${doctype}` } -const doNothing = () => {} +const doNothing = (operation, result = null) => {} const expiredTokenError = /Expired token/ export const isExpiredTokenError = pouchError => { return expiredTokenError.test(pouchError.error) @@ -79,10 +79,9 @@ class PouchLink extends CozyLink { * @param {number} [opts.replicationInterval] Milliseconds between replications * @param {string[]} opts.doctypes Doctypes to replicate * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") - * @returns {object} The PouchLink instance */ - constructor(opts = {}) { + constructor(opts) { const options = defaults({}, opts, DEFAULT_OPTIONS) super(options) const { doctypes, doctypesReplicationOptions } = options @@ -561,6 +560,10 @@ class PouchLink extends CozyLink { return parseMutationResult(document, res) } + async addReferencesTo(mutation) { + throw new Error('addReferencesTo is not implemented in CozyPouchLink') + } + async dbMethod(method, mutation) { const doctype = getDoctypeFromOperation(mutation) const { document: doc, documents: docs } = mutation diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 4c04ae4e29..87eee2017d 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -6,7 +6,6 @@ import map from 'lodash/map' import zip from 'lodash/zip' import startsWith from 'lodash/startsWith' import { isMobileApp } from 'cozy-device-helper' -import { QueryDefinition } from 'cozy-client' import Loop from './loop' import logger from './logger' @@ -18,7 +17,7 @@ import { getDatabaseName } from './utils' const DEFAULT_DELAY = 30 * 1000 /** - * @param {QueryDefinition} query The query definition whose name we're getting + * @param {import('cozy-client/types/types').Query} query The query definition whose name we're getting * * @returns {string} alias */ @@ -115,7 +114,11 @@ class PouchManager { }) } - /** Starts periodic syncing of the pouches */ + /** + * Starts periodic syncing of the pouches + * + * @returns {Promise} + */ async startReplicationLoop() { await this.ensureDatabasesExist() @@ -167,7 +170,11 @@ class PouchManager { logger.info('PouchManager: Starting replication iteration') - // Creating each replication + /** + * Creating each replication + * + * @type {import('./types').CancelablePromises} + */ this.replications = map(this.pouches, async (pouch, doctype) => { logger.info('PouchManager: Starting replication for ' + doctype) @@ -223,6 +230,7 @@ class PouchManager { const doctypes = Object.keys(this.pouches) const promises = Object.values(this.replications) try { + /** @type {import('./types').CancelablePromises} */ const res = await Promise.all(promises) if (process.env.NODE_ENV !== 'production') { diff --git a/packages/cozy-pouch-link/src/mango.js b/packages/cozy-pouch-link/src/mango.js index 046a5f2971..482bde9716 100644 --- a/packages/cozy-pouch-link/src/mango.js +++ b/packages/cozy-pouch-link/src/mango.js @@ -10,6 +10,8 @@ const getSortKeys = 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') } } diff --git a/packages/cozy-pouch-link/src/migrations/adapter.js b/packages/cozy-pouch-link/src/migrations/adapter.js index 491f36cc7f..4d2a95d16e 100644 --- a/packages/cozy-pouch-link/src/migrations/adapter.js +++ b/packages/cozy-pouch-link/src/migrations/adapter.js @@ -13,7 +13,7 @@ const getNewIndexedDBDatabaseName = dbName => { * @property {string} [toAdapter] - The new adapter type, e.g. 'indexeddb' * * @param {MigrationParams} params - The migration params - * @returns {object} - The migrated pouch + * @returns {Promise} - The migrated pouch */ export const migratePouch = async ({ dbName, fromAdapter, toAdapter }) => { let oldPouch = new PouchDB(dbName, { diff --git a/packages/cozy-pouch-link/src/remote.js b/packages/cozy-pouch-link/src/remote.js index b4995239c8..5017272ec7 100644 --- a/packages/cozy-pouch-link/src/remote.js +++ b/packages/cozy-pouch-link/src/remote.js @@ -5,7 +5,7 @@ import AccessToken from './AccessToken' * * @param {URL} url - The remote instance URL, including the credentials * @param {object} params - The params to query the remote instance - * @returns {object} The instance response + * @returns {Promise} The instance response */ export const fetchRemoteInstance = async (url, params = {}) => { const access = new AccessToken({ accessToken: url.password }) @@ -32,7 +32,7 @@ export const fetchRemoteInstance = async (url, params = {}) => { * Fetch last sequence from remote instance * * @param {string} baseUrl - The base URL of the remote instance - * @returns {string} The last sequence + * @returns {Promise} The last sequence */ export const fetchRemoteLastSequence = async baseUrl => { const remoteUrl = new URL(`${baseUrl}/_changes`) diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 7867cf83cb..4ff5d581af 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -26,6 +26,7 @@ const humanTimeDelta = timeMs => { str = `${cur}${lastUnit[0]}` + str return str } +/** @type {[string, number][]} */ const TIME_UNITS = [['ms', 1000], ['s', 60], ['m', 60], ['h', 24]] /** @@ -37,9 +38,10 @@ const TIME_UNITS = [['ms', 1000], ['s', 60], ['m', 60], ['h', 24]] * @param {string} replicationOptions.strategy The direction of the replication. Can be "fromRemote", "toRemote" or "sync" * @param {boolean} replicationOptions.initialReplication Whether or not this is an initial replication * @param {string} replicationOptions.doctype The doctype to replicate + * @param {import('cozy-client/types/types').Query[]} replicationOptions.warmupQueries The queries to warmup * @param {Function} getReplicationURL A function that should return the remote replication URL * - * @returns {Promise} A cancelable promise that resolves at the end of the replication + * @returns {import('./types').CancelablePromise} A cancelable promise that resolves at the end of the replication */ export const startReplication = ( pouch, @@ -49,6 +51,7 @@ export const startReplication = ( let replication let docs = {} const start = new Date() + /** @type {import('./types').CancelablePromise} */ const promise = new Promise((resolve, reject) => { const url = getReplicationURL() const { @@ -73,7 +76,7 @@ export const startReplication = ( if (process.env.NODE_ENV !== 'production') { logger.info( `PouchManager: initial replication with all_docs for ${url} took ${humanTimeDelta( - end - start + end.getTime() - start.getTime() )}` ) } @@ -108,7 +111,7 @@ export const startReplication = ( if (process.env.NODE_ENV !== 'production') { logger.info( `PouchManager: replication for ${url} took ${humanTimeDelta( - end - start + end.getTime() - start.getTime() )}` ) } @@ -141,7 +144,7 @@ const filterDocs = docs => { * @param {object} db - Pouch instance * @param {string} baseUrl - The remote instance * @param {string} doctype - The doctype to replicate - * @returns {Array} The retrieved documents + * @returns {Promise} The retrieved documents */ export const replicateAllDocs = async (db, baseUrl, doctype) => { const remoteUrlAllDocs = new URL(`${baseUrl}/_all_docs`) diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js new file mode 100644 index 0000000000..86fc108772 --- /dev/null +++ b/packages/cozy-pouch-link/src/types.js @@ -0,0 +1,14 @@ +/** + * @typedef {Object} Cancelable + * @property {Function} [cancel] - Cancel the promise + */ + +/** + * @typedef {Promise & Cancelable} CancelablePromise + */ + +/** + * @typedef {CancelablePromise[] & Cancelable} CancelablePromises + */ + +export default {} diff --git a/packages/cozy-pouch-link/tsconfig.json b/packages/cozy-pouch-link/tsconfig.json new file mode 100644 index 0000000000..0f1d72a867 --- /dev/null +++ b/packages/cozy-pouch-link/tsconfig.json @@ -0,0 +1,19 @@ +{ + "include": ["src/**/*"], + "exclude": ["**/*.spec.*"], + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "outDir": "types", + "emitDeclarationOnly": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "jsx": "react", + "declaration": true, + "target": "es6", + "moduleResolution": "node", + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "types": ["jest"] +} diff --git a/packages/cozy-pouch-link/types/AccessToken.d.ts b/packages/cozy-pouch-link/types/AccessToken.d.ts new file mode 100644 index 0000000000..9e417b54db --- /dev/null +++ b/packages/cozy-pouch-link/types/AccessToken.d.ts @@ -0,0 +1,16 @@ +export default class AccessToken { + static fromJSON(data: any): AccessToken; + constructor(opts: any); + tokenType: any; + accessToken: any; + refreshToken: any; + scope: any; + toAuthHeader(): string; + toBasicAuth(): string; + toJSON(): { + tokenType: any; + accessToken: any; + refreshToken: any; + scope: any; + }; +} diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts new file mode 100644 index 0000000000..2bbfe5e6cc --- /dev/null +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -0,0 +1,161 @@ +export function getReplicationURL(uri: any, token: any, doctype: any): string; +export function isExpiredTokenError(pouchError: any): boolean; +export default PouchLink; +export type SyncStatus = "idle" | "replicating"; +/** + * @typedef {"idle"|"replicating"} SyncStatus + */ +/** + * Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates + * PouchDB collections for each doctype that it supports and knows how + * to respond to queries and mutations. + */ +declare class PouchLink extends CozyLink { + /** + * Return the PouchDB adapter name. + * Should be IndexedDB for newest adapters. + * + * @returns {string} The adapter name + */ + static getPouchAdapterName: () => string; + /** + * constructor - Initializes a new PouchLink + * + * @param {object} [opts={}] + * @param {number} [opts.replicationInterval] Milliseconds between replications + * @param {string[]} opts.doctypes Doctypes to replicate + * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + */ + constructor(opts?: { + replicationInterval: number; + doctypes: string[]; + doctypesReplicationOptions: object[]; + }); + options: { + replicationInterval: number; + } & { + replicationInterval?: number; + doctypes: string[]; + doctypesReplicationOptions: object[]; + }; + doctypes: string[]; + doctypesReplicationOptions: any[]; + indexes: {}; + /** @type {Record} - Stores replication states per doctype */ + replicationStatus: Record; + getReplicationURL(doctype: any): string; + registerClient(client: any): Promise; + client: any; + /** + * Migrate the current adapter + * + * @typedef {object} MigrationParams + * @property {string} [fromAdapter] - The current adapter type, e.g. 'idb' + * @property {string} [toAdapter] - The new adapter type, e.g. 'indexeddb' + * @property {string} [url] - The Cozy URL + * @property {Array} [plugins] - The PouchDB plugins + * + * @param {MigrationParams} params - Migration params + */ + migrateAdapter({ fromAdapter, toAdapter, url, plugins }: { + /** + * - The current adapter type, e.g. 'idb' + */ + fromAdapter?: string; + /** + * - The new adapter type, e.g. 'indexeddb' + */ + toAdapter?: string; + /** + * - The Cozy URL + */ + url?: string; + /** + * - The PouchDB plugins + */ + plugins?: Array; + }): Promise; + onLogin(): Promise; + pouches: PouchManager; + reset(): Promise; + /** + * Receives PouchDB updates (documents grouped by doctype). + * Normalizes the data (.id -> ._id, .rev -> _rev). + * Passes the data to the client and to the onSync handler. + * + * Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done + */ + handleOnSync(doctypeUpdates: any): void; + handleDoctypeSyncStart(doctype: any): void; + handleDoctypeSyncEnd(doctype: any): void; + /** + * User of the link can call this to start ongoing replications. + * Typically, it can be used when the application regains focus. + * + * Emits pouchlink:sync:start event when the replication begins + * + * @public + * @returns {void} + */ + public startReplication(): void; + /** + * User of the link can call this to stop ongoing replications. + * Typically, it can be used when the applications loses focus. + * + * Emits pouchlink:sync:stop event + * + * @public + * @returns {void} + */ + public stopReplication(): void; + onSyncError(error: any): Promise; + getSyncInfo(doctype: any): any; + getPouch(doctype: any): any; + supportsOperation(operation: any): boolean; + /** + * + * Check if there is warmup queries for this doctype + * and return if those queries are already warmed up or not + * + * @param {string} doctype - Doctype to check + * @returns {boolean} the need to wait for the warmup + */ + needsToWaitWarmup(doctype: string): boolean; + hasIndex(name: any): boolean; + mergePartialIndexInSelector(selector: any, partialFilter: any): any; + ensureIndex(doctype: any, query: any): Promise; + executeQuery({ doctype, selector, sort, fields, limit, id, ids, skip, indexedFields, partialFilter }: { + doctype: any; + selector: any; + sort: any; + fields: any; + limit: any; + id: any; + ids: any; + skip: any; + indexedFields: any; + partialFilter: any; + }): Promise<{ + data: any; + meta: { + count: any; + }; + skip: any; + next: boolean; + } | { + data: any; + meta?: undefined; + skip?: undefined; + next?: undefined; + }>; + executeMutation(mutation: any, result: any, forward: any): Promise; + createDocument(mutation: any): Promise; + updateDocument(mutation: any): Promise; + updateDocuments(mutation: any): Promise; + deleteDocument(mutation: any): Promise; + addReferencesTo(mutation: any): Promise; + dbMethod(method: any, mutation: any): Promise; + syncImmediately(): Promise; +} +import { CozyLink } from "cozy-client"; +import PouchManager from "./PouchManager"; diff --git a/packages/cozy-pouch-link/types/PouchManager.d.ts b/packages/cozy-pouch-link/types/PouchManager.d.ts new file mode 100644 index 0000000000..a3919c36a1 --- /dev/null +++ b/packages/cozy-pouch-link/types/PouchManager.d.ts @@ -0,0 +1,64 @@ +export default PouchManager; +/** + * Handles the lifecycle of several pouches + * + * - Creates/Destroys the pouches + * - Replicates periodically + */ +declare class PouchManager { + constructor(doctypes: any, options: any); + options: any; + pouches: import("lodash").Dictionary; + syncedDoctypes: any; + warmedUpQueries: any; + getReplicationURL: any; + doctypesReplicationOptions: any; + listenerLaunched: boolean; + ensureDatabasesExistDone: boolean; + /** + * Starts periodic syncing of the pouches + * + * @returns {Promise} + */ + startReplicationLoop(): Promise; + /** Stop periodic syncing of the pouches */ + stopReplicationLoop(): void; + /** Starts replication */ + replicateOnce(): Promise; + executeQuery: any; + addListeners(): void; + removeListeners(): void; + destroy(): Promise; + /** + * Via a call to info() we ensure the database exist on the + * remote side. This is done only once since after the first + * call, we are sure that the databases have been created. + */ + ensureDatabasesExist(): Promise; + replicationLoop: Loop; + /** + * If a replication is currently ongoing, will start a replication + * just after it has finished. Otherwise it will start a replication + * immediately + */ + syncImmediately(): void; + /** + * Creating each replication + * + * @type {import('./types').CancelablePromises} + */ + replications: import('./types').CancelablePromises; + handleReplicationError(err: any): void; + cancelCurrentReplications(): void; + waitForCurrentReplications(): Promise | Promise; + getPouch(doctype: any): any; + updateSyncInfo(doctype: any): void; + getSyncInfo(doctype: any): any; + isSynced(doctype: any): boolean; + clearSyncedDoctypes(): void; + warmupQueries(doctype: any, queries: any): Promise; + checkToWarmupDoctype(doctype: any, replicationOptions: any): void; + areQueriesWarmedUp(doctype: any, queries: any): any; + clearWarmedUpQueries(): void; +} +import Loop from "./loop"; diff --git a/packages/cozy-pouch-link/types/__tests__/fixtures.d.ts b/packages/cozy-pouch-link/types/__tests__/fixtures.d.ts new file mode 100644 index 0000000000..578a3ee772 --- /dev/null +++ b/packages/cozy-pouch-link/types/__tests__/fixtures.d.ts @@ -0,0 +1,48 @@ +export namespace TODO_1 { + const _id: string; + const _type: string; + const label: string; + const done: boolean; +} +export namespace TODO_2 { + const _id_1: string; + export { _id_1 as _id }; + const _type_1: string; + export { _type_1 as _type }; + const label_1: string; + export { label_1 as label }; + const done_1: boolean; + export { done_1 as done }; +} +export namespace TODO_3 { + const _id_2: string; + export { _id_2 as _id }; + const _type_2: string; + export { _type_2 as _type }; + const label_2: string; + export { label_2 as label }; + const done_2: boolean; + export { done_2 as done }; +} +export namespace TODO_4 { + const _id_3: string; + export { _id_3 as _id }; + const _type_3: string; + export { _type_3 as _type }; + const label_3: string; + export { label_3 as label }; + const done_3: boolean; + export { done_3 as done }; +} +export namespace SCHEMA { + namespace todos { + const doctype: string; + namespace relationships { + namespace attachments { + export const type: string; + const doctype_1: string; + export { doctype_1 as doctype }; + } + } + } +} diff --git a/packages/cozy-pouch-link/types/__tests__/mocks.d.ts b/packages/cozy-pouch-link/types/__tests__/mocks.d.ts new file mode 100644 index 0000000000..70ad0c452a --- /dev/null +++ b/packages/cozy-pouch-link/types/__tests__/mocks.d.ts @@ -0,0 +1,4 @@ +export function pouchReplication(mockOptions: any): (url: any, options: any) => { + on: (event: any, fn: any) => any; + cancel: () => void; +}; diff --git a/packages/cozy-pouch-link/types/helpers.d.ts b/packages/cozy-pouch-link/types/helpers.d.ts new file mode 100644 index 0000000000..81dc61d84d --- /dev/null +++ b/packages/cozy-pouch-link/types/helpers.d.ts @@ -0,0 +1,11 @@ +export default helpers; +declare namespace helpers { + function isAdapterBugged(adapterName: any): boolean; + function withoutDesignDocuments(res: any): any; + function getDocs(db: any, fct: any, options?: {}): any; + function allDocs(db: any, options?: {}): Promise; + function find(db: any, options?: {}): Promise; + function isDesignDocument(doc: any): boolean; + function isDeletedDocument(doc: any): any; + function insertBulkDocs(db: any, docs: any): Promise; +} diff --git a/packages/cozy-pouch-link/types/index.d.ts b/packages/cozy-pouch-link/types/index.d.ts new file mode 100644 index 0000000000..23d7a258d6 --- /dev/null +++ b/packages/cozy-pouch-link/types/index.d.ts @@ -0,0 +1 @@ +export { default } from "./CozyPouchLink"; diff --git a/packages/cozy-pouch-link/types/jsonapi.d.ts b/packages/cozy-pouch-link/types/jsonapi.d.ts new file mode 100644 index 0000000000..defaa60591 --- /dev/null +++ b/packages/cozy-pouch-link/types/jsonapi.d.ts @@ -0,0 +1,14 @@ +export function normalizeDoc(doc: any, doctype: any): any; +export function fromPouchResult(res: any, withRows: any, doctype: any): { + data: any; + meta: { + count: any; + }; + skip: any; + next: boolean; +} | { + data: any; + meta?: undefined; + skip?: undefined; + next?: undefined; +}; diff --git a/packages/cozy-pouch-link/types/localStorage.d.ts b/packages/cozy-pouch-link/types/localStorage.d.ts new file mode 100644 index 0000000000..b3295b9c4c --- /dev/null +++ b/packages/cozy-pouch-link/types/localStorage.d.ts @@ -0,0 +1,28 @@ +export const LOCALSTORAGE_SYNCED_KEY: "cozy-client-pouch-link-synced"; +export const LOCALSTORAGE_WARMUPEDQUERIES_KEY: "cozy-client-pouch-link-warmupedqueries"; +export const LOCALSTORAGE_LASTSEQUENCES_KEY: "cozy-client-pouch-link-lastreplicationsequence"; +export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY: "cozy-client-pouch-link-lastreplicateddocid"; +export const LOCALSTORAGE_ADAPTERNAME: "cozy-client-pouch-link-adaptername"; +export function persistLastReplicatedDocID(doctype: string, id: string): void; +export function getAllLastReplicatedDocID(): any; +export function getLastReplicatedDocID(doctype: string): string; +export function destroyAllLastReplicatedDocID(): void; +export function persistSyncedDoctypes(syncedDoctypes: Record): void; +export function getPersistedSyncedDoctypes(): object; +export function destroySyncedDoctypes(): void; +export function persistDoctypeLastSequence(doctype: string, sequence: string): void; +export function getAllLastSequences(): any; +export function getDoctypeLastSequence(doctype: string): string; +export function destroyAllDoctypeLastSequence(): void; +export function destroyDoctypeLastSequence(doctype: string): void; +export function persistWarmedUpQueries(warmedUpQueries: object): void; +export function getPersistedWarmedUpQueries(): object; +export function destroyWarmedUpQueries(): void; +export function getAdapterName(): string; +export function persistAdapterName(adapter: string): void; +/** + * Persist the synchronized doctypes + */ +export type SyncInfo = { + Date: string; +}; diff --git a/packages/cozy-pouch-link/types/logger.d.ts b/packages/cozy-pouch-link/types/logger.d.ts new file mode 100644 index 0000000000..e4d82eb243 --- /dev/null +++ b/packages/cozy-pouch-link/types/logger.d.ts @@ -0,0 +1,2 @@ +export default logger; +declare const logger: any; diff --git a/packages/cozy-pouch-link/types/loop.d.ts b/packages/cozy-pouch-link/types/loop.d.ts new file mode 100644 index 0000000000..cfea491dc3 --- /dev/null +++ b/packages/cozy-pouch-link/types/loop.d.ts @@ -0,0 +1,60 @@ +export default Loop; +/** + * Utility to call a function (task) periodically + * and on demand immediately. + * + * Public API + * + * - start + * - stop + * - scheduleImmediateTask + * - waitForCurrentTask + */ +declare class Loop { + constructor(task: any, delay: any, _afterRound: any, _sleep: any); + task: any; + delay: any; + /** + * Runs immediate tasks and then schedule the next round. + * Immediate tasks are called sequentially without delay + * There is a delay between immediate tasks and normal periodic tasks. + */ + round(): Promise; + immediateTasks: any[]; + started: boolean; + afterRound: any; + sleep: any; + /** + * Starts the loop. Will run the task periodically each `this.delay` ms. + * Ignores multiple starts. + */ + start(): void; + /** + * Stops the loop, clears immediate tasks. + * Cancels current task if possible + */ + stop(): void; + waitForCurrent(): Promise; + /** + * Flushes the immediate tasks list and calls each task. + * Each task is awaited before the next is started. + */ + runImmediateTasks(): Promise; + /** + * Schedules a task to be run immediately at next round. + * Ignored if loop is not started. + * If not task is passed, the default task from the loop is used. + * + * @param {Function} task - Optional custom function to be run immediately + */ + scheduleImmediateTask(task?: Function): Promise; + clearImmediateTasks(): void; + /** + * Calls and saves current task. + * Stops loop in case of error of the task. + */ + runTask(task: any): Promise; + currentTask: any; + _rounding: boolean; + timeout: NodeJS.Timeout; +} diff --git a/packages/cozy-pouch-link/types/mango.d.ts b/packages/cozy-pouch-link/types/mango.d.ts new file mode 100644 index 0000000000..0604d9ea92 --- /dev/null +++ b/packages/cozy-pouch-link/types/mango.d.ts @@ -0,0 +1,9 @@ +export function getIndexNameFromFields(fields: any): string; +export function getIndexFields({ selector, sort }: { + selector?: { + _id: { + $gt: any; + }; + }; + sort?: {}; +}): string[]; diff --git a/packages/cozy-pouch-link/types/migrations/adapter.d.ts b/packages/cozy-pouch-link/types/migrations/adapter.d.ts new file mode 100644 index 0000000000..df5f04d238 --- /dev/null +++ b/packages/cozy-pouch-link/types/migrations/adapter.d.ts @@ -0,0 +1,18 @@ +export function migratePouch({ dbName, fromAdapter, toAdapter }: MigrationParams): Promise; +/** + * Migrate a PouchDB database to a new adapter. + */ +export type MigrationParams = { + /** + * - The database name + */ + dbName?: string; + /** + * - The current adapter type, e.g. 'idb' + */ + fromAdapter?: string; + /** + * - The new adapter type, e.g. 'indexeddb' + */ + toAdapter?: string; +}; diff --git a/packages/cozy-pouch-link/types/remote.d.ts b/packages/cozy-pouch-link/types/remote.d.ts new file mode 100644 index 0000000000..6fa21c070c --- /dev/null +++ b/packages/cozy-pouch-link/types/remote.d.ts @@ -0,0 +1,2 @@ +export function fetchRemoteInstance(url: URL, params?: object): Promise; +export function fetchRemoteLastSequence(baseUrl: string): Promise; diff --git a/packages/cozy-pouch-link/types/startReplication.d.ts b/packages/cozy-pouch-link/types/startReplication.d.ts new file mode 100644 index 0000000000..c820925f87 --- /dev/null +++ b/packages/cozy-pouch-link/types/startReplication.d.ts @@ -0,0 +1,7 @@ +export function startReplication(pouch: object, replicationOptions: { + strategy: string; + initialReplication: boolean; + doctype: string; + warmupQueries: import('cozy-client/types/types').Query[]; +}, getReplicationURL: Function): import('./types').CancelablePromise; +export function replicateAllDocs(db: object, baseUrl: string, doctype: string): Promise; diff --git a/packages/cozy-pouch-link/types/types.d.ts b/packages/cozy-pouch-link/types/types.d.ts new file mode 100644 index 0000000000..d38702d605 --- /dev/null +++ b/packages/cozy-pouch-link/types/types.d.ts @@ -0,0 +1,10 @@ +declare var _default: {}; +export default _default; +export type Cancelable = { + /** + * - Cancel the promise + */ + cancel?: Function; +}; +export type CancelablePromise = Promise & Cancelable; +export type CancelablePromises = CancelablePromise[] & Cancelable; diff --git a/packages/cozy-pouch-link/types/utils.d.ts b/packages/cozy-pouch-link/types/utils.d.ts new file mode 100644 index 0000000000..46f1350cc4 --- /dev/null +++ b/packages/cozy-pouch-link/types/utils.d.ts @@ -0,0 +1,2 @@ +export function getDatabaseName(prefix: string, doctype: string): string; +export function getPrefix(uri: string): string; From d99a97f9aa3957cf34c1ccfeb0d01d8d83acc73a Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:02:52 +0200 Subject: [PATCH 03/84] feat: Add doc generation to cozy-pouch-link through Typedoc With current implementation the documentation would be generated using `jsdoc2md` This package generates doc for both public and private classes Also it does not handle correctly typescript notations (i.e. imports) Like for `cozy-client` we now want to generate `cozy-pouch-link` documentation using Typedoc --- docs/api/cozy-pouch-link/.nojekyll | 1 + docs/api/cozy-pouch-link/README.md | 7 + docs/api/cozy-pouch-link/classes/PouchLink.md | 699 ++++++++++++++++++ package.json | 4 +- scripts/docs.js | 2 +- 5 files changed, 710 insertions(+), 3 deletions(-) create mode 100644 docs/api/cozy-pouch-link/.nojekyll create mode 100644 docs/api/cozy-pouch-link/README.md create mode 100644 docs/api/cozy-pouch-link/classes/PouchLink.md diff --git a/docs/api/cozy-pouch-link/.nojekyll b/docs/api/cozy-pouch-link/.nojekyll new file mode 100644 index 0000000000..e2ac6616ad --- /dev/null +++ b/docs/api/cozy-pouch-link/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/api/cozy-pouch-link/README.md b/docs/api/cozy-pouch-link/README.md new file mode 100644 index 0000000000..547f65421b --- /dev/null +++ b/docs/api/cozy-pouch-link/README.md @@ -0,0 +1,7 @@ +cozy-pouch-link + +# cozy-pouch-link + +## Classes + +* [PouchLink](classes/PouchLink.md) diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md new file mode 100644 index 0000000000..4a112f365f --- /dev/null +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -0,0 +1,699 @@ +[cozy-pouch-link](../README.md) / PouchLink + +# Class: PouchLink + +Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates +PouchDB collections for each doctype that it supports and knows how +to respond to queries and mutations. + +## Hierarchy + +* `default` + + ↳ **`PouchLink`** + +## Constructors + +### constructor + +• **new PouchLink**(`opts`) + +constructor - Initializes a new PouchLink + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `opts` | `Object` | - | +| `opts.doctypes` | `string`\[] | Doctypes to replicate | +| `opts.doctypesReplicationOptions` | `any`\[] | A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") | +| `opts.replicationInterval` | `number` | - | + +*Overrides* + +CozyLink.constructor + +*Defined in* + +[CozyPouchLink.js:84](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L84) + +## Properties + +### client + +• **client**: `any` + +*Defined in* + +[CozyPouchLink.js:132](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L132) + +*** + +### doctypes + +• **doctypes**: `string`\[] + +*Defined in* + +[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) + +*** + +### doctypesReplicationOptions + +• **doctypesReplicationOptions**: `any`\[] + +*Defined in* + +[CozyPouchLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L95) + +*** + +### indexes + +• **indexes**: `Object` + +*Defined in* + +[CozyPouchLink.js:96](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L96) + +*** + +### options + +• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `replicationInterval`: `number` } + +*Defined in* + +[CozyPouchLink.js:88](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L88) + +*** + +### pouches + +• **pouches**: `any` + +*Defined in* + +[CozyPouchLink.js:202](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L202) + +*** + +### replicationStatus + +• **replicationStatus**: `Record`<`string`, `SyncStatus`> + +*Defined in* + +[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) + +## Methods + +### addReferencesTo + +▸ **addReferencesTo**(`mutation`): `Promise`<`void`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:563](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L563) + +*** + +### createDocument + +▸ **createDocument**(`mutation`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:524](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L524) + +*** + +### dbMethod + +▸ **dbMethod**(`method`, `mutation`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `method` | `any` | +| `mutation` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) + +*** + +### deleteDocument + +▸ **deleteDocument**(`mutation`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:552](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L552) + +*** + +### ensureIndex + +▸ **ensureIndex**(`doctype`, `query`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | +| `query` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:423](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L423) + +*** + +### executeMutation + +▸ **executeMutation**(`mutation`, `result`, `forward`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | +| `result` | `any` | +| `forward` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:495](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L495) + +*** + +### executeQuery + +▸ **executeQuery**(`__namedParameters`): `Promise`<{ `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset } | { `data`: `any` ; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset }> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `__namedParameters` | `Object` | + +*Returns* + +`Promise`<{ `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset } | { `data`: `any` ; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset }> + +*Defined in* + +[CozyPouchLink.js:441](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L441) + +*** + +### getPouch + +▸ **getPouch**(`doctype`): `any` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | + +*Returns* + +`any` + +*Defined in* + +[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) + +*** + +### getReplicationURL + +▸ **getReplicationURL**(`doctype`): `string` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | + +*Returns* + +`string` + +*Defined in* + +[CozyPouchLink.js:112](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L112) + +*** + +### getSyncInfo + +▸ **getSyncInfo**(`doctype`): `any` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | + +*Returns* + +`any` + +*Defined in* + +[CozyPouchLink.js:313](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L313) + +*** + +### handleDoctypeSyncEnd + +▸ **handleDoctypeSyncEnd**(`doctype`): `void` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | + +*Returns* + +`void` + +*Defined in* + +[CozyPouchLink.js:254](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L254) + +*** + +### handleDoctypeSyncStart + +▸ **handleDoctypeSyncStart**(`doctype`): `void` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctype` | `any` | + +*Returns* + +`void` + +*Defined in* + +[CozyPouchLink.js:249](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L249) + +*** + +### handleOnSync + +▸ **handleOnSync**(`doctypeUpdates`): `void` + +Receives PouchDB updates (documents grouped by doctype). +Normalizes the data (.id -> .\_id, .rev -> \_rev). +Passes the data to the client and to the onSync handler. + +Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `doctypeUpdates` | `any` | + +*Returns* + +`void` + +*Defined in* + +[CozyPouchLink.js:235](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L235) + +*** + +### hasIndex + +▸ **hasIndex**(`name`): `boolean` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `name` | `any` | + +*Returns* + +`boolean` + +*Defined in* + +[CozyPouchLink.js:405](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L405) + +*** + +### mergePartialIndexInSelector + +▸ **mergePartialIndexInSelector**(`selector`, `partialFilter`): `any` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `selector` | `any` | +| `partialFilter` | `any` | + +*Returns* + +`any` + +*Defined in* + +[CozyPouchLink.js:410](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L410) + +*** + +### migrateAdapter + +▸ **migrateAdapter**(`params`): `Promise`<`void`> + +Migrate the current adapter + +**`property`** {string} \[fromAdapter] - The current adapter type, e.g. 'idb' + +**`property`** {string} \[toAdapter] - The new adapter type, e.g. 'indexeddb' + +**`property`** {string} \[url] - The Cozy URL + +**`property`** {Array} \[plugins] - The PouchDB plugins + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `params` | `MigrationParams` | Migration params | + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:146](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L146) + +*** + +### needsToWaitWarmup + +▸ **needsToWaitWarmup**(`doctype`): `boolean` + +Check if there is warmup queries for this doctype +and return if those queries are already warmed up or not + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `doctype` | `string` | Doctype to check | + +*Returns* + +`boolean` + +the need to wait for the warmup + +*Defined in* + +[CozyPouchLink.js:391](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L391) + +*** + +### onLogin + +▸ **onLogin**(): `Promise`<`void`> + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:165](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L165) + +*** + +### onSyncError + +▸ **onSyncError**(`error`): `Promise`<`void`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `error` | `any` | + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:293](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L293) + +*** + +### registerClient + +▸ **registerClient**(`client`): `Promise`<`void`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `client` | `any` | + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:131](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L131) + +*** + +### request + +▸ **request**(`operation`, `result?`, `forward?`): `void` | `Promise`<`any`> + +*Parameters* + +| Name | Type | Default value | +| :------ | :------ | :------ | +| `operation` | `any` | `undefined` | +| `result` | `any` | `null` | +| `forward` | (`operation`: `any`, `result`: `any`) => `void` | `doNothing` | + +*Returns* + +`void` | `Promise`<`any`> + +*Overrides* + +CozyLink.request + +*Defined in* + +[CozyPouchLink.js:336](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L336) + +*** + +### reset + +▸ **reset**(): `Promise`<`void`> + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:219](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L219) + +*** + +### startReplication + +▸ **startReplication**(): `void` + +User of the link can call this to start ongoing replications. +Typically, it can be used when the application regains focus. + +Emits pouchlink:sync:start event when the replication begins + +*Returns* + +`void` + +*Defined in* + +[CozyPouchLink.js:268](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L268) + +*** + +### stopReplication + +▸ **stopReplication**(): `void` + +User of the link can call this to stop ongoing replications. +Typically, it can be used when the applications loses focus. + +Emits pouchlink:sync:stop event + +*Returns* + +`void` + +*Defined in* + +[CozyPouchLink.js:285](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L285) + +*** + +### supportsOperation + +▸ **supportsOperation**(`operation`): `boolean` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `operation` | `any` | + +*Returns* + +`boolean` + +*Defined in* + +[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) + +*** + +### syncImmediately + +▸ **syncImmediately**(): `Promise`<`void`> + +*Returns* + +`Promise`<`void`> + +*Defined in* + +[CozyPouchLink.js:589](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L589) + +*** + +### updateDocument + +▸ **updateDocument**(`mutation`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[CozyPouchLink.js:529](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L529) + +*** + +### updateDocuments + +▸ **updateDocuments**(`mutation`): `Promise`<`any`\[]> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `mutation` | `any` | + +*Returns* + +`Promise`<`any`\[]> + +*Defined in* + +[CozyPouchLink.js:534](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L534) + +*** + +### getPouchAdapterName + +▸ `Static` **getPouchAdapterName**(): `string` + +Return the PouchDB adapter name. +Should be IndexedDB for newest adapters. + +*Returns* + +`string` + +The adapter name + +*Defined in* + +[CozyPouchLink.js:108](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L108) diff --git a/package.json b/package.json index 913ea539f9..32d807a5d1 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "commitmsg": "commitlint -e $GIT_PARAMS", "clean": "rm -rf packages/*/dist", "cleanTypes": "rm -rf packages/*/types", - "docs": "node scripts/docs.js && yarn docs:cozy-client", + "docs": "node scripts/docs.js && yarn docs:cozy-client && yarn docs:cozy-pouch-link", "docs:cozy-client": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-client/tsconfig.json packages/cozy-client/src/index.js --out docs/api/cozy-client --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-client/", - "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck" + "docs:cozy-pouch-link": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-pouch-link/tsconfig.json packages/cozy-pouch-link/src/index.js --out docs/api/cozy-pouch-link --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-pouch-link/", "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck && cd ../cozy-pouch-link && yarn typecheck" }, "commitlint": { diff --git a/scripts/docs.js b/scripts/docs.js index e59c9641d3..ddc74de769 100644 --- a/scripts/docs.js +++ b/scripts/docs.js @@ -20,7 +20,7 @@ const main = async () => { const packages = await globPromise('packages/*') await fs.mkdirp(path.resolve(docsFolder, 'api')) for (let pkg of packages) { - if (pkg === 'packages/cozy-client') { + if (pkg === 'packages/cozy-client' || pkg === 'packages/cozy-pouch-link') { continue // documentation for cozy-client is made via typedoc } const files = await globPromise(`${pkg}/src/**/*.js*`, { From 32366c51a51a8da59c8c70fc050ff631e472a903 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:15:46 +0200 Subject: [PATCH 04/84] fix: Clean `docs/api` folder before generating documentation When calling `yarn docs` all API documentation is generated inside of the `docs/api` folder However, there is no mechanism to remove documentations when their corresponding code is deleted To fix this, we want to clean the `docs/api` folder before regenerating documentation so we ensure no fantom doc would persist --- docs/api/cozy-pouch-link.md | 593 ------------------------------------ package.json | 3 +- 2 files changed, 2 insertions(+), 594 deletions(-) delete mode 100644 docs/api/cozy-pouch-link.md diff --git a/docs/api/cozy-pouch-link.md b/docs/api/cozy-pouch-link.md deleted file mode 100644 index 90d59e9f1e..0000000000 --- a/docs/api/cozy-pouch-link.md +++ /dev/null @@ -1,593 +0,0 @@ -## Classes - -
-
PouchLink
-

Link to be passed to a CozyClient instance to support CouchDB. It instantiates -PouchDB collections for each doctype that it supports and knows how -to respond to queries and mutations.

-
-
Loop
-

Utility to call a function (task) periodically -and on demand immediately.

-

Public API

-
    -
  • start
  • -
  • stop
  • -
  • scheduleImmediateTask
  • -
  • waitForCurrentTask
  • -
-
-
PouchManager
-

Handles the lifecycle of several pouches

-
    -
  • Creates/Destroys the pouches
  • -
  • Replicates periodically
  • -
-
-
- -## Constants - -
-
persistLastReplicatedDocID
-

Persist the last replicated doc id for a doctype

-
-
getLastReplicatedDocIDstring
-

Get the last replicated doc id for a doctype

-
-
destroyAllLastReplicatedDocID
-

Destroy all the replicated doc id

-
-
getPersistedSyncedDoctypesobject
-

Get the persisted doctypes

-
-
destroySyncedDoctypes
-

Destroy the synced doctypes

-
-
persistDoctypeLastSequence
-

Persist the last CouchDB sequence for a synced doctype

-
-
getDoctypeLastSequencestring
-

Get the last CouchDB sequence for a doctype

-
-
destroyAllDoctypeLastSequence
-

Destroy all the last sequence

-
-
destroyDoctypeLastSequence
-

Destroy the last sequence for a doctype

-
-
persistWarmedUpQueries
-

Persist the warmed up queries

-
-
getPersistedWarmedUpQueriesobject
-

Get the warmed up queries

-
-
destroyWarmedUpQueries
-

Destroy the warmed queries

-
-
getAdapterNamestring
-

Get the adapter name

-
-
persistAdapterName
-

Persist the adapter name

-
-
fetchRemoteInstanceobject
-

Fetch remote instance

-
-
fetchRemoteLastSequencestring
-

Fetch last sequence from remote instance

-
-
replicateAllDocsArray
-

Replicate all docs locally from a remote URL.

-

It uses the _all_docs view, and bulk insert the docs. -Note it saves the last replicated _id for each run and -starts from there in case the process stops before the end.

-
-
getDatabaseNamestring
-

Get the database name based on prefix and doctype

-
-
getPrefixstring
-

Get the URI prefix

-
-
- -## Functions - -
-
getQueryAlias(query)string
-
-
- -## Typedefs - -
-
SyncStatus : "idle" | "replicating"
-
-
MigrationParams : object
-

Migrate the current adapter

-
-
SyncInfo : object
-

Persist the synchronized doctypes

-
-
MigrationParamsobject
-

Migrate a PouchDB database to a new adapter.

-
-
- - - -## PouchLink -Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates -PouchDB collections for each doctype that it supports and knows how -to respond to queries and mutations. - -**Kind**: global class - -* [PouchLink](#PouchLink) - * [new PouchLink([opts])](#new_PouchLink_new) - * [.replicationStatus](#PouchLink+replicationStatus) : Record.<string, SyncStatus> - * [.getPouchAdapterName](#PouchLink+getPouchAdapterName) ⇒ string - * [.handleOnSync()](#PouchLink+handleOnSync) - * [.startReplication()](#PouchLink+startReplication) ⇒ void - * [.stopReplication()](#PouchLink+stopReplication) ⇒ void - * [.needsToWaitWarmup(doctype)](#PouchLink+needsToWaitWarmup) ⇒ boolean - - - -### new PouchLink([opts]) -constructor - Initializes a new PouchLink - -**Returns**: object - The PouchLink instance - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| [opts] | object | {} | | -| [opts.replicationInterval] | number | | Milliseconds between replications | -| opts.doctypes | Array.<string> | | Doctypes to replicate | -| opts.doctypesReplicationOptions | Array.<object> | | A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") | - - - -### pouchLink.replicationStatus : Record.<string, SyncStatus> -- Stores replication states per doctype - -**Kind**: instance property of [PouchLink](#PouchLink) - - -### pouchLink.getPouchAdapterName ⇒ string -Return the PouchDB adapter name. -Should be IndexedDB for newest adapters. - -**Kind**: instance property of [PouchLink](#PouchLink) -**Returns**: string - The adapter name - - -### pouchLink.handleOnSync() -Receives PouchDB updates (documents grouped by doctype). -Normalizes the data (.id -> ._id, .rev -> _rev). -Passes the data to the client and to the onSync handler. - -Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done - -**Kind**: instance method of [PouchLink](#PouchLink) - - -### pouchLink.startReplication() ⇒ void -User of the link can call this to start ongoing replications. -Typically, it can be used when the application regains focus. - -Emits pouchlink:sync:start event when the replication begins - -**Kind**: instance method of [PouchLink](#PouchLink) -**Access**: public - - -### pouchLink.stopReplication() ⇒ void -User of the link can call this to stop ongoing replications. -Typically, it can be used when the applications loses focus. - -Emits pouchlink:sync:stop event - -**Kind**: instance method of [PouchLink](#PouchLink) -**Access**: public - - -### pouchLink.needsToWaitWarmup(doctype) ⇒ boolean -Check if there is warmup queries for this doctype -and return if those queries are already warmed up or not - -**Kind**: instance method of [PouchLink](#PouchLink) -**Returns**: boolean - the need to wait for the warmup - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | Doctype to check | - - - -## Loop -Utility to call a function (task) periodically -and on demand immediately. - -Public API - -- start -- stop -- scheduleImmediateTask -- waitForCurrentTask - -**Kind**: global class - -* [Loop](#Loop) - * [.start()](#Loop+start) - * [.stop()](#Loop+stop) - * [.runImmediateTasks()](#Loop+runImmediateTasks) - * [.scheduleImmediateTask(task)](#Loop+scheduleImmediateTask) - * [.runTask()](#Loop+runTask) - * [.round()](#Loop+round) - - - -### loop.start() -Starts the loop. Will run the task periodically each `this.delay` ms. -Ignores multiple starts. - -**Kind**: instance method of [Loop](#Loop) - - -### loop.stop() -Stops the loop, clears immediate tasks. -Cancels current task if possible - -**Kind**: instance method of [Loop](#Loop) - - -### loop.runImmediateTasks() -Flushes the immediate tasks list and calls each task. -Each task is awaited before the next is started. - -**Kind**: instance method of [Loop](#Loop) - - -### loop.scheduleImmediateTask(task) -Schedules a task to be run immediately at next round. -Ignored if loop is not started. -If not task is passed, the default task from the loop is used. - -**Kind**: instance method of [Loop](#Loop) - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| task | function | | Optional custom function to be run immediately | - - - -### loop.runTask() -Calls and saves current task. -Stops loop in case of error of the task. - -**Kind**: instance method of [Loop](#Loop) - - -### loop.round() -Runs immediate tasks and then schedule the next round. -Immediate tasks are called sequentially without delay -There is a delay between immediate tasks and normal periodic tasks. - -**Kind**: instance method of [Loop](#Loop) - - -## PouchManager -Handles the lifecycle of several pouches - -- Creates/Destroys the pouches -- Replicates periodically - -**Kind**: global class - -* [PouchManager](#PouchManager) - * [.ensureDatabasesExist()](#PouchManager+ensureDatabasesExist) - * [.startReplicationLoop()](#PouchManager+startReplicationLoop) - * [.stopReplicationLoop()](#PouchManager+stopReplicationLoop) - * [.syncImmediately()](#PouchManager+syncImmediately) - * [.replicateOnce()](#PouchManager+replicateOnce) - - - -### pouchManager.ensureDatabasesExist() -Via a call to info() we ensure the database exist on the -remote side. This is done only once since after the first -call, we are sure that the databases have been created. - -**Kind**: instance method of [PouchManager](#PouchManager) - - -### pouchManager.startReplicationLoop() -Starts periodic syncing of the pouches - -**Kind**: instance method of [PouchManager](#PouchManager) - - -### pouchManager.stopReplicationLoop() -Stop periodic syncing of the pouches - -**Kind**: instance method of [PouchManager](#PouchManager) - - -### pouchManager.syncImmediately() -If a replication is currently ongoing, will start a replication -just after it has finished. Otherwise it will start a replication -immediately - -**Kind**: instance method of [PouchManager](#PouchManager) - - -### pouchManager.replicateOnce() -Starts replication - -**Kind**: instance method of [PouchManager](#PouchManager) - - -## persistLastReplicatedDocID -Persist the last replicated doc id for a doctype - -**Kind**: global constant - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | The replicated doctype | -| id | string | The docid | - - - -## getLastReplicatedDocID ⇒ string -Get the last replicated doc id for a doctype - -**Kind**: global constant -**Returns**: string - The last replicated docid - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | The doctype | - - - -## destroyAllLastReplicatedDocID -Destroy all the replicated doc id - -**Kind**: global constant - - -## getPersistedSyncedDoctypes ⇒ object -Get the persisted doctypes - -**Kind**: global constant -**Returns**: object - The synced doctypes - - -## destroySyncedDoctypes -Destroy the synced doctypes - -**Kind**: global constant - - -## persistDoctypeLastSequence -Persist the last CouchDB sequence for a synced doctype - -**Kind**: global constant - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | The synced doctype | -| sequence | string | The sequence hash | - - - -## getDoctypeLastSequence ⇒ string -Get the last CouchDB sequence for a doctype - -**Kind**: global constant -**Returns**: string - the last sequence - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | The doctype | - - - -## destroyAllDoctypeLastSequence -Destroy all the last sequence - -**Kind**: global constant - - -## destroyDoctypeLastSequence -Destroy the last sequence for a doctype - -**Kind**: global constant - -| Param | Type | Description | -| --- | --- | --- | -| doctype | string | The doctype | - - - -## persistWarmedUpQueries -Persist the warmed up queries - -**Kind**: global constant - -| Param | Type | Description | -| --- | --- | --- | -| warmedUpQueries | object | The warmedup queries | - - - -## getPersistedWarmedUpQueries ⇒ object -Get the warmed up queries - -**Kind**: global constant -**Returns**: object - the warmed up queries - - -## destroyWarmedUpQueries -Destroy the warmed queries - -**Kind**: global constant - - -## getAdapterName ⇒ string -Get the adapter name - -**Kind**: global constant -**Returns**: string - The adapter name - - -## persistAdapterName -Persist the adapter name - -**Kind**: global constant - -| Param | Type | Description | -| --- | --- | --- | -| adapter | string | The adapter name | - - - -## fetchRemoteInstance ⇒ object -Fetch remote instance - -**Kind**: global constant -**Returns**: object - The instance response - -| Param | Type | Description | -| --- | --- | --- | -| url | URL | The remote instance URL, including the credentials | -| params | object | The params to query the remote instance | - - - -## fetchRemoteLastSequence ⇒ string -Fetch last sequence from remote instance - -**Kind**: global constant -**Returns**: string - The last sequence - -| Param | Type | Description | -| --- | --- | --- | -| baseUrl | string | The base URL of the remote instance | - - - -## replicateAllDocs ⇒ Array -Replicate all docs locally from a remote URL. - -It uses the _all_docs view, and bulk insert the docs. -Note it saves the last replicated _id for each run and -starts from there in case the process stops before the end. - -**Kind**: global constant -**Returns**: Array - The retrieved documents - -| Param | Type | Description | -| --- | --- | --- | -| db | object | Pouch instance | -| baseUrl | string | The remote instance | -| doctype | string | The doctype to replicate | - - - -## getDatabaseName ⇒ string -Get the database name based on prefix and doctype - -**Kind**: global constant -**Returns**: string - The database name - -| Param | Type | Description | -| --- | --- | --- | -| prefix | string | The URL prefix | -| doctype | string | The database doctype | - - - -## getPrefix ⇒ string -Get the URI prefix - -**Kind**: global constant -**Returns**: string - The URI prefix - -| Param | Type | Description | -| --- | --- | --- | -| uri | string | The Cozy URI | - - - -## getQueryAlias(query) ⇒ string -**Kind**: global function -**Returns**: string - alias - -| Param | Type | Description | -| --- | --- | --- | -| query | QueryDefinition | The query definition whose name we're getting | - - - -## SyncStatus : "idle" \| "replicating" -**Kind**: global typedef - - -## MigrationParams : object -Migrate the current adapter - -**Kind**: global typedef - -| Param | Type | Description | -| --- | --- | --- | -| params | [MigrationParams](#MigrationParams) | Migration params | - -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| [fromAdapter] | string | The current adapter type, e.g. 'idb' | -| [toAdapter] | string | The new adapter type, e.g. 'indexeddb' | -| [url] | string | The Cozy URL | -| [plugins] | Array.<object> | The PouchDB plugins | - - - -## SyncInfo : object -Persist the synchronized doctypes - -**Kind**: global typedef - -| Param | Type | Description | -| --- | --- | --- | -| syncedDoctypes | Record.<string, SyncInfo> | The sync doctypes | - -**Properties** - -| Name | Type | -| --- | --- | -| Date | string | - - - -## MigrationParams ⇒ object -Migrate a PouchDB database to a new adapter. - -**Kind**: global typedef -**Returns**: object - - The migrated pouch - -| Param | Type | Description | -| --- | --- | --- | -| params | [MigrationParams](#MigrationParams) | The migration params | - -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| [dbName] | string | The database name | -| [fromAdapter] | string | The current adapter type, e.g. 'idb' | -| [toAdapter] | string | The new adapter type, e.g. 'indexeddb' | - diff --git a/package.json b/package.json index 32d807a5d1..d4cec92ad4 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "commitmsg": "commitlint -e $GIT_PARAMS", "clean": "rm -rf packages/*/dist", "cleanTypes": "rm -rf packages/*/types", - "docs": "node scripts/docs.js && yarn docs:cozy-client && yarn docs:cozy-pouch-link", + "cleanDocsApi": "rm -rf docs/api", + "docs": "yarn cleanDocsApi && node scripts/docs.js && yarn docs:cozy-client && yarn docs:cozy-pouch-link", "docs:cozy-client": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-client/tsconfig.json packages/cozy-client/src/index.js --out docs/api/cozy-client --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-client/", "docs:cozy-pouch-link": "yarn typedoc --readme none --hideInPageTOC --excludeExternals --excludePrivate --tsconfig packages/cozy-pouch-link/tsconfig.json packages/cozy-pouch-link/src/index.js --out docs/api/cozy-pouch-link --gitRevision master && yarn remark -o -u ./scripts/strip-typedoc-headings.mjs docs/api/cozy-pouch-link/", "types": "yarn cleanTypes && cd packages/cozy-client && yarn typecheck && cd ../cozy-pouch-link && yarn typecheck" From 803a55888ef5da1637d1970d8b71cc26c066478c Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 17 Jul 2024 09:14:07 +0200 Subject: [PATCH 05/84] fix: Call Travis type generation from project root instead --- scripts/travis.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/travis.sh b/scripts/travis.sh index 32d619d0f9..562f596d86 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -21,8 +21,7 @@ fi set -e set +e # The following command relies on exit 1 -cd packages/cozy-client -yarn typecheck +yarn types [ $? -eq 0 ] || exit 1 git diff --exit-code types_status=$? From a90648e718b20295f4e76fdb7e548a64f2066150 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:25:59 +0200 Subject: [PATCH 06/84] refactor(pouch-link): Make localStorage a class an return async methods We want to reduce coupling between CozyPouchLink and the browser's local storage The first step is to groupe existing methods into a PouchLocalStorage class, this will allow (in the next commit) to inject a storage adapter into the class constructor Also in some platforms like ReactNative, the localstorage methods are asynchronous. So in order to be compatible with all platforms we want those methods to become async by default The big main of making those methods async is that we cannot call them from the `PouchManager` constructor. So we introduce a new `.init()` method in the `PouchManager` class. This async method now contains all the initialization logic and should be called after creating a new `PouchManager` As `PouchManager` is called internally by `CozyPouchLink` and is not meant to be a publicly available class, then we don't consider this as a breaking change --- .../examples/periodic-sync/index.js | 8 +- packages/cozy-pouch-link/src/CozyPouchLink.js | 36 +- packages/cozy-pouch-link/src/PouchManager.js | 64 ++-- .../cozy-pouch-link/src/PouchManager.spec.js | 126 ++++--- packages/cozy-pouch-link/src/localStorage.js | 354 ++++++++++-------- .../cozy-pouch-link/src/startReplication.js | 20 +- .../src/startReplication.spec.js | 23 +- packages/cozy-pouch-link/src/types.js | 4 + 8 files changed, 348 insertions(+), 287 deletions(-) diff --git a/packages/cozy-pouch-link/examples/periodic-sync/index.js b/packages/cozy-pouch-link/examples/periodic-sync/index.js index ecfa6b5990..9e84eb7257 100755 --- a/packages/cozy-pouch-link/examples/periodic-sync/index.js +++ b/packages/cozy-pouch-link/examples/periodic-sync/index.js @@ -62,8 +62,9 @@ class App extends React.Component { } componentDidMount() { - this.createManager() - this.displayDocs() + this.createManager().then(() => { + this.displayDocs() + }) } componentWillUnmount() { @@ -83,7 +84,7 @@ class App extends React.Component { }) } - createManager() { + async createManager() { this.manager = new PouchManager([DOCTYPE], { replicationDelay: 2 * 1000, getReplicationURL: this.getReplicationURL, @@ -101,6 +102,7 @@ class App extends React.Component { this.displayDocs() } }) + await this.manager.init() } async displayDocs() { diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index cc4dd3f181..a341975a35 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -16,15 +16,10 @@ import { default as helpers } from './helpers' import { getIndexNameFromFields, getIndexFields } from './mango' import * as jsonapi from './jsonapi' import PouchManager from './PouchManager' +import { PouchLocalStorage } from './localStorage' import logger from './logger' import { migratePouch } from './migrations/adapter' import { getDatabaseName, getPrefix } from './utils' -import { - getPersistedSyncedDoctypes, - persistAdapterName, - getAdapterName, - destroyWarmedUpQueries -} from './localStorage' PouchDB.plugin(PouchDBFind) @@ -94,6 +89,7 @@ class PouchLink extends CozyLink { this.doctypes = doctypes this.doctypesReplicationOptions = doctypesReplicationOptions this.indexes = {} + this.storage = new PouchLocalStorage() /** @type {Record} - Stores replication states per doctype */ this.replicationStatus = this.replicationStatus || {} @@ -103,10 +99,11 @@ class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * - * @returns {string} The adapter name + * @returns {Promise} The adapter name */ static getPouchAdapterName = () => { - return getAdapterName() + const storage = new PouchLocalStorage() + return storage.getAdapterName() } getReplicationURL(doctype) { @@ -148,15 +145,15 @@ class PouchLink extends CozyLink { for (const plugin of plugins) { PouchDB.plugin(plugin) } - const doctypes = getPersistedSyncedDoctypes() + const doctypes = await this.storage.getPersistedSyncedDoctypes() for (const doctype of Object.keys(doctypes)) { const prefix = getPrefix(url) const dbName = getDatabaseName(prefix, doctype) await migratePouch({ dbName, fromAdapter, toAdapter }) - destroyWarmedUpQueries() // force recomputing indexes + await this.storage.destroyWarmedUpQueries() // force recomputing indexes } - persistAdapterName('indexeddb') + await this.storage.persistAdapterName('indexeddb') } catch (err) { console.error('PouchLink: PouchDB migration failed. ', err) } @@ -194,9 +191,9 @@ class PouchLink extends CozyLink { logger.log('Create pouches with ' + prefix + ' prefix') } - if (!getAdapterName()) { + if (!(await this.storage.getAdapterName())) { const adapter = get(this.options, 'pouch.options.adapter') - persistAdapterName(adapter) + await this.storage.persistAdapterName(adapter) } this.pouches = new PouchManager(this.doctypes, { @@ -210,6 +207,7 @@ class PouchLink extends CozyLink { prefix, executeQuery: this.executeQuery.bind(this) }) + await this.pouches.init() if (this.client && this.options.initialSync) { this.startReplication() @@ -333,7 +331,7 @@ class PouchLink extends CozyLink { return !!this.getPouch(impactedDoctype) } - request(operation, result = null, forward = doNothing) { + async request(operation, result = null, forward = doNothing) { const doctype = getDoctypeFromOperation(operation) if (!this.pouches) { @@ -355,7 +353,7 @@ class PouchLink extends CozyLink { return forward(operation) } - if (this.needsToWaitWarmup(doctype)) { + if (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` @@ -386,18 +384,18 @@ class PouchLink extends CozyLink { * and return if those queries are already warmed up or not * * @param {string} doctype - Doctype to check - * @returns {boolean} the need to wait for the warmup + * @returns {Promise} the need to wait for the warmup */ - needsToWaitWarmup(doctype) { + async needsToWaitWarmup(doctype) { if ( this.doctypesReplicationOptions && this.doctypesReplicationOptions[doctype] && this.doctypesReplicationOptions[doctype].warmupQueries ) { - return !this.pouches.areQueriesWarmedUp( + return !(await this.pouches.areQueriesWarmedUp( doctype, this.doctypesReplicationOptions[doctype].warmupQueries - ) + )) } return false } diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 87eee2017d..a9420410c9 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -7,6 +7,7 @@ import zip from 'lodash/zip' import startsWith from 'lodash/startsWith' import { isMobileApp } from 'cozy-device-helper' +import { PouchLocalStorage } from './localStorage' import Loop from './loop' import logger from './logger' import { fetchRemoteLastSequence } from './remote' @@ -34,20 +35,26 @@ const getQueryAlias = query => { class PouchManager { constructor(doctypes, options) { this.options = options - const pouchPlugins = get(options, 'pouch.plugins', []) - const pouchOptions = get(options, 'pouch.options', {}) + this.doctypes = doctypes + + this.storage = new PouchLocalStorage() + } + + async init() { + const pouchPlugins = get(this.options, 'pouch.plugins', []) + const pouchOptions = get(this.options, 'pouch.options', {}) - forEach(pouchPlugins, plugin => PouchDB.plugin(plugin)) this.pouches = fromPairs( - doctypes.map(doctype => [ + this.doctypes.map(doctype => [ doctype, - new PouchDB(getDatabaseName(options.prefix, doctype), pouchOptions) + new PouchDB(getDatabaseName(this.options.prefix, doctype), pouchOptions) ]) ) - this.syncedDoctypes = localStorage.getPersistedSyncedDoctypes() - this.warmedUpQueries = localStorage.getPersistedWarmedUpQueries() - this.getReplicationURL = options.getReplicationURL - this.doctypesReplicationOptions = options.doctypesReplicationOptions || {} + this.syncedDoctypes = await this.storage.getPersistedSyncedDoctypes() + this.warmedUpQueries = await this.storage.getPersistedWarmedUpQueries() + this.getReplicationURL = this.options.getReplicationURL + this.doctypesReplicationOptions = + this.options.doctypesReplicationOptions || {} this.listenerLaunched = false // We must ensure databases exist on the remote before @@ -84,13 +91,13 @@ class PouchManager { } } - destroy() { + async destroy() { this.stopReplicationLoop() this.removeListeners() - this.clearSyncedDoctypes() - this.clearWarmedUpQueries() - localStorage.destroyAllDoctypeLastSequence() - localStorage.destroyAllLastReplicatedDocID() + await this.clearSyncedDoctypes() + await this.clearWarmedUpQueries() + await this.storage.destroyAllDoctypeLastSequence() + await this.storage.destroyAllLastReplicatedDocID() return Promise.all( Object.values(this.pouches).map(pouch => pouch.destroy()) @@ -189,9 +196,9 @@ class PouchManager { // Before the first replication, get the last remote sequence, // which will be used as a checkpoint for the next replication const lastSeq = await fetchRemoteLastSequence(getReplicationURL()) - localStorage.persistDoctypeLastSequence(doctype, lastSeq) + await this.storage.persistDoctypeLastSequence(doctype, lastSeq) } else { - seq = localStorage.getDoctypeLastSequence(doctype) + seq = await this.storage.getDoctypeLastSequence(doctype) } const replicationOptions = get( @@ -210,15 +217,16 @@ class PouchManager { const res = await startReplication( pouch, replicationOptions, - getReplicationURL + getReplicationURL, + this.storage ) if (seq) { // We only need the sequence for the second replication, as PouchDB // will use a local checkpoint for the next runs. - localStorage.destroyDoctypeLastSequence(doctype) + await this.storage.destroyDoctypeLastSequence(doctype) } - this.updateSyncInfo(doctype) + await this.updateSyncInfo(doctype) this.checkToWarmupDoctype(doctype, replicationOptions) if (this.options.onDoctypeSyncEnd) { this.options.onDoctypeSyncEnd(doctype) @@ -281,9 +289,9 @@ class PouchManager { return this.pouches[doctype] } - updateSyncInfo(doctype) { + async updateSyncInfo(doctype) { this.syncedDoctypes[doctype] = { date: new Date().toISOString() } - localStorage.persistSyncedDoctypes(this.syncedDoctypes) + await this.storage.persistSyncedDoctypes(this.syncedDoctypes) } getSyncInfo(doctype) { @@ -295,9 +303,9 @@ class PouchManager { return info ? !!info.date : false } - clearSyncedDoctypes() { + async clearSyncedDoctypes() { this.syncedDoctypes = {} - localStorage.destroySyncedDoctypes() + await this.storage.destroySyncedDoctypes() } async warmupQueries(doctype, queries) { @@ -312,7 +320,7 @@ class PouchManager { } }) ) - localStorage.persistWarmedUpQueries(this.warmedUpQueries) + await this.storage.persistWarmedUpQueries(this.warmedUpQueries) logger.log('PouchManager: warmupQueries for ' + doctype + ' are done') } catch (err) { logger.error( @@ -332,8 +340,8 @@ class PouchManager { } } - areQueriesWarmedUp(doctype, queries) { - const persistWarmedUpQueries = localStorage.getPersistedWarmedUpQueries() + async areQueriesWarmedUp(doctype, queries) { + const persistWarmedUpQueries = await this.storage.getPersistedWarmedUpQueries() return queries.every( query => persistWarmedUpQueries[doctype] && @@ -341,9 +349,9 @@ class PouchManager { ) } - clearWarmedUpQueries() { + async clearWarmedUpQueries() { this.warmedUpQueries = {} - localStorage.destroyWarmedUpQueries() + await this.storage.destroyWarmedUpQueries() } } diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index 0fa00a477d..8cb42cc934 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -19,10 +19,16 @@ jest.mock('./remote', () => ({ import * as rep from './startReplication' import PouchDB from 'pouchdb-browser' -import * as ls from './localStorage' +import { + LOCALSTORAGE_SYNCED_KEY, + LOCALSTORAGE_WARMUPEDQUERIES_KEY, + PouchLocalStorage +} from './localStorage' import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' +const ls = new PouchLocalStorage() + const sleep = delay => { return new Promise(resolve => { setTimeout(resolve, delay) @@ -63,7 +69,7 @@ describe('PouchManager', () => { getReplicationURL, onSync = jest.fn() - beforeEach(() => { + beforeEach(async () => { getReplicationURL = () => 'http://replicationURL.local' managerOptions = { replicationDelay: 16, @@ -72,6 +78,7 @@ describe('PouchManager', () => { prefix: 'cozy.tools' } manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() const pouch = manager.getPouch('io.cozy.todos') const replication = mocks.pouchReplication({ direction: 'pull', @@ -133,6 +140,7 @@ describe('PouchManager', () => { 'io.cozy.readonly': { strategy: 'fromRemote' } } }) + await manager.init() const normalPouch = manager.getPouch('io.cozy.todos') const readOnlyPouch = manager.getPouch('io.cozy.readonly') readOnlyPouch.replicate = {} @@ -155,6 +163,7 @@ describe('PouchManager', () => { } } ) + await manager.init() const normalPouch = manager.getPouch('io.cozy.todos') const readOnlyPouch = manager.getPouch('io.cozy.readonly') readOnlyPouch.replicate = {} @@ -231,14 +240,16 @@ describe('PouchManager', () => { it('should add pouch plugin', async () => { const options = { ...managerOptions, pouch: { plugins: ['myPlugin'] } } - new PouchManager(['io.cozy.todos'], options) + const manager = new PouchManager(['io.cozy.todos'], options) + await manager.init() expect(PouchDB.plugin).toHaveBeenCalledTimes(1) }) it('should instanciate pouch with options', async () => { const pouchOptions = { adapter: 'cordova-sqlite', location: 'default' } const options = { ...managerOptions, pouch: { options: pouchOptions } } - new PouchManager(['io.cozy.todos'], options) + const manager = new PouchManager(['io.cozy.todos'], options) + await manager.init() expect(PouchDB).toHaveBeenCalledWith( 'cozy.tools_io.cozy.todos', pouchOptions @@ -246,33 +257,36 @@ describe('PouchManager', () => { }) describe('getPersistedSyncedDoctypes', () => { - it('should return an empty array if local storage is empty', () => { - expect(ls.getPersistedSyncedDoctypes()).toEqual({}) + it('should return an empty array if local storage is empty', async () => { + expect(await ls.getPersistedSyncedDoctypes()).toEqual({}) }) - it('should return an empty array if local storage contains something that is not an array', () => { - localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY] = 'true' - expect(ls.getPersistedSyncedDoctypes()).toEqual({}) + it('should return an empty array if local storage contains something that is not an array', async () => { + localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY] = 'true' + expect(await ls.getPersistedSyncedDoctypes()).toEqual({}) }) - it('should return the list of doctypes if local storage contains one', () => { + it('should return the list of doctypes if local storage contains one', async () => { const persistedSyncedDoctypes = { 'io.cozy.todos': { date: '2021-08-11T13:48:06.085Z' } } - localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY] = JSON.stringify( + localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY] = JSON.stringify( + persistedSyncedDoctypes + ) + expect(await ls.getPersistedSyncedDoctypes()).toEqual( persistedSyncedDoctypes ) - expect(ls.getPersistedSyncedDoctypes()).toEqual(persistedSyncedDoctypes) }) }) describe('persistSyncedDoctypes', () => { - it('should put the list of synced doctypes in localStorage', () => { + it('should put the list of synced doctypes in localStorage', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() manager.syncedDoctypes = ['io.cozy.todos'] ls.persistSyncedDoctypes(manager.syncedDoctypes) - expect(localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY]).toEqual( + expect(localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY]).toEqual( JSON.stringify(manager.syncedDoctypes) ) }) @@ -287,17 +301,19 @@ describe('PouchManager', () => { MockDate.reset() }) - it('should add the doctype to synced doctypes', () => { + it('should add the doctype to synced doctypes', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) - manager.updateSyncInfo('io.cozy.todos') + await manager.init() + await manager.updateSyncInfo('io.cozy.todos') expect(Object.keys(manager.syncedDoctypes)).toEqual(['io.cozy.todos']) }) - it('should persist the new synced doctypes list', () => { + it('should persist the new synced doctypes list', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() - manager.updateSyncInfo('io.cozy.todos') - expect(localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY]).toEqual( + await manager.updateSyncInfo('io.cozy.todos') + expect(localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY]).toEqual( JSON.stringify({ 'io.cozy.todos': { date: '2021-08-01T00:00:00.000Z' } }) @@ -308,12 +324,13 @@ describe('PouchManager', () => { describe('isSynced', () => { let manager - beforeEach(() => { + beforeEach(async () => { manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() }) - it('should return true if the doctype is synced', () => { - manager.updateSyncInfo('io.cozy.todos') + it('should return true if the doctype is synced', async () => { + await manager.updateSyncInfo('io.cozy.todos') expect(manager.isSynced('io.cozy.todos')).toBe(true) }) @@ -323,90 +340,92 @@ describe('PouchManager', () => { }) describe('destroySyncedDoctypes', () => { - it('should destroy the local storage item', () => { - ls.destroySyncedDoctypes() + it('should destroy the local storage item', async () => { + await ls.destroySyncedDoctypes() expect(localStorage.removeItem).toHaveBeenLastCalledWith( - ls.LOCALSTORAGE_SYNCED_KEY + LOCALSTORAGE_SYNCED_KEY ) }) - it('should reset syncedDoctypes', () => { + it('should reset syncedDoctypes', async () => { manager.syncedDoctypes = { 'io.cozy.todos': { date: '2021-08-11T13:48:06.085Z' } } - manager.clearSyncedDoctypes() + await manager.clearSyncedDoctypes() expect(manager.syncedDoctypes).toEqual({}) }) }) describe('getPersistedWarmedUpQueriess', () => { - it('should return an empty object if local storage is empty', () => { - expect(ls.getPersistedWarmedUpQueries()).toEqual({}) + it('should return an empty object if local storage is empty', async () => { + expect(await ls.getPersistedWarmedUpQueries()).toEqual({}) }) - it('should return the list of queries if local storage contains ones', () => { + it('should return the list of queries if local storage contains ones', async () => { const persistedQueries = [query().options.as] - localStorage.__STORE__[ - ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY - ] = JSON.stringify(persistedQueries) - expect(ls.getPersistedWarmedUpQueries()).toEqual(persistedQueries) + localStorage.__STORE__[LOCALSTORAGE_WARMUPEDQUERIES_KEY] = JSON.stringify( + persistedQueries + ) + expect(await ls.getPersistedWarmedUpQueries()).toEqual(persistedQueries) }) }) describe('persistWarmedUpQueries', () => { - it('should put the list of warmedUpQueries in localStorage', () => { + it('should put the list of warmedUpQueries in localStorage', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() manager.warmedUpQueries = { 'io.cozy.todos': ['query1', 'query2'] } - ls.persistWarmedUpQueries(manager.warmedUpQueries) + await ls.persistWarmedUpQueries(manager.warmedUpQueries) - expect( - localStorage.__STORE__[ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY] - ).toEqual(JSON.stringify(manager.warmedUpQueries)) + expect(localStorage.__STORE__[LOCALSTORAGE_WARMUPEDQUERIES_KEY]).toEqual( + JSON.stringify(manager.warmedUpQueries) + ) }) }) describe('areQueriesWarmedUp', () => { let manager - beforeEach(() => { + beforeEach(async () => { manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() }) - it('should return true if all the queries are warmuped', () => { + it('should return true if all the queries are warmuped', async () => { manager.warmedUpQueries = { 'io.cozy.todos': [query1().options.as, query2().options.as] } - ls.persistWarmedUpQueries(manager.warmedUpQueries) + await ls.persistWarmedUpQueries(manager.warmedUpQueries) expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(true) }) - it('should return false if at least one query is not warmuped', () => { + it('should return false if at least one query is not warmuped', async () => { manager.warmedUpQueries = { 'io.cozy.todos': [query2().options.as] } - ls.persistWarmedUpQueries() + await ls.persistWarmedUpQueries() expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(false) }) - it('should return false if the queries are not been done', () => { + it('should return false if the queries are not been done', async () => { expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(false) }) }) describe('clearWarmedupQueries', () => { - it('should clear the local storage item', () => { + it('should clear the local storage item', async () => { manager.clearWarmedUpQueries() expect(localStorage.removeItem).toHaveBeenLastCalledWith( - ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY + LOCALSTORAGE_WARMUPEDQUERIES_KEY ) }) it('should reset warmedupQueries', () => { @@ -490,7 +509,7 @@ describe('PouchManager', () => { describe('warmupQueries', () => { let manager const executeMock = jest.fn() - beforeEach(() => { + beforeEach(async () => { let newManagerOptions = { ...managerOptions, executeQuery: executeMock, @@ -502,6 +521,7 @@ describe('PouchManager', () => { } } manager = new PouchManager(['io.cozy.todos'], newManagerOptions) + await manager.init() }) it('should executes warmeupQueries on the first replicationLoop only', async () => { @@ -524,7 +544,7 @@ describe('PouchManager', () => { .definition() .toDefinition() ) - expect(ls.getPersistedWarmedUpQueries()).toEqual({ + expect(await ls.getPersistedWarmedUpQueries()).toEqual({ 'io.cozy.todos': ['query1', 'query2'] }) //Simulation of a loop. Let's replicate again @@ -541,7 +561,7 @@ describe('PouchManager', () => { await manager.replicateOnce() await sleep(10) - expect(ls.getPersistedWarmedUpQueries()).toEqual({}) + expect(await ls.getPersistedWarmedUpQueries()).toEqual({}) expect(manager.warmedUpQueries['io.cozy.todos']).toBeUndefined() }) }) diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index ad05e48d6a..58df2d1e1d 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -7,182 +7,212 @@ export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY = 'cozy-client-pouch-link-lastreplicateddocid' export const LOCALSTORAGE_ADAPTERNAME = 'cozy-client-pouch-link-adaptername' -/** - * Persist the last replicated doc id for a doctype - * - * @param {string} doctype - The replicated doctype - * @param {string} id - The docid - */ -export const persistLastReplicatedDocID = (doctype, id) => { - const docids = getAllLastReplicatedDocID() - docids[doctype] = id - - window.localStorage.setItem( - LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, - JSON.stringify(docids) - ) -} +export class PouchLocalStorage { + /** + * Persist the last replicated doc id for a doctype + * + * @param {string} doctype - The replicated doctype + * @param {string} id - The docid + * + * @returns {Promise} + */ + async persistLastReplicatedDocID(doctype, id) { + const docids = await this.getAllLastReplicatedDocID() + docids[doctype] = id + + await window.localStorage.setItem( + LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, + JSON.stringify(docids) + ) + } -export const getAllLastReplicatedDocID = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) - return item ? JSON.parse(item) : {} -} + /** + * @returns {Promise>} + */ + async getAllLastReplicatedDocID() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_LASTREPLICATEDDOCID_KEY + ) + return item ? JSON.parse(item) : {} + } -/** - * Get the last replicated doc id for a doctype - * - * @param {string} doctype - The doctype - * @returns {string} The last replicated docid - */ -export const getLastReplicatedDocID = doctype => { - const docids = getAllLastSequences() - return docids[doctype] -} + /** + * Get the last replicated doc id for a doctype + * + * @param {string} doctype - The doctype + * @returns {Promise} The last replicated docid + */ + async getLastReplicatedDocID(doctype) { + const docids = await this.getAllLastSequences() + return docids[doctype] + } -/** - * Destroy all the replicated doc id - */ -export const destroyAllLastReplicatedDocID = () => { - window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) -} + /** + * Destroy all the replicated doc id + * + * @returns {Promise} + */ + async destroyAllLastReplicatedDocID() { + await window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) + } -/** - * Persist the synchronized doctypes - * - * @typedef {object} SyncInfo - * @property {string} Date - * - * @param {Record} syncedDoctypes - The sync doctypes - */ -export const persistSyncedDoctypes = syncedDoctypes => { - window.localStorage.setItem( - LOCALSTORAGE_SYNCED_KEY, - JSON.stringify(syncedDoctypes) - ) -} + /** + * Persist the synchronized doctypes + * + * @param {Record} syncedDoctypes - The sync doctypes + * + * @returns {Promise} + */ + async persistSyncedDoctypes(syncedDoctypes) { + await window.localStorage.setItem( + LOCALSTORAGE_SYNCED_KEY, + JSON.stringify(syncedDoctypes) + ) + } -/** - * Get the persisted doctypes - * - * @returns {object} The synced doctypes - */ -export const getPersistedSyncedDoctypes = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) - const parsed = item ? JSON.parse(item) : {} - if (typeof parsed !== 'object') { - return {} - } - return parsed -} + /** + * Get the persisted doctypes + * + * @returns {Promise} The synced doctypes + */ + async getPersistedSyncedDoctypes() { + const item = await window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) + const parsed = item ? JSON.parse(item) : {} + if (typeof parsed !== 'object') { + return {} + } + return parsed + } -/** - * Destroy the synced doctypes - * - */ -export const destroySyncedDoctypes = () => { - window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) -} + /** + * Destroy the synced doctypes + * + * @returns {Promise} + */ + async destroySyncedDoctypes() { + await window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) + } -/** - * Persist the last CouchDB sequence for a synced doctype - * - * @param {string} doctype - The synced doctype - * @param {string} sequence - The sequence hash - */ -export const persistDoctypeLastSequence = (doctype, sequence) => { - const seqs = getAllLastSequences() - seqs[doctype] = sequence - - window.localStorage.setItem( - LOCALSTORAGE_LASTSEQUENCES_KEY, - JSON.stringify(seqs) - ) -} + /** + * Persist the last CouchDB sequence for a synced doctype + * + * @param {string} doctype - The synced doctype + * @param {string} sequence - The sequence hash + * + * @returns {Promise} + */ + async persistDoctypeLastSequence(doctype, sequence) { + const seqs = await this.getAllLastSequences() + seqs[doctype] = sequence + + await window.localStorage.setItem( + LOCALSTORAGE_LASTSEQUENCES_KEY, + JSON.stringify(seqs) + ) + } -export const getAllLastSequences = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_LASTSEQUENCES_KEY) - return item ? JSON.parse(item) : {} -} + /** + * @returns {Promise} + */ + async getAllLastSequences() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_LASTSEQUENCES_KEY + ) + return item ? JSON.parse(item) : {} + } -/** - * Get the last CouchDB sequence for a doctype - * - * @param {string} doctype - The doctype - * @returns {string} the last sequence - */ -export const getDoctypeLastSequence = doctype => { - const seqs = getAllLastSequences() - return seqs[doctype] -} + /** + * Get the last CouchDB sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} the last sequence + */ + async getDoctypeLastSequence(doctype) { + const seqs = await this.getAllLastSequences() + return seqs[doctype] + } -/** - * Destroy all the last sequence - */ -export const destroyAllDoctypeLastSequence = () => { - window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) -} + /** + * Destroy all the last sequence + * + * @returns {Promise} + */ + async destroyAllDoctypeLastSequence() { + await window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) + } -/** - * Destroy the last sequence for a doctype - * - * @param {string} doctype - The doctype - */ -export const destroyDoctypeLastSequence = doctype => { - const seqs = getAllLastSequences() - delete seqs[doctype] - window.localStorage.setItem( - LOCALSTORAGE_LASTSEQUENCES_KEY, - JSON.stringify(seqs) - ) -} + /** + * Destroy the last sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} + */ + async destroyDoctypeLastSequence(doctype) { + const seqs = await this.getAllLastSequences() + delete seqs[doctype] + await window.localStorage.setItem( + LOCALSTORAGE_LASTSEQUENCES_KEY, + JSON.stringify(seqs) + ) + } -/** - * Persist the warmed up queries - * - * @param {object} warmedUpQueries - The warmedup queries - */ -export const persistWarmedUpQueries = warmedUpQueries => { - window.localStorage.setItem( - LOCALSTORAGE_WARMUPEDQUERIES_KEY, - JSON.stringify(warmedUpQueries) - ) -} + /** + * Persist the warmed up queries + * + * @param {object} warmedUpQueries - The warmedup queries + * + * @returns {Promise} + */ + async persistWarmedUpQueries(warmedUpQueries) { + await window.localStorage.setItem( + LOCALSTORAGE_WARMUPEDQUERIES_KEY, + JSON.stringify(warmedUpQueries) + ) + } -/** - * Get the warmed up queries - * - * @returns {object} the warmed up queries - */ -export const getPersistedWarmedUpQueries = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) - if (!item) { - return {} - } - return JSON.parse(item) -} + /** + * Get the warmed up queries + * + * @returns {Promise} the warmed up queries + */ + async getPersistedWarmedUpQueries() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_WARMUPEDQUERIES_KEY + ) + if (!item) { + return {} + } + return JSON.parse(item) + } -/** - * Destroy the warmed queries - * - */ -export const destroyWarmedUpQueries = () => { - window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) -} + /** + * Destroy the warmed queries + * + * @returns {Promise} + */ + async destroyWarmedUpQueries() { + await window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) + } -/** - * Get the adapter name - * - * @returns {string} The adapter name - */ -export const getAdapterName = () => { - return window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) -} + /** + * Get the adapter name + * + * @returns {Promise} The adapter name + */ + async getAdapterName() { + return await window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) + } -/** - * Persist the adapter name - * - * @param {string} adapter - The adapter name - */ -export const persistAdapterName = adapter => { - window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + /** + * Persist the adapter name + * + * @param {string} adapter - The adapter name + * + * @returns {Promise} + */ + async persistAdapterName(adapter) { + await window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + } } diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 4ff5d581af..7774f8b13d 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -2,10 +2,7 @@ import { default as helpers } from './helpers' import startsWith from 'lodash/startsWith' import logger from './logger' import { fetchRemoteInstance } from './remote' -import { - getLastReplicatedDocID, - persistLastReplicatedDocID -} from './localStorage' + const { isDesignDocument, isDeletedDocument } = helpers const BATCH_SIZE = 1000 // we have mostly small documents @@ -40,13 +37,15 @@ const TIME_UNITS = [['ms', 1000], ['s', 60], ['m', 60], ['h', 24]] * @param {string} replicationOptions.doctype The doctype to replicate * @param {import('cozy-client/types/types').Query[]} replicationOptions.warmupQueries The queries to warmup * @param {Function} getReplicationURL A function that should return the remote replication URL + * @param {import('./localStorage').PouchLocalStorage} storage Methods to access local storage * * @returns {import('./types').CancelablePromise} A cancelable promise that resolves at the end of the replication */ export const startReplication = ( pouch, replicationOptions, - getReplicationURL + getReplicationURL, + storage ) => { let replication let docs = {} @@ -71,7 +70,7 @@ export const startReplication = ( // For the first remote->local replication, we manually replicate all docs // as it avoids to replicate all revs history, which can lead to // performances issues - docs = await replicateAllDocs(pouch, url, doctype) + docs = await replicateAllDocs(pouch, url, doctype, storage) const end = new Date() if (process.env.NODE_ENV !== 'production') { logger.info( @@ -144,13 +143,14 @@ const filterDocs = docs => { * @param {object} db - Pouch instance * @param {string} baseUrl - The remote instance * @param {string} doctype - The doctype to replicate + * @param {import('./localStorage').PouchLocalStorage} storage - Methods to access local storage * @returns {Promise} The retrieved documents */ -export const replicateAllDocs = async (db, baseUrl, doctype) => { +export const replicateAllDocs = async (db, baseUrl, doctype, storage) => { const remoteUrlAllDocs = new URL(`${baseUrl}/_all_docs`) const batchSize = BATCH_SIZE let hasMore = true - let startDocId = getLastReplicatedDocID(doctype) // Get last replicated _id in localStorage + let startDocId = await storage.getLastReplicatedDocID(doctype) // Get last replicated _id in localStorage let docs = [] while (hasMore) { @@ -169,7 +169,7 @@ export const replicateAllDocs = async (db, baseUrl, doctype) => { hasMore = false } await helpers.insertBulkDocs(db, docs) - persistLastReplicatedDocID(doctype, startDocId) + await storage.persistLastReplicatedDocID(doctype, startDocId) } } else { const res = await fetchRemoteInstance(remoteUrlAllDocs, { @@ -184,7 +184,7 @@ export const replicateAllDocs = async (db, baseUrl, doctype) => { filteredDocs.shift() // Remove first element, already included in previous request startDocId = filteredDocs[filteredDocs.length - 1]._id await helpers.insertBulkDocs(db, filteredDocs) - persistLastReplicatedDocID(doctype, startDocId) + await storage.persistLastReplicatedDocID(doctype, startDocId) docs = docs.concat(filteredDocs) if (res.rows.length < batchSize) { diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index 41acd9a503..874aea53af 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -1,13 +1,7 @@ import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' -import { getLastReplicatedDocID } from './localStorage' import { replicateAllDocs } from './startReplication' -jest.mock('./localStorage', () => ({ - getLastReplicatedDocID: jest.fn(), - persistLastReplicatedDocID: jest.fn() -})) - jest.mock('./remote', () => ({ fetchRemoteLastSequence: jest.fn(), fetchRemoteInstance: jest.fn() @@ -27,22 +21,27 @@ const generateDocs = nDocs => { return docs } +const storage = { + getLastReplicatedDocID: jest.fn(), + persistLastReplicatedDocID: jest.fn() +} + describe('replication through _all_docs', () => { beforeEach(() => { fetchRemoteLastSequence.mockResolvedValue('10-xyz') }) it('should replicate all docs', async () => { - getLastReplicatedDocID.mockReturnValue(null) + storage.getLastReplicatedDocID.mockReturnValue(null) const dummyDocs = generateDocs(2) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) }) it('should replicate all docs when it gets more docs than the batch limit', async () => { - getLastReplicatedDocID.mockReturnValue(null) + storage.getLastReplicatedDocID.mockReturnValue(null) const dummyDocs = generateDocs(1002) fetchRemoteInstance.mockResolvedValueOnce({ rows: dummyDocs.slice(0, 1001) @@ -51,17 +50,17 @@ describe('replication through _all_docs', () => { rows: dummyDocs.slice(1000, 1002) }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) }) it('should replicate from the last saved doc id', async () => { - getLastReplicatedDocID.mockReturnValue('5') + storage.getLastReplicatedDocID.mockReturnValue('5') const dummyDocs = generateDocs(10) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs.slice(5, 11) }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const calledUrl = new URL(`${url}/_all_docs`) expect(fetchRemoteInstance).toHaveBeenCalledWith(calledUrl, { diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index 86fc108772..ddf4a7a4c2 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -11,4 +11,8 @@ * @typedef {CancelablePromise[] & Cancelable} CancelablePromises */ +/** @typedef {object} SyncInfo + * @property {string} Date + */ + export default {} From 31ec182d50398a2954538f557c2f2a0471af845a Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:06:52 +0200 Subject: [PATCH 07/84] feat(pouch-link): Allow to inject a local storage in CozyPouchLink We want to reduce coupling between CozyPouchLink and the browser's local storage This will allow to use CozyPouchLink from a react-native project where `window.localStorage` API is not available If no custom storage is injected, then `platformWeb.js` will be used by default In order to inject a custom storage, create a new platform object containing the same API as `platformWeb.js` and add it into the PouchLink constructor's `platform` option ```js import { default as PouchLink } from 'cozy-pouch-link' // Class based on `platformWeb.js` import { CustomPlaftorm } from './CustomPlaftorm' const pouchLink = new PouchLink({ platform: new CustomPlaftorm() }) ``` --- packages/cozy-pouch-link/src/CozyPouchLink.js | 14 +++++--- packages/cozy-pouch-link/src/PouchManager.js | 6 ++-- .../cozy-pouch-link/src/PouchManager.spec.js | 3 +- packages/cozy-pouch-link/src/localStorage.js | 34 +++++++++++-------- packages/cozy-pouch-link/src/platformWeb.js | 15 ++++++++ packages/cozy-pouch-link/src/types.js | 12 +++++++ 6 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 packages/cozy-pouch-link/src/platformWeb.js diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index a341975a35..9b7f43da49 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -19,6 +19,7 @@ import PouchManager from './PouchManager' import { PouchLocalStorage } from './localStorage' import logger from './logger' import { migratePouch } from './migrations/adapter' +import { platformWeb } from './platformWeb' import { getDatabaseName, getPrefix } from './utils' PouchDB.plugin(PouchDBFind) @@ -74,6 +75,7 @@ class PouchLink extends CozyLink { * @param {number} [opts.replicationInterval] Milliseconds between replications * @param {string[]} opts.doctypes Doctypes to replicate * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods */ constructor(opts) { @@ -89,7 +91,9 @@ class PouchLink extends CozyLink { this.doctypes = doctypes this.doctypesReplicationOptions = doctypesReplicationOptions this.indexes = {} - this.storage = new PouchLocalStorage() + this.storage = new PouchLocalStorage( + options.platform?.storage || platformWeb.storage + ) /** @type {Record} - Stores replication states per doctype */ this.replicationStatus = this.replicationStatus || {} @@ -99,10 +103,11 @@ class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * + * @param {import('./types').LocalStorage} localStorage Methods to access local storage * @returns {Promise} The adapter name */ - static getPouchAdapterName = () => { - const storage = new PouchLocalStorage() + static getPouchAdapterName = localStorage => { + const storage = new PouchLocalStorage(localStorage || platformWeb.storage) return storage.getAdapterName() } @@ -205,7 +210,8 @@ class PouchLink extends CozyLink { onDoctypeSyncStart: this.handleDoctypeSyncStart.bind(this), onDoctypeSyncEnd: this.handleDoctypeSyncEnd.bind(this), prefix, - executeQuery: this.executeQuery.bind(this) + executeQuery: this.executeQuery.bind(this), + platform: this.options.platform }) await this.pouches.init() diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index a9420410c9..4d2fb67038 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -10,9 +10,9 @@ import { isMobileApp } from 'cozy-device-helper' import { PouchLocalStorage } from './localStorage' import Loop from './loop' import logger from './logger' +import { platformWeb } from './platformWeb' import { fetchRemoteLastSequence } from './remote' import { startReplication } from './startReplication' -import * as localStorage from './localStorage' import { getDatabaseName } from './utils' const DEFAULT_DELAY = 30 * 1000 @@ -37,7 +37,9 @@ class PouchManager { this.options = options this.doctypes = doctypes - this.storage = new PouchLocalStorage() + this.storage = new PouchLocalStorage( + options.platform?.storage || platformWeb.storage + ) } async init() { diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index 8cb42cc934..6a5f7187b5 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -24,10 +24,11 @@ import { LOCALSTORAGE_WARMUPEDQUERIES_KEY, PouchLocalStorage } from './localStorage' +import { platformWeb } from './platformWeb' import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' -const ls = new PouchLocalStorage() +const ls = new PouchLocalStorage(platformWeb.storage) const sleep = delay => { return new Promise(resolve => { diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index 58df2d1e1d..923f45553c 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -8,6 +8,10 @@ export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY = export const LOCALSTORAGE_ADAPTERNAME = 'cozy-client-pouch-link-adaptername' export class PouchLocalStorage { + constructor(storageEngine) { + this.storageEngine = storageEngine + } + /** * Persist the last replicated doc id for a doctype * @@ -20,7 +24,7 @@ export class PouchLocalStorage { const docids = await this.getAllLastReplicatedDocID() docids[doctype] = id - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, JSON.stringify(docids) ) @@ -30,7 +34,7 @@ export class PouchLocalStorage { * @returns {Promise>} */ async getAllLastReplicatedDocID() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_LASTREPLICATEDDOCID_KEY ) return item ? JSON.parse(item) : {} @@ -53,7 +57,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyAllLastReplicatedDocID() { - await window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) } /** @@ -64,7 +68,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistSyncedDoctypes(syncedDoctypes) { - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_SYNCED_KEY, JSON.stringify(syncedDoctypes) ) @@ -76,7 +80,7 @@ export class PouchLocalStorage { * @returns {Promise} The synced doctypes */ async getPersistedSyncedDoctypes() { - const item = await window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) + const item = await this.storageEngine.getItem(LOCALSTORAGE_SYNCED_KEY) const parsed = item ? JSON.parse(item) : {} if (typeof parsed !== 'object') { return {} @@ -90,7 +94,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroySyncedDoctypes() { - await window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_SYNCED_KEY) } /** @@ -105,7 +109,7 @@ export class PouchLocalStorage { const seqs = await this.getAllLastSequences() seqs[doctype] = sequence - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTSEQUENCES_KEY, JSON.stringify(seqs) ) @@ -115,7 +119,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async getAllLastSequences() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_LASTSEQUENCES_KEY ) return item ? JSON.parse(item) : {} @@ -139,7 +143,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyAllDoctypeLastSequence() { - await window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) } /** @@ -152,7 +156,7 @@ export class PouchLocalStorage { async destroyDoctypeLastSequence(doctype) { const seqs = await this.getAllLastSequences() delete seqs[doctype] - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTSEQUENCES_KEY, JSON.stringify(seqs) ) @@ -166,7 +170,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistWarmedUpQueries(warmedUpQueries) { - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_WARMUPEDQUERIES_KEY, JSON.stringify(warmedUpQueries) ) @@ -178,7 +182,7 @@ export class PouchLocalStorage { * @returns {Promise} the warmed up queries */ async getPersistedWarmedUpQueries() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_WARMUPEDQUERIES_KEY ) if (!item) { @@ -193,7 +197,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyWarmedUpQueries() { - await window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) } /** @@ -202,7 +206,7 @@ export class PouchLocalStorage { * @returns {Promise} The adapter name */ async getAdapterName() { - return await window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) + return await this.storageEngine.getItem(LOCALSTORAGE_ADAPTERNAME) } /** @@ -213,6 +217,6 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistAdapterName(adapter) { - await window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + await this.storageEngine.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) } } diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js new file mode 100644 index 0000000000..2eaa0eed3c --- /dev/null +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -0,0 +1,15 @@ +const storage = { + getItem: async key => { + return window.localStorage.getItem(key) + }, + setItem: async (key, value) => { + return window.localStorage.setItem(key, value) + }, + removeItem: async key => { + return window.localStorage.removeItem(key) + } +} + +export const platformWeb = { + storage +} diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index ddf4a7a4c2..26dd5b7d84 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -15,4 +15,16 @@ * @property {string} Date */ +/** + * @typedef {object} LocalStorage + * @property {function(string): Promise} getItem + * @property {function(string, string): Promise} setItem + * @property {function(string): Promise} removeItem + */ + +/** + * @typedef {object} LinkPlatform + * @property {LocalStorage} storage Methods to access local storage + */ + export default {} From 5a4e129e9eb9341f0a67ced7bcb7bf5339ed1765 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:20:41 +0200 Subject: [PATCH 08/84] feat(pouch-link): Allow to inject a Pouch adapter in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting a custom local storage API from a react-native project We also want to inject a custom PouchDB adapter as a react-native project would use a different PouchDB implementation Like for the local storage API, if no injection is given, then `pouchdb-browser` adapter will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 8 ++++++-- packages/cozy-pouch-link/src/platformWeb.js | 5 ++++- packages/cozy-pouch-link/src/types.js | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 4d2fb67038..daff21599a 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -1,4 +1,3 @@ -import PouchDB from 'pouchdb-browser' import fromPairs from 'lodash/fromPairs' import forEach from 'lodash/forEach' import get from 'lodash/get' @@ -40,16 +39,21 @@ class PouchManager { this.storage = new PouchLocalStorage( options.platform?.storage || platformWeb.storage ) + this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter } async init() { const pouchPlugins = get(this.options, 'pouch.plugins', []) const pouchOptions = get(this.options, 'pouch.options', {}) + forEach(pouchPlugins, plugin => this.PouchDB.plugin(plugin)) this.pouches = fromPairs( this.doctypes.map(doctype => [ doctype, - new PouchDB(getDatabaseName(this.options.prefix, doctype), pouchOptions) + new this.PouchDB( + getDatabaseName(this.options.prefix, doctype), + pouchOptions + ) ]) ) this.syncedDoctypes = await this.storage.getPersistedSyncedDoctypes() diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index 2eaa0eed3c..e09b9de5ca 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -1,3 +1,5 @@ +import PouchDB from 'pouchdb-browser' + const storage = { getItem: async key => { return window.localStorage.getItem(key) @@ -11,5 +13,6 @@ const storage = { } export const platformWeb = { - storage + storage, + pouchAdapter: PouchDB } diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index 26dd5b7d84..c017f07132 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -25,6 +25,7 @@ /** * @typedef {object} LinkPlatform * @property {LocalStorage} storage Methods to access local storage + * @property {any} pouchAdapter PouchDB class (can be pouchdb-core or pouchdb-browser) */ export default {} From 254d682d8c2e594dbab9c69f08c17e87e624746f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:07:06 +0200 Subject: [PATCH 09/84] feat(pouch-link): Allow to inject an `isOnline` method in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting custom local storage API and PouchDB adapter from a react-native project We also want to inject a custom `isOnline` method as react-native does not provide the `window.navigator.onLine` API Like for the other APIs, if no injection is given, then `window.navigator.onLine` will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 3 ++- packages/cozy-pouch-link/src/platformWeb.js | 7 ++++++- packages/cozy-pouch-link/src/types.js | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index daff21599a..4a8ab21ee4 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -40,6 +40,7 @@ class PouchManager { options.platform?.storage || platformWeb.storage ) this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter + this.isOnline = options.platform?.isOnline || platformWeb.isOnline } async init() { @@ -174,7 +175,7 @@ class PouchManager { /** Starts replication */ async replicateOnce() { - if (!window.navigator.onLine) { + if (!(await this.isOnline())) { logger.info( 'PouchManager: The device is offline so the replication has been skipped' ) diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index e09b9de5ca..168b8a3391 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -12,7 +12,12 @@ const storage = { } } +const isOnline = async () => { + return window.navigator.onLine +} + export const platformWeb = { storage, - pouchAdapter: PouchDB + pouchAdapter: PouchDB, + isOnline } diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index c017f07132..87f8d17963 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -26,6 +26,7 @@ * @typedef {object} LinkPlatform * @property {LocalStorage} storage Methods to access local storage * @property {any} pouchAdapter PouchDB class (can be pouchdb-core or pouchdb-browser) + * @property {function(): Promise} isOnline Method that check if the app is connected to internet */ export default {} From 21137813e25fbb1b998d70e0c58f030d0b9aa931 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:23:00 +0200 Subject: [PATCH 10/84] feat(pouch-link): Allow to inject event methods in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting custom local storage API, PouchDB adapter and isOnline method from a react-native project We also want to inject a custom evant emitter for online/offline and pause/resume events as react-native does not provide the `document.addEventListener` and `document.removeEventListener` APIs Like for the other APIs, if no injection is given, then `document` APIs will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 17 +++++++++-------- packages/cozy-pouch-link/src/platformWeb.js | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 4a8ab21ee4..b1eaf57ddd 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -41,6 +41,7 @@ class PouchManager { ) this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter this.isOnline = options.platform?.isOnline || platformWeb.isOnline + this.events = options.platform?.events || platformWeb.events } async init() { @@ -77,11 +78,11 @@ class PouchManager { addListeners() { if (!this.listenerLaunched) { if (isMobileApp()) { - document.addEventListener('pause', this.stopReplicationLoop) - document.addEventListener('resume', this.startReplicationLoop) + this.events.addEventListener('pause', this.stopReplicationLoop) + this.events.addEventListener('resume', this.startReplicationLoop) } - document.addEventListener('online', this.startReplicationLoop) - document.addEventListener('offline', this.stopReplicationLoop) + this.events.addEventListener('online', this.startReplicationLoop) + this.events.addEventListener('offline', this.stopReplicationLoop) this.listenerLaunched = true } } @@ -89,11 +90,11 @@ class PouchManager { removeListeners() { if (this.listenerLaunched) { if (isMobileApp()) { - document.removeEventListener('pause', this.stopReplicationLoop) - document.removeEventListener('resume', this.startReplicationLoop) + this.events.removeEventListener('pause', this.stopReplicationLoop) + this.events.removeEventListener('resume', this.startReplicationLoop) } - document.removeEventListener('online', this.startReplicationLoop) - document.removeEventListener('offline', this.stopReplicationLoop) + this.events.removeEventListener('online', this.startReplicationLoop) + this.events.removeEventListener('offline', this.stopReplicationLoop) this.listenerLaunched = false } } diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index 168b8a3391..9f96d78c38 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -1,5 +1,14 @@ import PouchDB from 'pouchdb-browser' +const events = { + addEventListener: (eventName, handler) => { + document.addEventListener(eventName, handler) + }, + removeEventListener: (eventName, handler) => { + document.removeEventListener(eventName, handler) + } +} + const storage = { getItem: async key => { return window.localStorage.getItem(key) @@ -18,6 +27,7 @@ const isOnline = async () => { export const platformWeb = { storage, + events, pouchAdapter: PouchDB, isOnline } From fbbc313795e656492cafc34f48bc8385c2e6cd2a Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:30:28 +0200 Subject: [PATCH 11/84] docs: Update types and documentation --- docs/api/cozy-pouch-link/classes/PouchLink.md | 103 +++++++------ .../cozy-pouch-link/types/CozyPouchLink.d.ts | 14 +- .../cozy-pouch-link/types/PouchManager.d.ts | 15 +- .../cozy-pouch-link/types/localStorage.d.ts | 142 +++++++++++++++--- .../cozy-pouch-link/types/platformWeb.d.ts | 17 +++ .../types/startReplication.d.ts | 4 +- packages/cozy-pouch-link/types/types.d.ts | 22 +++ 7 files changed, 241 insertions(+), 76 deletions(-) create mode 100644 packages/cozy-pouch-link/types/platformWeb.d.ts diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index 4a112f365f..a396a9c90e 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -27,6 +27,7 @@ constructor - Initializes a new PouchLink | `opts` | `Object` | - | | `opts.doctypes` | `string`\[] | Doctypes to replicate | | `opts.doctypesReplicationOptions` | `any`\[] | A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") | +| `opts.platform` | `LinkPlatform` | Platform specific adapters and methods | | `opts.replicationInterval` | `number` | - | *Overrides* @@ -35,7 +36,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:84](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L84) +[CozyPouchLink.js:81](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L81) ## Properties @@ -45,7 +46,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:132](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L132) +[CozyPouchLink.js:134](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L134) *** @@ -55,7 +56,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) +[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) *** @@ -65,7 +66,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L95) +[CozyPouchLink.js:92](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L92) *** @@ -75,17 +76,17 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:96](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L96) +[CozyPouchLink.js:93](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L93) *** ### options -• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `replicationInterval`: `number` } +• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `platform`: `LinkPlatform` ; `replicationInterval`: `number` } *Defined in* -[CozyPouchLink.js:88](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L88) +[CozyPouchLink.js:85](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L85) *** @@ -95,7 +96,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:202](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L202) +[CozyPouchLink.js:204](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L204) *** @@ -107,6 +108,16 @@ CozyLink.constructor [CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +*** + +### storage + +• **storage**: `PouchLocalStorage` + +*Defined in* + +[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) + ## Methods ### addReferencesTo @@ -125,7 +136,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:563](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L563) +[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) *** @@ -145,7 +156,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:524](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L524) +[CozyPouchLink.js:528](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L528) *** @@ -166,7 +177,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) +[CozyPouchLink.js:571](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L571) *** @@ -186,7 +197,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:552](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L552) +[CozyPouchLink.js:556](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L556) *** @@ -207,7 +218,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:423](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L423) +[CozyPouchLink.js:427](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L427) *** @@ -229,7 +240,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:495](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L495) +[CozyPouchLink.js:499](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L499) *** @@ -249,7 +260,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:441](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L441) +[CozyPouchLink.js:445](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L445) *** @@ -269,7 +280,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) +[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) *** @@ -289,7 +300,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:112](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L112) +[CozyPouchLink.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L114) *** @@ -309,7 +320,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:313](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L313) +[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) *** @@ -329,7 +340,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:254](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L254) +[CozyPouchLink.js:258](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L258) *** @@ -349,7 +360,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:249](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L249) +[CozyPouchLink.js:253](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L253) *** @@ -375,7 +386,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:235](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L235) +[CozyPouchLink.js:239](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L239) *** @@ -395,7 +406,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:405](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L405) +[CozyPouchLink.js:409](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L409) *** @@ -416,7 +427,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:410](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L410) +[CozyPouchLink.js:414](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L414) *** @@ -446,13 +457,13 @@ Migrate the current adapter *Defined in* -[CozyPouchLink.js:146](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L146) +[CozyPouchLink.js:148](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L148) *** ### needsToWaitWarmup -▸ **needsToWaitWarmup**(`doctype`): `boolean` +▸ **needsToWaitWarmup**(`doctype`): `Promise`<`boolean`> Check if there is warmup queries for this doctype and return if those queries are already warmed up or not @@ -465,13 +476,13 @@ and return if those queries are already warmed up or not *Returns* -`boolean` +`Promise`<`boolean`> the need to wait for the warmup *Defined in* -[CozyPouchLink.js:391](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L391) +[CozyPouchLink.js:395](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L395) *** @@ -485,7 +496,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:165](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L165) +[CozyPouchLink.js:167](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L167) *** @@ -505,7 +516,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:293](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L293) +[CozyPouchLink.js:297](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L297) *** @@ -525,13 +536,13 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:131](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L131) +[CozyPouchLink.js:133](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L133) *** ### request -▸ **request**(`operation`, `result?`, `forward?`): `void` | `Promise`<`any`> +▸ **request**(`operation`, `result?`, `forward?`): `Promise`<`any`> *Parameters* @@ -543,7 +554,7 @@ the need to wait for the warmup *Returns* -`void` | `Promise`<`any`> +`Promise`<`any`> *Overrides* @@ -551,7 +562,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:336](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L336) +[CozyPouchLink.js:340](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L340) *** @@ -565,7 +576,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:219](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L219) +[CozyPouchLink.js:223](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L223) *** @@ -584,7 +595,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[CozyPouchLink.js:268](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L268) +[CozyPouchLink.js:272](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L272) *** @@ -603,7 +614,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:285](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L285) +[CozyPouchLink.js:289](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L289) *** @@ -623,7 +634,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) +[CozyPouchLink.js:325](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L325) *** @@ -637,7 +648,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:589](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L589) +[CozyPouchLink.js:593](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L593) *** @@ -657,7 +668,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:529](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L529) +[CozyPouchLink.js:533](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L533) *** @@ -677,23 +688,29 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:534](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L534) +[CozyPouchLink.js:538](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L538) *** ### getPouchAdapterName -▸ `Static` **getPouchAdapterName**(): `string` +▸ `Static` **getPouchAdapterName**(`localStorage`): `Promise`<`string`> Return the PouchDB adapter name. Should be IndexedDB for newest adapters. +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `localStorage` | `LocalStorage` | Methods to access local storage | + *Returns* -`string` +`Promise`<`string`> The adapter name *Defined in* -[CozyPouchLink.js:108](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L108) +[CozyPouchLink.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L109) diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index 2bbfe5e6cc..127c172b69 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -15,9 +15,10 @@ declare class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * - * @returns {string} The adapter name + * @param {import('./types').LocalStorage} localStorage Methods to access local storage + * @returns {Promise} The adapter name */ - static getPouchAdapterName: () => string; + static getPouchAdapterName: (localStorage: import('./types').LocalStorage) => Promise; /** * constructor - Initializes a new PouchLink * @@ -25,11 +26,13 @@ declare class PouchLink extends CozyLink { * @param {number} [opts.replicationInterval] Milliseconds between replications * @param {string[]} opts.doctypes Doctypes to replicate * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods */ constructor(opts?: { replicationInterval: number; doctypes: string[]; doctypesReplicationOptions: object[]; + platform: import('./types').LinkPlatform; }); options: { replicationInterval: number; @@ -37,10 +40,12 @@ declare class PouchLink extends CozyLink { replicationInterval?: number; doctypes: string[]; doctypesReplicationOptions: object[]; + platform: import('./types').LinkPlatform; }; doctypes: string[]; doctypesReplicationOptions: any[]; indexes: {}; + storage: PouchLocalStorage; /** @type {Record} - Stores replication states per doctype */ replicationStatus: Record; getReplicationURL(doctype: any): string; @@ -118,9 +123,9 @@ declare class PouchLink extends CozyLink { * and return if those queries are already warmed up or not * * @param {string} doctype - Doctype to check - * @returns {boolean} the need to wait for the warmup + * @returns {Promise} the need to wait for the warmup */ - needsToWaitWarmup(doctype: string): boolean; + needsToWaitWarmup(doctype: string): Promise; hasIndex(name: any): boolean; mergePartialIndexInSelector(selector: any, partialFilter: any): any; ensureIndex(doctype: any, query: any): Promise; @@ -158,4 +163,5 @@ declare class PouchLink extends CozyLink { syncImmediately(): Promise; } import { CozyLink } from "cozy-client"; +import { PouchLocalStorage } from "./localStorage"; import PouchManager from "./PouchManager"; diff --git a/packages/cozy-pouch-link/types/PouchManager.d.ts b/packages/cozy-pouch-link/types/PouchManager.d.ts index a3919c36a1..cbb07a7373 100644 --- a/packages/cozy-pouch-link/types/PouchManager.d.ts +++ b/packages/cozy-pouch-link/types/PouchManager.d.ts @@ -8,6 +8,12 @@ export default PouchManager; declare class PouchManager { constructor(doctypes: any, options: any); options: any; + doctypes: any; + storage: PouchLocalStorage; + PouchDB: any; + isOnline: any; + events: any; + init(): Promise; pouches: import("lodash").Dictionary; syncedDoctypes: any; warmedUpQueries: any; @@ -52,13 +58,14 @@ declare class PouchManager { cancelCurrentReplications(): void; waitForCurrentReplications(): Promise | Promise; getPouch(doctype: any): any; - updateSyncInfo(doctype: any): void; + updateSyncInfo(doctype: any): Promise; getSyncInfo(doctype: any): any; isSynced(doctype: any): boolean; - clearSyncedDoctypes(): void; + clearSyncedDoctypes(): Promise; warmupQueries(doctype: any, queries: any): Promise; checkToWarmupDoctype(doctype: any, replicationOptions: any): void; - areQueriesWarmedUp(doctype: any, queries: any): any; - clearWarmedUpQueries(): void; + areQueriesWarmedUp(doctype: any, queries: any): Promise; + clearWarmedUpQueries(): Promise; } +import { PouchLocalStorage } from "./localStorage"; import Loop from "./loop"; diff --git a/packages/cozy-pouch-link/types/localStorage.d.ts b/packages/cozy-pouch-link/types/localStorage.d.ts index b3295b9c4c..c60f0f36be 100644 --- a/packages/cozy-pouch-link/types/localStorage.d.ts +++ b/packages/cozy-pouch-link/types/localStorage.d.ts @@ -3,26 +3,122 @@ export const LOCALSTORAGE_WARMUPEDQUERIES_KEY: "cozy-client-pouch-link-warmupedq export const LOCALSTORAGE_LASTSEQUENCES_KEY: "cozy-client-pouch-link-lastreplicationsequence"; export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY: "cozy-client-pouch-link-lastreplicateddocid"; export const LOCALSTORAGE_ADAPTERNAME: "cozy-client-pouch-link-adaptername"; -export function persistLastReplicatedDocID(doctype: string, id: string): void; -export function getAllLastReplicatedDocID(): any; -export function getLastReplicatedDocID(doctype: string): string; -export function destroyAllLastReplicatedDocID(): void; -export function persistSyncedDoctypes(syncedDoctypes: Record): void; -export function getPersistedSyncedDoctypes(): object; -export function destroySyncedDoctypes(): void; -export function persistDoctypeLastSequence(doctype: string, sequence: string): void; -export function getAllLastSequences(): any; -export function getDoctypeLastSequence(doctype: string): string; -export function destroyAllDoctypeLastSequence(): void; -export function destroyDoctypeLastSequence(doctype: string): void; -export function persistWarmedUpQueries(warmedUpQueries: object): void; -export function getPersistedWarmedUpQueries(): object; -export function destroyWarmedUpQueries(): void; -export function getAdapterName(): string; -export function persistAdapterName(adapter: string): void; -/** - * Persist the synchronized doctypes - */ -export type SyncInfo = { - Date: string; -}; +export class PouchLocalStorage { + constructor(storageEngine: any); + storageEngine: any; + /** + * Persist the last replicated doc id for a doctype + * + * @param {string} doctype - The replicated doctype + * @param {string} id - The docid + * + * @returns {Promise} + */ + persistLastReplicatedDocID(doctype: string, id: string): Promise; + /** + * @returns {Promise>} + */ + getAllLastReplicatedDocID(): Promise>; + /** + * Get the last replicated doc id for a doctype + * + * @param {string} doctype - The doctype + * @returns {Promise} The last replicated docid + */ + getLastReplicatedDocID(doctype: string): Promise; + /** + * Destroy all the replicated doc id + * + * @returns {Promise} + */ + destroyAllLastReplicatedDocID(): Promise; + /** + * Persist the synchronized doctypes + * + * @param {Record} syncedDoctypes - The sync doctypes + * + * @returns {Promise} + */ + persistSyncedDoctypes(syncedDoctypes: Record): Promise; + /** + * Get the persisted doctypes + * + * @returns {Promise} The synced doctypes + */ + getPersistedSyncedDoctypes(): Promise; + /** + * Destroy the synced doctypes + * + * @returns {Promise} + */ + destroySyncedDoctypes(): Promise; + /** + * Persist the last CouchDB sequence for a synced doctype + * + * @param {string} doctype - The synced doctype + * @param {string} sequence - The sequence hash + * + * @returns {Promise} + */ + persistDoctypeLastSequence(doctype: string, sequence: string): Promise; + /** + * @returns {Promise} + */ + getAllLastSequences(): Promise; + /** + * Get the last CouchDB sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} the last sequence + */ + getDoctypeLastSequence(doctype: string): Promise; + /** + * Destroy all the last sequence + * + * @returns {Promise} + */ + destroyAllDoctypeLastSequence(): Promise; + /** + * Destroy the last sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} + */ + destroyDoctypeLastSequence(doctype: string): Promise; + /** + * Persist the warmed up queries + * + * @param {object} warmedUpQueries - The warmedup queries + * + * @returns {Promise} + */ + persistWarmedUpQueries(warmedUpQueries: object): Promise; + /** + * Get the warmed up queries + * + * @returns {Promise} the warmed up queries + */ + getPersistedWarmedUpQueries(): Promise; + /** + * Destroy the warmed queries + * + * @returns {Promise} + */ + destroyWarmedUpQueries(): Promise; + /** + * Get the adapter name + * + * @returns {Promise} The adapter name + */ + getAdapterName(): Promise; + /** + * Persist the adapter name + * + * @param {string} adapter - The adapter name + * + * @returns {Promise} + */ + persistAdapterName(adapter: string): Promise; +} diff --git a/packages/cozy-pouch-link/types/platformWeb.d.ts b/packages/cozy-pouch-link/types/platformWeb.d.ts new file mode 100644 index 0000000000..9b8b8fdf71 --- /dev/null +++ b/packages/cozy-pouch-link/types/platformWeb.d.ts @@ -0,0 +1,17 @@ +export namespace platformWeb { + export { storage }; + export { events }; + export { PouchDB as pouchAdapter }; + export { isOnline }; +} +declare namespace storage { + function getItem(key: any): Promise; + function setItem(key: any, value: any): Promise; + function removeItem(key: any): Promise; +} +declare namespace events { + function addEventListener(eventName: any, handler: any): void; + function removeEventListener(eventName: any, handler: any): void; +} +declare function isOnline(): Promise; +export {}; diff --git a/packages/cozy-pouch-link/types/startReplication.d.ts b/packages/cozy-pouch-link/types/startReplication.d.ts index c820925f87..44ff8eb07b 100644 --- a/packages/cozy-pouch-link/types/startReplication.d.ts +++ b/packages/cozy-pouch-link/types/startReplication.d.ts @@ -3,5 +3,5 @@ export function startReplication(pouch: object, replicationOptions: { initialReplication: boolean; doctype: string; warmupQueries: import('cozy-client/types/types').Query[]; -}, getReplicationURL: Function): import('./types').CancelablePromise; -export function replicateAllDocs(db: object, baseUrl: string, doctype: string): Promise; +}, getReplicationURL: Function, storage: import('./localStorage').PouchLocalStorage): import('./types').CancelablePromise; +export function replicateAllDocs(db: object, baseUrl: string, doctype: string, storage: import('./localStorage').PouchLocalStorage): Promise; diff --git a/packages/cozy-pouch-link/types/types.d.ts b/packages/cozy-pouch-link/types/types.d.ts index d38702d605..2cb24b393c 100644 --- a/packages/cozy-pouch-link/types/types.d.ts +++ b/packages/cozy-pouch-link/types/types.d.ts @@ -8,3 +8,25 @@ export type Cancelable = { }; export type CancelablePromise = Promise & Cancelable; export type CancelablePromises = CancelablePromise[] & Cancelable; +export type SyncInfo = { + Date: string; +}; +export type LocalStorage = { + getItem: (arg0: string) => Promise; + setItem: (arg0: string, arg1: string) => Promise; + removeItem: (arg0: string) => Promise; +}; +export type LinkPlatform = { + /** + * Methods to access local storage + */ + storage: LocalStorage; + /** + * PouchDB class (can be pouchdb-core or pouchdb-browser) + */ + pouchAdapter: any; + /** + * Method that check if the app is connected to internet + */ + isOnline: () => Promise; +}; From 73adba21ccb6a7e6b97a35f9af082cf4c70c0a88 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:41:20 +0200 Subject: [PATCH 12/84] feat: Persist documents to local Pouch when they have no `rev` When a PouchLink is configured, the synchronization process would retrieve only documents that exists on the remote database Those documents would be served as usual when a `.query()` is made using the PouchLink Unfortunately, not all `.query()` correspond to a document that exists on the remote database. With the collection mechanism, some documents are retrieved using an alternative endpoint and then the cozy-stack generate the document on-demand For example this is the case for `io.cozy.settings.flags`, `io.cozy.settings.context` and `io.cozy.settings.disk-usage` Because those documents does not actually exist on the remote database, we cannot sync them using the PouchDB sync mechanism and so they won't be available when the device is offline To fix this, we chose to manually insert those document in the local PouchDB when we detect that a `.query()` call retrieve them from the cozy-stack The easiest way we found to detect them is to check for the existence of `meta.rev` object in the `.query()` result. If no `rev` is present, this means that the documents does not come from the remote database but has been dynamically generated by the cozy-stack Those documents are inserted into existing PouchBD with the `cozyLocalOnly` attributes, so we know that they should not be replicated to the remote database on next sync In order to make the persist mechanism possible, we implemented a new `persistData()` method to the CozyLink interface The impact is that every `Link` should now implement the `persistData()` interface that should persist the data if it is able to do it, or to forward the action otherwise If no `Link` process the action, then the final "default" method will do nothing (contrary to the default for `request()` that would throw an error) BREAKING CHANGE: CozyClient's links should now implement a `persistData()` method that should persist the given data if it is able to do it, or to forward the action to the next `Link` otherwise --- packages/cozy-client/src/CozyClient.js | 35 ++++++++++ packages/cozy-client/src/CozyClient.spec.js | 68 ++++++++++++++++++- packages/cozy-client/src/CozyLink.js | 34 ++++++++-- packages/cozy-client/src/StackLink.js | 4 ++ packages/cozy-client/src/types.js | 7 ++ packages/cozy-pouch-link/src/CozyPouchLink.js | 40 +++++++++++ 6 files changed, 183 insertions(+), 5 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index cb5aa55fc3..d6bd7af3e6 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -98,6 +98,8 @@ const DOC_CREATION = 'creation' const DOC_UPDATE = 'update' /** + * @typedef {import("./types").CozyClientDocument} CozyClientDocument + * * @typedef {object} ClientOptions * @property {object} [client] * @property {object} [link] @@ -1090,6 +1092,9 @@ client.query(Q('io.cozy.bills'))`) */ async requestQuery(definition) { const mainResponse = await this.chain.request(definition) + + this.persistVirtualDocuments(mainResponse.data) + if (!definition.includes) { return mainResponse } @@ -1100,6 +1105,36 @@ client.query(Q('io.cozy.bills'))`) return withIncluded } + /** + * Save the document or array of documents into the persisted storage (if any) + * + * @private + * @param {CozyClientDocument | Array} data - Document or array of documents to be saved + * @returns {Promise} + */ + async persistVirtualDocuments(data) { + if (!Array.isArray(data)) { + await this.persistVirtualDocument(data) + } else { + for (const document of data) { + await this.persistVirtualDocument(document) + } + } + } + + /** + * Save the document or array of documents into the persisted storage (if any) + * + * @private + * @param {CozyClientDocument} document - Document to be saved + * @returns {Promise} + */ + async persistVirtualDocument(document) { + if (document && !document.meta?.rev && !document.cozyLocalOnly) { + await this.chain.persistData(document) + } + } + /** * Fetch relationships for a response (can be several docs). * Fills the `relationships` attribute of each documents. diff --git a/packages/cozy-client/src/CozyClient.spec.js b/packages/cozy-client/src/CozyClient.spec.js index 05880d1123..d148b47903 100644 --- a/packages/cozy-client/src/CozyClient.spec.js +++ b/packages/cozy-client/src/CozyClient.spec.js @@ -693,7 +693,8 @@ describe('CozyClient login', () => { describe('CozyClient', () => { const requestHandler = jest.fn() - const link = new CozyLink(requestHandler) + const persistHandler = jest.fn() + const link = new CozyLink(requestHandler, persistHandler) const MOCKED_DATE = '2018-05-05T09:09:00.115Z' @@ -724,6 +725,7 @@ describe('CozyClient', () => { afterEach(() => { requestHandler.mockReset() + persistHandler.mockReset() }) describe('setAppMetadata', () => { it('should update the appMetadata', () => { @@ -1384,6 +1386,7 @@ describe('CozyClient', () => { it('should return the same result if the query is run while she is already in loading status whithout requesting the query twice', async () => { jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({}) const [resp, resp2] = await Promise.all([ client.query(query, { as: 'allTodos' }), @@ -1400,6 +1403,69 @@ describe('CozyClient', () => { expect(executeQueryFromState).toHaveBeenCalledTimes(1) }) + it('should persist virtual document when no meta.rev', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: { + _id: 'some_id' + } + }) + + await client.query(query, { as: 'allTodos' }) + + expect(persistHandler).toHaveBeenCalledWith( + { + _id: 'some_id' + }, + expect.anything() + ) + }) + + it('should persist array of virtual documents when no meta.rev', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: [ + { + _id: 'some_id' + }, + { + _id: 'some_id2' + } + ] + }) + + await client.query(query, { as: 'allTodos' }) + + expect(persistHandler).toHaveBeenCalledWith( + { + _id: 'some_id' + }, + expect.anything() + ) + expect(persistHandler).toHaveBeenCalledWith( + { + _id: 'some_id2' + }, + expect.anything() + ) + }) + + it('should not persist virtual documents if cozyLocalOnly', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: [ + { + _id: 'some_id', + cozyLocalOnly: true + } + ] + }) + + await client.query(query, { as: 'allTodos' }) + + expect(persistHandler).not.toHaveBeenCalled() + }) + describe('relationship with query failure', () => { beforeEach(() => { jest.spyOn(HasManyFiles, 'query').mockImplementation(() => { diff --git a/packages/cozy-client/src/CozyLink.js b/packages/cozy-client/src/CozyLink.js index 8b4f5a13fe..baec94a6b6 100644 --- a/packages/cozy-client/src/CozyLink.js +++ b/packages/cozy-client/src/CozyLink.js @@ -1,19 +1,27 @@ export default class CozyLink { - constructor(requestHandler) { + constructor(requestHandler, persistHandler) { if (typeof requestHandler === 'function') { this.request = requestHandler } + + if (typeof persistHandler === 'function') { + this.persistData = persistHandler + } } request(operation, result, forward) { throw new Error('request is not implemented') } + + persistData(data, forward) { + throw new Error('persistData is not implemented') + } } const toLink = handler => typeof handler === 'function' ? new CozyLink(handler) : handler -const defaultLinkHandler = (operation, result) => { +const defaultLinkRequestHandler = (operation, result) => { if (result) return result else if (operation.execute) return operation.execute() else @@ -22,14 +30,32 @@ const defaultLinkHandler = (operation, result) => { ) } +const defaultLinkPersistHandler = (operation, result) => { + // Do nothing +} + +const defaultLinkHandler = new CozyLink( + defaultLinkRequestHandler, + defaultLinkPersistHandler +) + export const chain = links => [...links, defaultLinkHandler].map(toLink).reduce(concat) const concat = (firstLink, nextLink) => { - return new CozyLink((operation, result, forward) => { + const requestHandler = (operation, result, forward) => { const nextForward = (op, res) => { return nextLink.request(op, res, forward) } return firstLink.request(operation, result, nextForward) - }) + } + + const persistHandler = (data, forward) => { + const nextForward = d => { + return nextLink.persistData(d, forward) + } + return firstLink.persistData(data, nextForward) + } + + return new CozyLink(requestHandler, persistHandler) } diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index 97e9a2fb92..e414c2b88b 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -83,6 +83,10 @@ export default class StackLink extends CozyLink { } return this.executeQuery(operation) } + + async persistData(data, forward) { + return forward(data) + } /** * * @param {QueryDefinition} query - Query to execute diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index bf3a2c4c29..a675fa4ead 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -419,6 +419,11 @@ import { QueryDefinition } from './queries/dsl' * @property {boolean} [favorite] - Whether the document is marked as favorite */ +/** + * @typedef {object} CozyClientDocumentMeta - Meta object as specified by JSON-API (https://jsonapi.org/format/#document-meta) + * @property {string} [rev] - Current revision of the document + */ + /** * @typedef {object} CozyClientDocument - A document * @property {string} [_id] - Id of the document @@ -429,6 +434,8 @@ import { QueryDefinition } from './queries/dsl' * @property {ReferencedByRelationship} [relationships] - Relationships of the document * @property {Reference[]} [referenced_by] - referenced by of another document * @property {CozyMetadata} [cozyMetadata] - Cozy Metadata + * @property {CozyClientDocumentMeta} [meta] - Pouch Metadata + * @property {boolean} [cozyLocalOnly] - When true the document should NOT be replicated to the remote database */ /** diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 9b7f43da49..7637b25a43 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -59,6 +59,8 @@ const normalizeAll = (docs, doctype) => { } /** + * @typedef {import('cozy-client/src/types').CozyClientDocument} CozyClientDocument + * * @typedef {"idle"|"replicating"} SyncStatus */ @@ -384,6 +386,44 @@ class PouchLink extends CozyLink { return this.executeQuery(operation) } } + + async persistData(data, forward = doNothing) { + const docWithoutType = sanitized(data) + docWithoutType.cozyLocalOnly = true + + const oldDoc = await this.getExistingDocument(data._id, data._type) + if (oldDoc) { + docWithoutType._rev = oldDoc._rev + } + + const db = this.pouches.getPouch(data._type) + await db.put(docWithoutType) + } + + /** + * Retrieve the existing document from Pouch + * + * @private + * @param {*} id - ID of the document to retrieve + * @param {*} type - Doctype of the document to retrieve + * @param {*} throwIfNotFound - If true the method will throw when the document is not found. Otherwise it will return null + * @returns {Promise} + */ + async getExistingDocument(id, type, throwIfNotFound = false) { + try { + const db = this.pouches.getPouch(type) + const existingDoc = await db.get(id) + + return existingDoc + } catch (err) { + if (err.name === 'not_found' && !throwIfNotFound) { + return null + } else { + throw err + } + } + } + /** * * Check if there is warmup queries for this doctype From ef5843f50397c7082f8daafa8697bffe8368b4ef Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 11:59:14 +0200 Subject: [PATCH 13/84] feat(pouch-link): Prevent synchronizing `cozyLocalOnly` documents In previous commit we manually inserted virtual documents into the local PouchDB with the `cozyLocalOnly` attribute We don't want to replicate those document into the remote database so we want to filter them from the synchronization process This is done using a PouchDB `selector` on the synchronization options --- packages/cozy-pouch-link/src/startReplication.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 7774f8b13d..f8db3ef048 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -62,7 +62,12 @@ export const startReplication = ( } = replicationOptions const options = { batch_size: BATCH_SIZE, - ...customReplicationOptions + ...customReplicationOptions, + selector: { + cozyLocalOnly: { + $exists: false + } + } } let replication if (initialReplication && strategy !== 'toRemote') { From fcd30144d623f389b4202aea13ef9f97ee22fc7d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 15 Jul 2024 16:05:08 +0200 Subject: [PATCH 14/84] test: Fix `normalizeDoc` test to add doctype --- packages/cozy-pouch-link/src/jsonapi.spec.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/cozy-pouch-link/src/jsonapi.spec.js b/packages/cozy-pouch-link/src/jsonapi.spec.js index acfb280972..12cf422b2f 100644 --- a/packages/cozy-pouch-link/src/jsonapi.spec.js +++ b/packages/cozy-pouch-link/src/jsonapi.spec.js @@ -28,17 +28,21 @@ const DELETED_DOC_FIXTURE = { describe('doc normalization', () => { it('keeps the highest between rev and _rev and removes the rev attribute', () => { - const normalized = normalizeDoc({ - _id: 1234, - _rev: '3-deadbeef', - rev: '4-cffee', - firstName: 'Bobba', - lastName: 'Fett' - }) + const normalized = normalizeDoc( + { + _id: 1234, + _rev: '3-deadbeef', + rev: '4-cffee', + firstName: 'Bobba', + lastName: 'Fett' + }, + 'io.cozy.contacts' + ) expect(normalized).toEqual({ _id: 1234, id: 1234, _rev: '4-cffee', + _type: 'io.cozy.contacts', firstName: 'Bobba', lastName: 'Fett' }) From 980c531b85dfaedec55708bccb73ceb77e3af212 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 18 Jun 2024 17:37:55 +0200 Subject: [PATCH 15/84] fix: Do not persist virtual documents when served from CozyPouchLink When querying documents from CozyPouchLink, then the `meta.rev` attribute is not added to the document. This happens because this attributes does not exist in database but is added by the cozy-stack when serving JSON:API format We don't want to add the `meta.rev` attribute by ourself as it may have side effects on the synchronization process (i.e. adding `meta.rev` in the database documents) Instead we want to introduce a `cozyFromPouch` attribute that is added by CozyPouchLink when returning the result --- packages/cozy-client/src/CozyClient.js | 7 ++++++- packages/cozy-client/src/types.js | 1 + packages/cozy-pouch-link/src/CozyPouchLink.spec.js | 1 + packages/cozy-pouch-link/src/jsonapi.js | 3 ++- packages/cozy-pouch-link/src/jsonapi.spec.js | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index d6bd7af3e6..475b05ba25 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1130,7 +1130,12 @@ client.query(Q('io.cozy.bills'))`) * @returns {Promise} */ async persistVirtualDocument(document) { - if (document && !document.meta?.rev && !document.cozyLocalOnly) { + if ( + document && + !document.meta?.rev && + !document.cozyLocalOnly && + !document.cozyFromPouch + ) { await this.chain.persistData(document) } } diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index a675fa4ead..80034aadc2 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -436,6 +436,7 @@ import { QueryDefinition } from './queries/dsl' * @property {CozyMetadata} [cozyMetadata] - Cozy Metadata * @property {CozyClientDocumentMeta} [meta] - Pouch Metadata * @property {boolean} [cozyLocalOnly] - When true the document should NOT be replicated to the remote database + * @property {boolean} [cozyFromPouch] - When true the document has been retrieved from a local PouchDB */ /** diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index 1ce0a82ec9..61489944c7 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -473,6 +473,7 @@ describe('CozyPouchLink', () => { _id: '1', _rev: '1-deadbeef', _type: 'io.cozy.todos', + cozyFromPouch: true, done: false, id: '1', label: 'Buy bread' diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js index 84901340cc..159435bf73 100644 --- a/packages/cozy-pouch-link/src/jsonapi.js +++ b/packages/cozy-pouch-link/src/jsonapi.js @@ -10,7 +10,8 @@ export const normalizeDoc = (doc, doctype) => { id, _id: id, _rev, - _type: doctype + _type: doctype, + cozyFromPouch: true } 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 12cf422b2f..86bfd067cc 100644 --- a/packages/cozy-pouch-link/src/jsonapi.spec.js +++ b/packages/cozy-pouch-link/src/jsonapi.spec.js @@ -43,6 +43,7 @@ describe('doc normalization', () => { id: 1234, _rev: '4-cffee', _type: 'io.cozy.contacts', + cozyFromPouch: true, firstName: 'Bobba', lastName: 'Fett' }) From 7ad0bad947f94f6d478a27961d38ebbe9ed646d7 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 12 Jul 2024 17:17:38 +0200 Subject: [PATCH 16/84] feat: Handle io.cozy.apps_registry query On the StackLink side, the `io.cozy.apps_registry` is handled by the AppsRegistryCollection that queries a specific cozy-stack route when the `slug` parameter equals `maintenance` This is a shortcut to query all apps under maintenance This is problematic because the local Pouch is synchronized on this doctype and stores all apps with their respective IDs. There is no `maintenance` document, so we cannot process a query that selects this ID To make this possible, we want to persist the `maintenance` query's result as a single document with the `maintenance` id --- packages/cozy-client/src/CozyClient.js | 18 ++++++++++++++++-- packages/cozy-pouch-link/src/jsonapi.js | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 475b05ba25..8a8dbfb39d 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1093,7 +1093,7 @@ client.query(Q('io.cozy.bills'))`) async requestQuery(definition) { const mainResponse = await this.chain.request(definition) - this.persistVirtualDocuments(mainResponse.data) + this.persistVirtualDocuments(definition, mainResponse.data) if (!definition.includes) { return mainResponse @@ -1112,7 +1112,21 @@ client.query(Q('io.cozy.bills'))`) * @param {CozyClientDocument | Array} data - Document or array of documents to be saved * @returns {Promise} */ - async persistVirtualDocuments(data) { + async persistVirtualDocuments(definition, data) { + if (definition.doctype === 'io.cozy.apps_registry') { + // io.cozy.apps_registry has a dedicated `maintenance` endpoint on cozy-stack that + // returns data different than the one stored in database + // As we want to have transparent queries, whether it uses the stack API or Pouch, + // we store the full response into a single doc, with a `maintenance` _id + // and a special `cozyPouchData` attribute, to highlight this special case + return await this.persistVirtualDocument({ + _type: 'io.cozy.apps_registry', + _id: 'maintenance', + // @ts-ignore + cozyPouchData: data + }) + } + if (!Array.isArray(data)) { await this.persistVirtualDocument(data) } else { diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js index 159435bf73..5ff0a18f58 100644 --- a/packages/cozy-pouch-link/src/jsonapi.js +++ b/packages/cozy-pouch-link/src/jsonapi.js @@ -22,6 +22,20 @@ export const normalizeDoc = (doc, doctype) => { const filterDeletedDocumentsFromRows = doc => !!doc export const fromPouchResult = (res, withRows, doctype) => { + // 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 + // is not the same as the one stored in the Couch database + // To handle this, we store the received data in the Pouch with a dedicated id and + // we store the query result in a `cozyPouchData` attribute + // So when `cozyPouchData` attribute exists, we know that we want to return its content + // as the result of the query + if (res.cozyPouchData) { + return { + data: res.cozyPouchData + } + } + if (withRows) { const docs = res.rows ? res.rows.map(row => row.doc).filter(filterDeletedDocumentsFromRows) From d9dfb290de832ef3386378bdbe9a4a1c3e7172cf Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 22 Jul 2024 17:21:15 +0200 Subject: [PATCH 17/84] fix: Persist `io.cozy.files.shortcuts` even if they have a `meta.rev` `io.cozy.files.shortcuts` doctype does not exist in Couch database This doctype is created by cozy-stack from `io.cozy.files` documents This has two impact: - First, we cannot call `PouchDB.replicate` on this doctype as it does not actually exist - Second, `io.cozy.files` documents have a `meta.rev` attributes that is included into the interpolated `io.cozy.files.shortcuts` one. So the virtual documents persistance mechanism is not triggered So with actual implementation we cannot persist `io.cozy.files.shortcuts` for offline usage To make it possible, we want to enforce virtual documents persistance for this doctype even if it has a `meta.rev` attribute --- packages/cozy-client/src/CozyClient.js | 29 +++++++++++++-------- packages/cozy-client/src/CozyClient.spec.js | 19 ++++++++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 8a8dbfb39d..3d144e7cd3 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1113,25 +1113,32 @@ client.query(Q('io.cozy.bills'))`) * @returns {Promise} */ async persistVirtualDocuments(definition, data) { + const enforceList = ['io.cozy.files.shortcuts'] + + const enforce = enforceList.includes(definition.doctype) + if (definition.doctype === 'io.cozy.apps_registry') { // io.cozy.apps_registry has a dedicated `maintenance` endpoint on cozy-stack that // returns data different than the one stored in database // As we want to have transparent queries, whether it uses the stack API or Pouch, // we store the full response into a single doc, with a `maintenance` _id // and a special `cozyPouchData` attribute, to highlight this special case - return await this.persistVirtualDocument({ - _type: 'io.cozy.apps_registry', - _id: 'maintenance', - // @ts-ignore - cozyPouchData: data - }) + return await this.persistVirtualDocument( + { + _type: 'io.cozy.apps_registry', + _id: 'maintenance', + // @ts-ignore + cozyPouchData: data + }, + enforce + ) } if (!Array.isArray(data)) { - await this.persistVirtualDocument(data) + await this.persistVirtualDocument(data, enforce) } else { for (const document of data) { - await this.persistVirtualDocument(document) + await this.persistVirtualDocument(document, enforce) } } } @@ -1141,12 +1148,12 @@ client.query(Q('io.cozy.bills'))`) * * @private * @param {CozyClientDocument} document - Document to be saved + * @param {boolean} enforce - When true, save the document even if `meta.rev` exists * @returns {Promise} */ - async persistVirtualDocument(document) { + async persistVirtualDocument(document, enforce) { if ( - document && - !document.meta?.rev && + ((document && !document.meta?.rev) || enforce) && !document.cozyLocalOnly && !document.cozyFromPouch ) { diff --git a/packages/cozy-client/src/CozyClient.spec.js b/packages/cozy-client/src/CozyClient.spec.js index d148b47903..05b61030e9 100644 --- a/packages/cozy-client/src/CozyClient.spec.js +++ b/packages/cozy-client/src/CozyClient.spec.js @@ -1466,6 +1466,25 @@ describe('CozyClient', () => { expect(persistHandler).not.toHaveBeenCalled() }) + it('should enforce persisting io.cozy.files.shortcuts as virtual documents even if meta.rev exists', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: [ + { + _id: 'some_id', + meta: { + rev: 'SOME_REV' + } + } + ] + }) + const shortcutsQuery = Q('io.cozy.files.shortcuts') + + await client.query(shortcutsQuery, { as: 'allShortcuts' }) + + expect(persistHandler).toHaveBeenCalled() + }) + describe('relationship with query failure', () => { beforeEach(() => { jest.spyOn(HasManyFiles, 'query').mockImplementation(() => { From 3e1157d4f4f6c19c33fc48b1dc9cdd008343592e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 13 Aug 2024 16:44:44 +0200 Subject: [PATCH 18/84] feat: Add check to `_rev` attribute when persisting virtual documents In previous commit we added a check to `meta.rev` attribute in order to discriminate documents that exist in remote CouchDB from the ones that don't This approach would work only for documents handled by the cozy-stack using JSON-API But some CouchDB documents are served directly through the `data` route and are not wrapped into JSON-API When this is the case, they have a `_rev` attribute instead of a `meta.rev` one So we want to check for both forms before persisting virtual documents --- packages/cozy-client/src/CozyClient.js | 12 ++++---- packages/cozy-client/src/CozyClient.spec.js | 34 +++++++++++++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 3d144e7cd3..9fc1c12aa7 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1148,15 +1148,15 @@ client.query(Q('io.cozy.bills'))`) * * @private * @param {CozyClientDocument} document - Document to be saved - * @param {boolean} enforce - When true, save the document even if `meta.rev` exists + * @param {boolean} enforce - When true, save the document even if `meta.rev` or `_rev` exist * @returns {Promise} */ async persistVirtualDocument(document, enforce) { - if ( - ((document && !document.meta?.rev) || enforce) && - !document.cozyLocalOnly && - !document.cozyFromPouch - ) { + if (!document || document.cozyLocalOnly || document.cozyFromPouch) { + return + } + + if ((!document.meta?.rev && !document._rev) || enforce) { await this.chain.persistData(document) } } diff --git a/packages/cozy-client/src/CozyClient.spec.js b/packages/cozy-client/src/CozyClient.spec.js index 05b61030e9..69688220c7 100644 --- a/packages/cozy-client/src/CozyClient.spec.js +++ b/packages/cozy-client/src/CozyClient.spec.js @@ -1403,7 +1403,7 @@ describe('CozyClient', () => { expect(executeQueryFromState).toHaveBeenCalledTimes(1) }) - it('should persist virtual document when no meta.rev', async () => { + it('should persist virtual document when no meta.rev nor _rev', async () => { jest.spyOn(client, 'requestQuery') requestHandler.mockResolvedValue({ data: { @@ -1421,7 +1421,37 @@ describe('CozyClient', () => { ) }) - it('should persist array of virtual documents when no meta.rev', async () => { + it('should not persist virtual document when meta.rev', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: { + _id: 'some_id', + meta: { + rev: 'SOME_REV' + } + } + }) + + await client.query(query, { as: 'allTodos' }) + + expect(persistHandler).not.toHaveBeenCalled() + }) + + it('should not persist virtual document when _rev', async () => { + jest.spyOn(client, 'requestQuery') + requestHandler.mockResolvedValue({ + data: { + _id: 'some_id', + _rev: 'SOME_REV' + } + }) + + await client.query(query, { as: 'allTodos' }) + + expect(persistHandler).not.toHaveBeenCalled() + }) + + it('should persist array of virtual documents when no meta.rev nor _rev', async () => { jest.spyOn(client, 'requestQuery') requestHandler.mockResolvedValue({ data: [ From 9cc04e09be07370d4fd811a92298ff16b0a3e5e7 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:36:46 +0200 Subject: [PATCH 19/84] docs: Update types and documentation --- docs/api/cozy-client/classes/CozyClient.md | 184 +++++++++--------- docs/api/cozy-client/classes/CozyLink.md | 26 ++- docs/api/cozy-client/classes/StackLink.md | 29 ++- docs/api/cozy-pouch-link/classes/PouchLink.md | 105 ++++++---- packages/cozy-client/types/CozyClient.d.ts | 65 +++++++ packages/cozy-client/types/CozyLink.d.ts | 3 +- packages/cozy-client/types/types.d.ts | 21 ++ .../cozy-pouch-link/types/CozyPouchLink.d.ts | 23 ++- packages/cozy-pouch-link/types/jsonapi.d.ts | 10 +- 9 files changed, 319 insertions(+), 147 deletions(-) diff --git a/docs/api/cozy-client/classes/CozyClient.md b/docs/api/cozy-client/classes/CozyClient.md index f54ff21992..052cd214fc 100644 --- a/docs/api/cozy-client/classes/CozyClient.md +++ b/docs/api/cozy-client/classes/CozyClient.md @@ -43,7 +43,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:153](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L153) +[packages/cozy-client/src/CozyClient.js:155](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L155) ## Properties @@ -53,7 +53,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:166](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L166) +[packages/cozy-client/src/CozyClient.js:168](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L168) *** @@ -63,7 +63,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:194](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L194) +[packages/cozy-client/src/CozyClient.js:196](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L196) *** @@ -73,7 +73,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:187](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L187) +[packages/cozy-client/src/CozyClient.js:189](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L189) *** @@ -83,7 +83,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1639](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1639) +[packages/cozy-client/src/CozyClient.js:1700](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1700) *** @@ -93,7 +93,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:174](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L174) +[packages/cozy-client/src/CozyClient.js:176](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L176) *** @@ -103,7 +103,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:173](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L173) +[packages/cozy-client/src/CozyClient.js:175](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L175) *** @@ -113,7 +113,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:488](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L488) +[packages/cozy-client/src/CozyClient.js:490](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L490) *** @@ -123,7 +123,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:184](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L184) +[packages/cozy-client/src/CozyClient.js:186](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L186) *** @@ -133,7 +133,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:167](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L167) +[packages/cozy-client/src/CozyClient.js:169](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L169) *** @@ -159,7 +159,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:170](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L170) +[packages/cozy-client/src/CozyClient.js:172](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L172) *** @@ -169,7 +169,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:197](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L197) +[packages/cozy-client/src/CozyClient.js:199](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L199) *** @@ -179,7 +179,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:172](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L172) +[packages/cozy-client/src/CozyClient.js:174](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L174) *** @@ -189,7 +189,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:189](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L189) +[packages/cozy-client/src/CozyClient.js:191](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L191) *** @@ -199,7 +199,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1614](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1614) +[packages/cozy-client/src/CozyClient.js:1675](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1675) *** @@ -209,7 +209,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1544](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1544) +[packages/cozy-client/src/CozyClient.js:1605](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1605) *** @@ -219,7 +219,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:222](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L222) +[packages/cozy-client/src/CozyClient.js:224](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L224) *** @@ -239,7 +239,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1297](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1297) +[packages/cozy-client/src/CozyClient.js:1358](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1358) *** @@ -284,7 +284,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:467](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L467) +[packages/cozy-client/src/CozyClient.js:469](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L469) *** @@ -304,7 +304,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:423](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L423) +[packages/cozy-client/src/CozyClient.js:425](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L425) *** @@ -324,7 +324,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:568](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L568) +[packages/cozy-client/src/CozyClient.js:570](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L570) *** @@ -353,7 +353,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1460](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1460) +[packages/cozy-client/src/CozyClient.js:1521](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1521) *** @@ -371,7 +371,7 @@ This mechanism is described in https://github.com/cozy/cozy-client/blob/master/p *Defined in* -[packages/cozy-client/src/CozyClient.js:1441](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1441) +[packages/cozy-client/src/CozyClient.js:1502](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1502) *** @@ -387,7 +387,7 @@ Returns whether the client has been revoked on the server *Defined in* -[packages/cozy-client/src/CozyClient.js:1556](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1556) +[packages/cozy-client/src/CozyClient.js:1617](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1617) *** @@ -412,7 +412,7 @@ Collection corresponding to the doctype *Defined in* -[packages/cozy-client/src/CozyClient.js:560](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L560) +[packages/cozy-client/src/CozyClient.js:562](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L562) *** @@ -450,7 +450,7 @@ await client.create('io.cozy.todos', { *Defined in* -[packages/cozy-client/src/CozyClient.js:615](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L615) +[packages/cozy-client/src/CozyClient.js:617](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L617) *** @@ -471,7 +471,7 @@ If `oauth` options are passed, stackClient is an OAuthStackClient. *Defined in* -[packages/cozy-client/src/CozyClient.js:1594](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1594) +[packages/cozy-client/src/CozyClient.js:1655](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1655) *** @@ -496,7 +496,7 @@ The document that has been deleted *Defined in* -[packages/cozy-client/src/CozyClient.js:871](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L871) +[packages/cozy-client/src/CozyClient.js:873](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L873) *** @@ -516,7 +516,7 @@ The document that has been deleted *Defined in* -[packages/cozy-client/src/CozyClient.js:1665](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1665) +[packages/cozy-client/src/CozyClient.js:1726](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1726) *** @@ -542,7 +542,7 @@ a method from cozy-client *Defined in* -[packages/cozy-client/src/CozyClient.js:236](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L236) +[packages/cozy-client/src/CozyClient.js:238](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L238) *** @@ -564,7 +564,7 @@ a method from cozy-client *Defined in* -[packages/cozy-client/src/CozyClient.js:685](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L685) +[packages/cozy-client/src/CozyClient.js:687](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L687) *** @@ -588,7 +588,7 @@ Makes sure that the query exists in the store *Defined in* -[packages/cozy-client/src/CozyClient.js:892](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L892) +[packages/cozy-client/src/CozyClient.js:894](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L894) *** @@ -602,7 +602,7 @@ Makes sure that the query exists in the store *Defined in* -[packages/cozy-client/src/CozyClient.js:1547](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1547) +[packages/cozy-client/src/CozyClient.js:1608](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1608) *** @@ -625,7 +625,7 @@ Makes sure that the query exists in the store *Defined in* -[packages/cozy-client/src/CozyClient.js:564](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L564) +[packages/cozy-client/src/CozyClient.js:566](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L566) *** @@ -654,7 +654,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:1394](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1394) +[packages/cozy-client/src/CozyClient.js:1455](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1455) *** @@ -675,7 +675,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:577](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L577) +[packages/cozy-client/src/CozyClient.js:579](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L579) *** @@ -689,7 +689,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:1272](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1272) +[packages/cozy-client/src/CozyClient.js:1333](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1333) *** @@ -710,7 +710,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:584](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L584) +[packages/cozy-client/src/CozyClient.js:586](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L586) *** @@ -733,7 +733,7 @@ Creates an association that is linked to the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1279](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1279) +[packages/cozy-client/src/CozyClient.js:1340](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1340) *** @@ -747,7 +747,7 @@ Creates an association that is linked to the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1647](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1647) +[packages/cozy-client/src/CozyClient.js:1708](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1708) *** @@ -771,7 +771,7 @@ Array of documents or null if the collection does not exist. *Defined in* -[packages/cozy-client/src/CozyClient.js:1315](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1315) +[packages/cozy-client/src/CozyClient.js:1376](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1376) *** @@ -796,7 +796,7 @@ Document or null if the object does not exist. *Defined in* -[packages/cozy-client/src/CozyClient.js:1332](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1332) +[packages/cozy-client/src/CozyClient.js:1393](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1393) *** @@ -831,7 +831,7 @@ One or more mutation to execute *Defined in* -[packages/cozy-client/src/CozyClient.js:784](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L784) +[packages/cozy-client/src/CozyClient.js:786](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L786) *** @@ -851,7 +851,7 @@ One or more mutation to execute *Defined in* -[packages/cozy-client/src/CozyClient.js:1199](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1199) +[packages/cozy-client/src/CozyClient.js:1260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1260) *** @@ -867,7 +867,7 @@ getInstanceOptions - Returns current instance options, such as domain or app slu *Defined in* -[packages/cozy-client/src/CozyClient.js:1674](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1674) +[packages/cozy-client/src/CozyClient.js:1735](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1735) *** @@ -894,7 +894,7 @@ Get a query from the internal store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1353](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1353) +[packages/cozy-client/src/CozyClient.js:1414](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1414) *** @@ -923,7 +923,7 @@ the store up, which in turn will update the ``s and re-render the data. *Defined in* -[packages/cozy-client/src/CozyClient.js:1295](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1295) +[packages/cozy-client/src/CozyClient.js:1356](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1356) *** @@ -955,7 +955,7 @@ extract the value corresponding to the given `key` *Defined in* -[packages/cozy-client/src/CozyClient.js:1775](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1775) +[packages/cozy-client/src/CozyClient.js:1836](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1836) *** @@ -969,7 +969,7 @@ extract the value corresponding to the given `key` *Defined in* -[packages/cozy-client/src/CozyClient.js:1654](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1654) +[packages/cozy-client/src/CozyClient.js:1715](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1715) *** @@ -991,7 +991,7 @@ Sets public attribute and emits event related to revocation *Defined in* -[packages/cozy-client/src/CozyClient.js:1565](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1565) +[packages/cozy-client/src/CozyClient.js:1626](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1626) *** @@ -1013,7 +1013,7 @@ Emits event when token is refreshed *Defined in* -[packages/cozy-client/src/CozyClient.js:1576](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1576) +[packages/cozy-client/src/CozyClient.js:1637](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1637) *** @@ -1039,7 +1039,7 @@ the relationship *Defined in* -[packages/cozy-client/src/CozyClient.js:1242](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1242) +[packages/cozy-client/src/CozyClient.js:1303](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1303) *** @@ -1064,7 +1064,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1219](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1219) +[packages/cozy-client/src/CozyClient.js:1280](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1280) *** @@ -1085,7 +1085,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1253](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1253) +[packages/cozy-client/src/CozyClient.js:1314](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1314) *** @@ -1099,7 +1099,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1417](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1417) +[packages/cozy-client/src/CozyClient.js:1478](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1478) *** @@ -1121,7 +1121,7 @@ loadInstanceOptionsFromDOM - Loads the dataset injected by the Stack in web page *Defined in* -[packages/cozy-client/src/CozyClient.js:1685](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1685) +[packages/cozy-client/src/CozyClient.js:1746](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1746) *** @@ -1139,7 +1139,7 @@ This method is not iso with loadInstanceOptionsFromDOM for now. *Defined in* -[packages/cozy-client/src/CozyClient.js:1706](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1706) +[packages/cozy-client/src/CozyClient.js:1767](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1767) *** @@ -1173,7 +1173,7 @@ Emits *Defined in* -[packages/cozy-client/src/CozyClient.js:456](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L456) +[packages/cozy-client/src/CozyClient.js:458](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L458) *** @@ -1196,7 +1196,7 @@ Emits *Defined in* -[packages/cozy-client/src/CozyClient.js:507](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L507) +[packages/cozy-client/src/CozyClient.js:509](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L509) *** @@ -1220,7 +1220,7 @@ and working. *Defined in* -[packages/cozy-client/src/CozyClient.js:1265](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1265) +[packages/cozy-client/src/CozyClient.js:1326](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1326) *** @@ -1241,7 +1241,7 @@ and working. *Defined in* -[packages/cozy-client/src/CozyClient.js:1041](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1041) +[packages/cozy-client/src/CozyClient.js:1043](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1043) *** @@ -1267,7 +1267,7 @@ Mutate a document *Defined in* -[packages/cozy-client/src/CozyClient.js:1059](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1059) +[packages/cozy-client/src/CozyClient.js:1061](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1061) *** @@ -1287,7 +1287,7 @@ Mutate a document *Defined in* -[packages/cozy-client/src/CozyClient.js:237](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L237) +[packages/cozy-client/src/CozyClient.js:239](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L239) *** @@ -1309,7 +1309,7 @@ Dehydrates and adds metadata before saving a document *Defined in* -[packages/cozy-client/src/CozyClient.js:755](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L755) +[packages/cozy-client/src/CozyClient.js:757](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L757) *** @@ -1340,7 +1340,7 @@ please use `fetchQueryAndGetFromState` instead *Defined in* -[packages/cozy-client/src/CozyClient.js:919](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L919) +[packages/cozy-client/src/CozyClient.js:921](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L921) *** @@ -1367,7 +1367,7 @@ All documents matching the query *Defined in* -[packages/cozy-client/src/CozyClient.js:1001](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1001) +[packages/cozy-client/src/CozyClient.js:1003](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1003) *** @@ -1401,7 +1401,7 @@ All documents matching the query *Defined in* -[packages/cozy-client/src/CozyClient.js:1661](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1661) +[packages/cozy-client/src/CozyClient.js:1722](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1722) *** @@ -1427,7 +1427,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1411](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1411) +[packages/cozy-client/src/CozyClient.js:1472](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1472) *** @@ -1441,7 +1441,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:427](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L427) +[packages/cozy-client/src/CozyClient.js:429](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L429) *** @@ -1509,7 +1509,7 @@ client.plugins.alerts *Defined in* -[packages/cozy-client/src/CozyClient.js:287](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L287) +[packages/cozy-client/src/CozyClient.js:289](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L289) *** @@ -1529,7 +1529,7 @@ client.plugins.alerts *Defined in* -[packages/cozy-client/src/CozyClient.js:238](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L238) +[packages/cozy-client/src/CozyClient.js:240](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L240) *** @@ -1548,7 +1548,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1506](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1506) +[packages/cozy-client/src/CozyClient.js:1567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1567) *** @@ -1568,7 +1568,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1183](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1183) +[packages/cozy-client/src/CozyClient.js:1244](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1244) *** @@ -1594,7 +1594,7 @@ This method will reset the query state to its initial state and refetch it. *Defined in* -[packages/cozy-client/src/CozyClient.js:1804](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1804) +[packages/cozy-client/src/CozyClient.js:1865](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1865) *** @@ -1617,7 +1617,7 @@ Create or update a document on the server *Defined in* -[packages/cozy-client/src/CozyClient.js:637](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L637) +[packages/cozy-client/src/CozyClient.js:639](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L639) *** @@ -1652,7 +1652,7 @@ save the new resulting settings into database *Defined in* -[packages/cozy-client/src/CozyClient.js:1792](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1792) +[packages/cozy-client/src/CozyClient.js:1853](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1853) *** @@ -1681,7 +1681,7 @@ Saves multiple documents in one batch *Defined in* -[packages/cozy-client/src/CozyClient.js:658](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L658) +[packages/cozy-client/src/CozyClient.js:660](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L660) *** @@ -1701,7 +1701,7 @@ Saves multiple documents in one batch *Defined in* -[packages/cozy-client/src/CozyClient.js:1758](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1758) +[packages/cozy-client/src/CozyClient.js:1819](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1819) *** @@ -1725,7 +1725,7 @@ set some data in the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1731](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1731) +[packages/cozy-client/src/CozyClient.js:1792](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1792) *** @@ -1749,7 +1749,7 @@ At any time put an error function *Defined in* -[packages/cozy-client/src/CozyClient.js:1744](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1744) +[packages/cozy-client/src/CozyClient.js:1805](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1805) *** @@ -1787,7 +1787,7 @@ use options.force = true. *Defined in* -[packages/cozy-client/src/CozyClient.js:1532](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1532) +[packages/cozy-client/src/CozyClient.js:1593](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1593) *** @@ -1811,7 +1811,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1427](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1427) +[packages/cozy-client/src/CozyClient.js:1488](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1488) *** @@ -1825,7 +1825,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1751](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1751) +[packages/cozy-client/src/CozyClient.js:1812](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1812) *** @@ -1846,7 +1846,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:856](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L856) +[packages/cozy-client/src/CozyClient.js:858](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L858) *** @@ -1868,7 +1868,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:881](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L881) +[packages/cozy-client/src/CozyClient.js:883](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L883) *** @@ -1888,7 +1888,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:626](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L626) +[packages/cozy-client/src/CozyClient.js:628](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L628) *** @@ -1908,7 +1908,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1034](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1034) +[packages/cozy-client/src/CozyClient.js:1036](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1036) *** @@ -1934,7 +1934,7 @@ the DOM. *Defined in* -[packages/cozy-client/src/CozyClient.js:390](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L390) +[packages/cozy-client/src/CozyClient.js:392](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L392) *** @@ -1958,7 +1958,7 @@ environment variables *Defined in* -[packages/cozy-client/src/CozyClient.js:361](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L361) +[packages/cozy-client/src/CozyClient.js:363](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L363) *** @@ -1982,7 +1982,7 @@ a client with a cookie-based instance of cozy-client-js. *Defined in* -[packages/cozy-client/src/CozyClient.js:311](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L311) +[packages/cozy-client/src/CozyClient.js:313](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L313) *** @@ -2010,7 +2010,7 @@ An instance of a client, configured from the old client *Defined in* -[packages/cozy-client/src/CozyClient.js:329](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L329) +[packages/cozy-client/src/CozyClient.js:331](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L331) *** @@ -2044,4 +2044,4 @@ There are at the moment only 2 hooks available. *Defined in* -[packages/cozy-client/src/CozyClient.js:850](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L850) +[packages/cozy-client/src/CozyClient.js:852](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L852) diff --git a/docs/api/cozy-client/classes/CozyLink.md b/docs/api/cozy-client/classes/CozyLink.md index 2cefac7a5b..911ff17705 100644 --- a/docs/api/cozy-client/classes/CozyLink.md +++ b/docs/api/cozy-client/classes/CozyLink.md @@ -12,13 +12,14 @@ ### constructor -• **new CozyLink**(`requestHandler`) +• **new CozyLink**(`requestHandler`, `persistHandler`) *Parameters* | Name | Type | | :------ | :------ | | `requestHandler` | `any` | +| `persistHandler` | `any` | *Defined in* @@ -26,6 +27,27 @@ ## Methods +### persistData + +▸ **persistData**(`data`, `forward`): `void` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `data` | `any` | +| `forward` | `any` | + +*Returns* + +`void` + +*Defined in* + +[packages/cozy-client/src/CozyLink.js:16](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L16) + +*** + ### request ▸ **request**(`operation`, `result`, `forward`): `void` @@ -44,4 +66,4 @@ *Defined in* -[packages/cozy-client/src/CozyLink.js:8](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L8) +[packages/cozy-client/src/CozyLink.js:12](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L12) diff --git a/docs/api/cozy-client/classes/StackLink.md b/docs/api/cozy-client/classes/StackLink.md index 7a8fb81bf5..28869785a3 100644 --- a/docs/api/cozy-client/classes/StackLink.md +++ b/docs/api/cozy-client/classes/StackLink.md @@ -62,7 +62,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L114) +[packages/cozy-client/src/StackLink.js:118](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L118) *** @@ -82,7 +82,32 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L91) +[packages/cozy-client/src/StackLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L95) + +*** + +### persistData + +▸ **persistData**(`data`, `forward`): `Promise`<`any`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `data` | `any` | +| `forward` | `any` | + +*Returns* + +`Promise`<`any`> + +*Overrides* + +[CozyLink](CozyLink.md).[persistData](CozyLink.md#persistdata) + +*Defined in* + +[packages/cozy-client/src/StackLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L87) *** diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index a396a9c90e..2a559b56e1 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -36,7 +36,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:81](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L81) +[CozyPouchLink.js:83](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L83) ## Properties @@ -46,7 +46,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:134](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L134) +[CozyPouchLink.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L136) *** @@ -56,7 +56,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) +[CozyPouchLink.js:93](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L93) *** @@ -66,7 +66,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:92](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L92) +[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) *** @@ -76,7 +76,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:93](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L93) +[CozyPouchLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L95) *** @@ -86,7 +86,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:85](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L85) +[CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) *** @@ -96,7 +96,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:204](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L204) +[CozyPouchLink.js:206](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L206) *** @@ -106,7 +106,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +[CozyPouchLink.js:101](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L101) *** @@ -116,7 +116,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) +[CozyPouchLink.js:96](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L96) ## Methods @@ -136,7 +136,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) +[CozyPouchLink.js:607](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L607) *** @@ -156,7 +156,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:528](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L528) +[CozyPouchLink.js:568](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L568) *** @@ -177,7 +177,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:571](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L571) +[CozyPouchLink.js:611](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L611) *** @@ -197,7 +197,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:556](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L556) +[CozyPouchLink.js:596](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L596) *** @@ -218,7 +218,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:427](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L427) +[CozyPouchLink.js:467](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L467) *** @@ -240,13 +240,13 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:499](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L499) +[CozyPouchLink.js:539](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L539) *** ### executeQuery -▸ **executeQuery**(`__namedParameters`): `Promise`<{ `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset } | { `data`: `any` ; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset }> +▸ **executeQuery**(`__namedParameters`): `Promise`<{ `data`: `any` = res.cozyPouchData; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset } | { `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset }> *Parameters* @@ -256,11 +256,11 @@ CozyLink.constructor *Returns* -`Promise`<{ `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset } | { `data`: `any` ; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset }> +`Promise`<{ `data`: `any` = res.cozyPouchData; `meta`: `undefined` ; `next`: `undefined` ; `skip`: `undefined` = offset } | { `data`: `any` ; `meta`: { `count`: `any` = docs.length } ; `next`: `boolean` ; `skip`: `any` = offset }> *Defined in* -[CozyPouchLink.js:445](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L445) +[CozyPouchLink.js:485](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L485) *** @@ -280,7 +280,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) +[CozyPouchLink.js:323](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L323) *** @@ -300,7 +300,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L114) +[CozyPouchLink.js:116](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L116) *** @@ -320,7 +320,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) +[CozyPouchLink.js:319](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L319) *** @@ -340,7 +340,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:258](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L258) +[CozyPouchLink.js:260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L260) *** @@ -360,7 +360,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:253](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L253) +[CozyPouchLink.js:255](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L255) *** @@ -386,7 +386,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:239](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L239) +[CozyPouchLink.js:241](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L241) *** @@ -406,7 +406,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:409](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L409) +[CozyPouchLink.js:449](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L449) *** @@ -427,7 +427,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:414](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L414) +[CozyPouchLink.js:454](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L454) *** @@ -457,7 +457,7 @@ Migrate the current adapter *Defined in* -[CozyPouchLink.js:148](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L148) +[CozyPouchLink.js:150](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L150) *** @@ -482,7 +482,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:395](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L395) +[CozyPouchLink.js:435](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L435) *** @@ -496,7 +496,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:167](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L167) +[CozyPouchLink.js:169](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L169) *** @@ -516,7 +516,32 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:297](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L297) +[CozyPouchLink.js:299](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L299) + +*** + +### persistData + +▸ **persistData**(`data`, `forward?`): `Promise`<`void`> + +*Parameters* + +| Name | Type | Default value | +| :------ | :------ | :------ | +| `data` | `any` | `undefined` | +| `forward` | (`operation`: `any`, `result`: `any`) => `void` | `doNothing` | + +*Returns* + +`Promise`<`void`> + +*Overrides* + +CozyLink.persistData + +*Defined in* + +[CozyPouchLink.js:390](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L390) *** @@ -536,7 +561,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:133](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L133) +[CozyPouchLink.js:135](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L135) *** @@ -562,7 +587,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:340](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L340) +[CozyPouchLink.js:342](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L342) *** @@ -576,7 +601,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:223](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L223) +[CozyPouchLink.js:225](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L225) *** @@ -595,7 +620,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[CozyPouchLink.js:272](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L272) +[CozyPouchLink.js:274](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L274) *** @@ -614,7 +639,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:289](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L289) +[CozyPouchLink.js:291](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L291) *** @@ -634,7 +659,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:325](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L325) +[CozyPouchLink.js:327](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L327) *** @@ -648,7 +673,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:593](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L593) +[CozyPouchLink.js:633](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L633) *** @@ -668,7 +693,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:533](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L533) +[CozyPouchLink.js:573](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L573) *** @@ -688,7 +713,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:538](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L538) +[CozyPouchLink.js:578](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L578) *** @@ -713,4 +738,4 @@ The adapter name *Defined in* -[CozyPouchLink.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L109) +[CozyPouchLink.js:111](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L111) diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index 9c45fe859a..106e61903f 100644 --- a/packages/cozy-client/types/CozyClient.d.ts +++ b/packages/cozy-client/types/CozyClient.d.ts @@ -1,4 +1,50 @@ export default CozyClient; +export type CozyClientDocument = { + /** + * - Id of the document + */ + _id?: string; + /** + * - Id of the document + */ + id?: string; + /** + * - Type of the document + */ + _type?: string; + /** + * - Current revision of the document + */ + _rev?: string; + /** + * - When the document has been deleted + */ + _deleted?: boolean; + /** + * - Relationships of the document + */ + relationships?: import("./types").ReferencedByRelationship; + /** + * - referenced by of another document + */ + referenced_by?: import("./types").Reference[]; + /** + * - Cozy Metadata + */ + cozyMetadata?: import("./types").CozyMetadata; + /** + * - Pouch Metadata + */ + meta?: import("./types").CozyClientDocumentMeta; + /** + * - When true the document should NOT be replicated to the remote database + */ + cozyLocalOnly?: boolean; + /** + * - When true the document has been retrieved from a local PouchDB + */ + cozyFromPouch?: boolean; +}; export type ClientOptions = { client?: object; link?: object; @@ -36,6 +82,8 @@ export type ClientOptions = { store?: boolean; }; /** + * @typedef {import("./types").CozyClientDocument} CozyClientDocument + * * @typedef {object} ClientOptions * @property {object} [client] * @property {object} [link] @@ -458,6 +506,23 @@ declare class CozyClient { * @returns {Promise} */ private requestQuery; + /** + * Save the document or array of documents into the persisted storage (if any) + * + * @private + * @param {CozyClientDocument | Array} data - Document or array of documents to be saved + * @returns {Promise} + */ + private persistVirtualDocuments; + /** + * Save the document or array of documents into the persisted storage (if any) + * + * @private + * @param {CozyClientDocument} document - Document to be saved + * @param {boolean} enforce - When true, save the document even if `meta.rev` or `_rev` exist + * @returns {Promise} + */ + private persistVirtualDocument; /** * Fetch relationships for a response (can be several docs). * Fills the `relationships` attribute of each documents. diff --git a/packages/cozy-client/types/CozyLink.d.ts b/packages/cozy-client/types/CozyLink.d.ts index fb2974b285..bf6173d9c0 100644 --- a/packages/cozy-client/types/CozyLink.d.ts +++ b/packages/cozy-client/types/CozyLink.d.ts @@ -1,5 +1,6 @@ export default class CozyLink { - constructor(requestHandler: any); + constructor(requestHandler: any, persistHandler: any); request(operation: any, result: any, forward: any): void; + persistData(data: any, forward: any): void; } export function chain(links: any): any; diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index cb8e3c01d4..0bd6f55931 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -705,6 +705,15 @@ export type CozyMetadata = { */ favorite?: boolean; }; +/** + * - Meta object as specified by JSON-API (https://jsonapi.org/format/#document-meta) + */ +export type CozyClientDocumentMeta = { + /** + * - Current revision of the document + */ + rev?: string; +}; /** * - A document */ @@ -741,6 +750,18 @@ export type CozyClientDocument = { * - Cozy Metadata */ cozyMetadata?: CozyMetadata; + /** + * - Pouch Metadata + */ + meta?: CozyClientDocumentMeta; + /** + * - When true the document should NOT be replicated to the remote database + */ + cozyLocalOnly?: boolean; + /** + * - When true the document has been retrieved from a local PouchDB + */ + cozyFromPouch?: boolean; }; /** * - A io.cozy.files document's metadata diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index 127c172b69..2023a26533 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -1,8 +1,11 @@ export function getReplicationURL(uri: any, token: any, doctype: any): string; export function isExpiredTokenError(pouchError: any): boolean; export default PouchLink; +export type CozyClientDocument = any; export type SyncStatus = "idle" | "replicating"; /** + * @typedef {import('cozy-client/src/types').CozyClientDocument} CozyClientDocument + * * @typedef {"idle"|"replicating"} SyncStatus */ /** @@ -117,6 +120,16 @@ declare class PouchLink extends CozyLink { getSyncInfo(doctype: any): any; getPouch(doctype: any): any; supportsOperation(operation: any): boolean; + /** + * Retrieve the existing document from Pouch + * + * @private + * @param {*} id - ID of the document to retrieve + * @param {*} type - Doctype of the document to retrieve + * @param {*} throwIfNotFound - If true the method will throw when the document is not found. Otherwise it will return null + * @returns {Promise} + */ + private getExistingDocument; /** * * Check if there is warmup queries for this doctype @@ -141,17 +154,17 @@ declare class PouchLink extends CozyLink { indexedFields: any; partialFilter: any; }): Promise<{ + data: any; + meta?: undefined; + skip?: undefined; + next?: undefined; + } | { data: any; meta: { count: any; }; skip: any; next: boolean; - } | { - data: any; - meta?: undefined; - skip?: undefined; - next?: undefined; }>; executeMutation(mutation: any, result: any, forward: any): Promise; createDocument(mutation: any): Promise; diff --git a/packages/cozy-pouch-link/types/jsonapi.d.ts b/packages/cozy-pouch-link/types/jsonapi.d.ts index defaa60591..bdd3d3ed37 100644 --- a/packages/cozy-pouch-link/types/jsonapi.d.ts +++ b/packages/cozy-pouch-link/types/jsonapi.d.ts @@ -1,14 +1,14 @@ export function normalizeDoc(doc: any, doctype: any): any; export function fromPouchResult(res: any, withRows: any, doctype: any): { + data: any; + meta?: undefined; + skip?: undefined; + next?: undefined; +} | { data: any; meta: { count: any; }; skip: any; next: boolean; -} | { - data: any; - meta?: undefined; - skip?: undefined; - next?: undefined; }; From 0ca8ba356482a642b5db9e50b5bd4effc2987c06 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 11:58:30 +0200 Subject: [PATCH 20/84] feat: Forward operation in `StackLink.request()` when offline On the Flagship app we want to serve `.query()` request using the `StackLink` when the device is connected, but we want to fallback to the `PouchLink` when we detect a connection loss To allow this, we allow the consuming app to provide an `isOnline()` method to the `StackLink` When provided, the `StackLink` will check for connectivity before doing its request. When offline, instead of processing the request, it will instead forward the request to the next `Link` The code is generic so the consuming app can configure a `PouchLink` or anything else as the next `Link` --- packages/cozy-client/src/StackLink.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index e414c2b88b..deaa22bafb 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -58,8 +58,9 @@ export default class StackLink extends CozyLink { * @param {object} [options] - Options * @param {object} [options.stackClient] - A StackClient * @param {object} [options.client] - A StackClient (deprecated) + * @param {import('cozy-pouch-link/dist/types').LinkPlatform} [options.platform] Platform specific adapters and methods */ - constructor({ client, stackClient } = {}) { + constructor({ client, stackClient, platform } = {}) { super() if (client) { logger.warn( @@ -67,6 +68,7 @@ export default class StackLink extends CozyLink { ) } this.stackClient = stackClient || client + this.isOnline = platform?.isOnline } registerClient(client) { @@ -77,7 +79,11 @@ export default class StackLink extends CozyLink { this.stackClient = null } - request(operation, result, forward) { + async request(operation, result, forward) { + if (this.isOnline && !(await this.isOnline())) { + return forward(operation) + } + if (operation.mutationType) { return this.executeMutation(operation, result, forward) } From 2a029d973ae1a2ec09e92101f2b82176317e3ff2 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:00:01 +0200 Subject: [PATCH 21/84] feat: Forward operation in `StackLink.request()` on Network error On the Flagship app we want to serve `.query()` request using the `StackLink` when the device is connected, but we want to fallback to the `PouchLink` when we detect a connection loss In previous commit we tried pro-actively detect for connexion loss by calling an `isOnline()` method before processing the request But we want to also catch network errors when the `isOnline()` methods fails to detect connection loss, then we also fallback to the next `Link` --- packages/cozy-client/src/StackLink.js | 14 +++++++++++--- packages/cozy-client/src/utils.js | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index deaa22bafb..038511a66e 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -5,6 +5,7 @@ import CozyLink from './CozyLink' import { DOCTYPE_FILES } from './const' import { BulkEditError } from './errors' import logger from './logger' +import { isReactNativeOfflineError } from './utils' /** * @@ -84,10 +85,17 @@ export default class StackLink extends CozyLink { return forward(operation) } - if (operation.mutationType) { - return this.executeMutation(operation, result, forward) + try { + if (operation.mutationType) { + return await this.executeMutation(operation, result, forward) + } + return await this.executeQuery(operation) + } catch (err) { + if (isReactNativeOfflineError(err)) { + return forward(operation) + } + throw err } - return this.executeQuery(operation) } async persistData(data, forward) { diff --git a/packages/cozy-client/src/utils.js b/packages/cozy-client/src/utils.js index 973ac5cf37..8ac4fe55b3 100644 --- a/packages/cozy-client/src/utils.js +++ b/packages/cozy-client/src/utils.js @@ -68,3 +68,15 @@ export const hasQueriesBeenLoaded = queriesResults => { hasQueryBeenLoaded(queryResult) ) } + +/** + * Check is the error is about ReactNative not having access to internet + * + * @param {Error} err - The error to check + * @returns {boolean} True if the error is a network error, otherwise false + */ +export const isReactNativeOfflineError = err => { + // This error message is specific to ReactNative + // Network errors on a browser would produce another error.message + return err.message === 'Network request failed' +} From b42b26935f2f926d72c8612fed52eb0a2823d0c6 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 17 Jul 2024 15:33:55 +0200 Subject: [PATCH 22/84] docs: Update types and documentation --- docs/api/cozy-client/classes/StackLink.md | 27 ++++++++++++++++------- packages/cozy-client/types/StackLink.d.ts | 5 ++++- packages/cozy-client/types/utils.d.ts | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/api/cozy-client/classes/StackLink.md b/docs/api/cozy-client/classes/StackLink.md index 28869785a3..ad99609ed5 100644 --- a/docs/api/cozy-client/classes/StackLink.md +++ b/docs/api/cozy-client/classes/StackLink.md @@ -22,6 +22,7 @@ Transfers queries and mutations to a remote stack | :------ | :------ | :------ | | `[options]` | `Object` | Options | | `[options].client` | `any` | - | +| `[options].platform` | `any` | - | | `[options].stackClient` | `any` | - | *Overrides* @@ -30,17 +31,27 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:62](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L62) +[packages/cozy-client/src/StackLink.js:64](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L64) ## Properties +### isOnline + +• **isOnline**: `any` + +*Defined in* + +[packages/cozy-client/src/StackLink.js:72](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L72) + +*** + ### stackClient • **stackClient**: `any` *Defined in* -[packages/cozy-client/src/StackLink.js:69](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L69) +[packages/cozy-client/src/StackLink.js:71](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L71) ## Methods @@ -62,7 +73,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:118](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L118) +[packages/cozy-client/src/StackLink.js:132](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L132) *** @@ -82,7 +93,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L95) +[packages/cozy-client/src/StackLink.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L109) *** @@ -107,7 +118,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L87) +[packages/cozy-client/src/StackLink.js:101](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L101) *** @@ -127,7 +138,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:72](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L72) +[packages/cozy-client/src/StackLink.js:75](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L75) *** @@ -153,7 +164,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:80](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L80) +[packages/cozy-client/src/StackLink.js:83](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L83) *** @@ -167,4 +178,4 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:76](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L76) +[packages/cozy-client/src/StackLink.js:79](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L79) diff --git a/packages/cozy-client/types/StackLink.d.ts b/packages/cozy-client/types/StackLink.d.ts index 80a4d6d97e..839117535d 100644 --- a/packages/cozy-client/types/StackLink.d.ts +++ b/packages/cozy-client/types/StackLink.d.ts @@ -9,12 +9,15 @@ export default class StackLink extends CozyLink { * @param {object} [options] - Options * @param {object} [options.stackClient] - A StackClient * @param {object} [options.client] - A StackClient (deprecated) + * @param {import('cozy-pouch-link/dist/types').LinkPlatform} [options.platform] Platform specific adapters and methods */ - constructor({ client, stackClient }?: { + constructor({ client, stackClient, platform }?: { stackClient: object; client: object; + platform: import('cozy-pouch-link/dist/types').LinkPlatform; }); stackClient: any; + isOnline: any; registerClient(client: any): void; reset(): void; /** diff --git a/packages/cozy-client/types/utils.d.ts b/packages/cozy-client/types/utils.d.ts index 031006d18c..603dfc0ac6 100644 --- a/packages/cozy-client/types/utils.d.ts +++ b/packages/cozy-client/types/utils.d.ts @@ -2,6 +2,7 @@ export function isQueryLoading(col: any): boolean; export function hasQueryBeenLoaded(col: any): any; export function isQueriesLoading(queriesResults: any): boolean; export function hasQueriesBeenLoaded(queriesResults: any): boolean; +export function isReactNativeOfflineError(err: Error): boolean; export type CancelablePromise = Promise; /** * @typedef {Promise} CancelablePromise From e9474d55d02893339a20cc88de01c1ba17dbeb79 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 13 Jun 2024 12:24:43 +0200 Subject: [PATCH 23/84] refactor: Nest startReplication tests in corresponding describe block --- .../src/startReplication.spec.js | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index 874aea53af..a79f5d3f93 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -26,49 +26,52 @@ const storage = { persistLastReplicatedDocID: jest.fn() } -describe('replication through _all_docs', () => { +describe('startReplication', () => { beforeEach(() => { fetchRemoteLastSequence.mockResolvedValue('10-xyz') }) - it('should replicate all docs', async () => { - storage.getLastReplicatedDocID.mockReturnValue(null) - const dummyDocs = generateDocs(2) - fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) - const rep = await replicateAllDocs(null, url, undefined, storage) - const expectedDocs = dummyDocs.map(doc => doc.doc) - expect(rep).toEqual(expectedDocs) - }) + describe('replication through _all_docs', () => { + it('should replicate all docs', async () => { + storage.getLastReplicatedDocID.mockReturnValue(null) + const dummyDocs = generateDocs(2) + fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) - it('should replicate all docs when it gets more docs than the batch limit', async () => { - storage.getLastReplicatedDocID.mockReturnValue(null) - const dummyDocs = generateDocs(1002) - fetchRemoteInstance.mockResolvedValueOnce({ - rows: dummyDocs.slice(0, 1001) - }) - fetchRemoteInstance.mockResolvedValueOnce({ - rows: dummyDocs.slice(1000, 1002) + const rep = await replicateAllDocs(null, url, undefined, storage) + const expectedDocs = dummyDocs.map(doc => doc.doc) + expect(rep).toEqual(expectedDocs) }) - const rep = await replicateAllDocs(null, url, undefined, storage) - const expectedDocs = dummyDocs.map(doc => doc.doc) - expect(rep).toEqual(expectedDocs) - }) + it('should replicate all docs when it gets more docs than the batch limit', async () => { + storage.getLastReplicatedDocID.mockReturnValue(null) + const dummyDocs = generateDocs(1002) + fetchRemoteInstance.mockResolvedValueOnce({ + rows: dummyDocs.slice(0, 1001) + }) + fetchRemoteInstance.mockResolvedValueOnce({ + rows: dummyDocs.slice(1000, 1002) + }) + + const rep = await replicateAllDocs(null, url, undefined, storage) + const expectedDocs = dummyDocs.map(doc => doc.doc) + expect(rep).toEqual(expectedDocs) + }) - it('should replicate from the last saved doc id', async () => { - storage.getLastReplicatedDocID.mockReturnValue('5') - const dummyDocs = generateDocs(10) - fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs.slice(5, 11) }) + it('should replicate from the last saved doc id', async () => { + storage.getLastReplicatedDocID.mockReturnValue('5') + const dummyDocs = generateDocs(10) + fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs.slice(5, 11) }) - const rep = await replicateAllDocs(null, url, undefined, storage) + const rep = await replicateAllDocs(null, url, undefined, storage) - const calledUrl = new URL(`${url}/_all_docs`) - expect(fetchRemoteInstance).toHaveBeenCalledWith(calledUrl, { - include_docs: true, - limit: 1000, - startkey_docid: '5' + const calledUrl = new URL(`${url}/_all_docs`) + expect(fetchRemoteInstance).toHaveBeenCalledWith(calledUrl, { + include_docs: true, + limit: 1000, + startkey_docid: '5' + }) + const expectedDocs = dummyDocs.map(doc => doc.doc).slice(6, 11) + expect(rep).toEqual(expectedDocs) }) - const expectedDocs = dummyDocs.map(doc => doc.doc).slice(6, 11) - expect(rep).toEqual(expectedDocs) }) }) From 40c4111b65f52a75b2273f38fa3c8a436a7bb6e0 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 13 Jun 2024 11:38:52 +0200 Subject: [PATCH 24/84] test: Check for correct calls in startReplication tests --- packages/cozy-pouch-link/src/startReplication.spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index a79f5d3f93..e2ebe3c8f0 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -1,6 +1,7 @@ import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' import { replicateAllDocs } from './startReplication' +import { insertBulkDocs } from './helpers' jest.mock('./remote', () => ({ fetchRemoteLastSequence: jest.fn(), @@ -28,6 +29,7 @@ const storage = { describe('startReplication', () => { beforeEach(() => { + jest.resetAllMocks() fetchRemoteLastSequence.mockResolvedValue('10-xyz') }) @@ -40,6 +42,8 @@ describe('startReplication', () => { const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) + expect(fetchRemoteInstance).toHaveBeenCalledTimes(1) + expect(insertBulkDocs).toHaveBeenCalledTimes(1) }) it('should replicate all docs when it gets more docs than the batch limit', async () => { @@ -55,6 +59,8 @@ describe('startReplication', () => { const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) + expect(fetchRemoteInstance).toHaveBeenCalledTimes(2) + expect(insertBulkDocs).toHaveBeenCalledTimes(2) }) it('should replicate from the last saved doc id', async () => { @@ -70,6 +76,8 @@ describe('startReplication', () => { limit: 1000, startkey_docid: '5' }) + expect(fetchRemoteInstance).toHaveBeenCalledTimes(1) + expect(insertBulkDocs).toHaveBeenCalledTimes(1) const expectedDocs = dummyDocs.map(doc => doc.doc).slice(6, 11) expect(rep).toEqual(expectedDocs) }) From d5ea629789ed58ef28c0981debb5fddae6d7c7ac Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 13 Jun 2024 12:27:15 +0200 Subject: [PATCH 25/84] fix: Do not call pouch.replication on initial replication When doing the first replication, we call `replicateAllDocs` instead of doing a `pouch.replicate` This is for performances reasons as `pouch.replicate` may be very slow With current implementation, the `replicateAllDocs` method would be call asynchronously and `pouch.replicate` would be called in parallel This is not what we want, we want to only call `replicateAllDocs` --- .../cozy-pouch-link/src/PouchManager.spec.js | 4 + .../cozy-pouch-link/src/startReplication.js | 1 + .../src/startReplication.spec.js | 86 ++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index 6a5f7187b5..ef437f3123 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -172,6 +172,9 @@ describe('PouchManager', () => { const writeOnlyPouch = manager.getPouch('io.cozy.writeonly') writeOnlyPouch.replicate = {} writeOnlyPouch.replicate.to = jest.fn() + manager.updateSyncInfo('io.cozy.todos') + manager.updateSyncInfo('io.cozy.readonly') + manager.updateSyncInfo('io.cozy.writeonly') manager.startReplicationLoop() await sleep(1000) expect(readOnlyPouch.replicate.from).toHaveBeenCalled() @@ -224,6 +227,7 @@ describe('PouchManager', () => { it('should call on sync with doctype updates', async () => { jest.spyOn(manager, 'replicateOnce') onSync.mockReset() + manager.updateSyncInfo('io.cozy.todos') await manager.replicateOnce() expect(onSync).toHaveBeenCalledWith({ 'io.cozy.todos': [ diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index f8db3ef048..9cfbf7126f 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -86,6 +86,7 @@ export const startReplication = ( } return resolve(docs) })() + return } if (strategy === 'fromRemote') { replication = pouch.replicate.from(url, options) diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index e2ebe3c8f0..f0618b4ba6 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -1,6 +1,7 @@ +import MicroEE from 'microee' import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' -import { replicateAllDocs } from './startReplication' +import { replicateAllDocs, startReplication } from './startReplication' import { insertBulkDocs } from './helpers' jest.mock('./remote', () => ({ @@ -27,6 +28,10 @@ const storage = { persistLastReplicatedDocID: jest.fn() } +function ReplicationOnMock() {} +MicroEE.mixin(ReplicationOnMock) +const mockReplicationOn = new ReplicationOnMock() + describe('startReplication', () => { beforeEach(() => { jest.resetAllMocks() @@ -82,4 +87,83 @@ describe('startReplication', () => { expect(rep).toEqual(expectedDocs) }) }) + + describe('startReplication', () => { + it('should call replicateAllDocs on initial replication', async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = true + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const dummyDocs = generateDocs(2) + fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) + + const pouch = getPouchMock() + + await startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + + expect(fetchRemoteInstance).toHaveBeenCalledWith( + new URL( + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files/_all_docs' + ), + { include_docs: true, limit: 1000 } + ) + expect(pouch.replicate.from).not.toHaveBeenCalled() + expect(pouch.replicate.to).not.toHaveBeenCalled() + expect(pouch.sync).not.toHaveBeenCalled() + }) + + it('should call Pouch replication on non-initial replications', async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + mockReplicationOn.emit('complete') + await promise + + expect(fetchRemoteInstance).not.toHaveBeenCalled() + expect(pouch.replicate.from).toHaveBeenCalledWith( + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files', + { batch_size: 1000, selector: { cozyLocalOnly: { $exists: false } } } + ) + }) + }) +}) + +const getPouchMock = () => { + const pouch = { + replicate: { + from: jest.fn(), + to: jest.fn() + }, + sync: jest.fn() + } + pouch.replicate.from.mockReturnValue(mockReplicationOn) + pouch.replicate.to.mockReturnValue(mockReplicationOn) + pouch.sync.mockReturnValue(mockReplicationOn) + + return pouch +} + +const getReplicationOptionsMock = () => ({ + strategy: 'fromRemote', + initialReplication: false, + warmupQueries: {}, + doctype: 'io.cozy.files' }) From ef32d138f820c233f965a0e7b2cb0945a4521f36 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 13 Jun 2024 13:01:08 +0200 Subject: [PATCH 26/84] test: Improve test coverage for startReplication --- .../src/startReplication.spec.js | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index f0618b4ba6..85b4920b1c 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -10,6 +10,7 @@ jest.mock('./remote', () => ({ })) jest.mock('./helpers', () => ({ + ...jest.requireActual('./helpers').default, insertBulkDocs: jest.fn() })) @@ -31,6 +32,9 @@ const storage = { function ReplicationOnMock() {} MicroEE.mixin(ReplicationOnMock) const mockReplicationOn = new ReplicationOnMock() +mockReplicationOn.cancel = () => { + mockReplicationOn.emit('complete') +} describe('startReplication', () => { beforeEach(() => { @@ -138,11 +142,191 @@ describe('startReplication', () => { await promise expect(fetchRemoteInstance).not.toHaveBeenCalled() + expect(pouch.replicate.to).not.toHaveBeenCalled() + expect(pouch.sync).not.toHaveBeenCalled() expect(pouch.replicate.from).toHaveBeenCalledWith( 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files', { batch_size: 1000, selector: { cozyLocalOnly: { $exists: false } } } ) }) + + it(`should call Pouch replication on initial replications AND strategy is 'toRemote'`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = true + replicationOptions.strategy = 'toRemote' + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + mockReplicationOn.emit('complete') + await promise + + expect(fetchRemoteInstance).not.toHaveBeenCalled() + expect(pouch.replicate.from).not.toHaveBeenCalled() + expect(pouch.sync).not.toHaveBeenCalled() + expect(pouch.replicate.to).toHaveBeenCalledWith( + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files', + { batch_size: 1000, selector: { cozyLocalOnly: { $exists: false } } } + ) + }) + + it(`should handle error result when Pouch replication`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + mockReplicationOn.emit('error', 'some_error_message') + await expect(promise).rejects.toEqual('some_error_message') + }) + + it(`should handle change event with Sync format and Replication format when Pouch replication`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + // Sync format + mockReplicationOn.emit('change', { + change: { + docs: [ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + } + ] + } + }) + // Replicaiton format + mockReplicationOn.emit('change', { + docs: [ + { + _id: 'SOME_DOCUMENT_ID_2', + some_property: 'some_value' + } + ] + }) + mockReplicationOn.emit('complete') + const result = await promise + + expect(result).toStrictEqual([ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + }, + { + _id: 'SOME_DOCUMENT_ID_2', + some_property: 'some_value' + } + ]) + }) + + it(`should filter design document from change event when Pouch replication`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + mockReplicationOn.emit('change', { + change: { + docs: [ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + }, + { + _id: '_design_SOME_DOCUMENT_ID_2', + some_property: 'some_value' + } + ] + } + }) + mockReplicationOn.emit('complete') + const result = await promise + + expect(result).toStrictEqual([ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + } + ]) + }) + + it(`should filter deleted document from change event when Pouch replication`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + mockReplicationOn.emit('change', { + change: { + docs: [ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + }, + { + _id: 'SOME_DOCUMENT_ID_2', + some_property: 'some_value', + _deleted: true + } + ] + } + }) + mockReplicationOn.emit('complete') + const result = await promise + + expect(result).toStrictEqual([ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + } + ]) + }) }) }) From b5fc2a9d4b64e521798970e96f42f8cd8daabc8d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 13 Jun 2024 13:01:59 +0200 Subject: [PATCH 27/84] fix: Make cancelling replication work The `startReplication` promise is expected to be cancelable This is done by storing the `pouch.replicate` call into a `replication` variable, so we can use it to call the `cancel()` method With current implementation, the `replication` variable exists in the `startReplication` scope but also in the child Promise scope, so the parent variable is hidden by the child one, which prevent to access it from the parent scope Deleting the child variable is enough to fix the problem --- .../cozy-pouch-link/src/startReplication.js | 2 +- .../src/startReplication.spec.js | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 9cfbf7126f..647961759d 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -69,7 +69,7 @@ export const startReplication = ( } } } - let replication + if (initialReplication && strategy !== 'toRemote') { ;(async () => { // For the first remote->local replication, we manually replicate all docs diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index 85b4920b1c..d3ea709c78 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -327,6 +327,48 @@ describe('startReplication', () => { } ]) }) + + it(`should allow to cancel promise when Pouch replication`, async () => { + const replicationOptions = getReplicationOptionsMock() + replicationOptions.initialReplication = false + + const getReplicationURL = () => + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.files' + + const pouch = getPouchMock() + + const promise = startReplication( + pouch, + replicationOptions, + getReplicationURL, + storage + ) + + expect(promise.cancel).toBeDefined() + + promise.cancel() + + // this change should be ignored + mockReplicationOn.emit('change', { + change: { + docs: [ + { + _id: 'SOME_DOCUMENT_ID_1', + some_property: 'some_value' + }, + { + _id: 'SOME_DOCUMENT_ID_2', + some_property: 'some_value', + _deleted: true + } + ] + } + }) + + const result = await promise + + expect(result).toStrictEqual([]) + }) }) }) From 8b9d6835f508367e53e4d26ed92502c042bfe754 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 15:25:58 +0200 Subject: [PATCH 28/84] test: Add unit tests for remote.js --- packages/cozy-pouch-link/src/remote.spec.js | 160 ++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 packages/cozy-pouch-link/src/remote.spec.js diff --git a/packages/cozy-pouch-link/src/remote.spec.js b/packages/cozy-pouch-link/src/remote.spec.js new file mode 100644 index 0000000000..20a3dc32db --- /dev/null +++ b/packages/cozy-pouch-link/src/remote.spec.js @@ -0,0 +1,160 @@ +import { enableFetchMocks, disableFetchMocks } from 'jest-fetch-mock' + +import { + fetchRemoteInstance, + fetchRemoteLastSequence +} from './remote' + +describe('remote', () => { + beforeAll(() => { + enableFetchMocks() + }) + + beforeEach(() => { + fetch.resetMocks() + }) + + afterAll(() => { + disableFetchMocks() + }) + + describe('fetchRemoteInstance', () => { + it(`Should add Authorization header based on URL's password`, async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes' + + mockDatabaseOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes' + ) + + await fetchRemoteInstance(new URL(remoteUrl)) + + const expectedHeaders = new Headers() + expectedHeaders.append('Accept', 'application/json') + expectedHeaders.append('Content-Type', 'application/json') + expectedHeaders.append('Authorization', 'Bearer SOME_TOKEN') + + expect(fetch).toHaveBeenCalledWith( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes', + { + headers: expectedHeaders + } + ) + }) + + it('Should return data when found', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + mockDatabaseOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + const result = await fetchRemoteInstance(new URL(remoteUrl)) + + expect(result).toStrictEqual({ + last_seq: '97-SOME_SEQ_VALUE', + pending: -1, + results: [ + { + id: 'SOME_ID', + seq: '97-SOME_SEQ_VALUE', + doc: null, + changes: [{ rev: '3-SOME_REV' }] + } + ] + }) + }) + + it('Should add parameters when given', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes' + + mockDatabaseOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + await fetchRemoteInstance(new URL(remoteUrl), { + limit: 1, + descending: true + }) + + const expectedHeaders = new Headers() + expectedHeaders.append('Accept', 'application/json') + expectedHeaders.append('Content-Type', 'application/json') + expectedHeaders.append('Authorization', 'Bearer SOME_TOKEN') + + expect(fetch).toHaveBeenCalledWith( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true', + expect.anything() + ) + }) + + it('Should return null when 404 error', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + + mockDatabaseNotFoundOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + const result = await fetchRemoteInstance(new URL(remoteUrl)) + + expect(result).toBeNull() + }) + }) + + describe('fetchRemoteLastSequence', () => { + it('Should return data when found', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts' + mockDatabaseOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + const result = await fetchRemoteLastSequence(remoteUrl) + + expect(result).toBe('97-SOME_SEQ_VALUE') + }) + + it('Shoud throw when 404 error', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts' + mockDatabaseNotFoundOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow() + }) + }) +}) + +const mockDatabaseNotFoundOn = url => { + fetch.mockOnceIf(url, JSON.stringify({}), { + error: 'not_found', + ok: false, + reason: 'Database does not exist.', + status: 404 + }) +} + +const mockDatabaseOn = url => { + fetch.mockOnceIf( + url, + JSON.stringify({ + last_seq: '97-SOME_SEQ_VALUE', + pending: -1, + results: [ + { + id: 'SOME_ID', + seq: '97-SOME_SEQ_VALUE', + doc: null, + changes: [{ rev: '3-SOME_REV' }] + } + ] + }), + { + ok: true, + status: 200 + } + ) +} From ce44cc2cda57d1b62947d5df14149cf8a25f9a1b Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 15:32:30 +0200 Subject: [PATCH 29/84] feat: Throw errors from remote.js when HTTP result is not `ok` With previous implementation, `fetchRemoteInstance` would return `null` when HTTP result is not `ok` This was problematic because `remote.fetchRemoteLastSequence` and `startReplication.replicateAllDocs` methods does not expect to receive a `null` value from this call and would then throw anyway with fewer debug data So we want `fetchRemoteInstance` to throw early when HTTP result is not `ok` Also we want 404 errors to have dedicated exception so we can intercept them later (see next commit) --- packages/cozy-pouch-link/src/remote.js | 18 ++++++++- packages/cozy-pouch-link/src/remote.spec.js | 41 ++++++++++++++++++--- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/cozy-pouch-link/src/remote.js b/packages/cozy-pouch-link/src/remote.js index 5017272ec7..cd07532317 100644 --- a/packages/cozy-pouch-link/src/remote.js +++ b/packages/cozy-pouch-link/src/remote.js @@ -1,5 +1,12 @@ import AccessToken from './AccessToken' +export const DATABASE_NOT_FOUND_ERROR = 'Database does not exist' + +export const isDatabaseNotFoundError = error => { + return error.message === DATABASE_NOT_FOUND_ERROR +} + + /** * Fetch remote instance * @@ -25,7 +32,16 @@ export const fetchRemoteInstance = async (url, params = {}) => { if (resp.ok) { return data } - return null + + if (resp.status === 404) { + throw new Error(DATABASE_NOT_FOUND_ERROR) + } + + throw new Error( + `Error (${resp.status}) while fetching remote instance: ${JSON.stringify( + data + )}` + ) } /** diff --git a/packages/cozy-pouch-link/src/remote.spec.js b/packages/cozy-pouch-link/src/remote.spec.js index 20a3dc32db..a4aed8aaf2 100644 --- a/packages/cozy-pouch-link/src/remote.spec.js +++ b/packages/cozy-pouch-link/src/remote.spec.js @@ -1,6 +1,7 @@ import { enableFetchMocks, disableFetchMocks } from 'jest-fetch-mock' import { + DATABASE_NOT_FOUND_ERROR, fetchRemoteInstance, fetchRemoteLastSequence } from './remote' @@ -89,7 +90,7 @@ describe('remote', () => { ) }) - it('Should return null when 404 error', async () => { + it('Should throw when 404 error', async () => { const remoteUrl = 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' @@ -97,9 +98,9 @@ describe('remote', () => { 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' ) - const result = await fetchRemoteInstance(new URL(remoteUrl)) - - expect(result).toBeNull() + await expect(fetchRemoteInstance(new URL(remoteUrl))).rejects.toThrow( + DATABASE_NOT_FOUND_ERROR + ) }) }) @@ -116,15 +117,30 @@ describe('remote', () => { expect(result).toBe('97-SOME_SEQ_VALUE') }) - it('Shoud throw when 404 error', async () => { + it('Shoud throw when HTTP error', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts' + mockUnknownErrorOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow( + 'Error (503) while fetching remote instance: {"error":"code=503, message=SOME UNKNOWN ERROR"}' + ) + }) + + it('Shoud throw dedicated error when 404 error', async () => { const remoteUrl = 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts' mockDatabaseNotFoundOn( 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' ) - await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow() + await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow( + DATABASE_NOT_FOUND_ERROR + ) }) + }) }) @@ -137,6 +153,19 @@ const mockDatabaseNotFoundOn = url => { }) } +const mockUnknownErrorOn = url => { + fetch.mockOnceIf( + url, + JSON.stringify({ + error: 'code=503, message=SOME UNKNOWN ERROR' + }), + { + ok: false, + status: 503 + } + ) +} + const mockDatabaseOn = url => { fetch.mockOnceIf( url, From ea3e3db8a659dbeb9e982a3dcb7b1263e578d2e9 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 16:23:15 +0200 Subject: [PATCH 30/84] refactor: Extract replicateOnce from PouchManager --- packages/cozy-pouch-link/src/PouchManager.js | 98 +--------------- packages/cozy-pouch-link/src/replicateOnce.js | 105 ++++++++++++++++++ 2 files changed, 110 insertions(+), 93 deletions(-) create mode 100644 packages/cozy-pouch-link/src/replicateOnce.js diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index b1eaf57ddd..306608e086 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -1,17 +1,13 @@ import fromPairs from 'lodash/fromPairs' import forEach from 'lodash/forEach' import get from 'lodash/get' -import map from 'lodash/map' -import zip from 'lodash/zip' -import startsWith from 'lodash/startsWith' import { isMobileApp } from 'cozy-device-helper' import { PouchLocalStorage } from './localStorage' import Loop from './loop' import logger from './logger' import { platformWeb } from './platformWeb' -import { fetchRemoteLastSequence } from './remote' -import { startReplication } from './startReplication' +import { replicateOnce } from './replicateOnce' import { getDatabaseName } from './utils' const DEFAULT_DELAY = 30 * 1000 @@ -73,6 +69,9 @@ class PouchManager { this.stopReplicationLoop = this.stopReplicationLoop.bind(this) this.replicateOnce = this.replicateOnce.bind(this) this.executeQuery = this.options.executeQuery + + /** @type {import('./types').CancelablePromise[]} - Stores replication promises */ + this.replications = undefined } addListeners() { @@ -176,94 +175,7 @@ class PouchManager { /** Starts replication */ async replicateOnce() { - if (!(await this.isOnline())) { - logger.info( - 'PouchManager: The device is offline so the replication has been skipped' - ) - return Promise.resolve() - } - - logger.info('PouchManager: Starting replication iteration') - - /** - * Creating each replication - * - * @type {import('./types').CancelablePromises} - */ - this.replications = map(this.pouches, async (pouch, doctype) => { - logger.info('PouchManager: Starting replication for ' + doctype) - - const getReplicationURL = () => this.getReplicationURL(doctype) - - const initialReplication = !this.isSynced(doctype) - const replicationFilter = doc => { - return !startsWith(doc._id, '_design') - } - let seq = '' - if (initialReplication) { - // Before the first replication, get the last remote sequence, - // which will be used as a checkpoint for the next replication - const lastSeq = await fetchRemoteLastSequence(getReplicationURL()) - await this.storage.persistDoctypeLastSequence(doctype, lastSeq) - } else { - seq = await this.storage.getDoctypeLastSequence(doctype) - } - - const replicationOptions = get( - this.doctypesReplicationOptions, - doctype, - {} - ) - replicationOptions.initialReplication = initialReplication - replicationOptions.filter = replicationFilter - replicationOptions.since = seq - replicationOptions.doctype = doctype - - if (this.options.onDoctypeSyncStart) { - this.options.onDoctypeSyncStart(doctype) - } - const res = await startReplication( - pouch, - replicationOptions, - getReplicationURL, - this.storage - ) - if (seq) { - // We only need the sequence for the second replication, as PouchDB - // will use a local checkpoint for the next runs. - await this.storage.destroyDoctypeLastSequence(doctype) - } - - await this.updateSyncInfo(doctype) - this.checkToWarmupDoctype(doctype, replicationOptions) - if (this.options.onDoctypeSyncEnd) { - this.options.onDoctypeSyncEnd(doctype) - } - return res - }) - - // Waiting on each replication - const doctypes = Object.keys(this.pouches) - const promises = Object.values(this.replications) - try { - /** @type {import('./types').CancelablePromises} */ - const res = await Promise.all(promises) - - if (process.env.NODE_ENV !== 'production') { - logger.info('PouchManager: Replication ended') - } - - if (this.options.onSync) { - const doctypeUpdates = fromPairs(zip(doctypes, res)) - this.options.onSync(doctypeUpdates) - } - - res.cancel = this.cancelCurrentReplications - - return res - } catch (err) { - this.handleReplicationError(err) - } + return replicateOnce(this) } handleReplicationError(err) { diff --git a/packages/cozy-pouch-link/src/replicateOnce.js b/packages/cozy-pouch-link/src/replicateOnce.js new file mode 100644 index 0000000000..c766ace469 --- /dev/null +++ b/packages/cozy-pouch-link/src/replicateOnce.js @@ -0,0 +1,105 @@ +import fromPairs from 'lodash/fromPairs' +import get from 'lodash/get' +import map from 'lodash/map' +import startsWith from 'lodash/startsWith' +import zip from 'lodash/zip' + +import logger from './logger' +import { fetchRemoteLastSequence } from './remote' +import { startReplication } from './startReplication' + +/** + * Process replication once for given PouchManager + * + * @param {import('./PouchManager').default} pouchManager - PouchManager that handle the replication + * @returns {Promise} res + */ +export const replicateOnce = async pouchManager => { + if (!(await pouchManager.isOnline())) { + logger.info( + 'PouchManager: The device is offline so the replication has been skipped' + ) + return Promise.resolve() + } + + logger.info('PouchManager: Starting replication iteration') + + // Creating each replication + pouchManager.replications = map( + pouchManager.pouches, + async (pouch, doctype) => { + logger.info('PouchManager: Starting replication for ' + doctype) + + const getReplicationURL = () => pouchManager.getReplicationURL(doctype) + + const initialReplication = !pouchManager.isSynced(doctype) + const replicationFilter = doc => { + return !startsWith(doc._id, '_design') + } + let seq = '' + if (initialReplication) { + // Before the first replication, get the last remote sequence, + // which will be used as a checkpoint for the next replication + const lastSeq = await fetchRemoteLastSequence(getReplicationURL()) + await pouchManager.storage.persistDoctypeLastSequence(doctype, lastSeq) + } else { + seq = await pouchManager.storage.getDoctypeLastSequence(doctype) + } + + const replicationOptions = get( + pouchManager.doctypesReplicationOptions, + doctype, + {} + ) + replicationOptions.initialReplication = initialReplication + replicationOptions.filter = replicationFilter + replicationOptions.since = seq + replicationOptions.doctype = doctype + + if (pouchManager.options.onDoctypeSyncStart) { + pouchManager.options.onDoctypeSyncStart(doctype) + } + const res = await startReplication( + pouch, + replicationOptions, + getReplicationURL, + pouchManager.storage + ) + if (seq) { + // We only need the sequence for the second replication, as PouchDB + // will use a local checkpoint for the next runs. + await pouchManager.storage.destroyDoctypeLastSequence(doctype) + } + + await pouchManager.updateSyncInfo(doctype) + pouchManager.checkToWarmupDoctype(doctype, replicationOptions) + if (pouchManager.options.onDoctypeSyncEnd) { + pouchManager.options.onDoctypeSyncEnd(doctype) + } + return res + } + ) + + // Waiting on each replication + const doctypes = Object.keys(pouchManager.pouches) + const promises = Object.values(pouchManager.replications) + try { + const res = await Promise.all(promises) + + if (process.env.NODE_ENV !== 'production') { + logger.info('PouchManager: Replication ended') + } + + if (pouchManager.options.onSync) { + const doctypeUpdates = fromPairs(zip(doctypes, res)) + pouchManager.options.onSync(doctypeUpdates) + } + + // @ts-ignore + res.cancel = pouchManager.cancelCurrentReplications + + return res + } catch (err) { + pouchManager.handleReplicationError(err) + } +} From e849bdc8ee706e2c29a59765769034897f976196 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 16:59:42 +0200 Subject: [PATCH 31/84] feat: Do not fail replication when some databases do no exist It would be tempting to fail the replication when one of the databases does not exist as it is not an expected behavior But is some situation it is expected. For example this is the case for io.cozy.accounts that does not exist on the server side until a Konnector is installed and configured So we prefer to have failing attempts on each try instead of blocking the entire replication for that kind of error BREAKING CHANGE: cozy-pouch-link now uses AggregateError class. So the library should be use on platforms that handle this class (i.e. ReactNative 72+, Node 15+) More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError --- packages/cozy-pouch-link/src/PouchManager.js | 12 ++- packages/cozy-pouch-link/src/replicateOnce.js | 88 ++++++++++++++++++- packages/cozy-pouch-link/src/utils.js | 8 ++ 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 306608e086..9a5367a412 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -8,7 +8,7 @@ import Loop from './loop' import logger from './logger' import { platformWeb } from './platformWeb' import { replicateOnce } from './replicateOnce' -import { getDatabaseName } from './utils' +import { formatAggregatedError, getDatabaseName } from './utils' const DEFAULT_DELAY = 30 * 1000 @@ -179,7 +179,15 @@ class PouchManager { } handleReplicationError(err) { - logger.warn('PouchManager: Error during replication', err) + let aggregatedMessage = '' + // @ts-ignore + // eslint-disable-next-line no-undef + if (err instanceof AggregateError) { + aggregatedMessage = formatAggregatedError(err) + } + logger.warn( + `PouchManager: Error during replication - ${err.message}${aggregatedMessage}` + ) // On error, replication stops, it needs to be started // again manually by the owner of PouchManager this.stopReplicationLoop() diff --git a/packages/cozy-pouch-link/src/replicateOnce.js b/packages/cozy-pouch-link/src/replicateOnce.js index c766ace469..7f148b168d 100644 --- a/packages/cozy-pouch-link/src/replicateOnce.js +++ b/packages/cozy-pouch-link/src/replicateOnce.js @@ -5,7 +5,7 @@ import startsWith from 'lodash/startsWith' import zip from 'lodash/zip' import logger from './logger' -import { fetchRemoteLastSequence } from './remote' +import { fetchRemoteLastSequence, isDatabaseNotFoundError } from './remote' import { startReplication } from './startReplication' /** @@ -84,15 +84,54 @@ export const replicateOnce = async pouchManager => { const doctypes = Object.keys(pouchManager.pouches) const promises = Object.values(pouchManager.replications) try { - const res = await Promise.all(promises) + const res = await allSettled(promises) if (process.env.NODE_ENV !== 'production') { logger.info('PouchManager: Replication ended') } if (pouchManager.options.onSync) { - const doctypeUpdates = fromPairs(zip(doctypes, res)) - pouchManager.options.onSync(doctypeUpdates) + const zippedDoctypes = zip(doctypes, res) + const successZippedDoctypes = zippedDoctypes + .filter(d => d[1].status === 'fulfilled') + .map(d => { + return [d[0], d[1].value] + }) + const failedZippedDoctypes = zippedDoctypes + .filter(d => d[1].status === 'rejected') + .map(d => { + return [d[0], d[1].value] + }) + + const blockingErrors = res.filter( + r => r.status === 'rejected' && !isDatabaseNotFoundError(r.reason) + ) + + if (blockingErrors.length > 0) { + const errors = blockingErrors.map(err => err.reason) + const reasons = errors.join('\n') + logger.debug( + `ReplicateOnce's promises failed with the following errors`, + reasons + ) + // @ts-ignore + // eslint-disable-next-line no-undef + throw new AggregateError(errors, 'Failed with blocking errors') + } else { + logger.debug(`ReplicateOnce's promises succeed with no blocking errors`) + } + + const doctypeUpdated = fromPairs(successZippedDoctypes) + const doctypeFailed = fromPairs(failedZippedDoctypes) + logger.debug( + 'Doctypes replications in error: ', + Object.keys(doctypeFailed) + ) + logger.debug( + 'Doctypes replications in success: ', + Object.keys(doctypeUpdated) + ) + pouchManager.options.onSync(doctypeUpdated) } // @ts-ignore @@ -103,3 +142,44 @@ export const replicateOnce = async pouchManager => { pouchManager.handleReplicationError(err) } } + +/** + * @typedef {object} FulfilledPromise + * @property {'fulfilled'} status - The status of the promise + * @property {undefined} reason - The Error rejected by the promise (undefined when fulfilled) + * @property {any} value - The resolved value of the promise + */ + +/** + * @typedef {object} RejectedPromise + * @property {'rejected'} status - The status of the promise + * @property {Error} reason - The Error rejected by the promise + * @property {undefined} value - The resolved value of the promise (undefined when rejected) + */ + +/** + * Takes an iterable of promises as input and returns a single Promise. + * This returned promise fulfills when all of the input's promises settle (including + * when an empty iterable is passed), with an array of objects that describe the + * outcome of each promise. + * + * @param {Promise[]} promises - Promise to be awaited + * @returns {Promise<(FulfilledPromise|RejectedPromise)[]>} + */ +const allSettled = promises => { + return Promise.all( + promises.map(promise => + promise + .then(value => /** @type {FulfilledPromise} */ ({ + status: 'fulfilled', + value + })) + .catch(( + /** @type {Error} */ reason + ) => /** @type {RejectedPromise} */ ({ + status: 'rejected', + reason + })) + ) + ) +} diff --git a/packages/cozy-pouch-link/src/utils.js b/packages/cozy-pouch-link/src/utils.js index abf1ceb2b4..33d0de4ad8 100644 --- a/packages/cozy-pouch-link/src/utils.js +++ b/packages/cozy-pouch-link/src/utils.js @@ -19,3 +19,11 @@ export const getDatabaseName = (prefix, doctype) => { export const getPrefix = uri => { return uri.replace(/^https?:\/\//, '') } + +export const formatAggregatedError = aggregatedError => { + const strings = aggregatedError.errors.map((e, index) => { + return '\n[' + index + ']: ' + e.message + '\n' + e.stack + }) + + return strings.join('\n') +} From 3c823da5e5b38a26994081324b3754144fe11db6 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 16:58:52 +0200 Subject: [PATCH 32/84] feat: Do not fail replication when some databases are unreadable Some databases are protected by cozy-stack and so are not readable This may change in the future but for now we want the replication to continue for other doctypes when this is encountered This would allow the PouchManager to handle empty databases filled with virtual documents through the local replication process --- packages/cozy-pouch-link/src/remote.js | 8 ++++++ packages/cozy-pouch-link/src/remote.spec.js | 25 +++++++++++++++++++ packages/cozy-pouch-link/src/replicateOnce.js | 11 ++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/cozy-pouch-link/src/remote.js b/packages/cozy-pouch-link/src/remote.js index cd07532317..2bb52fd712 100644 --- a/packages/cozy-pouch-link/src/remote.js +++ b/packages/cozy-pouch-link/src/remote.js @@ -1,11 +1,15 @@ import AccessToken from './AccessToken' export const DATABASE_NOT_FOUND_ERROR = 'Database does not exist' +export const DATABASE_RESERVED_DOCTYPE_ERROR = 'Reserved doctype' export const isDatabaseNotFoundError = error => { return error.message === DATABASE_NOT_FOUND_ERROR } +export const isDatabaseUnradableError = error => { + return error.message === DATABASE_RESERVED_DOCTYPE_ERROR +} /** * Fetch remote instance @@ -37,6 +41,10 @@ export const fetchRemoteInstance = async (url, params = {}) => { throw new Error(DATABASE_NOT_FOUND_ERROR) } + if (resp.status === 403 && data.error.includes('message=reserved doctype')) { + throw new Error(DATABASE_RESERVED_DOCTYPE_ERROR) + } + throw new Error( `Error (${resp.status}) while fetching remote instance: ${JSON.stringify( data diff --git a/packages/cozy-pouch-link/src/remote.spec.js b/packages/cozy-pouch-link/src/remote.spec.js index a4aed8aaf2..bb59000e80 100644 --- a/packages/cozy-pouch-link/src/remote.spec.js +++ b/packages/cozy-pouch-link/src/remote.spec.js @@ -2,6 +2,7 @@ import { enableFetchMocks, disableFetchMocks } from 'jest-fetch-mock' import { DATABASE_NOT_FOUND_ERROR, + DATABASE_RESERVED_DOCTYPE_ERROR, fetchRemoteInstance, fetchRemoteLastSequence } from './remote' @@ -141,6 +142,17 @@ describe('remote', () => { ) }) + it('Shoud throw dedicated error when Reserved Doctype error', async () => { + const remoteUrl = + 'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts' + mockDatabaseReservedDoctypeOn( + 'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true' + ) + + await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow( + DATABASE_RESERVED_DOCTYPE_ERROR + ) + }) }) }) @@ -153,6 +165,19 @@ const mockDatabaseNotFoundOn = url => { }) } +const mockDatabaseReservedDoctypeOn = url => { + fetch.mockOnceIf( + url, + JSON.stringify({ + error: 'code=403, message=reserved doctype io.cozy.sharings unreadable' + }), + { + ok: false, + status: 403 + } + ) +} + const mockUnknownErrorOn = url => { fetch.mockOnceIf( url, diff --git a/packages/cozy-pouch-link/src/replicateOnce.js b/packages/cozy-pouch-link/src/replicateOnce.js index 7f148b168d..617071b56a 100644 --- a/packages/cozy-pouch-link/src/replicateOnce.js +++ b/packages/cozy-pouch-link/src/replicateOnce.js @@ -5,7 +5,11 @@ import startsWith from 'lodash/startsWith' import zip from 'lodash/zip' import logger from './logger' -import { fetchRemoteLastSequence, isDatabaseNotFoundError } from './remote' +import { + fetchRemoteLastSequence, + isDatabaseNotFoundError, + isDatabaseUnradableError +} from './remote' import { startReplication } from './startReplication' /** @@ -104,7 +108,10 @@ export const replicateOnce = async pouchManager => { }) const blockingErrors = res.filter( - r => r.status === 'rejected' && !isDatabaseNotFoundError(r.reason) + r => + r.status === 'rejected' && + !isDatabaseNotFoundError(r.reason) && + !isDatabaseUnradableError(r.reason) ) if (blockingErrors.length > 0) { From c2dffe201e8f3533cc32d95627484d95d183b896 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 10:47:57 +0200 Subject: [PATCH 33/84] feat: Discriminate not_synced doctypes from ones with non-blocking error With current implementation, the CozyPouchLink would not process a query for a doctype that did not succeed to synchronize and it would forward the query to the next link This is problematic for 2 reasons First because we now expect the CozyPouchLink to be the last link of the chain, so forwarding the query would result to an exception thrown Second because we previously added a mechanism that allows some pouch to safely fail. This happens when the remote Couch does not contain the doctype yet (i.e. `io.cozy.accounts` exist only after configuring the first konnector). In that scenario we don't want the query to throw but to return an empty list To improve this, we want to forward the query only when the synchronization has never been started or when it unexpectedly failed We now have 3 synchronization status: - `not_synced` when synchronization never started or after an unexpected error - `not_complete` when synchronization occurred but failed with a non-blocking error - `synced` when synchronization succeeded --- packages/cozy-pouch-link/src/CozyPouchLink.js | 6 ++-- .../cozy-pouch-link/src/CozyPouchLink.spec.js | 28 +++++++++---------- packages/cozy-pouch-link/src/PouchManager.js | 27 +++++++++++++++--- .../cozy-pouch-link/src/PouchManager.spec.js | 20 +++++++++---- packages/cozy-pouch-link/src/replicateOnce.js | 15 ++++++++-- packages/cozy-pouch-link/src/types.js | 10 +++++-- 6 files changed, 75 insertions(+), 31 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 7637b25a43..816e916246 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -61,7 +61,7 @@ const normalizeAll = (docs, doctype) => { /** * @typedef {import('cozy-client/src/types').CozyClientDocument} CozyClientDocument * - * @typedef {"idle"|"replicating"} SyncStatus + * @typedef {"idle"|"replicating"} ReplicationStatus */ /** @@ -97,7 +97,7 @@ class PouchLink extends CozyLink { options.platform?.storage || platformWeb.storage ) - /** @type {Record} - Stores replication states per doctype */ + /** @type {Record} - Stores replication states per doctype */ this.replicationStatus = this.replicationStatus || {} } @@ -352,7 +352,7 @@ class PouchLink extends CozyLink { return forward(operation) } - if (!this.pouches.isSynced(doctype)) { + if (this.pouches.getSyncStatus(doctype) === 'not_synced') { if (process.env.NODE_ENV !== 'production') { logger.info( `Tried to access local ${doctype} but Cozy Pouch is not synced yet. Forwarding the operation to next link` diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index 61489944c7..48e78804eb 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -120,7 +120,7 @@ describe('CozyPouchLink', () => { 'io.cozy.files': { warmupQueries: [query1(), query2()] } } }) - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const query = Q(TODO_DOCTYPE) expect.assertions(0) @@ -174,7 +174,7 @@ describe('CozyPouchLink', () => { 'io.cozy.todos': { strategy: 'fromRemote' } } }) - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') await link.request( { doctype: TODO_DOCTYPE, @@ -194,7 +194,7 @@ describe('CozyPouchLink', () => { 'io.cozy.todos': { strategy: 'fromRemote' } } }) - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const mock = jest.fn() await link.request(Q(TODO_DOCTYPE), null, mock) expect(mock).not.toHaveBeenCalled() @@ -210,7 +210,7 @@ describe('CozyPouchLink', () => { const docs = [TODO_1, TODO_2, TODO_3, TODO_4] it('should be able to execute a query', async () => { await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) db.post({ label: 'Make PouchDB link work', @@ -223,7 +223,7 @@ describe('CozyPouchLink', () => { it('should be possible to query only one doc', async () => { await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) db.post({ _id: 'deadbeef', @@ -238,7 +238,7 @@ describe('CozyPouchLink', () => { it('should be possible to explicitly index fields', async () => { find.mockReturnValue({ docs: [TODO_3, TODO_4] }) await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) await db.bulkDocs(docs.map(x => omit(x, '_type'))) const query = Q(TODO_DOCTYPE) @@ -253,7 +253,7 @@ describe('CozyPouchLink', () => { it('should be possible to query multiple docs', async () => { withoutDesignDocuments.mockReturnValue({ docs: [TODO_1, TODO_3] }) await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) await db.bulkDocs(docs.map(x => omit(x, '_type'))) const ids = [TODO_1._id, TODO_3._id] @@ -268,7 +268,7 @@ describe('CozyPouchLink', () => { it('should be possible to select', async () => { find.mockReturnValue({ docs: [TODO_3, TODO_4] }) await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) await db.bulkDocs(docs.map(x => omit(x, '_type'))) const query = Q(TODO_DOCTYPE) @@ -321,7 +321,7 @@ describe('CozyPouchLink', () => { it("should add _id in the selected fields since CozyClient' store needs it", async () => { find.mockReturnValue({ docs: [TODO_3, TODO_4] }) await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const db = link.getPouch(TODO_DOCTYPE) await db.bulkDocs(docs.map(x => omit(x, '_type'))) const query = Q(TODO_DOCTYPE) @@ -342,7 +342,7 @@ describe('CozyPouchLink', () => { describe('mutations', () => { it('should be possible to save a new document', async () => { await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const { _id, ...NEW_TODO } = TODO_3 const mutation = client.getDocumentSavePlan(NEW_TODO) const res = await link.request(mutation) @@ -360,7 +360,7 @@ describe('CozyPouchLink', () => { it('should be possible to save multiple documents', async () => { await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const { _id, ...NEW_TODO } = TODO_3 const res = await client.saveAll([TODO_3, TODO_4, NEW_TODO]) expect(link.executeMutation).toHaveBeenCalled() @@ -394,7 +394,7 @@ describe('CozyPouchLink', () => { { ok: true, id: '3', rev: '1-cffeebabe' } ] } - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const { _id, ...NEW_TODO } = TODO_3 let err try { @@ -412,7 +412,7 @@ describe('CozyPouchLink', () => { it('should be possible to update a document', async () => { await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const { _id, ...NEW_TODO } = TODO_3 const saveMutation = client.getDocumentSavePlan(NEW_TODO) const saved = (await link.request(saveMutation)).data @@ -584,7 +584,7 @@ describe('CozyPouchLink', () => { it('uses the default index, the one from the sort', async () => { spy = jest.spyOn(PouchDB.prototype, 'createIndex') await setup() - link.pouches.isSynced = jest.fn().mockReturnValue(true) + link.pouches.getSyncStatus = jest.fn().mockReturnValue('synced') const query = Q(TODO_DOCTYPE) .where({}) .sortBy([{ name: 'asc' }]) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 9a5367a412..fbba66d78c 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -54,6 +54,7 @@ class PouchManager { ) ]) ) + /** @type {Record} - Stores synchronization info per doctype */ this.syncedDoctypes = await this.storage.getPersistedSyncedDoctypes() this.warmedUpQueries = await this.storage.getPersistedWarmedUpQueries() this.getReplicationURL = this.options.getReplicationURL @@ -217,18 +218,36 @@ class PouchManager { return this.pouches[doctype] } - async updateSyncInfo(doctype) { - this.syncedDoctypes[doctype] = { date: new Date().toISOString() } + /** + * Update the Sync info for the specifed doctype + * + * @param {string} doctype - The doctype to update + * @param {import('./types').SyncStatus} status - The new Sync status for the doctype + */ + async updateSyncInfo(doctype, status = 'synced') { + this.syncedDoctypes[doctype] = { date: new Date().toISOString(), status } await this.storage.persistSyncedDoctypes(this.syncedDoctypes) } + /** + * Get the Sync info for the specified doctype + * + * @param {string} doctype - The doctype to check + * @returns {import('./types').SyncInfo} + */ getSyncInfo(doctype) { return this.syncedDoctypes && this.syncedDoctypes[doctype] } - isSynced(doctype) { + /** + * Get the Sync status for the specified doctype + * + * @param {string} doctype - The doctype to check + * @returns {import('./types').SyncStatus} + */ + getSyncStatus(doctype) { const info = this.getSyncInfo(doctype) - return info ? !!info.date : false + return info?.status || 'not_synced' } async clearSyncedDoctypes() { diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index ef437f3123..4147c0341f 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -320,13 +320,16 @@ describe('PouchManager', () => { await manager.updateSyncInfo('io.cozy.todos') expect(localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY]).toEqual( JSON.stringify({ - 'io.cozy.todos': { date: '2021-08-01T00:00:00.000Z' } + 'io.cozy.todos': { + date: '2021-08-01T00:00:00.000Z', + status: 'synced' + } }) ) }) }) - describe('isSynced', () => { + describe('getSyncStatus', () => { let manager beforeEach(async () => { @@ -334,13 +337,18 @@ describe('PouchManager', () => { await manager.init() }) - it('should return true if the doctype is synced', async () => { + it(`should return 'synced' if the doctype is synced`, async () => { await manager.updateSyncInfo('io.cozy.todos') - expect(manager.isSynced('io.cozy.todos')).toBe(true) + expect(manager.getSyncStatus('io.cozy.todos')).toBe('synced') + }) + + it(`should return 'not_synced' if the doctype is not synced`, () => { + expect(manager.getSyncStatus('io.cozy.todos')).toBe('not_synced') }) - it('should return false if the doctype is not synced', () => { - expect(manager.isSynced('io.cozy.todos')).toBe(false) + it('should return status if updateSyncInfo was called with custom status', async () => { + await manager.updateSyncInfo('io.cozy.todos', 'not_complete') + expect(manager.getSyncStatus('io.cozy.todos')).toBe('not_complete') }) }) diff --git a/packages/cozy-pouch-link/src/replicateOnce.js b/packages/cozy-pouch-link/src/replicateOnce.js index 617071b56a..ab85037d32 100644 --- a/packages/cozy-pouch-link/src/replicateOnce.js +++ b/packages/cozy-pouch-link/src/replicateOnce.js @@ -36,7 +36,8 @@ export const replicateOnce = async pouchManager => { const getReplicationURL = () => pouchManager.getReplicationURL(doctype) - const initialReplication = !pouchManager.isSynced(doctype) + const initialReplication = + pouchManager.getSyncStatus(doctype) !== 'synced' const replicationFilter = doc => { return !startsWith(doc._id, '_design') } @@ -104,7 +105,7 @@ export const replicateOnce = async pouchManager => { const failedZippedDoctypes = zippedDoctypes .filter(d => d[1].status === 'rejected') .map(d => { - return [d[0], d[1].value] + return [d[0], d[1].reason] }) const blockingErrors = res.filter( @@ -114,6 +115,16 @@ export const replicateOnce = async pouchManager => { !isDatabaseUnradableError(r.reason) ) + const unblockingErrors = failedZippedDoctypes.filter( + r => isDatabaseNotFoundError(r[1]) || isDatabaseUnradableError(r[1]) + ) + + for (const unblockingError of unblockingErrors) { + const doctype = unblockingError[0] + // @ts-ignore + await pouchManager.updateSyncInfo(doctype, 'not_complete') + } + if (blockingErrors.length > 0) { const errors = blockingErrors.map(err => err.reason) const reasons = errors.join('\n') diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index 87f8d17963..91318c2863 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -11,8 +11,14 @@ * @typedef {CancelablePromise[] & Cancelable} CancelablePromises */ -/** @typedef {object} SyncInfo - * @property {string} Date +/** + * @typedef {"synced"|"not_synced"|"not_complete"} SyncStatus + */ + +/** + * @typedef {object} SyncInfo + * @property {string} date - The date of the last synchronization + * @property {SyncStatus} status - The current synchronization status */ /** From 6a1a3c18f5100e9f8cbbb6c72df508d9c86672e9 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 12 Jul 2024 17:21:28 +0200 Subject: [PATCH 34/84] fix: Call the correct storage method in `getLastReplicatedDocID` In a808320d962ab5a69bb67adbba2bf587e110aad9 we called `getAllLastSequences` instead of `getAllLastReplicatedDocID` inside of `getLastReplicatedDocID`. This is a typo and so this commit fixes it Related commit: a808320d962ab5a69bb67adbba2bf587e110aad9 --- packages/cozy-pouch-link/src/localStorage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index 923f45553c..3fa799be08 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -47,7 +47,7 @@ export class PouchLocalStorage { * @returns {Promise} The last replicated docid */ async getLastReplicatedDocID(doctype) { - const docids = await this.getAllLastSequences() + const docids = await this.getAllLastReplicatedDocID() return docids[doctype] } From 44e50367f495bcd3952cc306361f0fbc9b1d9e78 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 15:23:50 +0200 Subject: [PATCH 35/84] docs: Update types and documentation --- docs/api/cozy-pouch-link/classes/PouchLink.md | 2 +- .../cozy-pouch-link/types/CozyPouchLink.d.ts | 10 ++--- .../cozy-pouch-link/types/PouchManager.d.ts | 37 +++++++++++++------ packages/cozy-pouch-link/types/remote.d.ts | 4 ++ .../cozy-pouch-link/types/replicateOnce.d.ts | 29 +++++++++++++++ packages/cozy-pouch-link/types/types.d.ts | 10 ++++- packages/cozy-pouch-link/types/utils.d.ts | 1 + 7 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 packages/cozy-pouch-link/types/replicateOnce.d.ts diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index 2a559b56e1..48d8512c61 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -102,7 +102,7 @@ CozyLink.constructor ### replicationStatus -• **replicationStatus**: `Record`<`string`, `SyncStatus`> +• **replicationStatus**: `Record`<`string`, `ReplicationStatus`> *Defined in* diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index 2023a26533..92f829f75e 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -2,11 +2,11 @@ export function getReplicationURL(uri: any, token: any, doctype: any): string; export function isExpiredTokenError(pouchError: any): boolean; export default PouchLink; export type CozyClientDocument = any; -export type SyncStatus = "idle" | "replicating"; +export type ReplicationStatus = "idle" | "replicating"; /** * @typedef {import('cozy-client/src/types').CozyClientDocument} CozyClientDocument * - * @typedef {"idle"|"replicating"} SyncStatus + * @typedef {"idle"|"replicating"} ReplicationStatus */ /** * Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates @@ -49,8 +49,8 @@ declare class PouchLink extends CozyLink { doctypesReplicationOptions: any[]; indexes: {}; storage: PouchLocalStorage; - /** @type {Record} - Stores replication states per doctype */ - replicationStatus: Record; + /** @type {Record} - Stores replication states per doctype */ + replicationStatus: Record; getReplicationURL(doctype: any): string; registerClient(client: any): Promise; client: any; @@ -117,7 +117,7 @@ declare class PouchLink extends CozyLink { */ public stopReplication(): void; onSyncError(error: any): Promise; - getSyncInfo(doctype: any): any; + getSyncInfo(doctype: any): import("./types").SyncInfo; getPouch(doctype: any): any; supportsOperation(operation: any): boolean; /** diff --git a/packages/cozy-pouch-link/types/PouchManager.d.ts b/packages/cozy-pouch-link/types/PouchManager.d.ts index cbb07a7373..f654355a7c 100644 --- a/packages/cozy-pouch-link/types/PouchManager.d.ts +++ b/packages/cozy-pouch-link/types/PouchManager.d.ts @@ -15,7 +15,8 @@ declare class PouchManager { events: any; init(): Promise; pouches: import("lodash").Dictionary; - syncedDoctypes: any; + /** @type {Record} - Stores synchronization info per doctype */ + syncedDoctypes: Record; warmedUpQueries: any; getReplicationURL: any; doctypesReplicationOptions: any; @@ -30,8 +31,10 @@ declare class PouchManager { /** Stop periodic syncing of the pouches */ stopReplicationLoop(): void; /** Starts replication */ - replicateOnce(): Promise; + replicateOnce(): Promise; executeQuery: any; + /** @type {import('./types').CancelablePromise[]} - Stores replication promises */ + replications: import('./types').CancelablePromise[]; addListeners(): void; removeListeners(): void; destroy(): Promise; @@ -48,19 +51,31 @@ declare class PouchManager { * immediately */ syncImmediately(): void; - /** - * Creating each replication - * - * @type {import('./types').CancelablePromises} - */ - replications: import('./types').CancelablePromises; handleReplicationError(err: any): void; cancelCurrentReplications(): void; waitForCurrentReplications(): Promise | Promise; getPouch(doctype: any): any; - updateSyncInfo(doctype: any): Promise; - getSyncInfo(doctype: any): any; - isSynced(doctype: any): boolean; + /** + * Update the Sync info for the specifed doctype + * + * @param {string} doctype - The doctype to update + * @param {import('./types').SyncStatus} status - The new Sync status for the doctype + */ + updateSyncInfo(doctype: string, status?: import('./types').SyncStatus): Promise; + /** + * Get the Sync info for the specified doctype + * + * @param {string} doctype - The doctype to check + * @returns {import('./types').SyncInfo} + */ + getSyncInfo(doctype: string): import('./types').SyncInfo; + /** + * Get the Sync status for the specified doctype + * + * @param {string} doctype - The doctype to check + * @returns {import('./types').SyncStatus} + */ + getSyncStatus(doctype: string): import('./types').SyncStatus; clearSyncedDoctypes(): Promise; warmupQueries(doctype: any, queries: any): Promise; checkToWarmupDoctype(doctype: any, replicationOptions: any): void; diff --git a/packages/cozy-pouch-link/types/remote.d.ts b/packages/cozy-pouch-link/types/remote.d.ts index 6fa21c070c..864c5dcf98 100644 --- a/packages/cozy-pouch-link/types/remote.d.ts +++ b/packages/cozy-pouch-link/types/remote.d.ts @@ -1,2 +1,6 @@ +export const DATABASE_NOT_FOUND_ERROR: "Database does not exist"; +export const DATABASE_RESERVED_DOCTYPE_ERROR: "Reserved doctype"; +export function isDatabaseNotFoundError(error: any): boolean; +export function isDatabaseUnradableError(error: any): boolean; export function fetchRemoteInstance(url: URL, params?: object): Promise; export function fetchRemoteLastSequence(baseUrl: string): Promise; diff --git a/packages/cozy-pouch-link/types/replicateOnce.d.ts b/packages/cozy-pouch-link/types/replicateOnce.d.ts new file mode 100644 index 0000000000..9da41aaac2 --- /dev/null +++ b/packages/cozy-pouch-link/types/replicateOnce.d.ts @@ -0,0 +1,29 @@ +export function replicateOnce(pouchManager: import('./PouchManager').default): Promise; +export type FulfilledPromise = { + /** + * - The status of the promise + */ + status: 'fulfilled'; + /** + * - The Error rejected by the promise (undefined when fulfilled) + */ + reason: undefined; + /** + * - The resolved value of the promise + */ + value: any; +}; +export type RejectedPromise = { + /** + * - The status of the promise + */ + status: 'rejected'; + /** + * - The Error rejected by the promise + */ + reason: Error; + /** + * - The resolved value of the promise (undefined when rejected) + */ + value: undefined; +}; diff --git a/packages/cozy-pouch-link/types/types.d.ts b/packages/cozy-pouch-link/types/types.d.ts index 2cb24b393c..99e46385e6 100644 --- a/packages/cozy-pouch-link/types/types.d.ts +++ b/packages/cozy-pouch-link/types/types.d.ts @@ -8,8 +8,16 @@ export type Cancelable = { }; export type CancelablePromise = Promise & Cancelable; export type CancelablePromises = CancelablePromise[] & Cancelable; +export type SyncStatus = "synced" | "not_synced" | "not_complete"; export type SyncInfo = { - Date: string; + /** + * - The date of the last synchronization + */ + date: string; + /** + * - The current synchronization status + */ + status: SyncStatus; }; export type LocalStorage = { getItem: (arg0: string) => Promise; diff --git a/packages/cozy-pouch-link/types/utils.d.ts b/packages/cozy-pouch-link/types/utils.d.ts index 46f1350cc4..de3dca2514 100644 --- a/packages/cozy-pouch-link/types/utils.d.ts +++ b/packages/cozy-pouch-link/types/utils.d.ts @@ -1,2 +1,3 @@ export function getDatabaseName(prefix: string, doctype: string): string; export function getPrefix(uri: string): string; +export function formatAggregatedError(aggregatedError: any): any; From 8c8401c49b4fd11a1a544198b716ccda2c7a8ba3 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 16:19:29 +0200 Subject: [PATCH 36/84] feat: Upgrade cozy-intent to `2.23.0` `cozy-intent` has been upgraded to `2.23.0` to retrieve `flagshipLinkRequest` new intent Related PR: cozy/cozy-libs#2562 BREAKING CHANGE: cozy-client now requires cozy-intent >= 2.23.0 (used for offline support) --- packages/cozy-client/package.json | 4 ++-- yarn.lock | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/cozy-client/package.json b/packages/cozy-client/package.json index df85b06c96..e1a65437ba 100644 --- a/packages/cozy-client/package.json +++ b/packages/cozy-client/package.json @@ -50,7 +50,7 @@ "btoa": "1.2.1", "cozy-device-helper": "2.7.0", "cozy-flags": "2.10.2", - "cozy-intent": "1.17.3", + "cozy-intent": "2.23.0", "cozy-logger": "1.7.0", "cozy-ui": "93.1.1", "jsdoc-plugin-intersection": "1.0.4", @@ -65,7 +65,7 @@ "peerDependencies": { "cozy-device-helper": ">=2.1.0", "cozy-flags": ">2.8.6", - "cozy-intent": ">=1.3.0", + "cozy-intent": ">=2.23.0", "cozy-logger": ">1.7.0", "cozy-ui": ">=93.1.1", "react": "^16.7.0", diff --git a/yarn.lock b/yarn.lock index a7e438dc6b..26330ff4c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5037,11 +5037,12 @@ cozy-flags@2.10.2: dependencies: microee "^0.0.6" -cozy-intent@1.17.3: - version "1.17.3" - resolved "https://registry.yarnpkg.com/cozy-intent/-/cozy-intent-1.17.3.tgz#dcf8085a9c561ce56ab0c7afc69474243e4f9e9c" - integrity sha512-Qko/tUJlXWh5wYLfw+CknbIm+KeAW4F3lAk/n1CA+uKwcseua+LCoNIypC/04ttm9g6ntbEogb/u4h6d5+H6lg== +cozy-intent@2.23.0: + version "2.23.0" + resolved "https://registry.yarnpkg.com/cozy-intent/-/cozy-intent-2.23.0.tgz#b6f3a407413df05c108e848b9dcb074b8780824b" + integrity sha512-DFn0ny4B4HpOE+3PYuZTTa074gRnFHqID+XaJ3gY2OrPL2xUQKEZmmFLp2bPVWThi5FvgvsU3EQeWPHZNQPbaQ== dependencies: + cozy-minilog "^3.3.1" post-me "0.4.5" cozy-interapp@^0.5.4: @@ -5057,6 +5058,13 @@ cozy-logger@1.7.0: chalk "^2.4.2" json-stringify-safe "5.0.1" +cozy-minilog@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/cozy-minilog/-/cozy-minilog-3.3.1.tgz#472dccf4a9391c479120a83d26b435cf9d609c72" + integrity sha512-NLQNQ1Q/bvJrqNv9w5bLjfAxYKv+pESobJgUKXondxP616kx7k0mpiRrCZBaJRbEbpKryT/eJ0JJwLdVaIP5NA== + dependencies: + microee "0.0.6" + cozy-ui@93.1.1: version "93.1.1" resolved "https://registry.yarnpkg.com/cozy-ui/-/cozy-ui-93.1.1.tgz#a09512a53a55a0b8ecab21e0572bbd12678ad128" From f0d4fd7338b8c7c2653f461db2cfe8da36fc7130 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 18 Jun 2024 17:18:19 +0200 Subject: [PATCH 37/84] feat: Implement FlagshipLink When hosted in the Flagship app, we want the cozy-apps to be able to forward their queries to the native code This will allow the native code to have control on the data lifecycle and chose between the cozy-stack or a local Pouch database as the source for executing the query For now we use cozy-intent in order to communicate through post messages as it is the fastest implementation possible but this may change in the future if we encounter performances issues (i.e. using a local HTTP server) --- packages/cozy-client/src/FlagshipLink.js | 38 ++++++++++++++++++++++++ packages/cozy-client/src/index.js | 1 + 2 files changed, 39 insertions(+) create mode 100644 packages/cozy-client/src/FlagshipLink.js diff --git a/packages/cozy-client/src/FlagshipLink.js b/packages/cozy-client/src/FlagshipLink.js new file mode 100644 index 0000000000..761446ecad --- /dev/null +++ b/packages/cozy-client/src/FlagshipLink.js @@ -0,0 +1,38 @@ +import CozyLink from './CozyLink' +import logger from './logger' + +export default class FlagshipLink extends CozyLink { + /** + * @param {object} [options] - Options + * @param {object} [options.stackClient] - A StackClient + * @param {object} [options.client] - A StackClient (deprecated) + * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference + */ + constructor({ client, stackClient, webviewIntent } = {}) { + super() + if (client) { + logger.warn( + 'Using options.client is deprecated, prefer options.stackClient' + ) + } + this.stackClient = stackClient || client + this.webviewIntent = webviewIntent + } + + registerClient(client) { + this.stackClient = client.stackClient || client.client + } + + reset() { + this.stackClient = null + } + + async request(operation, result, forward) { + return this.webviewIntent.call('flagshipLinkRequest', operation) + } + + async persistData(data, forward) { + // Persist data should do nothing here as data is already persisted on Flagship side + return + } +} diff --git a/packages/cozy-client/src/index.js b/packages/cozy-client/src/index.js index 741324297e..614ca19f00 100644 --- a/packages/cozy-client/src/index.js +++ b/packages/cozy-client/src/index.js @@ -1,6 +1,7 @@ export { default } from './CozyClient' export { default as CozyLink } from './CozyLink' export { default as StackLink } from './StackLink' +export { default as FlagshipLink } from './FlagshipLink' export { default as compose } from 'lodash/flow' export { QueryDefinition, From 26ee739e7e259f884315ce887dcd09af347bf0cd Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 16:19:59 +0200 Subject: [PATCH 38/84] docs: Update types and documentation --- docs/api/cozy-client/README.md | 1 + docs/api/cozy-client/classes/CozyLink.md | 2 + docs/api/cozy-client/classes/FlagshipLink.md | 137 +++++++++++++++++++ packages/cozy-client/types/FlagshipLink.d.ts | 18 +++ packages/cozy-client/types/index.d.ts | 1 + 5 files changed, 159 insertions(+) create mode 100644 docs/api/cozy-client/classes/FlagshipLink.md create mode 100644 packages/cozy-client/types/FlagshipLink.d.ts diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index a3e0bcbd7b..bb806e46ce 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -16,6 +16,7 @@ cozy-client * [CozyClient](classes/CozyClient.md) * [CozyLink](classes/CozyLink.md) * [CozyProvider](classes/CozyProvider.md) +* [FlagshipLink](classes/FlagshipLink.md) * [HasMany](classes/HasMany.md) * [HasManyInPlace](classes/HasManyInPlace.md) * [HasManyTriggers](classes/HasManyTriggers.md) diff --git a/docs/api/cozy-client/classes/CozyLink.md b/docs/api/cozy-client/classes/CozyLink.md index 911ff17705..ce4ace3801 100644 --- a/docs/api/cozy-client/classes/CozyLink.md +++ b/docs/api/cozy-client/classes/CozyLink.md @@ -8,6 +8,8 @@ ↳ [`StackLink`](StackLink.md) + ↳ [`FlagshipLink`](FlagshipLink.md) + ## Constructors ### constructor diff --git a/docs/api/cozy-client/classes/FlagshipLink.md b/docs/api/cozy-client/classes/FlagshipLink.md new file mode 100644 index 0000000000..f45a9368f7 --- /dev/null +++ b/docs/api/cozy-client/classes/FlagshipLink.md @@ -0,0 +1,137 @@ +[cozy-client](../README.md) / FlagshipLink + +# Class: FlagshipLink + +## Hierarchy + +* [`CozyLink`](CozyLink.md) + + ↳ **`FlagshipLink`** + +## Constructors + +### constructor + +• **new FlagshipLink**(`[options]?`) + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `[options]` | `Object` | Options | +| `[options].client` | `any` | - | +| `[options].stackClient` | `any` | - | +| `[options].webviewIntent` | `WebviewService` | - | + +*Overrides* + +[CozyLink](CozyLink.md).[constructor](CozyLink.md#constructor) + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:11](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L11) + +## Properties + +### stackClient + +• **stackClient**: `any` + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:18](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L18) + +*** + +### webviewIntent + +• **webviewIntent**: `WebviewService` + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:19](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L19) + +## Methods + +### persistData + +▸ **persistData**(`data`, `forward`): `Promise`<`void`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `data` | `any` | +| `forward` | `any` | + +*Returns* + +`Promise`<`void`> + +*Overrides* + +[CozyLink](CozyLink.md).[persistData](CozyLink.md#persistdata) + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:34](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L34) + +*** + +### registerClient + +▸ **registerClient**(`client`): `void` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `client` | `any` | + +*Returns* + +`void` + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:22](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L22) + +*** + +### request + +▸ **request**(`operation`, `result`, `forward`): `Promise`<`boolean`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `operation` | `any` | +| `result` | `any` | +| `forward` | `any` | + +*Returns* + +`Promise`<`boolean`> + +*Overrides* + +[CozyLink](CozyLink.md).[request](CozyLink.md#request) + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:30](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L30) + +*** + +### reset + +▸ **reset**(): `void` + +*Returns* + +`void` + +*Defined in* + +[packages/cozy-client/src/FlagshipLink.js:26](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L26) diff --git a/packages/cozy-client/types/FlagshipLink.d.ts b/packages/cozy-client/types/FlagshipLink.d.ts new file mode 100644 index 0000000000..4dfbc56053 --- /dev/null +++ b/packages/cozy-client/types/FlagshipLink.d.ts @@ -0,0 +1,18 @@ +export default class FlagshipLink extends CozyLink { + /** + * @param {object} [options] - Options + * @param {object} [options.stackClient] - A StackClient + * @param {object} [options.client] - A StackClient (deprecated) + * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference + */ + constructor({ client, stackClient, webviewIntent }?: { + stackClient: object; + client: object; + webviewIntent: import('cozy-intent').WebviewService; + }); + stackClient: any; + webviewIntent: import("cozy-intent").WebviewService; + registerClient(client: any): void; + reset(): void; +} +import CozyLink from "./CozyLink"; diff --git a/packages/cozy-client/types/index.d.ts b/packages/cozy-client/types/index.d.ts index 3d88dd768d..198ff8e3ff 100644 --- a/packages/cozy-client/types/index.d.ts +++ b/packages/cozy-client/types/index.d.ts @@ -1,6 +1,7 @@ export { default } from "./CozyClient"; export { default as CozyLink } from "./CozyLink"; export { default as StackLink } from "./StackLink"; +export { default as FlagshipLink } from "./FlagshipLink"; export { default as compose } from "lodash/flow"; export { default as Registry } from "./registry"; export { default as RealTimeQueries } from "./RealTimeQueries"; From 3a196faf48fef36011f00cfa1fca29aca4192ee5 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 18 Jun 2024 17:27:46 +0200 Subject: [PATCH 39/84] 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 b7405c69af35c5c5794b4f6b8a441d07e2ed2ee3 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 19 Jun 2024 15:16:32 +0200 Subject: [PATCH 40/84] 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 645b05e4bd..ac1e2ac889 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 060541092ad5992577f2cf72fbfeba2e4656ab45 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 15 Jul 2024 16:14:27 +0200 Subject: [PATCH 41/84] 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 7edb9cfb27ab8127080c0774612e54f68dd062f9 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:27:16 +0200 Subject: [PATCH 42/84] 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 cc4fdb9641725eb50b950e7e067777fc5874129e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:29:03 +0200 Subject: [PATCH 43/84] 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 10aae41d509cd414453600035d002fa101592183 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 22 Jul 2024 19:36:36 +0200 Subject: [PATCH 44/84] 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 ccba8cf462759d5b245d1f2bf131ae7cf94bec25 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 18 Jun 2024 17:29:22 +0200 Subject: [PATCH 45/84] 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 fdac5b82aee7429e6b8233afdef36844afb58927 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 15 Jul 2024 16:00:22 +0200 Subject: [PATCH 46/84] 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 b54381239cd881d4f1e256bbfd1c8ab3d8b30a47 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 10:54:33 +0200 Subject: [PATCH 47/84] 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 5bc807c8f2cb3c4747f7c1df37c3d7d51570f7cf Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:39:13 +0200 Subject: [PATCH 48/84] 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

makeKeyFromPartialFilterstring

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

getIndexNameFromFieldsstring

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

transformSortMangoSort

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'; +}; From 1eae0c4ec858fa868610dbc671a9f4a7143fbd2d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:20:53 +0200 Subject: [PATCH 49/84] docs: Extract types for StackLink and CozyPouchLink Previous declaration would generate non-optional attributes for options in `.d.ts` files --- packages/cozy-client/src/StackLink.js | 12 ++++++++---- packages/cozy-pouch-link/src/CozyPouchLink.js | 14 +++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index 038511a66e..95ea4e9d10 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -51,15 +51,19 @@ export const transformBulkDocsResponse = (bulkResponse, originalDocuments) => { } } +/** + * @typedef {object} StackLinkOptions + * @property {object} [stackClient] - A StackClient + * @property {object} [client] - A StackClient (deprecated) + * @property {import('cozy-pouch-link/dist/types').LinkPlatform} [platform] - Platform specific adapters and methods + */ + /** * Transfers queries and mutations to a remote stack */ export default class StackLink extends CozyLink { /** - * @param {object} [options] - Options - * @param {object} [options.stackClient] - A StackClient - * @param {object} [options.client] - A StackClient (deprecated) - * @param {import('cozy-pouch-link/dist/types').LinkPlatform} [options.platform] Platform specific adapters and methods + * @param {StackLinkOptions} [options] - Options */ constructor({ client, stackClient, platform } = {}) { super() diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 201abcfe49..de89790d21 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -64,6 +64,14 @@ const normalizeAll = client => (docs, doctype) => { * @typedef {"idle"|"replicating"} ReplicationStatus */ +/** + * @typedef {object} PouchLinkOptions + * @property {number} [replicationInterval] Milliseconds between replications + * @property {string[]} doctypes Doctypes to replicate + * @property {object[]} doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @property {import('./types').LinkPlatform} platform Platform specific adapters and methods + */ + /** * Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates * PouchDB collections for each doctype that it supports and knows how @@ -73,11 +81,7 @@ class PouchLink extends CozyLink { /** * constructor - Initializes a new PouchLink * - * @param {object} [opts={}] - * @param {number} [opts.replicationInterval] Milliseconds between replications - * @param {string[]} opts.doctypes Doctypes to replicate - * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") - * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods + * @param {PouchLinkOptions} [opts={}] */ constructor(opts) { From d0f75550073fd9f246a29770e5e0140623175821 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:43:22 +0200 Subject: [PATCH 50/84] docs: Fix type for CozyPouchLink constructor --- packages/cozy-pouch-link/src/CozyPouchLink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index de89790d21..0e73efdd94 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -68,7 +68,7 @@ const normalizeAll = client => (docs, doctype) => { * @typedef {object} PouchLinkOptions * @property {number} [replicationInterval] Milliseconds between replications * @property {string[]} doctypes Doctypes to replicate - * @property {object[]} doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @property {Record} doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") * @property {import('./types').LinkPlatform} platform Platform specific adapters and methods */ From 2952954cbbe82b8d9c884bc5163521da73417951 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 18:43:34 +0200 Subject: [PATCH 51/84] docs: Update types and documentation --- docs/api/cozy-client/classes/StackLink.md | 23 ++--- docs/api/cozy-pouch-link/classes/PouchLink.md | 94 +++++++++---------- packages/cozy-client/types/StackLink.d.ts | 31 ++++-- .../cozy-pouch-link/types/CozyPouchLink.d.ts | 47 ++++++---- 4 files changed, 106 insertions(+), 89 deletions(-) diff --git a/docs/api/cozy-client/classes/StackLink.md b/docs/api/cozy-client/classes/StackLink.md index ad99609ed5..5bdcd6533b 100644 --- a/docs/api/cozy-client/classes/StackLink.md +++ b/docs/api/cozy-client/classes/StackLink.md @@ -20,10 +20,7 @@ Transfers queries and mutations to a remote stack | Name | Type | Description | | :------ | :------ | :------ | -| `[options]` | `Object` | Options | -| `[options].client` | `any` | - | -| `[options].platform` | `any` | - | -| `[options].stackClient` | `any` | - | +| `[options]` | `StackLinkOptions` | Options | *Overrides* @@ -31,7 +28,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:64](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L64) +[packages/cozy-client/src/StackLink.js:68](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L68) ## Properties @@ -41,7 +38,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:72](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L72) +[packages/cozy-client/src/StackLink.js:76](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L76) *** @@ -51,7 +48,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:71](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L71) +[packages/cozy-client/src/StackLink.js:75](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L75) ## Methods @@ -73,7 +70,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:132](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L132) +[packages/cozy-client/src/StackLink.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L136) *** @@ -93,7 +90,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L109) +[packages/cozy-client/src/StackLink.js:113](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L113) *** @@ -118,7 +115,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:101](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L101) +[packages/cozy-client/src/StackLink.js:105](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L105) *** @@ -138,7 +135,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:75](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L75) +[packages/cozy-client/src/StackLink.js:79](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L79) *** @@ -164,7 +161,7 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:83](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L83) +[packages/cozy-client/src/StackLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L87) *** @@ -178,4 +175,4 @@ Transfers queries and mutations to a remote stack *Defined in* -[packages/cozy-client/src/StackLink.js:79](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L79) +[packages/cozy-client/src/StackLink.js:83](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/StackLink.js#L83) diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index fa89c7c635..6402d9c84d 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -22,13 +22,9 @@ constructor - Initializes a new PouchLink *Parameters* -| Name | Type | Description | -| :------ | :------ | :------ | -| `opts` | `Object` | - | -| `opts.doctypes` | `string`\[] | Doctypes to replicate | -| `opts.doctypesReplicationOptions` | `any`\[] | A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") | -| `opts.platform` | `LinkPlatform` | Platform specific adapters and methods | -| `opts.replicationInterval` | `number` | - | +| Name | Type | +| :------ | :------ | +| `opts` | `PouchLinkOptions` | *Overrides* @@ -36,7 +32,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:83](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L83) +[CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) ## Properties @@ -46,7 +42,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:137](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L137) +[CozyPouchLink.js:141](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L141) *** @@ -56,17 +52,17 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:93](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L93) +[CozyPouchLink.js:97](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L97) *** ### doctypesReplicationOptions -• **doctypesReplicationOptions**: `any`\[] +• **doctypesReplicationOptions**: `Record`<`string`, `any`> *Defined in* -[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) +[CozyPouchLink.js:98](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L98) *** @@ -76,7 +72,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +[CozyPouchLink.js:103](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L103) *** @@ -86,17 +82,17 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L95) +[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) *** ### options -• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `platform`: `LinkPlatform` ; `replicationInterval`: `number` } +• **options**: { `replicationInterval`: `number` } & `PouchLinkOptions` *Defined in* -[CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) +[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) *** @@ -106,7 +102,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:207](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L207) +[CozyPouchLink.js:211](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L211) *** @@ -116,7 +112,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:102](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L102) +[CozyPouchLink.js:106](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L106) *** @@ -126,7 +122,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:96](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L96) +[CozyPouchLink.js:100](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L100) ## Methods @@ -146,7 +142,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:666](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L666) +[CozyPouchLink.js:670](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L670) *** @@ -166,7 +162,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L627) +[CozyPouchLink.js:631](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L631) *** @@ -192,7 +188,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:464](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L464) +[CozyPouchLink.js:468](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L468) *** @@ -213,7 +209,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:670](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L670) +[CozyPouchLink.js:674](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L674) *** @@ -233,7 +229,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:655](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L655) +[CozyPouchLink.js:659](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L659) *** @@ -255,7 +251,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:597](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L597) +[CozyPouchLink.js:601](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L601) *** @@ -275,7 +271,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:528](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L528) +[CozyPouchLink.js:532](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L532) *** @@ -299,7 +295,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:488](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L488) +[CozyPouchLink.js:492](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L492) *** @@ -319,7 +315,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:324](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L324) +[CozyPouchLink.js:328](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L328) *** @@ -339,7 +335,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:117](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L117) +[CozyPouchLink.js:121](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L121) *** @@ -359,7 +355,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:320](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L320) +[CozyPouchLink.js:324](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L324) *** @@ -379,7 +375,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:261](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L261) +[CozyPouchLink.js:265](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L265) *** @@ -399,7 +395,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:256](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L256) +[CozyPouchLink.js:260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L260) *** @@ -425,7 +421,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:242](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L242) +[CozyPouchLink.js:246](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L246) *** @@ -445,7 +441,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:450](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L450) +[CozyPouchLink.js:454](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L454) *** @@ -475,7 +471,7 @@ Migrate the current adapter *Defined in* -[CozyPouchLink.js:151](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L151) +[CozyPouchLink.js:155](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L155) *** @@ -500,7 +496,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:436](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L436) +[CozyPouchLink.js:440](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L440) *** @@ -514,7 +510,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:170](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L170) +[CozyPouchLink.js:174](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L174) *** @@ -534,7 +530,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:300](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L300) +[CozyPouchLink.js:304](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L304) *** @@ -559,7 +555,7 @@ CozyLink.persistData *Defined in* -[CozyPouchLink.js:391](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L391) +[CozyPouchLink.js:395](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L395) *** @@ -579,7 +575,7 @@ CozyLink.persistData *Defined in* -[CozyPouchLink.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L136) +[CozyPouchLink.js:140](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L140) *** @@ -605,7 +601,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:343](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L343) +[CozyPouchLink.js:347](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L347) *** @@ -619,7 +615,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:226](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L226) +[CozyPouchLink.js:230](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L230) *** @@ -638,7 +634,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[CozyPouchLink.js:275](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L275) +[CozyPouchLink.js:279](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L279) *** @@ -657,7 +653,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:292](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L292) +[CozyPouchLink.js:296](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L296) *** @@ -677,7 +673,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:328](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L328) +[CozyPouchLink.js:332](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L332) *** @@ -691,7 +687,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:692](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L692) +[CozyPouchLink.js:696](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L696) *** @@ -711,7 +707,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:632](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L632) +[CozyPouchLink.js:636](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L636) *** @@ -731,7 +727,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:637](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L637) +[CozyPouchLink.js:641](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L641) *** @@ -756,4 +752,4 @@ The adapter name *Defined in* -[CozyPouchLink.js:112](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L112) +[CozyPouchLink.js:116](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L116) diff --git a/packages/cozy-client/types/StackLink.d.ts b/packages/cozy-client/types/StackLink.d.ts index 839117535d..918ef72ef8 100644 --- a/packages/cozy-client/types/StackLink.d.ts +++ b/packages/cozy-client/types/StackLink.d.ts @@ -1,21 +1,20 @@ export function transformBulkDocsResponse(bulkResponse: import("./types").CouchDBBulkResult[], originalDocuments: import("./types").CozyClientDocument[]): { data: import("./types").CozyClientDocument[]; }; +/** + * @typedef {object} StackLinkOptions + * @property {object} [stackClient] - A StackClient + * @property {object} [client] - A StackClient (deprecated) + * @property {import('cozy-pouch-link/dist/types').LinkPlatform} [platform] - Platform specific adapters and methods + */ /** * Transfers queries and mutations to a remote stack */ export default class StackLink extends CozyLink { /** - * @param {object} [options] - Options - * @param {object} [options.stackClient] - A StackClient - * @param {object} [options.client] - A StackClient (deprecated) - * @param {import('cozy-pouch-link/dist/types').LinkPlatform} [options.platform] Platform specific adapters and methods + * @param {StackLinkOptions} [options] - Options */ - constructor({ client, stackClient, platform }?: { - stackClient: object; - client: object; - platform: import('cozy-pouch-link/dist/types').LinkPlatform; - }); + constructor({ client, stackClient, platform }?: StackLinkOptions); stackClient: any; isOnline: any; registerClient(client: any): void; @@ -28,5 +27,19 @@ export default class StackLink extends CozyLink { executeQuery(query: QueryDefinition): Promise; executeMutation(mutation: any, result: any, forward: any): Promise; } +export type StackLinkOptions = { + /** + * - A StackClient + */ + stackClient?: object; + /** + * - A StackClient (deprecated) + */ + client?: object; + /** + * - Platform specific adapters and methods + */ + platform?: any; +}; import CozyLink from "./CozyLink"; import { QueryDefinition } from "./queries/dsl"; diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index 373e26f8ff..edac36c855 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -3,11 +3,36 @@ export function isExpiredTokenError(pouchError: any): boolean; export default PouchLink; export type CozyClientDocument = any; export type ReplicationStatus = "idle" | "replicating"; +export type PouchLinkOptions = { + /** + * Milliseconds between replications + */ + replicationInterval?: number; + /** + * Doctypes to replicate + */ + doctypes: string[]; + /** + * A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + */ + doctypesReplicationOptions: Record; + /** + * Platform specific adapters and methods + */ + platform: import('./types').LinkPlatform; +}; /** * @typedef {import('cozy-client/src/types').CozyClientDocument} CozyClientDocument * * @typedef {"idle"|"replicating"} ReplicationStatus */ +/** + * @typedef {object} PouchLinkOptions + * @property {number} [replicationInterval] Milliseconds between replications + * @property {string[]} doctypes Doctypes to replicate + * @property {Record} doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @property {import('./types').LinkPlatform} platform Platform specific adapters and methods + */ /** * Link to be passed to a `CozyClient` instance to support CouchDB. It instantiates * PouchDB collections for each doctype that it supports and knows how @@ -25,28 +50,14 @@ declare class PouchLink extends CozyLink { /** * constructor - Initializes a new PouchLink * - * @param {object} [opts={}] - * @param {number} [opts.replicationInterval] Milliseconds between replications - * @param {string[]} opts.doctypes Doctypes to replicate - * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") - * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods + * @param {PouchLinkOptions} [opts={}] */ - constructor(opts?: { - replicationInterval: number; - doctypes: string[]; - doctypesReplicationOptions: object[]; - platform: import('./types').LinkPlatform; - }); + constructor(opts?: PouchLinkOptions); options: { replicationInterval: number; - } & { - replicationInterval?: number; - doctypes: string[]; - doctypesReplicationOptions: object[]; - platform: import('./types').LinkPlatform; - }; + } & PouchLinkOptions; doctypes: string[]; - doctypesReplicationOptions: any[]; + doctypesReplicationOptions: Record; indexes: {}; storage: PouchLocalStorage; ignoreWarmup: any; From a18c9f9858e4f3bb19de75c0f6e7f8ce4f15cbe9 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Sat, 3 Aug 2024 13:24:56 +0200 Subject: [PATCH 52/84] feat: Add `downloadFile` method in `file` model For the Flagship app offline feature, we want to make some files available offline To make this possible, we want the cozy-apps to download files as usual except when they are hosted in the FlagshipApp In that scenario, we want the cozy-app to call the new `downloadFile` intent Also to ease future compatibility, we want to implement a new method hosted in the `file` model instead of in the collection as before So in order to handle offline files, the cozy-app will now need to call `downloadFile()` method from `models/file` --- packages/cozy-client/src/models/file.js | 31 +++++++ packages/cozy-client/src/models/file.spec.js | 90 ++++++++++++++++++- packages/cozy-client/src/types.js | 1 + .../cozy-stack-client/src/FileCollection.js | 1 + 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/packages/cozy-client/src/models/file.js b/packages/cozy-client/src/models/file.js index c1daed00dd..50e66f77f9 100644 --- a/packages/cozy-client/src/models/file.js +++ b/packages/cozy-client/src/models/file.js @@ -1,3 +1,5 @@ +import { isFlagshipApp } from 'cozy-device-helper' + import get from 'lodash/get' import isString from 'lodash/isString' import has from 'lodash/has' @@ -696,3 +698,32 @@ export const copy = async (client, file, destination) => { throw e } } + +/** + * Download the requested file + * + * This method can be used in a web page context or in a WebView hosted by a Flagship app + * + * When used in a FlagshipApp WebView context, then the action is redirected to the host app + * that will process the download + * + * @param {object} params - The download parameters + * @param {CozyClient} params.client - Instance of CozyClient + * @param {import("../types").IOCozyFile} params.file - io.cozy.files metadata of the document to downloaded + * @param {string} [params.url] - Blob url that should be used to download encrypted files + * @param {import('cozy-intent').WebviewService} [params.webviewIntent] - webviewIntent that can be used to redirect the download to host Flagship app + * + * @returns {Promise} + */ +export const downloadFile = async ({ client, file, url, webviewIntent }) => { + const filesCollection = client.collection(DOCTYPE_FILES) + + if (isFlagshipApp() && webviewIntent && !isEncrypted(file)) { + return await webviewIntent.call('downloadFile', file) + } + + if (isEncrypted(file)) { + return filesCollection.forceFileDownload(url, file.name) + } + return filesCollection.download(file) +} diff --git a/packages/cozy-client/src/models/file.spec.js b/packages/cozy-client/src/models/file.spec.js index fd96557bd3..5822047784 100644 --- a/packages/cozy-client/src/models/file.spec.js +++ b/packages/cozy-client/src/models/file.spec.js @@ -1,3 +1,5 @@ +import { isFlagshipApp } from 'cozy-device-helper' + import * as fileModel from './file' import { Qualification } from './document/qualification' import { QueryDefinition } from '../queries/dsl' @@ -5,6 +7,9 @@ const CozyClient = require('cozy-client/dist/CozyClient').default const CozyStackClient = require('cozy-stack-client').default jest.mock('cozy-stack-client') +jest.mock('cozy-device-helper', () => ({ + isFlagshipApp: jest.fn() +})) const cozyClient = new CozyClient({ stackClient: new CozyStackClient() @@ -19,6 +24,8 @@ const fetchFileContentByIdSpy = jest.fn().mockName('fetchFileContentById') const moveSpy = jest.fn().mockName('move') const moveToCozySpy = jest.fn().mockName('moveToCozy') const moveFromCozySpy = jest.fn().mockName('moveFromCozy') +const downloadFromCozySpy = jest.fn().mockName('downloadFromCozy') +const forceFileDownloadFromCozySpy = jest.fn().mockName('forceFileDownload') beforeAll(() => { cozyClient.stackClient.collection.mockReturnValue({ @@ -31,10 +38,16 @@ beforeAll(() => { fetchFileContentById: fetchFileContentByIdSpy, move: moveSpy, moveToCozy: moveToCozySpy, - moveFromCozy: moveFromCozySpy + moveFromCozy: moveFromCozySpy, + download: downloadFromCozySpy, + forceFileDownload: forceFileDownloadFromCozySpy }) }) +beforeEach(() => { + jest.clearAllMocks() +}) + describe('File Model', () => { it('should test if a file is a note or not', () => { const fileDocument = { @@ -856,3 +869,78 @@ describe('File qualification', () => { }) }) }) + +describe('downloadFile', () => { + it('should handle download in web page', async () => { + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME' + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + webviewIntent: null + }) + + expect(downloadFromCozySpy).toHaveBeenCalledWith(file) + }) + + it('should handle download in Flagship app', async () => { + isFlagshipApp.mockReturnValue(true) + const webviewIntent = { + call: jest.fn() + } + + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME' + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + // @ts-ignore + webviewIntent + }) + + expect(downloadFromCozySpy).not.toHaveBeenCalled() + expect(webviewIntent.call).toHaveBeenCalledWith('downloadFile', file) + }) + + it('should download encrypted files from web page as this is not supported yet by Flagship app', async () => { + isFlagshipApp.mockReturnValue(true) + const webviewIntent = { + call: jest.fn() + } + + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME', + encrypted: true + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + url: 'SOME_URL', + // @ts-ignore + webviewIntent + }) + + expect(forceFileDownloadFromCozySpy).toHaveBeenCalledWith( + 'SOME_URL', + 'SOME_FILE_NAME' + ) + expect(webviewIntent.call).not.toHaveBeenCalled() + }) +}) diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index 80034aadc2..945bb91ee4 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -471,6 +471,7 @@ import { QueryDefinition } from './queries/dsl' /** * @typedef {object} FileDocument - An io.cozy.files document * @property {string} _id - Id of the file + * @property {string} _rev - Rev of the file * @property {FilesDoctype} _type - Doctype of the file * @property {string} dir_id - Id of the parent folder * @property {string} [path] - Path of the file diff --git a/packages/cozy-stack-client/src/FileCollection.js b/packages/cozy-stack-client/src/FileCollection.js index 520dd5091d..69c73e83de 100644 --- a/packages/cozy-stack-client/src/FileCollection.js +++ b/packages/cozy-stack-client/src/FileCollection.js @@ -57,6 +57,7 @@ import logger from './logger' * * @typedef {object} FileDocument * @property {string} _id - Id of the file + * @property {string} _rev - Rev of the file * @property {FileAttributes} attributes - Attributes of the file * @property {object} meta - Meta * @property {object} relationships - Relationships From 83a76b9a61dba8ae3ae80a1bd5975ed17cbe823e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 22 Aug 2024 16:54:04 +0200 Subject: [PATCH 53/84] docs: Update types and documentation --- .../models.file.FileUploadOptions.md | 12 +- docs/api/cozy-client/modules/models.file.md | 103 ++++++++++++------ docs/api/cozy-stack-client.md | 1 + packages/cozy-client/types/models/file.d.ts | 6 + packages/cozy-client/types/types.d.ts | 4 + 5 files changed, 84 insertions(+), 42 deletions(-) diff --git a/docs/api/cozy-client/interfaces/models.file.FileUploadOptions.md b/docs/api/cozy-client/interfaces/models.file.FileUploadOptions.md index 41adfd3631..1f4eedaf66 100644 --- a/docs/api/cozy-client/interfaces/models.file.FileUploadOptions.md +++ b/docs/api/cozy-client/interfaces/models.file.FileUploadOptions.md @@ -14,7 +14,7 @@ Conflict options *Defined in* -[packages/cozy-client/src/models/file.js:494](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L494) +[packages/cozy-client/src/models/file.js:496](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L496) *** @@ -26,7 +26,7 @@ Erase / rename *Defined in* -[packages/cozy-client/src/models/file.js:493](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L493) +[packages/cozy-client/src/models/file.js:495](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L495) *** @@ -38,7 +38,7 @@ The file Content-Type *Defined in* -[packages/cozy-client/src/models/file.js:492](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L492) +[packages/cozy-client/src/models/file.js:494](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L494) *** @@ -50,7 +50,7 @@ The dirId to upload the file to *Defined in* -[packages/cozy-client/src/models/file.js:490](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L490) +[packages/cozy-client/src/models/file.js:492](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L492) *** @@ -62,7 +62,7 @@ An object containing the metadata to attach *Defined in* -[packages/cozy-client/src/models/file.js:491](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L491) +[packages/cozy-client/src/models/file.js:493](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L493) *** @@ -74,4 +74,4 @@ The file name to upload *Defined in* -[packages/cozy-client/src/models/file.js:489](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L489) +[packages/cozy-client/src/models/file.js:491](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L491) diff --git a/docs/api/cozy-client/modules/models.file.md b/docs/api/cozy-client/modules/models.file.md index efdb749cf2..9b90abfc7f 100644 --- a/docs/api/cozy-client/modules/models.file.md +++ b/docs/api/cozy-client/modules/models.file.md @@ -16,7 +16,7 @@ *Defined in* -[packages/cozy-client/src/models/file.js:14](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L14) +[packages/cozy-client/src/models/file.js:16](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L16) ## Functions @@ -44,7 +44,7 @@ Copies a file to a specified destination. *Defined in* -[packages/cozy-client/src/models/file.js:662](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L662) +[packages/cozy-client/src/models/file.js:664](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L664) *** @@ -68,7 +68,38 @@ Upload a file on a mobile *Defined in* -[packages/cozy-client/src/models/file.js:599](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L599) +[packages/cozy-client/src/models/file.js:601](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L601) + +*** + +### downloadFile + +▸ **downloadFile**(`params`): `Promise`<`any`> + +Download the requested file + +This method can be used in a web page context or in a WebView hosted by a Flagship app + +When used in a FlagshipApp WebView context, then the action is redirected to the host app +that will process the download + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `params` | `Object` | The download parameters | +| `params.client` | [`CozyClient`](../classes/CozyClient.md) | Instance of CozyClient | +| `params.file` | `IOCozyFile` | io.cozy.files metadata of the document to downloaded | +| `params.url` | `string` | - | +| `params.webviewIntent` | `WebviewService` | - | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[packages/cozy-client/src/models/file.js:718](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L718) *** @@ -93,7 +124,7 @@ file object with path attribute *Defined in* -[packages/cozy-client/src/models/file.js:136](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L136) +[packages/cozy-client/src/models/file.js:138](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L138) *** @@ -114,7 +145,7 @@ file object with path attribute *Defined in* -[packages/cozy-client/src/models/file.js:645](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L645) +[packages/cozy-client/src/models/file.js:647](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L647) *** @@ -139,7 +170,7 @@ The files found by the rules *Defined in* -[packages/cozy-client/src/models/file.js:256](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L256) +[packages/cozy-client/src/models/file.js:258](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L258) *** @@ -163,7 +194,7 @@ Generate a file name for a revision *Defined in* -[packages/cozy-client/src/models/file.js:479](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L479) +[packages/cozy-client/src/models/file.js:481](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L481) *** @@ -188,7 +219,7 @@ A filename with the right suffix *Defined in* -[packages/cozy-client/src/models/file.js:449](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L449) +[packages/cozy-client/src/models/file.js:451](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L451) *** @@ -214,7 +245,7 @@ The full path of the file in the cozy *Defined in* -[packages/cozy-client/src/models/file.js:291](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L291) +[packages/cozy-client/src/models/file.js:293](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L293) *** @@ -238,7 +269,7 @@ id of the parent folder, if any *Defined in* -[packages/cozy-client/src/models/file.js:150](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L150) +[packages/cozy-client/src/models/file.js:152](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L152) *** @@ -262,7 +293,7 @@ A description of the status *Defined in* -[packages/cozy-client/src/models/file.js:162](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L162) +[packages/cozy-client/src/models/file.js:164](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L164) *** @@ -286,7 +317,7 @@ A doctype *Defined in* -[packages/cozy-client/src/models/file.js:182](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L182) +[packages/cozy-client/src/models/file.js:184](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L184) *** @@ -310,7 +341,7 @@ The mime-type of the target file, or an empty string is the target is not a file *Defined in* -[packages/cozy-client/src/models/file.js:172](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L172) +[packages/cozy-client/src/models/file.js:174](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L174) *** @@ -330,7 +361,7 @@ The mime-type of the target file, or an empty string is the target is not a file *Defined in* -[packages/cozy-client/src/models/file.js:625](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L625) +[packages/cozy-client/src/models/file.js:627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L627) *** @@ -354,7 +385,7 @@ Whether the file's metadata attribute exists *Defined in* -[packages/cozy-client/src/models/file.js:280](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L280) +[packages/cozy-client/src/models/file.js:282](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L282) *** @@ -374,7 +405,7 @@ Whether the file's metadata attribute exists *Defined in* -[packages/cozy-client/src/models/file.js:617](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L617) +[packages/cozy-client/src/models/file.js:619](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L619) *** @@ -394,7 +425,7 @@ Whether the file's metadata attribute exists *Defined in* -[packages/cozy-client/src/models/file.js:46](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L46) +[packages/cozy-client/src/models/file.js:48](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L48) *** @@ -416,7 +447,7 @@ Whether the file is client-side encrypted *Defined in* -[packages/cozy-client/src/models/file.js:74](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L74) +[packages/cozy-client/src/models/file.js:76](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L76) *** @@ -436,7 +467,7 @@ Whether the file is client-side encrypted *Defined in* -[packages/cozy-client/src/models/file.js:40](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L40) +[packages/cozy-client/src/models/file.js:42](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L42) *** @@ -456,7 +487,7 @@ Whether the file is client-side encrypted *Defined in* -[packages/cozy-client/src/models/file.js:636](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L636) +[packages/cozy-client/src/models/file.js:638](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L638) *** @@ -478,7 +509,7 @@ Is file param a correct note *Defined in* -[packages/cozy-client/src/models/file.js:54](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L54) +[packages/cozy-client/src/models/file.js:56](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L56) *** @@ -500,7 +531,7 @@ Whether the file is supported by Only Office *Defined in* -[packages/cozy-client/src/models/file.js:84](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L84) +[packages/cozy-client/src/models/file.js:86](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L86) *** @@ -521,7 +552,7 @@ Whether the file is supported by Only Office *Defined in* -[packages/cozy-client/src/models/file.js:609](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L609) +[packages/cozy-client/src/models/file.js:611](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L611) *** @@ -545,7 +576,7 @@ Returns whether the file is a shortcut to a sharing *Defined in* -[packages/cozy-client/src/models/file.js:202](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L202) +[packages/cozy-client/src/models/file.js:204](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L204) *** @@ -569,7 +600,7 @@ Returns whether the sharing shortcut is new *Defined in* -[packages/cozy-client/src/models/file.js:227](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L227) +[packages/cozy-client/src/models/file.js:229](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L229) *** @@ -591,7 +622,7 @@ Returns whether the file is a shortcut to a sharing *Defined in* -[packages/cozy-client/src/models/file.js:192](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L192) +[packages/cozy-client/src/models/file.js:194](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L194) *** @@ -613,7 +644,7 @@ Returns whether the sharing shortcut is new *Defined in* -[packages/cozy-client/src/models/file.js:216](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L216) +[packages/cozy-client/src/models/file.js:218](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L218) *** @@ -635,7 +666,7 @@ true if the file is a shortcut *Defined in* -[packages/cozy-client/src/models/file.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L109) +[packages/cozy-client/src/models/file.js:111](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L111) *** @@ -670,7 +701,7 @@ Manage 4 cases : *Defined in* -[packages/cozy-client/src/models/file.js:320](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L320) +[packages/cozy-client/src/models/file.js:322](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L322) *** @@ -696,7 +727,7 @@ full normalized object *Defined in* -[packages/cozy-client/src/models/file.js:122](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L122) +[packages/cozy-client/src/models/file.js:124](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L124) *** @@ -723,7 +754,7 @@ The overrided file *Defined in* -[packages/cozy-client/src/models/file.js:415](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L415) +[packages/cozy-client/src/models/file.js:417](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L417) *** @@ -745,7 +776,7 @@ Read a file on a mobile *Defined in* -[packages/cozy-client/src/models/file.js:552](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L552) +[packages/cozy-client/src/models/file.js:554](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L554) *** @@ -771,7 +802,7 @@ The saved file *Defined in* -[packages/cozy-client/src/models/file.js:242](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L242) +[packages/cozy-client/src/models/file.js:244](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L244) *** @@ -795,7 +826,7 @@ But we want to exclude .txt and .md because the CozyUI Viewer can already show t *Defined in* -[packages/cozy-client/src/models/file.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L99) +[packages/cozy-client/src/models/file.js:101](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L101) *** @@ -822,7 +853,7 @@ Returns base filename and extension *Defined in* -[packages/cozy-client/src/models/file.js:24](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L24) +[packages/cozy-client/src/models/file.js:26](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L26) *** @@ -855,4 +886,4 @@ If there is a conflict, then we apply the conflict strategy : `erase` or `rename *Defined in* -[packages/cozy-client/src/models/file.js:512](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L512) +[packages/cozy-client/src/models/file.js:514](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/file.js#L514) diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md index 23db3115c9..4d684b1db7 100644 --- a/docs/api/cozy-stack-client.md +++ b/docs/api/cozy-stack-client.md @@ -2570,6 +2570,7 @@ Document representing a io.cozy.files | Name | Type | Description | | --- | --- | --- | | _id | string | Id of the file | +| _rev | string | Rev of the file | | attributes | [FileAttributes](#FileAttributes) | Attributes of the file | | meta | object | Meta | | relationships | object | Relationships | diff --git a/packages/cozy-client/types/models/file.d.ts b/packages/cozy-client/types/models/file.d.ts index f4bfb55e15..4cb039ad4c 100644 --- a/packages/cozy-client/types/models/file.d.ts +++ b/packages/cozy-client/types/models/file.d.ts @@ -68,6 +68,12 @@ export function hasCertifications(file: import("../types").IOCozyFile): boolean; export function isFromKonnector(file: import("../types").IOCozyFile): boolean; export function fetchBlobFileById(client: CozyClient, fileId: string): Promise; export function copy(client: object, file: object, destination: object): Promise; +export function downloadFile({ client, file, url, webviewIntent }: { + client: CozyClient; + file: import("../types").IOCozyFile; + url: string; + webviewIntent: import('cozy-intent').WebviewService; +}): Promise; export type FileUploadOptions = { /** * - The file name to upload diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index 0bd6f55931..48fe11151d 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -834,6 +834,10 @@ export type FileDocument = { * - Id of the file */ _id: string; + /** + * - Rev of the file + */ + _rev: string; /** * - Doctype of the file */ From 0bb184e103747af6437b513036aa0501f52fdd3b Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 17:54:33 +0200 Subject: [PATCH 54/84] feat: Ensure storageEngine implements correct interface In order to prevent implementation errors, we want to check that storageEngine implements the correct methods This replies to https://github.com/cozy/cozy-client/pull/1483#discussion_r1626141973 --- packages/cozy-pouch-link/src/localStorage.js | 21 ++++++++ .../cozy-pouch-link/src/localStorage.spec.js | 48 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 packages/cozy-pouch-link/src/localStorage.spec.js diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index 3fa799be08..0e213604a3 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -9,6 +9,7 @@ export const LOCALSTORAGE_ADAPTERNAME = 'cozy-client-pouch-link-adaptername' export class PouchLocalStorage { constructor(storageEngine) { + checkStorageEngine(storageEngine) this.storageEngine = storageEngine } @@ -220,3 +221,23 @@ export class PouchLocalStorage { await this.storageEngine.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) } } + +/** + * Throw if the given storage engine does not implement the expected Interface + * + * @param {*} storageEngine - Object containing storage access methods + */ +const checkStorageEngine = storageEngine => { + const requiredMethods = ['setItem', 'getItem', 'removeItem'] + + const missingMethods = requiredMethods.filter( + requiredMethod => !storageEngine[requiredMethod] + ) + + if (missingMethods.length > 0) { + const missingMethodsString = missingMethods.join(', ') + throw new Error( + `Provided storageEngine is missing the following methods: ${missingMethodsString}` + ) + } +} diff --git a/packages/cozy-pouch-link/src/localStorage.spec.js b/packages/cozy-pouch-link/src/localStorage.spec.js new file mode 100644 index 0000000000..7d7c4ee225 --- /dev/null +++ b/packages/cozy-pouch-link/src/localStorage.spec.js @@ -0,0 +1,48 @@ +import { PouchLocalStorage } from './localStorage' + +describe('LocalStorage', () => { + describe('Type assertion', () => { + it('should throw if setItem method is missing', () => { + expect(() => { + new PouchLocalStorage({ + getItem: jest.fn(), + removeItem: jest.fn() + }) + }).toThrow( + 'Provided storageEngine is missing the following methods: setItem' + ) + }) + + it('should throw if getItem method is missing', () => { + expect(() => { + new PouchLocalStorage({ + setItem: jest.fn(), + removeItem: jest.fn() + }) + }).toThrow( + 'Provided storageEngine is missing the following methods: getItem' + ) + }) + + it('should throw if removeItem method is missing', () => { + expect(() => { + new PouchLocalStorage({ + getItem: jest.fn(), + setItem: jest.fn() + }) + }).toThrow( + 'Provided storageEngine is missing the following methods: removeItem' + ) + }) + + it('should throw if multiple methods are missing', () => { + expect(() => { + new PouchLocalStorage({ + getItem: jest.fn() + }) + }).toThrow( + 'Provided storageEngine is missing the following methods: setItem, removeItem' + ) + }) + }) +}) From 310317dd9716336d1957d930f7ce157d12ac1d87 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 17:59:18 +0200 Subject: [PATCH 55/84] refactor: Use parameter destructuring for `replicateAllDocs` This replies to https://github.com/cozy/cozy-client/pull/1483#discussion_r1626156446 --- .../cozy-pouch-link/src/startReplication.js | 18 ++++++++++------ .../src/startReplication.spec.js | 21 ++++++++++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 647961759d..88731b8a40 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -75,7 +75,12 @@ export const startReplication = ( // For the first remote->local replication, we manually replicate all docs // as it avoids to replicate all revs history, which can lead to // performances issues - docs = await replicateAllDocs(pouch, url, doctype, storage) + docs = await replicateAllDocs({ + db: pouch, + baseUrl: url, + doctype, + storage + }) const end = new Date() if (process.env.NODE_ENV !== 'production') { logger.info( @@ -146,13 +151,14 @@ const filterDocs = docs => { * Note it saves the last replicated _id for each run and * starts from there in case the process stops before the end. * - * @param {object} db - Pouch instance - * @param {string} baseUrl - The remote instance - * @param {string} doctype - The doctype to replicate - * @param {import('./localStorage').PouchLocalStorage} storage - Methods to access local storage + * @param {object} params - The replications parameters + * @param {object} params.db - Pouch instance + * @param {string} params.baseUrl - The remote instance + * @param {string} params.doctype - The doctype to replicate + * @param {import('./localStorage').PouchLocalStorage} params.storage - Methods to access local storage * @returns {Promise} The retrieved documents */ -export const replicateAllDocs = async (db, baseUrl, doctype, storage) => { +export const replicateAllDocs = async ({ db, baseUrl, doctype, storage }) => { const remoteUrlAllDocs = new URL(`${baseUrl}/_all_docs`) const batchSize = BATCH_SIZE let hasMore = true diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index d3ea709c78..e6bc72a7d5 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -48,7 +48,12 @@ describe('startReplication', () => { const dummyDocs = generateDocs(2) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) - const rep = await replicateAllDocs(null, url, undefined, storage) + const rep = await replicateAllDocs({ + db: null, + baseUrl: url, + doctype: undefined, + storage + }) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) expect(fetchRemoteInstance).toHaveBeenCalledTimes(1) @@ -65,7 +70,12 @@ describe('startReplication', () => { rows: dummyDocs.slice(1000, 1002) }) - const rep = await replicateAllDocs(null, url, undefined, storage) + const rep = await replicateAllDocs({ + db: null, + baseUrl: url, + doctype: undefined, + storage + }) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) expect(fetchRemoteInstance).toHaveBeenCalledTimes(2) @@ -77,7 +87,12 @@ describe('startReplication', () => { const dummyDocs = generateDocs(10) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs.slice(5, 11) }) - const rep = await replicateAllDocs(null, url, undefined, storage) + const rep = await replicateAllDocs({ + db: null, + baseUrl: url, + doctype: undefined, + storage + }) const calledUrl = new URL(`${url}/_all_docs`) expect(fetchRemoteInstance).toHaveBeenCalledWith(calledUrl, { From adaebbb5db949d3827afd8d2ccb4f0154b00cfeb Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 18:16:53 +0200 Subject: [PATCH 56/84] refactor: Remove unused client/stackClient parameters from FlagshipLink This replies to https://github.com/cozy/cozy-client/pull/1505#discussion_r1714986889 --- packages/cozy-client/src/FlagshipLink.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/cozy-client/src/FlagshipLink.js b/packages/cozy-client/src/FlagshipLink.js index 761446ecad..0e9ff0348c 100644 --- a/packages/cozy-client/src/FlagshipLink.js +++ b/packages/cozy-client/src/FlagshipLink.js @@ -1,30 +1,21 @@ import CozyLink from './CozyLink' -import logger from './logger' export default class FlagshipLink extends CozyLink { /** * @param {object} [options] - Options - * @param {object} [options.stackClient] - A StackClient - * @param {object} [options.client] - A StackClient (deprecated) * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference */ - constructor({ client, stackClient, webviewIntent } = {}) { + constructor({ webviewIntent } = {}) { super() - if (client) { - logger.warn( - 'Using options.client is deprecated, prefer options.stackClient' - ) - } - this.stackClient = stackClient || client this.webviewIntent = webviewIntent } registerClient(client) { - this.stackClient = client.stackClient || client.client + // does nothing, we don't need any client for this kind of link } reset() { - this.stackClient = null + // does nothing, we don't need any client for this kind of link } async request(operation, result, forward) { From 5147fc3f2b0c19daef8f64c8cafbf1f702b3fab2 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 18:21:45 +0200 Subject: [PATCH 57/84] refactor: Use parameter destructuring for `jsonapi.fromPouchResult` This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716554118 --- packages/cozy-pouch-link/src/CozyPouchLink.js | 19 +++++--- packages/cozy-pouch-link/src/jsonapi.js | 2 +- packages/cozy-pouch-link/src/jsonapi.spec.js | 48 ++++++++++++++++--- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 0e73efdd94..c8b421b1ab 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -595,7 +595,12 @@ class PouchLink extends CozyLink { res.limit = limit withRows = true } - return jsonapi.fromPouchResult(res, withRows, doctype, this.client) + return jsonapi.fromPouchResult({ + res, + withRows, + doctype, + client: this.client + }) } async executeMutation(mutation, result, forward) { @@ -620,12 +625,12 @@ class PouchLink extends CozyLink { return forward(mutation, result) } - return jsonapi.fromPouchResult( - pouchRes, - false, - getDoctypeFromOperation(mutation), - this.client - ) + return jsonapi.fromPouchResult({ + res: pouchRes, + withRows: false, + doctype: getDoctypeFromOperation(mutation), + client: this.client + }) } async createDocument(mutation) { diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js index 990155786b..b3a21389f9 100644 --- a/packages/cozy-pouch-link/src/jsonapi.js +++ b/packages/cozy-pouch-link/src/jsonapi.js @@ -53,7 +53,7 @@ const normalizeLinks = (docRef, doctype, client) => { const filterDeletedDocumentsFromRows = doc => !!doc -export const fromPouchResult = (res, withRows, doctype, client) => { +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 diff --git a/packages/cozy-pouch-link/src/jsonapi.spec.js b/packages/cozy-pouch-link/src/jsonapi.spec.js index 9dcafd01e8..ba928c8e38 100644 --- a/packages/cozy-pouch-link/src/jsonapi.spec.js +++ b/packages/cozy-pouch-link/src/jsonapi.spec.js @@ -1,3 +1,5 @@ +import CozyClient from 'cozy-client' + import { fromPouchResult, normalizeDoc } from './jsonapi' const BART_FIXTURE = { @@ -26,6 +28,10 @@ const DELETED_DOC_FIXTURE = { delete: true } +const token = 'fake_token' +const uri = 'https://claude.mycozy.cloud' +const client = new CozyClient({ token, uri }) + describe('doc normalization', () => { it('keeps the highest between rev and _rev and removes the rev attribute', () => { const normalized = normalizeDoc( @@ -58,7 +64,12 @@ describe('jsonapi', () => { const res = { rows: [BART_FIXTURE, LISA_FIXTURE, MARGE_FIXTURE, DELETED_DOC_FIXTURE] } - const normalized = fromPouchResult(res, true, 'io.cozy.simpsons') + const normalized = fromPouchResult({ + res, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(normalized.data[0].name).toBe('Bart') expect(normalized.data[0].id).toBe(1) expect(normalized.data[0]._id).toBe(1) @@ -76,13 +87,23 @@ describe('jsonapi', () => { describe('pagination', () => { it('has no next when there is no pagination information', () => { const res = { rows: [BART_FIXTURE] } - const normalized = fromPouchResult(res, true, 'io.cozy.simpsons') + const normalized = fromPouchResult({ + res, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(normalized.next).toBe(false) }) it('paginates when there is a total_rows field greater than the rows number', () => { const res = { rows: [BART_FIXTURE], total_rows: 3 } - const normalized = fromPouchResult(res, true, 'io.cozy.simpsons') + const normalized = fromPouchResult({ + res, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(normalized.next).toBe(true) }) @@ -91,17 +112,32 @@ describe('jsonapi', () => { rows: [BART_FIXTURE, MARGE_FIXTURE, LISA_FIXTURE], total_rows: 3 } - const normalized = fromPouchResult(res, true, 'io.cozy.simpsons') + const normalized = fromPouchResult({ + res, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(normalized.next).toBe(false) }) it('paginates when there is a limit field', () => { const res = { rows: [BART_FIXTURE, LISA_FIXTURE], limit: 2 } - const normalized = fromPouchResult(res, true, 'io.cozy.simpsons') + const normalized = fromPouchResult({ + res, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(normalized.next).toBe(true) const lastRes = { rows: [MARGE_FIXTURE], limit: 2 } - const lastNormalized = fromPouchResult(lastRes, true, 'io.cozy.simpsons') + const lastNormalized = fromPouchResult({ + res: lastRes, + withRows: true, + doctype: 'io.cozy.simpsons', + client + }) expect(lastNormalized.next).toBe(false) }) }) From fd3414cc7113692f638bb34a51cf044672677d10 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 18:46:46 +0200 Subject: [PATCH 58/84] refactor: Extract `normalizeFindSelector` method from `executeQuery` We want to extract this code logic into its own method in order to ease readability and testing Also we refactored the code to make it easier to read This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716485575 --- packages/cozy-pouch-link/src/CozyPouchLink.js | 24 +++-------- .../cozy-pouch-link/src/CozyPouchLink.spec.js | 2 + packages/cozy-pouch-link/src/helpers.js | 17 ++++++++ packages/cozy-pouch-link/src/helpers.spec.js | 40 ++++++++++++++++++- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index c8b421b1ab..e9329beb0f 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -556,25 +556,11 @@ 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(findSelector).includes(indexedField)) { - findSelector[indexedField] = { - $gt: null - } - } - } - } - if (shouldAddId) { - findSelector['_id'] = { - $gt: null - } - } + const findSelector = helpers.normalizeFindSelector( + selector, + indexedFields + ) + const findOpts = { sort, selector: findSelector, diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index 3aab87189f..a9689ea143 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -4,6 +4,8 @@ import { find, allDocs, withoutDesignDocuments } from './helpers' jest.mock('./helpers', () => ({ find: jest.fn(), allDocs: jest.fn(), + normalizeFindSelector: jest.requireActual('./helpers').default + .normalizeFindSelector, withoutDesignDocuments: jest.fn() })) diff --git a/packages/cozy-pouch-link/src/helpers.js b/packages/cozy-pouch-link/src/helpers.js index e71ec98a17..a5eb4fd97d 100644 --- a/packages/cozy-pouch-link/src/helpers.js +++ b/packages/cozy-pouch-link/src/helpers.js @@ -61,4 +61,21 @@ helpers.insertBulkDocs = async (db, docs) => { return db.bulkDocs(docs, { new_edits: false }) } +helpers.normalizeFindSelector = (selector, indexedFields) => { + let findSelector = selector || {} + if (indexedFields) { + for (const indexedField of indexedFields) { + if (!Object.keys(findSelector).includes(indexedField)) { + findSelector[indexedField] = { + $gt: null + } + } + } + } + + return Object.keys(findSelector).length > 0 + ? findSelector + : { _id: { $gt: null } } // PouchDB does not accept empty selector +} + export default helpers diff --git a/packages/cozy-pouch-link/src/helpers.spec.js b/packages/cozy-pouch-link/src/helpers.spec.js index 5f57ed557f..b77fd7fc50 100644 --- a/packages/cozy-pouch-link/src/helpers.spec.js +++ b/packages/cozy-pouch-link/src/helpers.spec.js @@ -1,5 +1,10 @@ import helpers from './helpers' -const { withoutDesignDocuments, isDeletedDocument, isDesignDocument } = helpers +const { + withoutDesignDocuments, + isDeletedDocument, + isDesignDocument, + normalizeFindSelector +} = helpers import PouchDB from 'pouchdb-browser' import PouchDBFind from 'pouchdb-find' @@ -113,4 +118,37 @@ describe('Helpers', () => { expect(isDeletedDocument({ _id: 'notdeleted' })).toBeFalsy() }) }) + + describe('normalizeFindSelector', () => { + it('should add indexed fields in the selector if they are missing', () => { + const selector = { + SOME_FIELD: { $gt: null } + } + const indexedFields = ['SOME_INDEXED_FIELD'] + + const findSelector = normalizeFindSelector(selector, indexedFields) + expect(findSelector).toStrictEqual({ + SOME_FIELD: { $gt: null }, + SOME_INDEXED_FIELD: { $gt: null } + }) + }) + + it('should prevent empty selector by adding a selector on _id when no selector is provided', () => { + const selector = undefined + const indexedFields = undefined + + const findSelector = normalizeFindSelector(selector, indexedFields) + expect(findSelector).toStrictEqual({ _id: { $gt: null } }) + }) + + it('should not add selector on _id when no selector is provided but there are some indexed fields', () => { + const selector = undefined + const indexedFields = ['SOME_INDEXED_FIELD'] + + const findSelector = normalizeFindSelector(selector, indexedFields) + expect(findSelector).toStrictEqual({ + SOME_INDEXED_FIELD: { $gt: null } + }) + }) + }) }) From 66e8275e996ff79619c1a95b249ee4d2d7f29dfb Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:43:46 +0200 Subject: [PATCH 59/84] test: Add a unit test for `ensureIndex` that tests a partial filter This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716481241 --- .../cozy-pouch-link/src/CozyPouchLink.spec.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index a9689ea143..16465b27ef 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -599,6 +599,28 @@ describe('CozyPouchLink', () => { }) }) + it('should handle partial filters', async () => { + spy = jest.spyOn(PouchDB.prototype, 'createIndex').mockReturnValue({}) + await setup() + await link.ensureIndex(TODO_DOCTYPE, { + indexedFields: ['myIndex'], + partialFilter: { SOME_FIELD: { $exists: true } } + }) + expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith({ + index: { + ddoc: 'by_myIndex_filter_(SOME_FIELD_$exists_true)', + fields: ['myIndex'], + indexName: 'by_myIndex_filter_(SOME_FIELD_$exists_true)', + partial_filter_selector: { + SOME_FIELD: { + $exists: true + } + } + } + }) + }) + it('uses the specified index', async () => { let spyIndex = jest .spyOn(CozyPouchLink.prototype, 'ensureIndex') From 3133d09fa91dfcc64054cd2975e1567a0544231f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 27 Aug 2024 18:50:53 +0200 Subject: [PATCH 60/84] feat: Add a warning when an indexedField is missing in query selector This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716484732 --- packages/cozy-pouch-link/src/helpers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/cozy-pouch-link/src/helpers.js b/packages/cozy-pouch-link/src/helpers.js index a5eb4fd97d..9336adfd8a 100644 --- a/packages/cozy-pouch-link/src/helpers.js +++ b/packages/cozy-pouch-link/src/helpers.js @@ -1,5 +1,7 @@ import startsWith from 'lodash/startsWith' +import logger from './logger' + const helpers = {} // https://github.com/pouchdb/pouchdb/issues/7011 @@ -66,6 +68,10 @@ helpers.normalizeFindSelector = (selector, indexedFields) => { if (indexedFields) { for (const indexedField of indexedFields) { if (!Object.keys(findSelector).includes(indexedField)) { + const selectorJson = JSON.stringify(selector) + logger.warn( + `${indexedField} was missing in selector, it has been automatically added from indexed fields. Please consider adding this field to your query's selector as required by PouchDB. The query's selector is: ${selectorJson}` + ) findSelector[indexedField] = { $gt: null } From d7bd364d90a575fce0342de03a160faffbb1b8fa Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 28 Aug 2024 12:47:41 +0200 Subject: [PATCH 61/84] refactor: Rename `persistData` into `persistCozyData` This replies to https://github.com/cozy/cozy-client/pull/1486#issuecomment-2284105592 --- packages/cozy-client/src/CozyClient.js | 2 +- packages/cozy-client/src/CozyLink.js | 10 +++++----- packages/cozy-client/src/FlagshipLink.js | 2 +- packages/cozy-client/src/StackLink.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 9fc1c12aa7..9e8077839b 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1157,7 +1157,7 @@ client.query(Q('io.cozy.bills'))`) } if ((!document.meta?.rev && !document._rev) || enforce) { - await this.chain.persistData(document) + await this.chain.persistCozyData(document) } } diff --git a/packages/cozy-client/src/CozyLink.js b/packages/cozy-client/src/CozyLink.js index baec94a6b6..cb808cadc1 100644 --- a/packages/cozy-client/src/CozyLink.js +++ b/packages/cozy-client/src/CozyLink.js @@ -5,7 +5,7 @@ export default class CozyLink { } if (typeof persistHandler === 'function') { - this.persistData = persistHandler + this.persistCozyData = persistHandler } } @@ -13,8 +13,8 @@ export default class CozyLink { throw new Error('request is not implemented') } - persistData(data, forward) { - throw new Error('persistData is not implemented') + persistCozyData(data, forward) { + throw new Error('persistCozyData is not implemented') } } @@ -52,9 +52,9 @@ const concat = (firstLink, nextLink) => { const persistHandler = (data, forward) => { const nextForward = d => { - return nextLink.persistData(d, forward) + return nextLink.persistCozyData(d, forward) } - return firstLink.persistData(data, nextForward) + return firstLink.persistCozyData(data, nextForward) } return new CozyLink(requestHandler, persistHandler) diff --git a/packages/cozy-client/src/FlagshipLink.js b/packages/cozy-client/src/FlagshipLink.js index 0e9ff0348c..d9bccb2c7c 100644 --- a/packages/cozy-client/src/FlagshipLink.js +++ b/packages/cozy-client/src/FlagshipLink.js @@ -22,7 +22,7 @@ export default class FlagshipLink extends CozyLink { return this.webviewIntent.call('flagshipLinkRequest', operation) } - async persistData(data, forward) { + async persistCozyData(data, forward) { // Persist data should do nothing here as data is already persisted on Flagship side return } diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index 95ea4e9d10..65d623700e 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -102,7 +102,7 @@ export default class StackLink extends CozyLink { } } - async persistData(data, forward) { + async persistCozyData(data, forward) { return forward(data) } /** From 3903b71f3ca26251c6de960e7d535e2856eed698 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 28 Aug 2024 12:48:39 +0200 Subject: [PATCH 62/84] refactor: Rename `normalizeLinks` into `normalizeAppsLinks` This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716550440 --- packages/cozy-pouch-link/src/jsonapi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js index b3a21389f9..f815877cc2 100644 --- a/packages/cozy-pouch-link/src/jsonapi.js +++ b/packages/cozy-pouch-link/src/jsonapi.js @@ -25,12 +25,12 @@ export const normalizeDoc = (doc, doctype, client) => { delete normalizedDoc.rev } - normalizeLinks(normalizedDoc, doctype, client) + normalizeAppsLinks(normalizedDoc, doctype, client) return normalizedDoc } -const normalizeLinks = (docRef, doctype, client) => { +const normalizeAppsLinks = (docRef, doctype, client) => { if (doctype !== 'io.cozy.apps') { return } From 824633ad3a2d74de381b6f21ac0ba101c3d5236d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 28 Aug 2024 12:51:47 +0200 Subject: [PATCH 63/84] feat: Force _rev for queries with .select fields This commit is a copy of #1517 applied to CozyPouchLink When specifying fields in a query, e.g. `Q('io.cozy.todos').where({done: true}).select(['date'])`, the revision was missing if not explicitly given. This is now problematic because we rely on the revision existence to identify "virtual" documents, i.e. not persisted in CouchDB, that never have any revision. See #1486 for more insights. --- packages/cozy-pouch-link/src/CozyPouchLink.js | 6 +++--- packages/cozy-pouch-link/src/CozyPouchLink.spec.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index e9329beb0f..4b44c131c0 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -564,9 +564,9 @@ class PouchLink extends CozyLink { const findOpts = { sort, 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, + // same selector as Document Collection. + // _id is necessary for the store, and _rev is required for offline. See https://github.com/cozy/cozy-client/blob/95978d39546023920b0c01d689fed5dd41577a02/packages/cozy-client/src/CozyClient.js#L1153 + fields: fields ? [...fields, '_id', '_rev'] : undefined, limit, skip } diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index 16465b27ef..d0c3333bba 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -313,7 +313,7 @@ describe('CozyPouchLink', () => { expect(find).toHaveBeenLastCalledWith( expect.anything(), expect.objectContaining({ - fields: ['label', 'done', '_id', '_type', 'class'] + fields: ['label', 'done', '_id', '_rev'] }) ) }) From 185c5c242a69af3eaa12a4e5d992785629ff8e23 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 28 Aug 2024 16:51:21 +0200 Subject: [PATCH 64/84] feat(pouch-link): Handle missing selector predicates for sorted values With CouchDB, it is possible to make a mango query on an index without having any predicate on an sorted field in the selector It is not possible with PouchDB that requires to have any sorted fields to be in the selector We automatically handle that to avoid breaking existing queries --- packages/cozy-pouch-link/src/CozyPouchLink.js | 5 +- packages/cozy-pouch-link/src/helpers.js | 17 +++++- packages/cozy-pouch-link/src/helpers.spec.js | 52 +++++++++++++++++-- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 4b44c131c0..32d35b7ea9 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -556,10 +556,11 @@ class PouchLink extends CozyLink { res = withoutDesignDocuments(res) withRows = true } else { - const findSelector = helpers.normalizeFindSelector( + const findSelector = helpers.normalizeFindSelector({ selector, + sort, indexedFields - ) + }) const findOpts = { sort, diff --git a/packages/cozy-pouch-link/src/helpers.js b/packages/cozy-pouch-link/src/helpers.js index 9336adfd8a..afb5d79534 100644 --- a/packages/cozy-pouch-link/src/helpers.js +++ b/packages/cozy-pouch-link/src/helpers.js @@ -63,7 +63,7 @@ helpers.insertBulkDocs = async (db, docs) => { return db.bulkDocs(docs, { new_edits: false }) } -helpers.normalizeFindSelector = (selector, indexedFields) => { +helpers.normalizeFindSelector = ({ selector, sort, indexedFields }) => { let findSelector = selector || {} if (indexedFields) { for (const indexedField of indexedFields) { @@ -79,6 +79,21 @@ helpers.normalizeFindSelector = (selector, indexedFields) => { } } + if (sort) { + const sortedFields = sort.flatMap(s => Object.keys(s)) + for (const sortedField of sortedFields) { + if (!Object.keys(findSelector).includes(sortedField)) { + const selectorJson = JSON.stringify(selector) + logger.warn( + `${sortedField} was missing in selector, it has been automatically added from sorted fields. Please consider adding this field to your query's selector as required by PouchDB. The query's selector is: ${selectorJson}` + ) + findSelector[sortedField] = { + $gt: null + } + } + } + } + return Object.keys(findSelector).length > 0 ? findSelector : { _id: { $gt: null } } // PouchDB does not accept empty selector diff --git a/packages/cozy-pouch-link/src/helpers.spec.js b/packages/cozy-pouch-link/src/helpers.spec.js index b77fd7fc50..14eaec0b5a 100644 --- a/packages/cozy-pouch-link/src/helpers.spec.js +++ b/packages/cozy-pouch-link/src/helpers.spec.js @@ -124,28 +124,74 @@ describe('Helpers', () => { const selector = { SOME_FIELD: { $gt: null } } + const sort = undefined const indexedFields = ['SOME_INDEXED_FIELD'] - const findSelector = normalizeFindSelector(selector, indexedFields) + const findSelector = normalizeFindSelector({ + selector, + sort, + indexedFields + }) expect(findSelector).toStrictEqual({ SOME_FIELD: { $gt: null }, SOME_INDEXED_FIELD: { $gt: null } }) }) + it('should add sorted fields in the selector if they are missing', () => { + const selector = {} + const sort = [{ SOME_SORTED_FIELD: 'asc' }] + const indexedFields = undefined + + const findSelector = normalizeFindSelector({ + selector, + sort, + indexedFields + }) + expect(findSelector).toStrictEqual({ + SOME_SORTED_FIELD: { $gt: null } + }) + }) + + it('should add indexed fields AND sorted fields in the selector if they are missing', () => { + const selector = undefined + const sort = [{ SOME_SORTED_FIELD: 'asc' }] + const indexedFields = ['SOME_INDEXED_FIELD'] + + const findSelector = normalizeFindSelector({ + selector, + sort, + indexedFields + }) + expect(findSelector).toStrictEqual({ + SOME_INDEXED_FIELD: { $gt: null }, + SOME_SORTED_FIELD: { $gt: null } + }) + }) + it('should prevent empty selector by adding a selector on _id when no selector is provided', () => { const selector = undefined + const sort = undefined const indexedFields = undefined - const findSelector = normalizeFindSelector(selector, indexedFields) + const findSelector = normalizeFindSelector({ + selector, + sort, + indexedFields + }) expect(findSelector).toStrictEqual({ _id: { $gt: null } }) }) it('should not add selector on _id when no selector is provided but there are some indexed fields', () => { const selector = undefined + const sort = undefined const indexedFields = ['SOME_INDEXED_FIELD'] - const findSelector = normalizeFindSelector(selector, indexedFields) + const findSelector = normalizeFindSelector({ + selector, + sort, + indexedFields + }) expect(findSelector).toStrictEqual({ SOME_INDEXED_FIELD: { $gt: null } }) From ba711e48a0e1bfa375dc37179c01d9a4b09246f4 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 17:13:05 +0200 Subject: [PATCH 65/84] revert: Remove `cozyFromPouch` guard This guard has been added to handle documents with no `meta.rev` but since we added a check on `_rev` (in addition to `meta.rev`) to trigger the persistence, then we don't need this guard anymore This replies to https://github.com/cozy/cozy-client/pull/1486#discussion_r1713802232 Related commit: b797eb3f021760b68f8277c22f8dc3229a4a2a0c Related commit: 62290edde1fa102e7ffb7e51b700a49b70ee4cd5 --- packages/cozy-client/src/CozyClient.js | 2 +- packages/cozy-client/src/types.js | 1 - packages/cozy-pouch-link/src/CozyPouchLink.spec.js | 1 - packages/cozy-pouch-link/src/jsonapi.js | 1 - packages/cozy-pouch-link/src/jsonapi.spec.js | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 9e8077839b..b2beddd7b3 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1152,7 +1152,7 @@ client.query(Q('io.cozy.bills'))`) * @returns {Promise} */ async persistVirtualDocument(document, enforce) { - if (!document || document.cozyLocalOnly || document.cozyFromPouch) { + if (!document || document.cozyLocalOnly) { return } diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index 945bb91ee4..6c6a8a7a6b 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -436,7 +436,6 @@ import { QueryDefinition } from './queries/dsl' * @property {CozyMetadata} [cozyMetadata] - Cozy Metadata * @property {CozyClientDocumentMeta} [meta] - Pouch Metadata * @property {boolean} [cozyLocalOnly] - When true the document should NOT be replicated to the remote database - * @property {boolean} [cozyFromPouch] - When true the document has been retrieved from a local PouchDB */ /** diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js index d0c3333bba..f08c82b70d 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.spec.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.spec.js @@ -453,7 +453,6 @@ describe('CozyPouchLink', () => { _id: '1', _rev: '1-deadbeef', _type: 'io.cozy.todos', - cozyFromPouch: true, done: false, id: '1', label: 'Buy bread', diff --git a/packages/cozy-pouch-link/src/jsonapi.js b/packages/cozy-pouch-link/src/jsonapi.js index f815877cc2..a54223ed94 100644 --- a/packages/cozy-pouch-link/src/jsonapi.js +++ b/packages/cozy-pouch-link/src/jsonapi.js @@ -15,7 +15,6 @@ export const normalizeDoc = (doc, doctype, client) => { _id: id, _rev, _type: doctype, - cozyFromPouch: true, relationships: { ...relationships, referenced_by diff --git a/packages/cozy-pouch-link/src/jsonapi.spec.js b/packages/cozy-pouch-link/src/jsonapi.spec.js index ba928c8e38..f507c1034b 100644 --- a/packages/cozy-pouch-link/src/jsonapi.spec.js +++ b/packages/cozy-pouch-link/src/jsonapi.spec.js @@ -49,7 +49,6 @@ describe('doc normalization', () => { id: 1234, _rev: '4-cffee', _type: 'io.cozy.contacts', - cozyFromPouch: true, firstName: 'Bobba', lastName: 'Fett', relationships: { From 09226ae454cdd943f01b2fd917d7afc414a5364c Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 17:25:26 +0200 Subject: [PATCH 66/84] revert: Remove `ignoreWarmup` option Previous implementation was from a misunderstanding of CozyPouchLink mechanisms As we don't need to specify warmup queries in CozyPouchLink instanciation, we don't need the `ignoreWarmup` as we would result to the same behavior of having no warmup queries Warmup queries concept is meant to be removed into the future as we won't be able to use them when offline and the scenario that needed them (cozy-banks and cozy-drive apps) does not exist anymore This replies to https://github.com/cozy/cozy-client/pull/1506#discussion_r1716561921 Related commit: bb43ae986139aac12f286e6a7011c5ff104addc8 --- 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 32d35b7ea9..7d444976bf 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -87,7 +87,7 @@ class PouchLink extends CozyLink { constructor(opts) { const options = defaults({}, opts, DEFAULT_OPTIONS) super(options) - const { doctypes, doctypesReplicationOptions, ignoreWarmup } = options + const { doctypes, doctypesReplicationOptions } = options this.options = options if (!doctypes) { throw new Error( @@ -100,7 +100,6 @@ 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 || {} @@ -366,7 +365,7 @@ class PouchLink extends CozyLink { return forward(operation) } - if (!this.ignoreWarmup && (await this.needsToWaitWarmup(doctype))) { + if (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 d4f0adccf1cb1d379401ef53bf95fdc4c391fad3 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 18:11:43 +0200 Subject: [PATCH 67/84] docs: Update types and documentation --- docs/api/cozy-client/classes/CozyLink.md | 4 +- docs/api/cozy-client/classes/FlagshipLink.md | 30 ++--- docs/api/cozy-client/classes/StackLink.md | 6 +- docs/api/cozy-pouch-link/classes/PouchLink.md | 117 ++++++++++-------- packages/cozy-client/types/CozyClient.d.ts | 4 - packages/cozy-client/types/CozyLink.d.ts | 2 +- packages/cozy-client/types/FlagshipLink.d.ts | 7 +- packages/cozy-client/types/types.d.ts | 4 - .../cozy-pouch-link/types/CozyPouchLink.d.ts | 2 +- packages/cozy-pouch-link/types/helpers.d.ts | 5 + packages/cozy-pouch-link/types/jsonapi.d.ts | 7 +- .../types/startReplication.d.ts | 7 +- 12 files changed, 98 insertions(+), 97 deletions(-) diff --git a/docs/api/cozy-client/classes/CozyLink.md b/docs/api/cozy-client/classes/CozyLink.md index ce4ace3801..ad48952f95 100644 --- a/docs/api/cozy-client/classes/CozyLink.md +++ b/docs/api/cozy-client/classes/CozyLink.md @@ -29,9 +29,9 @@ ## Methods -### persistData +### persistCozyData -▸ **persistData**(`data`, `forward`): `void` +▸ **persistCozyData**(`data`, `forward`): `void` *Parameters* diff --git a/docs/api/cozy-client/classes/FlagshipLink.md b/docs/api/cozy-client/classes/FlagshipLink.md index f45a9368f7..e1147b5552 100644 --- a/docs/api/cozy-client/classes/FlagshipLink.md +++ b/docs/api/cozy-client/classes/FlagshipLink.md @@ -19,8 +19,6 @@ | Name | Type | Description | | :------ | :------ | :------ | | `[options]` | `Object` | Options | -| `[options].client` | `any` | - | -| `[options].stackClient` | `any` | - | | `[options].webviewIntent` | `WebviewService` | - | *Overrides* @@ -29,33 +27,23 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:11](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L11) +[packages/cozy-client/src/FlagshipLink.js:8](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L8) ## Properties -### stackClient - -• **stackClient**: `any` - -*Defined in* - -[packages/cozy-client/src/FlagshipLink.js:18](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L18) - -*** - ### webviewIntent • **webviewIntent**: `WebviewService` *Defined in* -[packages/cozy-client/src/FlagshipLink.js:19](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L19) +[packages/cozy-client/src/FlagshipLink.js:10](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L10) ## Methods -### persistData +### persistCozyData -▸ **persistData**(`data`, `forward`): `Promise`<`void`> +▸ **persistCozyData**(`data`, `forward`): `Promise`<`void`> *Parameters* @@ -70,11 +58,11 @@ *Overrides* -[CozyLink](CozyLink.md).[persistData](CozyLink.md#persistdata) +[CozyLink](CozyLink.md).[persistCozyData](CozyLink.md#persistcozydata) *Defined in* -[packages/cozy-client/src/FlagshipLink.js:34](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L34) +[packages/cozy-client/src/FlagshipLink.js:25](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L25) *** @@ -94,7 +82,7 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:22](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L22) +[packages/cozy-client/src/FlagshipLink.js:13](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L13) *** @@ -120,7 +108,7 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:30](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L30) +[packages/cozy-client/src/FlagshipLink.js:21](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L21) *** @@ -134,4 +122,4 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:26](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L26) +[packages/cozy-client/src/FlagshipLink.js:17](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L17) diff --git a/docs/api/cozy-client/classes/StackLink.md b/docs/api/cozy-client/classes/StackLink.md index 5bdcd6533b..45f4530f4f 100644 --- a/docs/api/cozy-client/classes/StackLink.md +++ b/docs/api/cozy-client/classes/StackLink.md @@ -94,9 +94,9 @@ Transfers queries and mutations to a remote stack *** -### persistData +### persistCozyData -▸ **persistData**(`data`, `forward`): `Promise`<`any`> +▸ **persistCozyData**(`data`, `forward`): `Promise`<`any`> *Parameters* @@ -111,7 +111,7 @@ Transfers queries and mutations to a remote stack *Overrides* -[CozyLink](CozyLink.md).[persistData](CozyLink.md#persistdata) +[CozyLink](CozyLink.md).[persistCozyData](CozyLink.md#persistcozydata) *Defined in* diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index 6402d9c84d..db60627986 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -32,7 +32,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) +[cozy-pouch-link/src/CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) ## Properties @@ -42,7 +42,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:141](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L141) +[cozy-pouch-link/src/CozyPouchLink.js:140](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L140) *** @@ -52,7 +52,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:97](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L97) +[cozy-pouch-link/src/CozyPouchLink.js:97](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L97) *** @@ -62,17 +62,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:98](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L98) - -*** - -### ignoreWarmup - -• **ignoreWarmup**: `any` - -*Defined in* - -[CozyPouchLink.js:103](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L103) +[cozy-pouch-link/src/CozyPouchLink.js:98](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L98) *** @@ -82,7 +72,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +[cozy-pouch-link/src/CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) *** @@ -92,7 +82,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) +[cozy-pouch-link/src/CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) *** @@ -102,7 +92,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:211](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L211) +[cozy-pouch-link/src/CozyPouchLink.js:210](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L210) *** @@ -112,7 +102,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:106](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L106) +[cozy-pouch-link/src/CozyPouchLink.js:105](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L105) *** @@ -122,7 +112,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:100](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L100) +[cozy-pouch-link/src/CozyPouchLink.js:100](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L100) ## Methods @@ -142,7 +132,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:670](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L670) +[cozy-pouch-link/src/CozyPouchLink.js:661](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L661) *** @@ -162,7 +152,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:631](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L631) +[cozy-pouch-link/src/CozyPouchLink.js:622](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L622) *** @@ -188,7 +178,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:468](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L468) +[cozy-pouch-link/src/CozyPouchLink.js:467](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L467) *** @@ -209,7 +199,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:674](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L674) +[cozy-pouch-link/src/CozyPouchLink.js:665](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L665) *** @@ -229,7 +219,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:659](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L659) +[cozy-pouch-link/src/CozyPouchLink.js:650](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L650) *** @@ -251,7 +241,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:601](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L601) +[cozy-pouch-link/src/CozyPouchLink.js:592](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L592) *** @@ -271,7 +261,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:532](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L532) +[cozy-pouch-link/src/CozyPouchLink.js:531](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L531) *** @@ -295,7 +285,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:492](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L492) +[cozy-pouch-link/src/CozyPouchLink.js:491](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L491) *** @@ -315,7 +305,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:328](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L328) +[cozy-pouch-link/src/CozyPouchLink.js:327](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L327) *** @@ -335,7 +325,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:121](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L121) +[cozy-pouch-link/src/CozyPouchLink.js:120](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L120) *** @@ -355,7 +345,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:324](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L324) +[cozy-pouch-link/src/CozyPouchLink.js:323](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L323) *** @@ -375,7 +365,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:265](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L265) +[cozy-pouch-link/src/CozyPouchLink.js:264](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L264) *** @@ -395,7 +385,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[CozyPouchLink.js:260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L260) +[cozy-pouch-link/src/CozyPouchLink.js:259](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L259) *** @@ -421,7 +411,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:246](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L246) +[cozy-pouch-link/src/CozyPouchLink.js:245](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L245) *** @@ -441,7 +431,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:454](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L454) +[cozy-pouch-link/src/CozyPouchLink.js:453](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L453) *** @@ -471,7 +461,7 @@ Migrate the current adapter *Defined in* -[CozyPouchLink.js:155](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L155) +[cozy-pouch-link/src/CozyPouchLink.js:154](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L154) *** @@ -496,7 +486,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:440](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L440) +[cozy-pouch-link/src/CozyPouchLink.js:439](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L439) *** @@ -510,7 +500,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:174](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L174) +[cozy-pouch-link/src/CozyPouchLink.js:173](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L173) *** @@ -530,7 +520,32 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:304](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L304) +[cozy-pouch-link/src/CozyPouchLink.js:303](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L303) + +*** + +### persistCozyData + +▸ **persistCozyData**(`data`, `forward`): `void` + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `data` | `any` | +| `forward` | `any` | + +*Returns* + +`void` + +*Inherited from* + +CozyLink.persistCozyData + +*Defined in* + +[cozy-client/types/CozyLink.d.ts:4](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/types/CozyLink.d.ts#L4) *** @@ -549,13 +564,9 @@ the need to wait for the warmup `Promise`<`void`> -*Overrides* - -CozyLink.persistData - *Defined in* -[CozyPouchLink.js:395](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L395) +[cozy-pouch-link/src/CozyPouchLink.js:394](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L394) *** @@ -575,7 +586,7 @@ CozyLink.persistData *Defined in* -[CozyPouchLink.js:140](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L140) +[cozy-pouch-link/src/CozyPouchLink.js:139](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L139) *** @@ -601,7 +612,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:347](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L347) +[cozy-pouch-link/src/CozyPouchLink.js:346](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L346) *** @@ -615,7 +626,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:230](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L230) +[cozy-pouch-link/src/CozyPouchLink.js:229](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L229) *** @@ -634,7 +645,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[CozyPouchLink.js:279](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L279) +[cozy-pouch-link/src/CozyPouchLink.js:278](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L278) *** @@ -653,7 +664,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:296](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L296) +[cozy-pouch-link/src/CozyPouchLink.js:295](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L295) *** @@ -673,7 +684,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:332](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L332) +[cozy-pouch-link/src/CozyPouchLink.js:331](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L331) *** @@ -687,7 +698,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:696](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L696) +[cozy-pouch-link/src/CozyPouchLink.js:687](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L687) *** @@ -707,7 +718,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:636](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L636) +[cozy-pouch-link/src/CozyPouchLink.js:627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L627) *** @@ -727,7 +738,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:641](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L641) +[cozy-pouch-link/src/CozyPouchLink.js:632](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L632) *** @@ -752,4 +763,4 @@ The adapter name *Defined in* -[CozyPouchLink.js:116](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L116) +[cozy-pouch-link/src/CozyPouchLink.js:115](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L115) diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index 106e61903f..89845ac3b0 100644 --- a/packages/cozy-client/types/CozyClient.d.ts +++ b/packages/cozy-client/types/CozyClient.d.ts @@ -40,10 +40,6 @@ export type CozyClientDocument = { * - When true the document should NOT be replicated to the remote database */ cozyLocalOnly?: boolean; - /** - * - When true the document has been retrieved from a local PouchDB - */ - cozyFromPouch?: boolean; }; export type ClientOptions = { client?: object; diff --git a/packages/cozy-client/types/CozyLink.d.ts b/packages/cozy-client/types/CozyLink.d.ts index bf6173d9c0..5d229867d8 100644 --- a/packages/cozy-client/types/CozyLink.d.ts +++ b/packages/cozy-client/types/CozyLink.d.ts @@ -1,6 +1,6 @@ export default class CozyLink { constructor(requestHandler: any, persistHandler: any); request(operation: any, result: any, forward: any): void; - persistData(data: any, forward: any): void; + persistCozyData(data: any, forward: any): void; } export function chain(links: any): any; diff --git a/packages/cozy-client/types/FlagshipLink.d.ts b/packages/cozy-client/types/FlagshipLink.d.ts index 4dfbc56053..3b3e389fa8 100644 --- a/packages/cozy-client/types/FlagshipLink.d.ts +++ b/packages/cozy-client/types/FlagshipLink.d.ts @@ -1,16 +1,11 @@ export default class FlagshipLink extends CozyLink { /** * @param {object} [options] - Options - * @param {object} [options.stackClient] - A StackClient - * @param {object} [options.client] - A StackClient (deprecated) * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference */ - constructor({ client, stackClient, webviewIntent }?: { - stackClient: object; - client: object; + constructor({ webviewIntent }?: { webviewIntent: import('cozy-intent').WebviewService; }); - stackClient: any; webviewIntent: import("cozy-intent").WebviewService; registerClient(client: any): void; reset(): void; diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index 48fe11151d..103a91fe6b 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -758,10 +758,6 @@ export type CozyClientDocument = { * - When true the document should NOT be replicated to the remote database */ cozyLocalOnly?: boolean; - /** - * - When true the document has been retrieved from a local PouchDB - */ - cozyFromPouch?: boolean; }; /** * - A io.cozy.files document's metadata diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index edac36c855..b972a56b36 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -60,7 +60,6 @@ declare class PouchLink extends CozyLink { doctypesReplicationOptions: Record; indexes: {}; storage: PouchLocalStorage; - ignoreWarmup: any; /** @type {Record} - Stores replication states per doctype */ replicationStatus: Record; getReplicationURL(doctype: any): string; @@ -132,6 +131,7 @@ declare class PouchLink extends CozyLink { getSyncInfo(doctype: any): import("./types").SyncInfo; getPouch(doctype: any): any; supportsOperation(operation: any): boolean; + persistData(data: any, forward?: (operation: any, result?: any) => void): Promise; /** * Retrieve the existing document from Pouch * diff --git a/packages/cozy-pouch-link/types/helpers.d.ts b/packages/cozy-pouch-link/types/helpers.d.ts index 81dc61d84d..f533cb7334 100644 --- a/packages/cozy-pouch-link/types/helpers.d.ts +++ b/packages/cozy-pouch-link/types/helpers.d.ts @@ -8,4 +8,9 @@ declare namespace helpers { function isDesignDocument(doc: any): boolean; function isDeletedDocument(doc: any): any; function insertBulkDocs(db: any, docs: any): Promise; + function normalizeFindSelector({ selector, sort, indexedFields }: { + selector: any; + sort: any; + indexedFields: any; + }): any; } diff --git a/packages/cozy-pouch-link/types/jsonapi.d.ts b/packages/cozy-pouch-link/types/jsonapi.d.ts index 6dcd082731..886261a900 100644 --- a/packages/cozy-pouch-link/types/jsonapi.d.ts +++ b/packages/cozy-pouch-link/types/jsonapi.d.ts @@ -1,5 +1,10 @@ export function normalizeDoc(doc: any, doctype: any, client: any): any; -export function fromPouchResult(res: any, withRows: any, doctype: any, client: any): { +export function fromPouchResult({ res, withRows, doctype, client }: { + res: any; + withRows: any; + doctype: any; + client: any; +}): { data: any; meta?: undefined; skip?: undefined; diff --git a/packages/cozy-pouch-link/types/startReplication.d.ts b/packages/cozy-pouch-link/types/startReplication.d.ts index 44ff8eb07b..5b769edb96 100644 --- a/packages/cozy-pouch-link/types/startReplication.d.ts +++ b/packages/cozy-pouch-link/types/startReplication.d.ts @@ -4,4 +4,9 @@ export function startReplication(pouch: object, replicationOptions: { doctype: string; warmupQueries: import('cozy-client/types/types').Query[]; }, getReplicationURL: Function, storage: import('./localStorage').PouchLocalStorage): import('./types').CancelablePromise; -export function replicateAllDocs(db: object, baseUrl: string, doctype: string, storage: import('./localStorage').PouchLocalStorage): Promise; +export function replicateAllDocs({ db, baseUrl, doctype, storage }: { + db: object; + baseUrl: string; + doctype: string; + storage: import('./localStorage').PouchLocalStorage; +}): Promise; From 118a34385fa94a4d9690399428c87cc62fa08845 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 28 Aug 2024 12:50:03 +0200 Subject: [PATCH 68/84] fix: Remove `attributes` and `meta` attributes before persisting docs Those doc's attributes are specific to the JSON API and should not be inserted into the Pouch database This implies that we will have a difference on documents regarding if they are served through the cozy-stack or through a local PouchDB, the first one may include those fields in their result, but not the second one So from now we should avoid, as much as possible, to relies on the `attributes` member to prevent bugs on Offline mode. Multiple commits to fix usages of `attributes` in cozy-client will be done after this one Usage of `attributes` and `meta` members on cozy-app will also have to be fixed. This will be a requirement to implement Offline mode on cozy-apps This replies to https://github.com/cozy/cozy-client/pull/1486#discussion_r1713788468 --- packages/cozy-pouch-link/src/CozyPouchLink.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index 7d444976bf..fc7e1ccb62 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -391,17 +391,44 @@ class PouchLink extends CozyLink { } } - async persistData(data, forward = doNothing) { + sanitizeJsonApi(data) { const docWithoutType = sanitized(data) - docWithoutType.cozyLocalOnly = true + + /* + We persist in the local Pouch database all the documents that do not + exist on the remote Couch database + + Those documents are computed by the cozy-stack then are sent to the + client using JSON-API format containing `attributes` and `meta` + attributes + + Then the cozy-stack-client would normalize those documents by spreading + `attributes` and `meta` content into the document's root + + So we don't need to store `attributes` and `meta` data into the Pouch + database as their data already exists in the document's root + + Note that this is also the case for `links` and `relationships` + attributes, but we don't remove them for now. They are also part of the + JSON-API, but the normalization do not spread them in the document's + root, so we have to check their usefulnes first + */ + const sanitizedDoc = omit(docWithoutType, ['attributes', 'meta']) + + return sanitizedDoc + } + + async persistCozyData(data, forward = doNothing) { + const sanitizedDoc = this.sanitizeJsonApi(data) + sanitizedDoc.cozyLocalOnly = true const oldDoc = await this.getExistingDocument(data._id, data._type) if (oldDoc) { - docWithoutType._rev = oldDoc._rev + sanitizedDoc._rev = oldDoc._rev } const db = this.pouches.getPouch(data._type) - await db.put(docWithoutType) + await db.put(sanitizedDoc) } /** From 5dbeeacd06b0501e2112f13ccccbefba83d0146f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 3 Sep 2024 10:52:11 +0200 Subject: [PATCH 69/84] fix: Don't use attributes on loadInstanceOptionsFromStack --- packages/cozy-client/src/CozyClient.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index b2beddd7b3..4e0ba47f92 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1774,9 +1774,9 @@ instantiation of the client.` ) this.instanceOptions = { - capabilities: data.attributes, - locale: instanceData.attributes?.locale, - tracking: instanceData.attributes?.tracking + capabilities: data, + locale: instanceData.locale, + tracking: instanceData.tracking } this.capabilities = this.instanceOptions.capabilities || null From bf8af3850b37b6b7ed6c93f44dfbce56c4cfb0b5 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:35:55 +0200 Subject: [PATCH 70/84] fix: Don't use attributes on HasManyFiles --- packages/cozy-client/src/associations/HasManyFiles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cozy-client/src/associations/HasManyFiles.js b/packages/cozy-client/src/associations/HasManyFiles.js index ac83c044df..dac5c6b220 100644 --- a/packages/cozy-client/src/associations/HasManyFiles.js +++ b/packages/cozy-client/src/associations/HasManyFiles.js @@ -57,7 +57,7 @@ export default class HasManyFiles extends HasMany { lastRelationship._type, lastRelationship._id ) - const lastDatetime = getFileDatetime(lastRelDoc.attributes) + const lastDatetime = getFileDatetime(lastRelDoc) // cursor-based pagination const cursor = newCursor( [this.target._type, this.target._id, lastDatetime], From 44ad68a70ddee220620f33723496f0a4a0ae1f6e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:36:41 +0200 Subject: [PATCH 71/84] fix: Don't use attributes on useCapabilities By doing so we add unnecessary values to the capabilities object that will now contain more data than just `.attributes` As those are supernumerary values we consider that this is not problematic, but we may want to find a cleaner solution in the future --- packages/cozy-client/src/hooks/useCapabilities.jsx | 2 +- packages/cozy-client/src/hooks/useCapabilities.spec.jsx | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cozy-client/src/hooks/useCapabilities.jsx b/packages/cozy-client/src/hooks/useCapabilities.jsx index ec660316cc..e61d62a583 100644 --- a/packages/cozy-client/src/hooks/useCapabilities.jsx +++ b/packages/cozy-client/src/hooks/useCapabilities.jsx @@ -13,7 +13,7 @@ const useCapabilities = client => { Q('io.cozy.settings').getById('io.cozy.settings.capabilities') ) - setCapabilities(get(capabilitiesResult, 'data.attributes', {})) + setCapabilities(get(capabilitiesResult, 'data', {})) setFetchStatus('loaded') } catch (e) { setFetchStatus('failed') diff --git a/packages/cozy-client/src/hooks/useCapabilities.spec.jsx b/packages/cozy-client/src/hooks/useCapabilities.spec.jsx index 1ad9992221..297e6952f9 100644 --- a/packages/cozy-client/src/hooks/useCapabilities.spec.jsx +++ b/packages/cozy-client/src/hooks/useCapabilities.spec.jsx @@ -33,7 +33,8 @@ describe('useCapabilities', () => { data: { type: 'io.cozy.settings', id: 'io.cozy.settings.capabilities', - attributes: { file_versioning: true, flat_subdomains: true }, + file_versioning: true, + flat_subdomains: true, meta: {}, links: { self: '/settings/capabilities' } } @@ -44,8 +45,12 @@ describe('useCapabilities', () => { await waitForNextUpdate() expect(result.current.capabilities).toEqual({ + type: 'io.cozy.settings', + id: 'io.cozy.settings.capabilities', file_versioning: true, - flat_subdomains: true + flat_subdomains: true, + meta: {}, + links: { self: '/settings/capabilities' } }) }) From 6f6df5cade2620e93b794bcbc9684f7431e2f4cf Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:37:37 +0200 Subject: [PATCH 72/84] fix: Don't use attributes on useFetchShortctut --- .../src/hooks/useFetchShortcut.jsx | 7 +-- .../src/hooks/useFetchShortcut.spec.jsx | 43 ++++++++----------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/packages/cozy-client/src/hooks/useFetchShortcut.jsx b/packages/cozy-client/src/hooks/useFetchShortcut.jsx index 8d0e35d91b..fe5a1b6977 100644 --- a/packages/cozy-client/src/hooks/useFetchShortcut.jsx +++ b/packages/cozy-client/src/hooks/useFetchShortcut.jsx @@ -24,8 +24,7 @@ const useFetchShortcut = (client, id) => { } }) - const targetApp = - shortcutInfosResult?.data?.attributes?.metadata?.target?.app + const targetApp = shortcutInfosResult?.data?.metadata?.target?.app if (targetApp) { const targetAppIconUrl = await client.getStackClient().getIconURL({ type: 'app', @@ -34,9 +33,7 @@ const useFetchShortcut = (client, id) => { }) setShortcutImg(targetAppIconUrl) } else { - const shortcutRemoteUrl = new URL( - shortcutInfosResult.data.attributes.url - ) + const shortcutRemoteUrl = new URL(shortcutInfosResult.data.url) const imgUrl = `${client.getStackClient().uri}/bitwarden/icons/${ shortcutRemoteUrl.host diff --git a/packages/cozy-client/src/hooks/useFetchShortcut.spec.jsx b/packages/cozy-client/src/hooks/useFetchShortcut.spec.jsx index 4f6ebc5219..22e3168983 100644 --- a/packages/cozy-client/src/hooks/useFetchShortcut.spec.jsx +++ b/packages/cozy-client/src/hooks/useFetchShortcut.spec.jsx @@ -16,13 +16,11 @@ describe('useFetchShortcut', () => { { type: 'io.cozy.files.shortcuts', id: 'b7470059d40c88e4bd30031d5e0109d3', - attributes: { - _id: '', - name: 'cozy.url', - dir_id: '8034db0016d0548ded99b9627e003270', - url: 'https://cozy.io', - metadata: { extractor_version: 2 } - }, + _id: 'b7470059d40c88e4bd30031d5e0109d3', + name: 'cozy.url', + dir_id: '8034db0016d0548ded99b9627e003270', + url: 'https://cozy.io', + metadata: { extractor_version: 2 }, meta: { rev: '1-60e1359e63fa7fa9fa000a2726d5d4c7' } } ] @@ -37,16 +35,14 @@ describe('useFetchShortcut', () => { { type: 'io.cozy.files.shortcuts', id: 'linkToCozyApp', - attributes: { - _id: '', - name: 'cozy.url', - dir_id: '8034db0016d0548ded99b9627e003270', - url: 'https://cozy.io', - metadata: { - extractor_version: 2, - target: { - app: 'notes' - } + _id: '', + name: 'cozy.url', + dir_id: '8034db0016d0548ded99b9627e003270', + url: 'https://cozy.io', + metadata: { + extractor_version: 2, + target: { + app: 'notes' } }, meta: { rev: '1-60e1359e63fa7fa9fa000a2726d5d4c7' } @@ -93,15 +89,12 @@ describe('useFetchShortcut', () => { data: { _id: 'b7470059d40c88e4bd30031d5e0109d3', _type: 'io.cozy.files.shortcuts', - type: 'io.cozy.files.shortcuts', id: 'b7470059d40c88e4bd30031d5e0109d3', - attributes: { - _id: '', - name: 'cozy.url', - dir_id: '8034db0016d0548ded99b9627e003270', - url: 'https://cozy.io', - metadata: { extractor_version: 2 } - }, + type: 'io.cozy.files.shortcuts', + name: 'cozy.url', + dir_id: '8034db0016d0548ded99b9627e003270', + url: 'https://cozy.io', + metadata: { extractor_version: 2 }, meta: { rev: '1-60e1359e63fa7fa9fa000a2726d5d4c7' } } }) From 98453f8b5b300e3c0b95264502c61aa2e029b44d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:38:25 +0200 Subject: [PATCH 73/84] fix: Don't use attributes on models/applications Calls of `isInstalled` and all related methods have been checked and `apps` parameter is always a result of `client.query()` so we know that `.slug` can be used in replacement of `.attributes.slug` --- .../src/hooks/useAppLinkWithStoreFallback.spec.jsx | 8 ++------ packages/cozy-client/src/models/applications.js | 4 +--- packages/cozy-client/src/models/applications.spec.js | 8 ++------ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/cozy-client/src/hooks/useAppLinkWithStoreFallback.spec.jsx b/packages/cozy-client/src/hooks/useAppLinkWithStoreFallback.spec.jsx index 922559bd9f..c782e20785 100644 --- a/packages/cozy-client/src/hooks/useAppLinkWithStoreFallback.spec.jsx +++ b/packages/cozy-client/src/hooks/useAppLinkWithStoreFallback.spec.jsx @@ -30,9 +30,7 @@ describe('useAppLinkWithStoreFallback', () => { mockClient.query.mockResolvedValue({ data: [ { - attributes: { - slug: testAppSlug - }, + slug: testAppSlug, links: { related: 'http://testapp.cozy.io' } } ] @@ -52,9 +50,7 @@ describe('useAppLinkWithStoreFallback', () => { mockClient.query.mockResolvedValue({ data: [ { - attributes: { - slug: 'store' - }, + slug: 'store', links: { related: 'http://store.cozy.io' } } ] diff --git a/packages/cozy-client/src/models/applications.js b/packages/cozy-client/src/models/applications.js index a81c2cd2d6..ee3889178e 100644 --- a/packages/cozy-client/src/models/applications.js +++ b/packages/cozy-client/src/models/applications.js @@ -48,9 +48,7 @@ export const getStoreInstallationURL = (appData = [], app = {}) => { * @returns {object} The io.cozy.app is installed or undefined if not */ export const isInstalled = (apps = [], wantedApp = {}) => { - return apps.find( - app => app.attributes && app.attributes.slug === wantedApp.slug - ) + return apps.find(app => app.slug === wantedApp.slug) } /** diff --git a/packages/cozy-client/src/models/applications.spec.js b/packages/cozy-client/src/models/applications.spec.js index 78e12dbe52..a27f355593 100644 --- a/packages/cozy-client/src/models/applications.spec.js +++ b/packages/cozy-client/src/models/applications.spec.js @@ -94,9 +94,7 @@ describe('applications model', () => { describe('when the store app is installed', () => { it('should return the store url for the given app', () => { const storeApp = { - attributes: { - slug: 'store' - }, + slug: 'store', links: { related: 'http://store.cozy.tools:8080/' } @@ -120,9 +118,7 @@ describe('applications model', () => { describe('when the store app is installed', () => { it('should return the store installation url for the given app', () => { const storeApp = { - attributes: { - slug: 'store' - }, + slug: 'store', links: { related: 'http://store.cozy.tools:8080/' } From 187053728531479118233e527a62b6f4236200f7 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 15:40:43 +0200 Subject: [PATCH 74/84] fix: Don't use attributes on models/instance Calls of those methods have been checked and `instanceInfo` parameter is always a result of `useInstanceInfo` so we know that `.attributes` can be omited --- packages/cozy-client/src/models/instance.js | 20 +++------ .../cozy-client/src/models/instance.spec.js | 42 ++++++------------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/packages/cozy-client/src/models/instance.js b/packages/cozy-client/src/models/instance.js index fa8dcaadc6..1238ad8034 100644 --- a/packages/cozy-client/src/models/instance.js +++ b/packages/cozy-client/src/models/instance.js @@ -20,19 +20,17 @@ const PREMIUM_QUOTA = 50 * GB // If manager URL is present, then the instance is not self-hosted export const isSelfHosted = instanceInfo => { - return get(instanceInfo, 'context.data.attributes.manager_url') ? false : true + return get(instanceInfo, 'context.data.manager_url') ? false : true } export const arePremiumLinksEnabled = instanceInfo => { - return get(instanceInfo, 'context.data.attributes.enable_premium_links') - ? true - : false + return get(instanceInfo, 'context.data.enable_premium_links') ? true : false } export const isFreemiumUser = instanceInfo => { - const quota = get(instanceInfo, 'diskUsage.data.attributes.quota', false) + const quota = get(instanceInfo, 'diskUsage.data.quota', false) return parseInt(quota) <= PREMIUM_QUOTA } export const getUuid = instanceInfo => { - return get(instanceInfo, 'instance.data.attributes.uuid') + return get(instanceInfo, 'instance.data.uuid') } /** @@ -70,11 +68,7 @@ export const hasAnOffer = data => { * @param {InstanceInfo} instanceInfo - Instance information */ export const buildPremiumLink = instanceInfo => { - const managerUrl = get( - instanceInfo, - 'context.data.attributes.manager_url', - false - ) + const managerUrl = get(instanceInfo, 'context.data.manager_url', false) const uuid = getUuid(instanceInfo) if (managerUrl && uuid) { return `${managerUrl}/cozy/instances/${uuid}/premium` @@ -92,9 +86,7 @@ export const buildPremiumLink = instanceInfo => { export const hasPasswordDefinedAttribute = async client => { try { const { - data: { - attributes: { password_defined } - } + data: { password_defined } } = await client.fetchQueryAndGetFromState({ definition: Q('io.cozy.settings').getById('io.cozy.settings.instance'), options: { diff --git a/packages/cozy-client/src/models/instance.spec.js b/packages/cozy-client/src/models/instance.spec.js index d3957d3d4a..3fd3e3027e 100644 --- a/packages/cozy-client/src/models/instance.spec.js +++ b/packages/cozy-client/src/models/instance.spec.js @@ -3,39 +3,29 @@ import { instance } from './' const noSelfHostedInstance = { context: { data: { - attributes: { - manager_url: 'https://manager.cozy.cc', - enable_premium_links: true - } + manager_url: 'https://manager.cozy.cc', + enable_premium_links: true } }, instance: { data: { - attributes: { - uuid: '1234' - } + uuid: '1234' } }, diskUsage: { data: { - attributes: { - quota: '400000000' - } + quota: '400000000' } } } const selftHostedInstance = { context: { - data: { - attributes: {} - } + data: {} }, diskUsage: { data: { - attributes: { - quota: '6000000000000' - } + quota: '6000000000000' } } } @@ -43,24 +33,18 @@ const selftHostedInstance = { const hadAnOfferInstance = { context: { data: { - attributes: { - manager_url: 'https://manager.cozy.cc', - enable_premium_links: true - } + manager_url: 'https://manager.cozy.cc', + enable_premium_links: true } }, instance: { data: { - attributes: { - uuid: '1234' - } + uuid: '1234' } }, diskUsage: { data: { - attributes: { - quota: '60000000000' - } + quota: '60000000000' } } } @@ -163,17 +147,17 @@ describe('instance', () => { }) it('should return false if attribute password_defined is undefined', async () => { - const res = await setup({ attributes: { password_defined: undefined } }) + const res = await setup({ password_defined: undefined }) expect(res).toBe(false) }) it('should return false if attribute password_defined is false', async () => { - const res = await setup({ attributes: { password_defined: false } }) + const res = await setup({ password_defined: false }) expect(res).toBe(false) }) it('should return true if attribute password_defined is true', async () => { - const res = await setup({ attributes: { password_defined: true } }) + const res = await setup({ password_defined: true }) expect(res).toBe(true) }) }) From cb7297e88cdc40b6c028da2c1b34210e6bcf3542 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 4 Sep 2024 18:13:20 +0200 Subject: [PATCH 75/84] docs: Update types and documentation --- .../interfaces/models.instance.DiskInfos.md | 6 +- .../models.instance.DiskInfosRaw.md | 6 +- .../modules/models.applications.md | 4 +- .../cozy-client/modules/models.instance.md | 14 +- docs/api/cozy-pouch-link/classes/PouchLink.md | 129 +++++++++--------- .../cozy-pouch-link/types/CozyPouchLink.d.ts | 2 +- 6 files changed, 80 insertions(+), 81 deletions(-) diff --git a/docs/api/cozy-client/interfaces/models.instance.DiskInfos.md b/docs/api/cozy-client/interfaces/models.instance.DiskInfos.md index 2b59c4d611..332a46461d 100644 --- a/docs/api/cozy-client/interfaces/models.instance.DiskInfos.md +++ b/docs/api/cozy-client/interfaces/models.instance.DiskInfos.md @@ -14,7 +14,7 @@ Space used in GB rounded *Defined in* -[packages/cozy-client/src/models/instance.js:121](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L121) +[packages/cozy-client/src/models/instance.js:113](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L113) *** @@ -26,7 +26,7 @@ Maximum space available in GB rounded *Defined in* -[packages/cozy-client/src/models/instance.js:122](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L122) +[packages/cozy-client/src/models/instance.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L114) *** @@ -38,4 +38,4 @@ Usage percent of the disk rounded *Defined in* -[packages/cozy-client/src/models/instance.js:123](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L123) +[packages/cozy-client/src/models/instance.js:115](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L115) diff --git a/docs/api/cozy-client/interfaces/models.instance.DiskInfosRaw.md b/docs/api/cozy-client/interfaces/models.instance.DiskInfosRaw.md index 5036a9d3de..a2a71efe0d 100644 --- a/docs/api/cozy-client/interfaces/models.instance.DiskInfosRaw.md +++ b/docs/api/cozy-client/interfaces/models.instance.DiskInfosRaw.md @@ -14,7 +14,7 @@ Space used in GB *Defined in* -[packages/cozy-client/src/models/instance.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L114) +[packages/cozy-client/src/models/instance.js:106](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L106) *** @@ -26,7 +26,7 @@ Maximum space available in GB *Defined in* -[packages/cozy-client/src/models/instance.js:115](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L115) +[packages/cozy-client/src/models/instance.js:107](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L107) *** @@ -38,4 +38,4 @@ Usage percent of the disk *Defined in* -[packages/cozy-client/src/models/instance.js:116](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L116) +[packages/cozy-client/src/models/instance.js:108](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L108) diff --git a/docs/api/cozy-client/modules/models.applications.md b/docs/api/cozy-client/modules/models.applications.md index 1082ccbadb..defa3e3b11 100644 --- a/docs/api/cozy-client/modules/models.applications.md +++ b/docs/api/cozy-client/modules/models.applications.md @@ -27,7 +27,7 @@ Name of the app suitable for display *Defined in* -[packages/cozy-client/src/models/applications.js:73](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/applications.js#L73) +[packages/cozy-client/src/models/applications.js:71](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/applications.js#L71) *** @@ -99,7 +99,7 @@ url to the app *Defined in* -[packages/cozy-client/src/models/applications.js:61](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/applications.js#L61) +[packages/cozy-client/src/models/applications.js:59](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/applications.js#L59) *** diff --git a/docs/api/cozy-client/modules/models.instance.md b/docs/api/cozy-client/modules/models.instance.md index edca32687b..ea90003778 100644 --- a/docs/api/cozy-client/modules/models.instance.md +++ b/docs/api/cozy-client/modules/models.instance.md @@ -80,7 +80,7 @@ Returns the link to the Premium page on the Cozy's Manager *Defined in* -[packages/cozy-client/src/models/instance.js:72](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L72) +[packages/cozy-client/src/models/instance.js:70](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L70) *** @@ -100,7 +100,7 @@ Returns the link to the Premium page on the Cozy's Manager *Defined in* -[packages/cozy-client/src/models/instance.js:34](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L34) +[packages/cozy-client/src/models/instance.js:32](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L32) *** @@ -124,7 +124,7 @@ Does the cozy have offers *Defined in* -[packages/cozy-client/src/models/instance.js:58](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L58) +[packages/cozy-client/src/models/instance.js:56](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L56) *** @@ -148,7 +148,7 @@ Checks the value of the password_defined attribute *Defined in* -[packages/cozy-client/src/models/instance.js:92](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L92) +[packages/cozy-client/src/models/instance.js:86](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L86) *** @@ -168,7 +168,7 @@ Checks the value of the password_defined attribute *Defined in* -[packages/cozy-client/src/models/instance.js:30](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L30) +[packages/cozy-client/src/models/instance.js:28](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L28) *** @@ -219,7 +219,7 @@ Make human readable information from disk information (usage, quota) *Defined in* -[packages/cozy-client/src/models/instance.js:164](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L164) +[packages/cozy-client/src/models/instance.js:156](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L156) *** @@ -243,4 +243,4 @@ Should we display offers *Defined in* -[packages/cozy-client/src/models/instance.js:44](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L44) +[packages/cozy-client/src/models/instance.js:42](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/instance.js#L42) diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index db60627986..b6d41f5d80 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -32,7 +32,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) +[CozyPouchLink.js:87](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L87) ## Properties @@ -42,7 +42,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:140](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L140) +[CozyPouchLink.js:140](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L140) *** @@ -52,7 +52,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:97](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L97) +[CozyPouchLink.js:97](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L97) *** @@ -62,7 +62,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:98](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L98) +[CozyPouchLink.js:98](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L98) *** @@ -72,7 +72,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +[CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) *** @@ -82,7 +82,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) +[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) *** @@ -92,7 +92,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:210](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L210) +[CozyPouchLink.js:210](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L210) *** @@ -102,7 +102,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:105](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L105) +[CozyPouchLink.js:105](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L105) *** @@ -112,7 +112,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:100](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L100) +[CozyPouchLink.js:100](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L100) ## Methods @@ -132,7 +132,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:661](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L661) +[CozyPouchLink.js:688](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L688) *** @@ -152,7 +152,7 @@ CozyLink.constructor *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:622](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L622) +[CozyPouchLink.js:649](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L649) *** @@ -178,7 +178,7 @@ Create the PouchDB index if not existing *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:467](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L467) +[CozyPouchLink.js:494](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L494) *** @@ -199,7 +199,7 @@ Create the PouchDB index if not existing *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:665](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L665) +[CozyPouchLink.js:692](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L692) *** @@ -219,7 +219,7 @@ Create the PouchDB index if not existing *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:650](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L650) +[CozyPouchLink.js:677](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L677) *** @@ -241,7 +241,7 @@ Create the PouchDB index if not existing *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:592](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L592) +[CozyPouchLink.js:619](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L619) *** @@ -261,7 +261,7 @@ Create the PouchDB index if not existing *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:531](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L531) +[CozyPouchLink.js:558](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L558) *** @@ -285,7 +285,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:491](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L491) +[CozyPouchLink.js:518](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L518) *** @@ -305,7 +305,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:327](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L327) +[CozyPouchLink.js:327](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L327) *** @@ -325,7 +325,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:120](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L120) +[CozyPouchLink.js:120](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L120) *** @@ -345,7 +345,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:323](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L323) +[CozyPouchLink.js:323](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L323) *** @@ -365,7 +365,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:264](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L264) +[CozyPouchLink.js:264](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L264) *** @@ -385,7 +385,7 @@ Retrieve the PouchDB index if exist, undefined otherwise *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:259](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L259) +[CozyPouchLink.js:259](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L259) *** @@ -411,7 +411,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:245](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L245) +[CozyPouchLink.js:245](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L245) *** @@ -431,7 +431,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:453](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L453) +[CozyPouchLink.js:480](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L480) *** @@ -461,7 +461,7 @@ Migrate the current adapter *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:154](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L154) +[CozyPouchLink.js:154](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L154) *** @@ -486,7 +486,7 @@ the need to wait for the warmup *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:439](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L439) +[CozyPouchLink.js:466](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L466) *** @@ -500,7 +500,7 @@ the need to wait for the warmup *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:173](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L173) +[CozyPouchLink.js:173](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L173) *** @@ -520,38 +520,13 @@ the need to wait for the warmup *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:303](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L303) +[CozyPouchLink.js:303](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L303) *** ### persistCozyData -▸ **persistCozyData**(`data`, `forward`): `void` - -*Parameters* - -| Name | Type | -| :------ | :------ | -| `data` | `any` | -| `forward` | `any` | - -*Returns* - -`void` - -*Inherited from* - -CozyLink.persistCozyData - -*Defined in* - -[cozy-client/types/CozyLink.d.ts:4](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/types/CozyLink.d.ts#L4) - -*** - -### persistData - -▸ **persistData**(`data`, `forward?`): `Promise`<`void`> +▸ **persistCozyData**(`data`, `forward?`): `Promise`<`void`> *Parameters* @@ -564,9 +539,13 @@ CozyLink.persistCozyData `Promise`<`void`> +*Overrides* + +CozyLink.persistCozyData + *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:394](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L394) +[CozyPouchLink.js:421](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L421) *** @@ -586,7 +565,7 @@ CozyLink.persistCozyData *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:139](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L139) +[CozyPouchLink.js:139](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L139) *** @@ -612,7 +591,7 @@ CozyLink.request *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:346](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L346) +[CozyPouchLink.js:346](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L346) *** @@ -626,7 +605,27 @@ CozyLink.request *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:229](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L229) +[CozyPouchLink.js:229](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L229) + +*** + +### sanitizeJsonApi + +▸ **sanitizeJsonApi**(`data`): `Omit`<`Pick`<`any`, `string` | `number` | `symbol`>, `"attributes"` | `"meta"`> + +*Parameters* + +| Name | Type | +| :------ | :------ | +| `data` | `any` | + +*Returns* + +`Omit`<`Pick`<`any`, `string` | `number` | `symbol`>, `"attributes"` | `"meta"`> + +*Defined in* + +[CozyPouchLink.js:394](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L394) *** @@ -645,7 +644,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:278](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L278) +[CozyPouchLink.js:278](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L278) *** @@ -664,7 +663,7 @@ Emits pouchlink:sync:stop event *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:295](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L295) +[CozyPouchLink.js:295](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L295) *** @@ -684,7 +683,7 @@ Emits pouchlink:sync:stop event *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:331](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L331) +[CozyPouchLink.js:331](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L331) *** @@ -698,7 +697,7 @@ Emits pouchlink:sync:stop event *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:687](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L687) +[CozyPouchLink.js:714](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L714) *** @@ -718,7 +717,7 @@ Emits pouchlink:sync:stop event *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L627) +[CozyPouchLink.js:654](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L654) *** @@ -738,7 +737,7 @@ Emits pouchlink:sync:stop event *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:632](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L632) +[CozyPouchLink.js:659](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L659) *** @@ -763,4 +762,4 @@ The adapter name *Defined in* -[cozy-pouch-link/src/CozyPouchLink.js:115](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L115) +[CozyPouchLink.js:115](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L115) diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index b972a56b36..a8b6a30bfd 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -131,7 +131,7 @@ declare class PouchLink extends CozyLink { getSyncInfo(doctype: any): import("./types").SyncInfo; getPouch(doctype: any): any; supportsOperation(operation: any): boolean; - persistData(data: any, forward?: (operation: any, result?: any) => void): Promise; + sanitizeJsonApi(data: any): Pick, string | number | symbol>; /** * Retrieve the existing document from Pouch * From 480b406e0914598aa0eb65703443c959ced5da61 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 11 Sep 2024 16:22:28 +0200 Subject: [PATCH 76/84] fix(cozy-pouch-link): Merge partialFilter into selector on `find` query For some reason the Pouch engine would ignore any partialFilter if it is not included in the selector This may be the same reason we had to do this fix: https://github.com/cozy/cozy-client/commit/7c69838d2962be8f59ada6806d65761dbfe47082#diff-41848dd46551544674c134f359a5d7cddea46dd1e47c21da6814e1d1d585173dR482-R489 --- packages/cozy-pouch-link/src/CozyPouchLink.js | 3 ++- packages/cozy-pouch-link/src/helpers.js | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index fc7e1ccb62..c369144198 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -585,7 +585,8 @@ class PouchLink extends CozyLink { const findSelector = helpers.normalizeFindSelector({ selector, sort, - indexedFields + indexedFields, + partialFilter }) const findOpts = { diff --git a/packages/cozy-pouch-link/src/helpers.js b/packages/cozy-pouch-link/src/helpers.js index afb5d79534..f630efbe05 100644 --- a/packages/cozy-pouch-link/src/helpers.js +++ b/packages/cozy-pouch-link/src/helpers.js @@ -1,3 +1,4 @@ +import merge from 'lodash/merge' import startsWith from 'lodash/startsWith' import logger from './logger' @@ -63,7 +64,12 @@ helpers.insertBulkDocs = async (db, docs) => { return db.bulkDocs(docs, { new_edits: false }) } -helpers.normalizeFindSelector = ({ selector, sort, indexedFields }) => { +helpers.normalizeFindSelector = ({ + selector, + sort, + indexedFields, + partialFilter +}) => { let findSelector = selector || {} if (indexedFields) { for (const indexedField of indexedFields) { @@ -94,8 +100,12 @@ helpers.normalizeFindSelector = ({ selector, sort, indexedFields }) => { } } - return Object.keys(findSelector).length > 0 - ? findSelector + const mergedSelector = partialFilter + ? merge({ ...findSelector }, partialFilter) + : findSelector + + return Object.keys(mergedSelector).length > 0 + ? mergedSelector : { _id: { $gt: null } } // PouchDB does not accept empty selector } From a5da8fa65cebc74f3e51fb90acecea106dde03bf Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 11 Sep 2024 16:33:42 +0200 Subject: [PATCH 77/84] fix: Check for Flagship's downloadFile method support before calling it Previous implementation would not be backward compatible with older Flagship app versions By checking for method availability, we ensure the code is called only on the correct Flagship app versions, otherwise the old process is called Related PR: #1518 --- packages/cozy-client/src/models/file.js | 7 +++- packages/cozy-client/src/models/file.spec.js | 35 +++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/cozy-client/src/models/file.js b/packages/cozy-client/src/models/file.js index 50e66f77f9..feaf972b41 100644 --- a/packages/cozy-client/src/models/file.js +++ b/packages/cozy-client/src/models/file.js @@ -719,7 +719,12 @@ export const downloadFile = async ({ client, file, url, webviewIntent }) => { const filesCollection = client.collection(DOCTYPE_FILES) if (isFlagshipApp() && webviewIntent && !isEncrypted(file)) { - return await webviewIntent.call('downloadFile', file) + const isFlagshipDownloadAvailable = + (await webviewIntent?.call('isAvailable', 'downloadFile')) ?? false + + if (isFlagshipDownloadAvailable) { + return await webviewIntent.call('downloadFile', file) + } } if (isEncrypted(file)) { diff --git a/packages/cozy-client/src/models/file.spec.js b/packages/cozy-client/src/models/file.spec.js index 5822047784..f96e72bc89 100644 --- a/packages/cozy-client/src/models/file.spec.js +++ b/packages/cozy-client/src/models/file.spec.js @@ -892,7 +892,7 @@ describe('downloadFile', () => { it('should handle download in Flagship app', async () => { isFlagshipApp.mockReturnValue(true) const webviewIntent = { - call: jest.fn() + call: jest.fn().mockResolvedValue(true) } const file = { @@ -911,9 +911,42 @@ describe('downloadFile', () => { }) expect(downloadFromCozySpy).not.toHaveBeenCalled() + expect(webviewIntent.call).toHaveBeenCalledWith( + 'isAvailable', + 'downloadFile' + ) expect(webviewIntent.call).toHaveBeenCalledWith('downloadFile', file) }) + it('should download files from web page in old Flagship app versions', async () => { + isFlagshipApp.mockReturnValue(true) + const webviewIntent = { + call: jest.fn().mockResolvedValue(false) // `isAvailable` returns `false` when not implemented + } + + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME' + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + // @ts-ignore + webviewIntent + }) + + expect(downloadFromCozySpy).toHaveBeenCalled() + expect(webviewIntent.call).toHaveBeenCalledWith( + 'isAvailable', + 'downloadFile' + ) + expect(webviewIntent.call).not.toHaveBeenCalledWith('downloadFile', file) + }) + it('should download encrypted files from web page as this is not supported yet by Flagship app', async () => { isFlagshipApp.mockReturnValue(true) const webviewIntent = { From 2b05e1de365a661c228592e94e4ada2ab1d3a117 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 11 Sep 2024 16:34:51 +0200 Subject: [PATCH 78/84] fix: Await for `persistVirtualDocuments` result before finishing request In previous implementation we did not await for the `persistVirtualDocuments()` result before completing the request This was done because the persistance was not mandatory for the request's result and doing it in parallel would be a good way to optimize timings But in some recent experiments we found a scenario where we would benefit from the `await` in order to some external code until the data is actually persisted Related PR: #1486 --- packages/cozy-client/src/CozyClient.js | 2 +- packages/cozy-client/src/associations.spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 4e0ba47f92..243cd28fc7 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1093,7 +1093,7 @@ client.query(Q('io.cozy.bills'))`) async requestQuery(definition) { const mainResponse = await this.chain.request(definition) - this.persistVirtualDocuments(definition, mainResponse.data) + await this.persistVirtualDocuments(definition, mainResponse.data) if (!definition.includes) { return mainResponse diff --git a/packages/cozy-client/src/associations.spec.js b/packages/cozy-client/src/associations.spec.js index 8d53bf42e5..d4dac6c2a2 100644 --- a/packages/cozy-client/src/associations.spec.js +++ b/packages/cozy-client/src/associations.spec.js @@ -7,7 +7,8 @@ import { SCHEMA, TODO_1, TODO_2 } from './__tests__/fixtures' describe('Associations', () => { const requestHandler = jest.fn() - const link = new CozyLink(requestHandler) + const persistHandler = jest.fn() + const link = new CozyLink(requestHandler, persistHandler) const client = new CozyClient({ links: [link], schema: SCHEMA }) const getTodo = id => From f2d30b12ea2991d103086fb3d6b637cd8817f897 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 11 Sep 2024 16:55:24 +0200 Subject: [PATCH 79/84] docs: Update types and documentation --- docs/api/cozy-pouch-link/classes/PouchLink.md | 16 ++++++++-------- packages/cozy-pouch-link/types/helpers.d.ts | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index b6d41f5d80..75827771b3 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -132,7 +132,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:688](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L688) +[CozyPouchLink.js:689](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L689) *** @@ -152,7 +152,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:649](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L649) +[CozyPouchLink.js:650](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L650) *** @@ -199,7 +199,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:692](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L692) +[CozyPouchLink.js:693](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L693) *** @@ -219,7 +219,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:677](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L677) +[CozyPouchLink.js:678](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L678) *** @@ -241,7 +241,7 @@ Create the PouchDB index if not existing *Defined in* -[CozyPouchLink.js:619](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L619) +[CozyPouchLink.js:620](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L620) *** @@ -697,7 +697,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:714](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L714) +[CozyPouchLink.js:715](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L715) *** @@ -717,7 +717,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:654](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L654) +[CozyPouchLink.js:655](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L655) *** @@ -737,7 +737,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:659](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L659) +[CozyPouchLink.js:660](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L660) *** diff --git a/packages/cozy-pouch-link/types/helpers.d.ts b/packages/cozy-pouch-link/types/helpers.d.ts index f533cb7334..a4e88a8034 100644 --- a/packages/cozy-pouch-link/types/helpers.d.ts +++ b/packages/cozy-pouch-link/types/helpers.d.ts @@ -8,9 +8,10 @@ declare namespace helpers { function isDesignDocument(doc: any): boolean; function isDeletedDocument(doc: any): any; function insertBulkDocs(db: any, docs: any): Promise; - function normalizeFindSelector({ selector, sort, indexedFields }: { + function normalizeFindSelector({ selector, sort, indexedFields, partialFilter }: { selector: any; sort: any; indexedFields: any; + partialFilter: any; }): any; } From 339e5a616d865b93a234dcca9bce1955223c3c57 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Sun, 15 Sep 2024 15:12:50 +0200 Subject: [PATCH 80/84] feat: Improve CozyLink typing and add `reset()` method In the Flagship app, we want to be able to reset the local PouchDB files in order to prevent beta testers to be blocked by an erroneous Pouch replication To make this possible we want to expose a public method that resets all the cozy-client's links --- packages/cozy-client/src/CozyLink.js | 28 ++++++++++++++++++++++-- packages/cozy-client/src/FlagshipLink.js | 2 +- packages/cozy-client/src/StackLink.js | 2 +- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/cozy-client/src/CozyLink.js b/packages/cozy-client/src/CozyLink.js index cb808cadc1..055b61b1e8 100644 --- a/packages/cozy-client/src/CozyLink.js +++ b/packages/cozy-client/src/CozyLink.js @@ -9,13 +9,37 @@ export default class CozyLink { } } - request(operation, result, forward) { + /** + * Request the given operation from the link + * + * @param {any} operation - The operation to request + * @param {any} result - The result from the previous request of the chain + * @param {any} forward - The next request of the chain + * @returns {Promise} + */ + async request(operation, result, forward) { throw new Error('request is not implemented') } - persistCozyData(data, forward) { + /** + * Persist the given data into the links storage + * + * @param {any} data - The document to persist + * @param {any} forward - The next persistCozyData of the chain + * @returns {Promise} + */ + async persistCozyData(data, forward) { throw new Error('persistCozyData is not implemented') } + + /** + * Reset the link data + * + * @returns {Promise} + */ + async reset() { + throw new Error('reset is not implemented') + } } const toLink = handler => diff --git a/packages/cozy-client/src/FlagshipLink.js b/packages/cozy-client/src/FlagshipLink.js index d9bccb2c7c..901443c8f4 100644 --- a/packages/cozy-client/src/FlagshipLink.js +++ b/packages/cozy-client/src/FlagshipLink.js @@ -14,7 +14,7 @@ export default class FlagshipLink extends CozyLink { // does nothing, we don't need any client for this kind of link } - reset() { + async reset() { // does nothing, we don't need any client for this kind of link } diff --git a/packages/cozy-client/src/StackLink.js b/packages/cozy-client/src/StackLink.js index 65d623700e..41ea7c3245 100644 --- a/packages/cozy-client/src/StackLink.js +++ b/packages/cozy-client/src/StackLink.js @@ -80,7 +80,7 @@ export default class StackLink extends CozyLink { this.stackClient = client.stackClient || client.client } - reset() { + async reset() { this.stackClient = null } From 3b7d9fa456b7b2f14658c1433cb28cad48c0975e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 19 Sep 2024 15:36:58 +0200 Subject: [PATCH 81/84] docs: Update types and documentation --- docs/api/cozy-client/classes/CozyLink.md | 50 +++++++++++++------ docs/api/cozy-client/classes/FlagshipLink.md | 14 +++++- docs/api/cozy-client/classes/StackLink.md | 14 +++++- docs/api/cozy-pouch-link/classes/PouchLink.md | 4 ++ packages/cozy-client/types/CozyLink.d.ts | 25 +++++++++- packages/cozy-client/types/FlagshipLink.d.ts | 1 - packages/cozy-client/types/StackLink.d.ts | 1 - .../cozy-pouch-link/types/CozyPouchLink.d.ts | 1 - 8 files changed, 86 insertions(+), 24 deletions(-) diff --git a/docs/api/cozy-client/classes/CozyLink.md b/docs/api/cozy-client/classes/CozyLink.md index ad48952f95..1a4fa2f4a5 100644 --- a/docs/api/cozy-client/classes/CozyLink.md +++ b/docs/api/cozy-client/classes/CozyLink.md @@ -31,41 +31,61 @@ ### persistCozyData -▸ **persistCozyData**(`data`, `forward`): `void` +▸ **persistCozyData**(`data`, `forward`): `Promise`<`any`> + +Persist the given data into the links storage *Parameters* -| Name | Type | -| :------ | :------ | -| `data` | `any` | -| `forward` | `any` | +| Name | Type | Description | +| :------ | :------ | :------ | +| `data` | `any` | The document to persist | +| `forward` | `any` | The next persistCozyData of the chain | *Returns* -`void` +`Promise`<`any`> *Defined in* -[packages/cozy-client/src/CozyLink.js:16](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L16) +[packages/cozy-client/src/CozyLink.js:31](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L31) *** ### request -▸ **request**(`operation`, `result`, `forward`): `void` +▸ **request**(`operation`, `result`, `forward`): `Promise`<`any`> + +Request the given operation from the link *Parameters* -| Name | Type | -| :------ | :------ | -| `operation` | `any` | -| `result` | `any` | -| `forward` | `any` | +| Name | Type | Description | +| :------ | :------ | :------ | +| `operation` | `any` | The operation to request | +| `result` | `any` | The result from the previous request of the chain | +| `forward` | `any` | The next request of the chain | + +*Returns* + +`Promise`<`any`> + +*Defined in* + +[packages/cozy-client/src/CozyLink.js:20](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L20) + +*** + +### reset + +▸ **reset**(): `Promise`<`any`> + +Reset the link data *Returns* -`void` +`Promise`<`any`> *Defined in* -[packages/cozy-client/src/CozyLink.js:12](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L12) +[packages/cozy-client/src/CozyLink.js:40](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyLink.js#L40) diff --git a/docs/api/cozy-client/classes/FlagshipLink.md b/docs/api/cozy-client/classes/FlagshipLink.md index e1147b5552..1ea0f0a621 100644 --- a/docs/api/cozy-client/classes/FlagshipLink.md +++ b/docs/api/cozy-client/classes/FlagshipLink.md @@ -45,6 +45,8 @@ ▸ **persistCozyData**(`data`, `forward`): `Promise`<`void`> +Persist the given data into the links storage + *Parameters* | Name | Type | @@ -90,6 +92,8 @@ ▸ **request**(`operation`, `result`, `forward`): `Promise`<`boolean`> +Request the given operation from the link + *Parameters* | Name | Type | @@ -114,11 +118,17 @@ ### reset -▸ **reset**(): `void` +▸ **reset**(): `Promise`<`void`> + +Reset the link data *Returns* -`void` +`Promise`<`void`> + +*Overrides* + +[CozyLink](CozyLink.md).[reset](CozyLink.md#reset) *Defined in* diff --git a/docs/api/cozy-client/classes/StackLink.md b/docs/api/cozy-client/classes/StackLink.md index 45f4530f4f..6e69ab319d 100644 --- a/docs/api/cozy-client/classes/StackLink.md +++ b/docs/api/cozy-client/classes/StackLink.md @@ -98,6 +98,8 @@ Transfers queries and mutations to a remote stack ▸ **persistCozyData**(`data`, `forward`): `Promise`<`any`> +Persist the given data into the links storage + *Parameters* | Name | Type | @@ -143,6 +145,8 @@ Transfers queries and mutations to a remote stack ▸ **request**(`operation`, `result`, `forward`): `Promise`<`any`> +Request the given operation from the link + *Parameters* | Name | Type | @@ -167,11 +171,17 @@ Transfers queries and mutations to a remote stack ### reset -▸ **reset**(): `void` +▸ **reset**(): `Promise`<`void`> + +Reset the link data *Returns* -`void` +`Promise`<`void`> + +*Overrides* + +[CozyLink](CozyLink.md).[reset](CozyLink.md#reset) *Defined in* diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index 75827771b3..6e74bf75c0 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -603,6 +603,10 @@ CozyLink.request `Promise`<`void`> +*Overrides* + +CozyLink.reset + *Defined in* [CozyPouchLink.js:229](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L229) diff --git a/packages/cozy-client/types/CozyLink.d.ts b/packages/cozy-client/types/CozyLink.d.ts index 5d229867d8..27dda27f57 100644 --- a/packages/cozy-client/types/CozyLink.d.ts +++ b/packages/cozy-client/types/CozyLink.d.ts @@ -1,6 +1,27 @@ export default class CozyLink { constructor(requestHandler: any, persistHandler: any); - request(operation: any, result: any, forward: any): void; - persistCozyData(data: any, forward: any): void; + /** + * Request the given operation from the link + * + * @param {any} operation - The operation to request + * @param {any} result - The result from the previous request of the chain + * @param {any} forward - The next request of the chain + * @returns {Promise} + */ + request(operation: any, result: any, forward: any): Promise; + /** + * Persist the given data into the links storage + * + * @param {any} data - The document to persist + * @param {any} forward - The next persistCozyData of the chain + * @returns {Promise} + */ + persistCozyData(data: any, forward: any): Promise; + /** + * Reset the link data + * + * @returns {Promise} + */ + reset(): Promise; } export function chain(links: any): any; diff --git a/packages/cozy-client/types/FlagshipLink.d.ts b/packages/cozy-client/types/FlagshipLink.d.ts index 3b3e389fa8..a54035c3bb 100644 --- a/packages/cozy-client/types/FlagshipLink.d.ts +++ b/packages/cozy-client/types/FlagshipLink.d.ts @@ -8,6 +8,5 @@ export default class FlagshipLink extends CozyLink { }); webviewIntent: import("cozy-intent").WebviewService; registerClient(client: any): void; - reset(): void; } import CozyLink from "./CozyLink"; diff --git a/packages/cozy-client/types/StackLink.d.ts b/packages/cozy-client/types/StackLink.d.ts index 918ef72ef8..2c353a3c65 100644 --- a/packages/cozy-client/types/StackLink.d.ts +++ b/packages/cozy-client/types/StackLink.d.ts @@ -18,7 +18,6 @@ export default class StackLink extends CozyLink { stackClient: any; isOnline: any; registerClient(client: any): void; - reset(): void; /** * * @param {QueryDefinition} query - Query to execute diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index a8b6a30bfd..49b9fce8f0 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -96,7 +96,6 @@ declare class PouchLink extends CozyLink { }): Promise; onLogin(): Promise; pouches: PouchManager; - reset(): Promise; /** * Receives PouchDB updates (documents grouped by doctype). * Normalizes the data (.id -> ._id, .rev -> _rev). From 6f23dbe692e036d89f5878f174ec0e206b076686 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 19 Sep 2024 14:58:07 +0200 Subject: [PATCH 82/84] feat: Handle queries that fetch Konnectors by channel `fetchJSON()` calls are not compatible with offline mode unless we wrap them in the `.query()` dsl Recently we added a `fetchJSON()` call inside of cozy-home in order to display the new grouped-by-category folders in the home's konnectors section So we want to move this call inside of the AppsRegistryCollection in order to make it compatible with offline mode Related PR: cozy/cozy-home#2186 --- packages/cozy-client/src/CozyClient.js | 2 +- .../src/AppsRegistryCollection.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 243cd28fc7..11669ec522 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -1126,7 +1126,7 @@ client.query(Q('io.cozy.bills'))`) return await this.persistVirtualDocument( { _type: 'io.cozy.apps_registry', - _id: 'maintenance', + _id: definition.id, // @ts-ignore cozyPouchData: data }, diff --git a/packages/cozy-stack-client/src/AppsRegistryCollection.js b/packages/cozy-stack-client/src/AppsRegistryCollection.js index 1967a14854..a34da894b9 100644 --- a/packages/cozy-stack-client/src/AppsRegistryCollection.js +++ b/packages/cozy-stack-client/src/AppsRegistryCollection.js @@ -27,6 +27,16 @@ export const normalizeAppFromRegistry = (data, doctype) => { } } +const fetchKonnectorsByChannel = async (channel, doctype, stackClient) => { + const resp = await stackClient.fetchJSON( + 'GET', + `/registry?versionsChannel=${channel}&filter[type]=konnector&limit=500` + ) + return { + data: resp.data.map(data => normalizeAppFromRegistry(data, doctype)) + } +} + /** * Extends `DocumentCollection` API along with specific methods for `io.cozy.apps_registry`. */ @@ -44,6 +54,12 @@ class AppsRegistryCollection extends DocumentCollection { * @throws {FetchError} */ async get(slug) { + if (slug.startsWith('konnectors/')) { + const channel = slug.split('/')[1] + + return fetchKonnectorsByChannel(channel, this.doctype, this.stackClient) + } + const resp = await this.stackClient.fetchJSON( 'GET', `${this.endpoint}${slug}` From 7e5f1de623d8f671ee8972a1e4887d125155000b Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 23 Sep 2024 18:50:43 +0200 Subject: [PATCH 83/84] feat: Rename FlagshipLink to WebFlagshipLink This should clarify that this link should be run on the web side and not on the Flagship app side --- .../cozy-client/src/{FlagshipLink.js => WebFlagshipLink.js} | 2 +- packages/cozy-client/src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/cozy-client/src/{FlagshipLink.js => WebFlagshipLink.js} (93%) diff --git a/packages/cozy-client/src/FlagshipLink.js b/packages/cozy-client/src/WebFlagshipLink.js similarity index 93% rename from packages/cozy-client/src/FlagshipLink.js rename to packages/cozy-client/src/WebFlagshipLink.js index 901443c8f4..cc0e664225 100644 --- a/packages/cozy-client/src/FlagshipLink.js +++ b/packages/cozy-client/src/WebFlagshipLink.js @@ -1,6 +1,6 @@ import CozyLink from './CozyLink' -export default class FlagshipLink extends CozyLink { +export default class WebFlagshipLink extends CozyLink { /** * @param {object} [options] - Options * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference diff --git a/packages/cozy-client/src/index.js b/packages/cozy-client/src/index.js index 614ca19f00..9d77701cc1 100644 --- a/packages/cozy-client/src/index.js +++ b/packages/cozy-client/src/index.js @@ -1,7 +1,7 @@ export { default } from './CozyClient' export { default as CozyLink } from './CozyLink' export { default as StackLink } from './StackLink' -export { default as FlagshipLink } from './FlagshipLink' +export { default as WebFlagshipLink } from './WebFlagshipLink' export { default as compose } from 'lodash/flow' export { QueryDefinition, From b13b3db429beec6f51b0f08a5ac28895773494a1 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 23 Sep 2024 18:57:47 +0200 Subject: [PATCH 84/84] docs: Update types and documentation --- docs/api/cozy-client/README.md | 2 +- docs/api/cozy-client/classes/CozyLink.md | 2 +- .../{FlagshipLink.md => WebFlagshipLink.md} | 20 +++++++++---------- ...FlagshipLink.d.ts => WebFlagshipLink.d.ts} | 2 +- packages/cozy-client/types/index.d.ts | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) rename docs/api/cozy-client/classes/{FlagshipLink.md => WebFlagshipLink.md} (60%) rename packages/cozy-client/types/{FlagshipLink.d.ts => WebFlagshipLink.d.ts} (87%) diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index bb806e46ce..e426106802 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -16,7 +16,6 @@ cozy-client * [CozyClient](classes/CozyClient.md) * [CozyLink](classes/CozyLink.md) * [CozyProvider](classes/CozyProvider.md) -* [FlagshipLink](classes/FlagshipLink.md) * [HasMany](classes/HasMany.md) * [HasManyInPlace](classes/HasManyInPlace.md) * [HasManyTriggers](classes/HasManyTriggers.md) @@ -29,6 +28,7 @@ cozy-client * [QueryDefinition](classes/QueryDefinition.md) * [Registry](classes/Registry.md) * [StackLink](classes/StackLink.md) +* [WebFlagshipLink](classes/WebFlagshipLink.md) ## Properties diff --git a/docs/api/cozy-client/classes/CozyLink.md b/docs/api/cozy-client/classes/CozyLink.md index 1a4fa2f4a5..9c8abfae53 100644 --- a/docs/api/cozy-client/classes/CozyLink.md +++ b/docs/api/cozy-client/classes/CozyLink.md @@ -8,7 +8,7 @@ ↳ [`StackLink`](StackLink.md) - ↳ [`FlagshipLink`](FlagshipLink.md) + ↳ [`WebFlagshipLink`](WebFlagshipLink.md) ## Constructors diff --git a/docs/api/cozy-client/classes/FlagshipLink.md b/docs/api/cozy-client/classes/WebFlagshipLink.md similarity index 60% rename from docs/api/cozy-client/classes/FlagshipLink.md rename to docs/api/cozy-client/classes/WebFlagshipLink.md index 1ea0f0a621..b405fc2b31 100644 --- a/docs/api/cozy-client/classes/FlagshipLink.md +++ b/docs/api/cozy-client/classes/WebFlagshipLink.md @@ -1,18 +1,18 @@ -[cozy-client](../README.md) / FlagshipLink +[cozy-client](../README.md) / WebFlagshipLink -# Class: FlagshipLink +# Class: WebFlagshipLink ## Hierarchy * [`CozyLink`](CozyLink.md) - ↳ **`FlagshipLink`** + ↳ **`WebFlagshipLink`** ## Constructors ### constructor -• **new FlagshipLink**(`[options]?`) +• **new WebFlagshipLink**(`[options]?`) *Parameters* @@ -27,7 +27,7 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:8](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L8) +[packages/cozy-client/src/WebFlagshipLink.js:8](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L8) ## Properties @@ -37,7 +37,7 @@ *Defined in* -[packages/cozy-client/src/FlagshipLink.js:10](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L10) +[packages/cozy-client/src/WebFlagshipLink.js:10](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L10) ## Methods @@ -64,7 +64,7 @@ Persist the given data into the links storage *Defined in* -[packages/cozy-client/src/FlagshipLink.js:25](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L25) +[packages/cozy-client/src/WebFlagshipLink.js:25](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L25) *** @@ -84,7 +84,7 @@ Persist the given data into the links storage *Defined in* -[packages/cozy-client/src/FlagshipLink.js:13](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L13) +[packages/cozy-client/src/WebFlagshipLink.js:13](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L13) *** @@ -112,7 +112,7 @@ Request the given operation from the link *Defined in* -[packages/cozy-client/src/FlagshipLink.js:21](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L21) +[packages/cozy-client/src/WebFlagshipLink.js:21](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L21) *** @@ -132,4 +132,4 @@ Reset the link data *Defined in* -[packages/cozy-client/src/FlagshipLink.js:17](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/FlagshipLink.js#L17) +[packages/cozy-client/src/WebFlagshipLink.js:17](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/WebFlagshipLink.js#L17) diff --git a/packages/cozy-client/types/FlagshipLink.d.ts b/packages/cozy-client/types/WebFlagshipLink.d.ts similarity index 87% rename from packages/cozy-client/types/FlagshipLink.d.ts rename to packages/cozy-client/types/WebFlagshipLink.d.ts index a54035c3bb..f69ec81d10 100644 --- a/packages/cozy-client/types/FlagshipLink.d.ts +++ b/packages/cozy-client/types/WebFlagshipLink.d.ts @@ -1,4 +1,4 @@ -export default class FlagshipLink extends CozyLink { +export default class WebFlagshipLink extends CozyLink { /** * @param {object} [options] - Options * @param {import('cozy-intent').WebviewService} [options.webviewIntent] - The webview's intent reference diff --git a/packages/cozy-client/types/index.d.ts b/packages/cozy-client/types/index.d.ts index 198ff8e3ff..763bef556c 100644 --- a/packages/cozy-client/types/index.d.ts +++ b/packages/cozy-client/types/index.d.ts @@ -1,7 +1,7 @@ export { default } from "./CozyClient"; export { default as CozyLink } from "./CozyLink"; export { default as StackLink } from "./StackLink"; -export { default as FlagshipLink } from "./FlagshipLink"; +export { default as WebFlagshipLink } from "./WebFlagshipLink"; export { default as compose } from "lodash/flow"; export { default as Registry } from "./registry"; export { default as RealTimeQueries } from "./RealTimeQueries";