import { Database , GetAllOptions , GetOptions , QueryOptions } from "./database" ; import { LoggingService } from "../logging/logging.service" ; import PouchDB from "pouchdb-browser" ; import memory from "pouchdb-adapter-memory" ; import { PerformanceAnalysisLogging } from "../../utils/performance-analysis-logging" ; /**
 * Wrapper for a PouchDB instance to decouple the code from
 * that external library.
 *
 * Additional convenience functions on top of the PouchDB API
 * should be implemented in the abstract  Database .
  export class PouchDatabase extends Database { param data static createWithData ( data ) { const instance = PouchDatabase . createWithInMemoryDB ( ) ; data . forEach ( ( doc ) => instance . put ( doc , true ) ) ; return instance ; } static createWithInMemoryDB ( dbname = "in-memory-mock-database" , loggingService = new LoggingService ( ) ) { PouchDB . plugin ( memory ) ; return new PouchDatabase ( new PouchDB ( dbname , { adapter : "memory" } ) , loggingService ) ; } static createWithIndexedDB ( dbname = "in-browser-database" , loggingService = new LoggingService ( ) ) { return new PouchDatabase ( new PouchDB ( dbname ) , loggingService ) ; } private indexPromises : Promise < any > [ ] = [ ] ; param _pouchDB param loggingService constructor ( private _pouchDB , private loggingService ) { super ( ) ; } /**
     * Load a single document by id from the database.
     * (see  Database )
     *  param id param options param returnUndefined get ( id , options = { } , returnUndefined ? ) { return this . _pouchDB . get ( id , options ) . catch ( ( err ) => { if ( err . status === 404 ) { this . loggingService . debug ( "Doc not found in database: " + id ) ; if ( returnUndefined ) { return undefined ; } } throw err ; } ) ; } /**
     * Load all documents (matching the given PouchDB options) from the database.
     * (see  Database )
     *
     * Normally you should rather use "getAll()" or another well typed method of this class
     * instead of passing PouchDB specific options here
     * because that will make your code tightly coupled with PouchDB rather than any other database provider.
     *
     *  param options allDocs ( options ? ) { return this . _pouchDB . allDocs ( options ) . then ( ( result ) => { const resultArray = [ ] ; for ( const row of result . rows ) { resultArray . push ( row . doc ) ; } return resultArray ; } ) ; } /**
     * Save a document to the database.
     * (see  Database )
     *
     *  param object param forceOverwrite put ( object , forceOverwrite ? ) { const options = { } ; if ( forceOverwrite ) { object . _rev = undefined ; } return this . _pouchDB . put ( object , options ) . catch ( ( err ) => { if ( err . status === 409 ) { return this . resolveConflict ( object , forceOverwrite , err ) ; } else { throw err ; } } ) ; } /**
     * Delete a document from the database
     * (see  Database )
     *
     *  param object remove ( object ) { return this . _pouchDB . remove ( object ) . catch ( ( err ) => { throw err ; } ) ; } param remoteDatabase sync ( remoteDatabase ) { return this . _pouchDB . sync ( remoteDatabase , { batch_size : 500 , } ) ; } public async destroy ( ) { await Promise . all ( this . indexPromises ) ; return this . _pouchDB . destroy ( ) ; } /**
     * Query data from the database based on a more complex, indexed request.
     * (see  Database )
     *
     * This is directly calling the PouchDB implementation of this function.
     * Also see the documentation there:  https param fun param options query ( fun , options ) { return this . _pouchDB . query ( fun , options ) ; } /**
     * Create a database index to `query()` certain data more efficiently in the future.
     * (see  Database )
     *
     * Also see the PouchDB documentation regarding indices and queries:  https param designDoc saveDatabaseIndex ( designDoc ) { const creationPromise = this . createOrUpdateDesignDoc ( designDoc ) ; this . indexPromises . push ( creationPromise ) ; return creationPromise ; } private async createOrUpdateDesignDoc ( designDoc ) { const existingDesignDoc = await this . get ( designDoc . _id , { } , true ) ; if ( ! existingDesignDoc ) { this . loggingService . debug ( "creating new database index" ) ; } else if ( JSON . stringify ( existingDesignDoc . views ) !== JSON . stringify ( designDoc . views ) ) { this . loggingService . debug ( "replacing existing database index" ) ; designDoc . _rev = existingDesignDoc . _rev ; } else { return ; } await this . put ( designDoc ) ; await this . prebuildViewsOfDesignDoc ( designDoc ) ; } @ PerformanceAnalysisLogging private async prebuildViewsOfDesignDoc ( designDoc ) { for ( const viewName of Object . keys ( designDoc . views ) ) { const queryName = designDoc . _id . replace ( /_design\// , "" ) + "/" + viewName ; await this . query ( queryName , { key : "1" } ) ; } } param newObject param overwriteChanges param existingError private async resolveConflict ( newObject , overwriteChanges , existingError ) { const existingObject = await this . get ( newObject . _id ) ; const resolvedObject = this . mergeObjects ( existingObject , newObject ) ; if ( resolvedObject ) { this . loggingService . debug ( "resolved document conflict automatically (" + resolvedObject . _id + ")" ) ; return this . put ( resolvedObject ) ; } else if ( overwriteChanges ) { this . loggingService . debug ( "overwriting conflicting document version (" + newObject . _id + ")" ) ; newObject . _rev = existingObject . _rev ; return this . put ( newObject ) ; } else { existingError . message = existingError . message + " (unable to resolve)" ; existingError . affectedDocument = newObject . _id ; throw existingError ; } } private mergeObjects ( existingObject , newObject ) { return undefined ; } }