UNPKG

rxdb

Version:

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

207 lines (204 loc) 8.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _exportNames = { backupSingleDocument: true, RxBackupState: true, backup: true, RxDBBackupPlugin: true }; exports.RxDBBackupPlugin = exports.RxBackupState = void 0; exports.backup = backup; exports.backupSingleDocument = backupSingleDocument; var path = _interopRequireWildcard(require("node:path")); var _rxjs = require("rxjs"); var _index = require("../../plugins/utils/index.js"); var _fileUtil = require("./file-util.js"); Object.keys(_fileUtil).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; if (key in exports && exports[key] === _fileUtil[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _fileUtil[key]; } }); }); var _rxStorageHelper = require("../../rx-storage-helper.js"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * Backups a single documents, * returns the paths to all written files */ async function backupSingleDocument(rxDocument, options) { var data = rxDocument.toJSON(true); var writtenFiles = []; var docFolder = (0, _fileUtil.documentFolder)(options, rxDocument.primary); await (0, _fileUtil.clearFolder)(docFolder); var fileLocation = path.join(docFolder, 'document.json'); await (0, _fileUtil.writeJsonToFile)(fileLocation, data); writtenFiles.push(fileLocation); if (options.attachments) { var attachmentsFolder = path.join(docFolder, 'attachments'); (0, _fileUtil.ensureFolderExists)(attachmentsFolder); var attachments = rxDocument.allAttachments(); await Promise.all(attachments.map(async attachment => { var content = await attachment.getData(); var attachmentFileLocation = path.join(attachmentsFolder, attachment.id); await (0, _fileUtil.writeToFile)(attachmentFileLocation, content); writtenFiles.push(attachmentFileLocation); })); } return writtenFiles; } var BACKUP_STATES_BY_DB = new WeakMap(); function addToBackupStates(db, state) { var ar = (0, _index.getFromMapOrCreate)(BACKUP_STATES_BY_DB, db, () => []); ar.push(state); } var RxBackupState = exports.RxBackupState = /*#__PURE__*/function () { function RxBackupState(database, options) { this.isStopped = false; this.subs = []; this.persistRunning = _index.PROMISE_RESOLVE_VOID; this.initialReplicationDone$ = new _rxjs.BehaviorSubject(false); this.internalWriteEvents$ = new _rxjs.Subject(); this.writeEvents$ = this.internalWriteEvents$.asObservable(); this.database = database; this.options = options; if (!this.options.batchSize) { this.options.batchSize = 10; } addToBackupStates(database, this); (0, _fileUtil.prepareFolders)(database, options); } /** * Persists all data from all collections, * beginning from the oldest sequence checkpoint * to the newest one. * Do not call this while it is already running. * Returns true if there are more documents to process */ var _proto = RxBackupState.prototype; _proto.persistOnce = function persistOnce() { return this.persistRunning = this.persistRunning.then(() => this._persistOnce()); }; _proto._persistOnce = async function _persistOnce() { var _this = this; var meta = await (0, _fileUtil.getMeta)(this.options); await Promise.all(Object.entries(this.database.collections).map(async ([collectionName, collection]) => { var primaryKey = collection.schema.primaryPath; var processedDocuments = new Set(); await this.database.requestIdlePromise(); if (!meta.collectionStates[collectionName]) { meta.collectionStates[collectionName] = {}; } var lastCheckpoint = meta.collectionStates[collectionName].checkpoint; var hasMore = true; var _loop = async function () { await _this.database.requestIdlePromise(); var changesResult = await (0, _rxStorageHelper.getChangedDocumentsSince)(collection.storageInstance, _this.options.batchSize ? _this.options.batchSize : 0, lastCheckpoint); lastCheckpoint = changesResult.documents.length > 0 ? changesResult.checkpoint : lastCheckpoint; meta.collectionStates[collectionName].checkpoint = lastCheckpoint; var docIds = changesResult.documents.map(doc => doc[primaryKey]).filter(id => { if (processedDocuments.has(id)) { return false; } else { processedDocuments.add(id); return true; } }).filter((elem, pos, arr) => arr.indexOf(elem) === pos); // unique await _this.database.requestIdlePromise(); var docs = await collection.findByIds(docIds).exec(); if (docs.size === 0) { hasMore = false; return 1; // continue } await Promise.all(Array.from(docs.values()).map(async doc => { var writtenFiles = await backupSingleDocument(doc, _this.options); _this.internalWriteEvents$.next({ collectionName: collection.name, documentId: doc.primary, files: writtenFiles, deleted: false }); })); // handle deleted documents await Promise.all(docIds.filter(docId => !docs.has(docId)).map(async docId => { await (0, _fileUtil.deleteFolder)((0, _fileUtil.documentFolder)(_this.options, docId)); _this.internalWriteEvents$.next({ collectionName: collection.name, documentId: docId, files: [], deleted: true }); })); }; while (hasMore && !this.isStopped) { if (await _loop()) continue; } meta.collectionStates[collectionName].checkpoint = lastCheckpoint; await (0, _fileUtil.setMeta)(this.options, meta); })); if (!this.initialReplicationDone$.getValue()) { this.initialReplicationDone$.next(true); } }; _proto.watchForChanges = function watchForChanges() { var collections = Object.values(this.database.collections); collections.forEach(collection => { var changes$ = collection.storageInstance.changeStream(); var sub = changes$.subscribe(() => { this.persistOnce(); }); this.subs.push(sub); }); } /** * Returns a promise that resolves when the initial backup is done * and the filesystem is in sync with the database state */; _proto.awaitInitialBackup = function awaitInitialBackup() { return (0, _rxjs.firstValueFrom)(this.initialReplicationDone$.pipe((0, _rxjs.filter)(v => !!v), (0, _rxjs.map)(() => true))); }; _proto.cancel = function cancel() { if (this.isStopped) { return _index.PROMISE_RESOLVE_FALSE; } this.isStopped = true; this.subs.forEach(sub => sub.unsubscribe()); return _index.PROMISE_RESOLVE_TRUE; }; return RxBackupState; }(); function backup(options) { var backupState = new RxBackupState(this, options); backupState.persistOnce(); if (options.live) { backupState.watchForChanges(); } return backupState; } var RxDBBackupPlugin = exports.RxDBBackupPlugin = { name: 'backup', rxdb: true, prototypes: { RxDatabase(proto) { proto.backup = backup; } }, hooks: { preCloseRxDatabase: { after: function preCloseRxDatabase(db) { var states = BACKUP_STATES_BY_DB.get(db); if (states) { states.forEach(state => state.cancel()); } } } } }; //# sourceMappingURL=index.js.map