import { Collection , IndexSpecification , MongoError } from 'mongodb' ; import chalk from 'chalk' ; import { FieldOrSpec , MongoIndexSpec } from './configure-indexes' ; import { MongoErrorCode } from './mongo-error-code' ; import { Logger } from '../../../commons/logger/logger' ; const logger = new Logger ( 'metroline.server:db.indexes.configure' ) ; function findIndexWithSameKey ( existingIndexes , fieldOrSpec ) { const fields = typeof fieldOrSpec === 'string' ? [ fieldOrSpec ] : Object . keys ( fieldOrSpec ) ; return existingIndexes . find ( index => { const objectWithFields = index . weights || index . key ; return fields . every ( fieldName => Object . prototype . hasOwnProperty . call ( objectWithFields , fieldName ) ) && fields . length === Object . keys ( objectWithFields ) . length ; } ) ; } function computeIndexName ( fieldOrSpec ) { return typeof fieldOrSpec === 'string' ? fieldOrSpec : Object . keys ( fieldOrSpec ) . join ( '_' ) ; } async function createIndexIfNotExists ( spec , collection ) { const indexName = spec . options . name ; const indexNamespace = `${ chalk . blue ( collection . collectionName ) }.${ chalk . cyan ( indexName ) }` ; const createIndex = ( ) => collection . createIndex ( spec . fieldOrSpec , spec . options ) ; try { await createIndex ( ) ; logger . debug ( `Configured index ${ indexNamespace }` ) ; } catch ( e ) { if ( e instanceof MongoError && ( e . code === MongoErrorCode . INDEX_OPTIONS_CONFLICT || e . code === MongoErrorCode . INDEX_KEY_SPECS_CONFLICT ) ) { logger . debug ( `Updating index ${ indexNamespace }` ) ; const existingIndexes = await collection . listIndexes ( ) . toArray ( ) ; const indexWithSameName = existingIndexes . find ( value => value . name === spec . options . name ) ; if ( indexWithSameName ) { await collection . dropIndex ( indexName ) ; } else { const indexWithSameKey = findIndexWithSameKey ( existingIndexes , spec . fieldOrSpec ) ; await collection . dropIndex ( indexWithSameKey . name ) ; } logger . debug ( `Updated index ${ indexNamespace }` ) ; await createIndex ( ) ; } else { throw e ; } } } export async function configureIndexesForCollection ( collection , specs ) { specs . filter ( spec => ! spec . options ?. name ) . forEach ( spec => { spec . options = { ... spec . options , name : computeIndexName ( spec . fieldOrSpec ) , } ; } ) ; await Promise . all ( specs . map ( spec => createIndexIfNotExists ( spec , collection ) ) ) ; const existingIndexes = await collection . listIndexes ( ) . toArray ( ) ; const indexesToDrop = existingIndexes . filter ( index => index . name !== '_id_' ) . filter ( index => specs . every ( spec => spec . options . name !== index . name ) ) ; await Promise . all ( indexesToDrop . map ( ( { name } ) => { logger . info ( `Dropping index ${ chalk . bold ( name ) }` ) ; return collection . dropIndex ( name ) ; } ) ) ; }