diff --git a/src/MongoDB.ts b/src/MongoDB.ts index 4312ad1..7d2089a 100644 --- a/src/MongoDB.ts +++ b/src/MongoDB.ts @@ -1,5 +1,5 @@ import { inspect } from 'node:util'; -import { MongoClient, MongoClientOptions, Db, Document, WithId, ObjectId, Filter, IndexSpecification, CreateIndexesOptions, FindOptions, MongoServerError } from 'mongodb'; +import { MongoClient, MongoClientOptions, Db, Document, WithId, ObjectId, Filter, IndexSpecification, CreateIndexesOptions, FindOptions } from 'mongodb'; import { IServer, ILogger, LoggerClientOptions } from './interfaces/index.js'; type Credentials = { @@ -24,6 +24,20 @@ export type MongoOptions = { load?: boolean } +type StringIndexable = {[key: string]: boolean | string | number | Document | object} + +const objIsSubset = (superObj: StringIndexable, subObj: StringIndexable): boolean => +{ + return Object.keys(subObj).every(ele => + { + if (typeof subObj[ele] === 'object' && typeof superObj[ele] === 'object') + { + return objIsSubset(superObj[ele] as StringIndexable, subObj[ele] as StringIndexable); + } + return subObj[ele] === superObj[ele]; + }); +}; + /** * A dedicated class to locally wrap the mongodb API wrapper * @@ -363,36 +377,25 @@ class MongoDB return this.#db.collection(coll).countDocuments(query); } - async ensureIndex (collection: string, index: IndexSpecification, options?: CreateIndexesOptions): Promise + async ensureIndex (collection: string, index: IndexSpecification, options?: CreateIndexesOptions & StringIndexable): Promise { if (!this.#db) return Promise.reject(new Error('MongoDB not connected')); if (!(index instanceof Array)) index = [ index ]; - try - { - await this.#db.collection(collection).createIndex(index, options); - } - catch (err) - { - // If the index exists, recreate it with the new spec - const error = err as MongoServerError; - // Mongo changes the given name of the index to reflect the direciton, so find it by matching the start of the name - const indexes = await this.#db.collection(collection).indexes(); - const existing = indexes.find(idx => idx.name.startsWith(index)); - if (error.codeName === 'IndexKeySpecsConflict' && existing) - { - await this.#db.collection(collection).dropIndex(existing.name); - await this.#db.collection(collection).createIndex(index, options); - } - else - { - throw error; - } - } + + const indexes = await this.#db.collection(collection).indexes(); + const existing = indexes.find(idx => idx.name.startsWith(index)); + if (existing && this.#indexesEqual(existing, options)) + return; + + if (existing) + await this.#db.collection(collection).dropIndex(existing.name); + + await this.#db.collection(collection).createIndex(index, options); } - async ensureIndices (collection: string, indices: IndexSpecification[], options?: CreateIndexesOptions): Promise + async ensureIndices (collection: string, indices: IndexSpecification[], options?: CreateIndexesOptions & StringIndexable): Promise { if (!this.#db) return Promise.reject(new Error('MongoDB not connected')); @@ -400,6 +403,30 @@ class MongoDB await this.ensureIndex(collection, index, options); } + #indexesEqual (existing: Document, options?: CreateIndexesOptions & StringIndexable) + { + // 3 keys on the existing means that only the name was given + if (!options && Object.keys(existing).length === 3) + return true; + else if (!options) + return false; + + const keys = Object.keys(options); + for (const key of keys) + { + if (typeof options[key] !== typeof existing[key]) + return false; + + if (typeof options[key] === 'object' && !objIsSubset(existing, options)) + return false; + else if (options[key] !== existing[key]) + return false; + } + + return true; + + } + } export { MongoDB }; \ No newline at end of file