UNPKG

rxdb

Version:

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

176 lines (172 loc) 8.02 kB
import { RxDBLeaderElectionPlugin } from "../leader-election/index.js"; import { RxReplicationState, startReplicationOnLeaderShip } from "../replication/index.js"; import { addRxPlugin } from "../../index.js"; import { Subject } from 'rxjs'; import { initDriveStructure } from "./init.js"; import { handleUpstreamBatch } from "./upstream.js"; import { fetchChanges } from "./downstream.js"; import { runInTransaction } from "./transaction.js"; import { ensureProcessNextTickIsSet } from "../replication-webrtc/connection-handler-simple-peer.js"; import { SignalingState } from "./signaling.js"; import { deserializeDocAttachments, serializeDocAttachments } from "./document-handling.js"; export * from "./google-drive-types.js"; export * from "./google-drive-helper.js"; export * from "./transaction.js"; export * from "./init.js"; export * from "./document-handling.js"; export * from "./multipart.js"; export * from "./downstream.js"; export * from "./upstream.js"; export * from "./signaling.js"; export var DEFAULT_TRANSACTION_TIMEOUT = 60 * 1000; export class RxGoogleDriveReplicationState extends RxReplicationState { /** * Only exists on live replication */ constructor(googleDrive, driveStructure, replicationIdentifierHash, collection, pull, push, signalingOptions, live = true, retryTime = 1000 * 5, autoStart = true) { super(replicationIdentifierHash, collection, '_deleted', pull, push, live, retryTime, autoStart); this.googleDrive = googleDrive; this.driveStructure = driveStructure; this.replicationIdentifierHash = replicationIdentifierHash; this.collection = collection; this.pull = pull; this.push = push; this.signalingOptions = signalingOptions; this.live = live; this.retryTime = retryTime; this.autoStart = autoStart; } /** * Notify other peers that something * has or might have changed so that * they can pull from their checkpoints. */ async notifyPeers() { if (this.signalingState) { await this.signalingState.pingPeers('RESYNC'); } } } export async function replicateGoogleDrive(options) { var collection = options.collection; addRxPlugin(RxDBLeaderElectionPlugin); var googleDriveOptionsWithDefaults = Object.assign({ apiEndpoint: 'https://www.googleapis.com', transactionTimeout: DEFAULT_TRANSACTION_TIMEOUT, space: 'drive', folderPath: '' }, options.googleDrive); if (typeof googleDriveOptionsWithDefaults.folderPath !== 'string') { googleDriveOptionsWithDefaults.folderPath = ''; } var driveStructure = await initDriveStructure(googleDriveOptionsWithDefaults); /** * When true, attachment binary data is serialised as base64 inside the * document JSON file stored on Google Drive. Defaults to true only when * the collection schema has `attachments: {}` defined. Can be disabled * explicitly by passing `attachments: false` in the options. */ var replicateAttachments = options.attachments !== false && !!collection.schema.jsonSchema.attachments; var replicationState; var pullStream$ = new Subject(); var replicationPrimitivesPull; options.live = typeof options.live === 'undefined' ? true : options.live; options.waitForLeadership = typeof options.waitForLeadership === 'undefined' ? true : options.waitForLeadership; if (options.pull) { replicationPrimitivesPull = { async handler(lastPulledCheckpoint, batchSize) { return runInTransaction(googleDriveOptionsWithDefaults, driveStructure, collection.schema.primaryPath, async () => { var changes = await fetchChanges(googleDriveOptionsWithDefaults, driveStructure, lastPulledCheckpoint, batchSize); /** * Convert base64 attachment data that was stored in the * Google Drive JSON file back to Blobs so that the * downstream replication protocol can write them to the * fork storage instance correctly. */ if (replicateAttachments) { await Promise.all(changes.documents.map(doc => deserializeDocAttachments(doc))); } return changes; }); }, batchSize: options.pull.batchSize, modifier: options.pull.modifier, stream$: pullStream$.asObservable(), initialCheckpoint: options.pull.initialCheckpoint }; } var replicationPrimitivesPush; if (options.push) { replicationPrimitivesPush = { async handler(rows) { /** * Convert Blob attachment data to base64 strings before the * rows are written to the WAL file or stored as document JSON * in Google Drive. We create new row objects so the originals * (which the replication protocol still uses for meta updates) * are not mutated. * When attachments are disabled we still run the conversion so * that Blob values are stripped rather than serialised as `{}` * by JSON.stringify. */ var rowsForDrive = await Promise.all(rows.map(async row => { var newState = await serializeDocAttachments(row.newDocumentState, replicateAttachments); return { ...row, newDocumentState: newState }; })); return runInTransaction(googleDriveOptionsWithDefaults, driveStructure, collection.schema.primaryPath, async () => { var conflicts = await handleUpstreamBatch(googleDriveOptionsWithDefaults, driveStructure, options.collection.schema.primaryPath, rowsForDrive); /** * When attachment replication is enabled, deserialise the * base64 attachment data that Google Drive stored in the * `_attachments_data` field back into Blobs on each conflict * document before returning them to the replication protocol. * * The protocol passes these as `realMasterState` to * `resolveConflictError`, which (with the fix in * `conflicts.ts`) then writes the resolved state — including * the correct attachment Blob — to the fork instance. * Without this deserialisation the fork would receive stubs * with no binary data and the attachment would be lost. */ if (replicateAttachments && conflicts.length > 0) { await Promise.all(conflicts.map(c => deserializeDocAttachments(c))); } return conflicts; }, () => replicationState.notifyPeers().catch(() => {})); }, batchSize: options.push.batchSize, modifier: options.push.modifier }; } replicationState = new RxGoogleDriveReplicationState(googleDriveOptionsWithDefaults, driveStructure, options.replicationIdentifier, collection, replicationPrimitivesPull, replicationPrimitivesPush, options.signalingOptions, options.live, options.retryTime, options.autoStart); /** * Google drive has no websocket or server-send-events * to observe file changes. Therefore we use WebRTC to * connect clients which then can ping each other on changes. * Instead of a signaling server, we use the google-drive itself * to exchange signaling data. */ if (options.live && options.pull) { ensureProcessNextTickIsSet(); var startBefore = replicationState.start.bind(replicationState); var cancelBefore = replicationState.cancel.bind(replicationState); replicationState.start = () => { replicationState.signalingState = new SignalingState(replicationState.googleDrive, replicationState.driveStructure, options.signalingOptions ? options.signalingOptions : {}); var sub = replicationState.signalingState.resync$.subscribe(() => { replicationState.reSync(); }); replicationState.cancel = () => { sub.unsubscribe(); replicationState.signalingState?.close(); return cancelBefore(); }; return startBefore(); }; } startReplicationOnLeaderShip(options.waitForLeadership, replicationState); return replicationState; } //# sourceMappingURL=index.js.map