UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

168 lines (166 loc) 7.57 kB
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import { RxReplicationState, startReplicationOnLeaderShip } from "../replication/index.js"; import { addRxPlugin } from "../../plugin.js"; import { RxDBLeaderElectionPlugin } from "../leader-election/index.js"; import { Databases, Query } from 'appwrite'; import { lastOfArray } from "../utils/utils-array.js"; import { appwriteDocToRxDB, rxdbDocToAppwrite } from "./appwrite-helpers.js"; import { flatClone } from "../utils/utils-object.js"; import { Subject } from 'rxjs'; import { getFromMapOrThrow } from "../utils/index.js"; export var RxAppwriteReplicationState = /*#__PURE__*/function (_RxReplicationState) { function RxAppwriteReplicationState(replicationIdentifierHash, collection, pull, push, live = true, retryTime = 1000 * 5, autoStart = true) { var _this; _this = _RxReplicationState.call(this, replicationIdentifierHash, collection, '_deleted', pull, push, live, retryTime, autoStart) || this; _this.replicationIdentifierHash = replicationIdentifierHash; _this.collection = collection; _this.pull = pull; _this.push = push; _this.live = live; _this.retryTime = retryTime; _this.autoStart = autoStart; return _this; } _inheritsLoose(RxAppwriteReplicationState, _RxReplicationState); return RxAppwriteReplicationState; }(RxReplicationState); export function replicateAppwrite(options) { var collection = options.collection; var primaryKey = collection.schema.primaryPath; var pullStream$ = new Subject(); addRxPlugin(RxDBLeaderElectionPlugin); options.live = typeof options.live === 'undefined' ? true : options.live; options.deletedField = options.deletedField ? options.deletedField : '_deleted'; options.waitForLeadership = typeof options.waitForLeadership === 'undefined' ? true : options.waitForLeadership; var databases = new Databases(options.client); var replicationPrimitivesPull = options.pull ? { batchSize: options.pull.batchSize, modifier: options.pull.modifier, stream$: pullStream$.asObservable(), initialCheckpoint: options.pull.initialCheckpoint, handler: async (lastPulledCheckpoint, batchSize) => { var queries = []; if (lastPulledCheckpoint) { queries.push(Query.or([Query.greaterThan('$updatedAt', lastPulledCheckpoint.updatedAt), Query.and([Query.equal('$updatedAt', lastPulledCheckpoint.updatedAt), Query.greaterThan('$id', lastPulledCheckpoint.id)])])); } queries.push(Query.orderAsc('$updatedAt')); queries.push(Query.orderAsc('$id')); queries.push(Query.limit(batchSize)); var result = await databases.listDocuments(options.databaseId, options.collectionId, queries); var lastDoc = lastOfArray(result.documents); var newCheckpoint = lastDoc ? { id: lastDoc.$id, updatedAt: lastDoc.$updatedAt } : undefined; var resultDocs = result.documents.map(doc => { return appwriteDocToRxDB(doc, primaryKey, options.deletedField); }); return { checkpoint: newCheckpoint, documents: resultDocs }; } } : undefined; var replicationPrimitivesPush = options.push ? { async handler(rows) { var query; // inserts will conflict on write var nonInsertRows = rows.filter(row => row.assumedMasterState); var updateDocsInDbById = new Map(); if (nonInsertRows.length > 0) { if (nonInsertRows.length > 1) { query = Query.or(nonInsertRows.map(row => { var id = row.newDocumentState[primaryKey]; return Query.equal('$id', id); })); } else { var id = nonInsertRows[0].newDocumentState[primaryKey]; query = Query.equal('$id', id); } var updateDocsOnServer = await databases.listDocuments(options.databaseId, options.collectionId, [query]); updateDocsOnServer.documents.forEach(doc => { var docDataInDb = appwriteDocToRxDB(doc, primaryKey, options.deletedField); var docId = doc.$id; docDataInDb[primaryKey] = docId; updateDocsInDbById.set(docId, docDataInDb); }); } var conflicts = []; await Promise.all(rows.map(async writeRow => { var docId = writeRow.newDocumentState[primaryKey]; if (!writeRow.assumedMasterState) { // INSERT var insertDoc = rxdbDocToAppwrite(writeRow.newDocumentState, primaryKey, options.deletedField); try { await databases.createDocument(options.databaseId, options.collectionId, docId, insertDoc // ["read("any")"] // permissions (optional) ); } catch (err) { if (err.code == 409 && err.name === 'AppwriteException') { // document already exists -> conflict var docOnServer = await databases.getDocument(options.databaseId, options.collectionId, docId); var docOnServerData = appwriteDocToRxDB(docOnServer, primaryKey, options.deletedField); conflicts.push(docOnServerData); } else { throw err; } } } else { // UPDATE /** * TODO appwrite does not have a update-if-equals-X method, * so we pre-fetch the documents and compare them locally. * This might cause problems when multiple users update the * same documents very fast. */ var docInDb = getFromMapOrThrow(updateDocsInDbById, docId); if (!writeRow.assumedMasterState || collection.conflictHandler.isEqual(docInDb, writeRow.assumedMasterState, 'replication-appwrite-push') === false) { // conflict conflicts.push(docInDb); } else { // no conflict var writeDoc = flatClone(writeRow.newDocumentState); delete writeDoc[primaryKey]; writeDoc[options.deletedField] = writeDoc._deleted; if (options.deletedField !== '_deleted') { delete writeDoc._deleted; } await databases.updateDocument(options.databaseId, options.collectionId, docId, writeDoc // ["read("any")"] // permissions (optional) ); } } })); return conflicts; } } : undefined; var replicationState = new RxAppwriteReplicationState(options.replicationIdentifier, collection, replicationPrimitivesPull, replicationPrimitivesPush, options.live, options.retryTime, options.autoStart); /** * Subscribe to changes for the pull.stream$ */ if (options.live && options.pull) { var startBefore = replicationState.start.bind(replicationState); var cancelBefore = replicationState.cancel.bind(replicationState); replicationState.start = () => { var channel = 'databases.' + options.databaseId + '.collections.' + options.collectionId + '.documents'; var unsubscribe = options.client.subscribe(channel, response => { var docData = appwriteDocToRxDB(response.payload, primaryKey, options.deletedField); pullStream$.next({ checkpoint: { id: docData[primaryKey], updatedAt: response.payload.$updatedAt }, documents: [docData] }); }); replicationState.cancel = () => { unsubscribe(); return cancelBefore(); }; return startBefore(); }; } startReplicationOnLeaderShip(options.waitForLeadership, replicationState); return replicationState; } //# sourceMappingURL=index.js.map