import { Injectable } from "@angular/core" ; import { Database , QueryOptions } from "../../database/database" ; import { BehaviorSubject , Observable } from "rxjs" ; import { BackgroundProcessState } from "../../sync-status/background-process-state.interface" ; import { Entity , EntityConstructor } from "../model/entity" ; import { EntitySchemaService } from "../schema/entity-schema.service" ; import { first } from "rxjs/operators" ; /**
 * Manage database query index creation and use, working as a facade in front of the Database service.
 * This allows to track pending indexing processes and also show them to users in the UI.
 */ @ Injectable ( { providedIn : "root" , } ) export class DatabaseIndexingService { private _indicesRegistered = new BehaviorSubject < BackgroundProcessState [ ] > ( [ ] ) ; /** All currently registered indices with their status */ get indicesRegistered ( ) : Observable < BackgroundProcessState [ ] > { return this . _indicesRegistered . asObservable ( ) ; } constructor ( private db , private entitySchemaService ) { } param designDoc link async createIndex ( designDoc ) { const indexState = { title : $localize `Preparing data (Indexing)` , details : designDoc . _id . replace ( /_design\// , "" ) , pending : true , } ; const indexCreationPromise = this . db . saveDatabaseIndex ( designDoc ) ; this . _indicesRegistered . next ( [ ... this . _indicesRegistered . value , indexState , ] ) ; try { await indexCreationPromise ; } catch ( err ) { indexState . pending = false ; indexState . error = err ; this . _indicesRegistered . next ( this . _indicesRegistered . value ) ; throw err ; } indexState . pending = false ; this . _indicesRegistered . next ( this . _indicesRegistered . value ) ; } param entityConstructor param indexName param options async queryIndexDocs < T extends Entity > ( entityConstructor , indexName , options = { } ) { if ( typeof options === "string" ) { options = { key : options } ; } options . include_docs = true ; const rawResults = await this . queryIndexRaw ( indexName , options ) ; return rawResults . rows . map ( ( loadedRecord ) => { const entity = new entityConstructor ( "" ) ; this . entitySchemaService . loadDataIntoEntity ( entity , loadedRecord . doc ) ; return entity ; } ) ; } param entityConstructor param indexName param startkey param endkey async queryIndexDocsRange < T extends Entity > ( entityConstructor , indexName , startkey , endkey ? ) { return this . queryIndexDocs ( entityConstructor , indexName , { startkey : startkey , endkey : endkey + "\ufff0" , } ) ; } async queryIndexStats ( indexName , options = { reduce : true , group : true , } ) { return await this . queryIndexRaw ( indexName , options ) ; } param indexName param options param doNotWaitForIndexCreation async queryIndexRaw ( indexName , options , doNotWaitForIndexCreation ? ) { if ( ! doNotWaitForIndexCreation ) { await this . waitForIndexAvailable ( indexName ) ; } return await this . db . query ( indexName , options ) ; } param indexName private private async waitForIndexAvailable ( indexName ) { function relevantIndexIsReady ( processes , requiredIndexName ) { const relevantProcess = processes . find ( ( process ) => process . details === requiredIndexName || requiredIndexName . startsWith ( process . details + "/" ) ) ; return relevantProcess && ! relevantProcess . pending ; } if ( relevantIndexIsReady ( this . _indicesRegistered . value , indexName ) ) { return ; } await this . _indicesRegistered . pipe ( first ( ( processes ) => relevantIndexIsReady ( processes , indexName ) ) ) . toPromise ( ) ; } }