@toa.io/storages.mongodb 1.0.0-alpha.0 → 1.0.0-alpha.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/storages.mongodb",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.107",
4
4
  "description": "Toa MongoDB Storage Connector",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -19,12 +19,13 @@
19
19
  "test": "echo \"Error: run tests from root\" && exit 1"
20
20
  },
21
21
  "dependencies": {
22
- "@toa.io/console": "1.0.0-alpha.0",
23
- "@toa.io/core": "1.0.0-alpha.0",
24
- "@toa.io/generic": "1.0.0-alpha.0",
25
- "@toa.io/pointer": "1.0.0-alpha.0",
26
- "mongodb": "5.9.1",
22
+ "@toa.io/conveyor": "1.0.0-alpha.93",
23
+ "@toa.io/core": "1.0.0-alpha.107",
24
+ "@toa.io/generic": "1.0.0-alpha.93",
25
+ "@toa.io/pointer": "1.0.0-alpha.93",
26
+ "mongodb": "6.7.0",
27
+ "openspan": "1.0.0-alpha.93",
27
28
  "saslprep": "1.0.3"
28
29
  },
29
- "gitHead": "06c64546f6292cc07c52f74b31415101037f7616"
30
+ "gitHead": "0c69afa09e2515ed76f88eaa9c9f5e5c97d8c5db"
30
31
  }
package/src/client.js ADDED
@@ -0,0 +1,160 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @typedef {import('mongodb').MongoClient} MongoClient
5
+ * @typedef {{ count: number, client: MongoClient }} Instance
6
+ * @typedef {import('@toa.io/core').Locator} Locator
7
+ */
8
+
9
+ const { console } = require('openspan')
10
+ const { Connector } = require('@toa.io/core')
11
+ const { resolve } = require('@toa.io/pointer')
12
+ const { ID } = require('./deployment')
13
+ const { MongoClient } = require('mongodb')
14
+
15
+ /**
16
+ * @type {Record<string, Promise<Instance>>}
17
+ */
18
+ const INSTANCES = {}
19
+
20
+ class Client extends Connector {
21
+ name
22
+
23
+ /**
24
+ * @public
25
+ * @type {import('mongodb').Collection}
26
+ */
27
+ collection
28
+
29
+ /**
30
+ * @private
31
+ * @type {Locator}
32
+ */
33
+ locator
34
+
35
+ /**
36
+ * @private
37
+ * @type {Instance}
38
+ */
39
+ instance
40
+
41
+ /**
42
+ * @private
43
+ * @type {string}
44
+ */
45
+ key
46
+
47
+ /**
48
+ * @param {Locator} locator
49
+ */
50
+ constructor (locator) {
51
+ super()
52
+
53
+ this.locator = locator
54
+ }
55
+
56
+ /**
57
+ * @protected
58
+ * @override
59
+ * @return {Promise<void>}
60
+ */
61
+ async open () {
62
+ const urls = await this.resolveURLs()
63
+ const dbname = this.resolveDB()
64
+
65
+ this.name = this.locator.lowercase
66
+ this.key = getKey(dbname, urls)
67
+
68
+ INSTANCES[this.key] ??= this.createInstance(urls)
69
+
70
+ this.instance = await INSTANCES[this.key]
71
+ this.instance.count++
72
+
73
+ const db = this.instance.client.db(dbname)
74
+
75
+ try {
76
+ this.collection = await db.createCollection(this.name)
77
+ } catch (e) {
78
+ if (e.code !== ALREADY_EXISTS) {
79
+ throw e
80
+ }
81
+
82
+ this.collection = db.collection(this.name)
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @protected
88
+ * @override
89
+ * @return {Promise<void>}
90
+ */
91
+ async close () {
92
+ const instance = await INSTANCES[this.key]
93
+
94
+ instance.count--
95
+
96
+ if (instance.count === 0) {
97
+ await instance.client.close()
98
+ delete INSTANCES[this.key]
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @private
104
+ * @param {string[]} urls
105
+ * @return {Promise<Instance>}
106
+ */
107
+ async createInstance (urls) {
108
+ const client = new MongoClient(urls.join(','), OPTIONS)
109
+ const hosts = urls.map((str) => new URL(str).host)
110
+
111
+ console.info('Connecting to MongoDB', { address: hosts.join(', ') })
112
+
113
+ await client.connect()
114
+
115
+ return {
116
+ count: 0,
117
+ client
118
+ }
119
+ }
120
+
121
+ /**
122
+ * @private
123
+ * @return {Promise<string[]>}
124
+ */
125
+ async resolveURLs () {
126
+ if (process.env.TOA_DEV === '1') {
127
+ return ['mongodb://developer:secret@localhost']
128
+ } else {
129
+ return await resolve(ID, this.locator.id)
130
+ }
131
+ }
132
+
133
+ /**
134
+ * @private
135
+ * @return {string}
136
+ */
137
+ resolveDB () {
138
+ if (process.env.TOA_CONTEXT !== undefined) {
139
+ return process.env.TOA_CONTEXT
140
+ }
141
+
142
+ if (process.env.TOA_DEV === '1') {
143
+ return 'toa-dev'
144
+ }
145
+
146
+ throw new Error('Environment variable TOA_CONTEXT is not defined')
147
+ }
148
+ }
149
+
150
+ function getKey (db, urls) {
151
+ return db + ':' + urls.sort().join(' ')
152
+ }
153
+
154
+ const OPTIONS = {
155
+ ignoreUndefined: true
156
+ }
157
+
158
+ const ALREADY_EXISTS = 48
159
+
160
+ exports.Client = Client
package/src/factory.js CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { Connection } = require('./connection')
3
+ const { Client } = require('./client')
4
4
  const { Storage } = require('./storage')
5
5
 
6
6
  class Factory {
7
- storage (locator) {
8
- const connection = new Connection(locator)
7
+ storage (locator, entity) {
8
+ const client = new Client(locator)
9
9
 
10
- return new Storage(connection)
10
+ return new Storage(client, entity)
11
11
  }
12
12
  }
13
13
 
package/src/record.js CHANGED
@@ -1,21 +1,14 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * @param {toa.core.storages.Record} entity
5
- * @returns {toa.mongodb.Record}
6
- */
7
- const to = (entity) => {
8
- const { id, _version, ...rest } = entity
3
+ function to (entity) {
4
+ const { id, ...rest } = entity
9
5
 
10
- return /** @type {toa.mongodb.Record} */ { _id: id, _version: _version + 1, ...rest }
6
+ return /** @type {toa.mongodb.Record} */ { _id: id, ...rest }
11
7
  }
12
8
 
13
- /**
14
- * @param {toa.mongodb.Record} record
15
- * @returns {toa.core.storages.Record}
16
- */
17
- const from = (record) => {
18
- if (record === undefined || record === null) return null
9
+ function from (record) {
10
+ if (record === undefined || record === null)
11
+ return null
19
12
 
20
13
  const { _id, ...rest } = record
21
14
 
package/src/storage.js CHANGED
@@ -1,82 +1,259 @@
1
1
  'use strict'
2
2
 
3
- const { Connector } = require('@toa.io/core')
4
-
3
+ const { Connector, exceptions } = require('@toa.io/core')
4
+ const { console } = require('openspan')
5
5
  const { translate } = require('./translate')
6
6
  const { to, from } = require('./record')
7
+ const { ReturnDocument } = require('mongodb')
7
8
 
8
- /**
9
- * @implements {toa.core.Storage}
10
- */
11
9
  class Storage extends Connector {
12
- /** @type {toa.mongodb.Connection} */
13
- #connection
10
+ #client
11
+
12
+ /** @type {import('mongodb').Collection} */
13
+ #collection
14
+ #entity
14
15
 
15
- /**
16
- * @param {toa.mongodb.Connection} connection
17
- */
18
- constructor (connection) {
16
+ constructor (client, entity) {
19
17
  super()
20
18
 
21
- this.#connection = connection
19
+ this.#client = client
20
+ this.#entity = entity
21
+
22
+ this.depends(client)
23
+ }
24
+
25
+ async open () {
26
+ this.#collection = this.#client.collection
22
27
 
23
- this.depends(connection)
28
+ await this.index()
24
29
  }
25
30
 
26
31
  async get (query) {
27
32
  const { criteria, options } = translate(query)
28
33
 
29
- const record = await this.#connection.get(criteria, options)
34
+ this.debug('findOne', { criteria, options })
35
+
36
+ const record = await this.#collection.findOne(criteria, options)
30
37
 
31
38
  return from(record)
32
39
  }
33
40
 
34
41
  async find (query) {
35
42
  const { criteria, options } = translate(query)
36
- const recordset = await this.#connection.find(criteria, options)
43
+
44
+ criteria._deleted = null
45
+
46
+ this.debug('find', { criteria, options })
47
+
48
+ const recordset = await this.#collection.find(criteria, options).toArray()
37
49
 
38
50
  return recordset.map((item) => from(item))
39
51
  }
40
52
 
53
+ async stream (query = undefined) {
54
+ const { criteria, options } = translate(query)
55
+
56
+ this.debug('find (stream)', { criteria, options })
57
+
58
+ return this.#collection.find(criteria, options).stream({ transform: from })
59
+ }
60
+
41
61
  async add (entity) {
42
62
  const record = to(entity)
43
63
 
44
- return await this.#connection.add(record)
64
+ this.debug('insertOne', { record })
65
+
66
+ const result = await this.#collection.insertOne(record)
67
+
68
+ return result.acknowledged
45
69
  }
46
70
 
47
71
  async set (entity) {
48
- const criteria = { _id: entity.id, _version: entity._version }
49
- const result = await this.#connection.replace(criteria, to(entity))
72
+ const criteria = {
73
+ _id: entity.id,
74
+ _version: entity._version - 1
75
+ }
50
76
 
51
- return result.value !== null
77
+ const record = to(entity)
78
+
79
+ this.debug('findOneAndReplace', { criteria, record })
80
+
81
+ const result = await this.#collection.findOneAndReplace(criteria, record)
82
+
83
+ return result !== null
52
84
  }
53
85
 
54
- async store (entity) {
55
- if (entity._version === 0) return this.add(entity)
56
- else return this.set(entity)
86
+ async store (entity, attempt = 0) {
87
+ try {
88
+ if (entity._version === 1)
89
+ return await this.add(entity)
90
+ else
91
+ return await this.set(entity)
92
+ } catch (error) {
93
+ if (error.code === ERR_DUPLICATE_KEY) {
94
+ const id = error.keyPattern === undefined
95
+ ? error.message.includes(' index: _id_ ') // AWS DocumentDB
96
+ : error.keyPattern._id === 1
97
+
98
+ if (id)
99
+ return false
100
+ else
101
+ throw new exceptions.DuplicateException(this.#client.name, entity)
102
+ } else if (error.cause?.code === 'ECONNREFUSED') {
103
+ // This is temporary and should be replaced with a class decorator.
104
+ if (attempt > 10)
105
+ throw error
106
+
107
+ await new Promise((resolve) => setTimeout(resolve, 1000))
108
+
109
+ return this.store(entity)
110
+ } else
111
+ throw error
112
+ }
57
113
  }
58
114
 
59
- async upsert (query, changeset, insert) {
115
+ async upsert (query, changeset) {
60
116
  const { criteria, options } = translate(query)
61
- const update = { $set: { ...changeset }, $inc: { _version: 1 } }
62
117
 
63
- if (insert !== undefined) {
64
- delete insert._version
118
+ if (!('_deleted' in changeset) || changeset._deleted === null) {
119
+ delete criteria._deleted
120
+ changeset._deleted = null
121
+ }
122
+
123
+ const update = {
124
+ $set: { ...changeset },
125
+ $inc: { _version: 1 }
126
+ }
127
+
128
+ options.returnDocument = ReturnDocument.AFTER
129
+
130
+ this.debug('findOneAndUpdate', { criteria, update, options })
131
+
132
+ const result = await this.#collection.findOneAndUpdate(criteria, update, options)
133
+
134
+ return from(result)
135
+ }
136
+
137
+ async ensure (query, properties, state) {
138
+ let { criteria, options } = translate(query)
139
+
140
+ if (query === undefined)
141
+ criteria = properties
65
142
 
66
- options.upsert = true
143
+ const update = { $setOnInsert: to(state) }
67
144
 
68
- if (criteria._id !== undefined) insert._id = criteria._id
69
- else return null // this shouldn't ever happen
145
+ options.upsert = true
146
+ options.returnDocument = ReturnDocument.AFTER
70
147
 
71
- if (Object.keys(insert) > 0) update.$setOnInsert = insert
148
+ console.debug('Database query', { method: 'findOneAndUpdate', criteria, update, options })
149
+
150
+ const result = await this.#collection.findOneAndUpdate(criteria, update, options)
151
+
152
+ if (result._deleted !== undefined && result._deleted !== null)
153
+ return null
154
+ else
155
+ return from(result)
156
+ }
157
+
158
+ async index () {
159
+ const indexes = []
160
+
161
+ if (this.#entity.unique !== undefined) {
162
+ for (const [name, fields] of Object.entries(this.#entity.unique)) {
163
+ const sparse = this.checkFields(fields)
164
+ const unique = await this.uniqueIndex(name, fields, sparse)
165
+
166
+ indexes.push(unique)
167
+ }
168
+ }
169
+
170
+ if (this.#entity.index !== undefined) {
171
+ for (const [suffix, declaration] of Object.entries(this.#entity.index)) {
172
+ const name = 'index_' + suffix
173
+ const fields = Object.fromEntries(Object.entries(declaration)
174
+ .map(([name, type]) => [name, INDEX_TYPES[type]]))
175
+
176
+ const sparse = this.checkFields(Object.keys(fields))
177
+
178
+ await this.#collection.createIndex(fields, { name, sparse })
179
+ .catch((e) => console.warn('Index creation failed', { name, fields, error: e }))
180
+
181
+ indexes.push(name)
182
+ }
72
183
  }
73
184
 
74
- options.returnDocument = 'after'
185
+ await this.removeObsoleteIndexes(indexes)
186
+ }
187
+
188
+ async uniqueIndex (name, properties, sparse = false) {
189
+ const fields = properties.reduce((acc, property) => {
190
+ acc[property] = 1
191
+ return acc
192
+ }, {})
193
+
194
+ name = 'unique_' + name
75
195
 
76
- const result = await this.#connection.update(criteria, update, options)
196
+ await this.#collection.createIndex(fields, { name, unique: true, sparse })
197
+ .catch((e) => console.warn('Unique index creation failed', { name, fields, error: e }))
77
198
 
78
- return from(result.value)
199
+ return name
79
200
  }
201
+
202
+ async removeObsoleteIndexes (desired) {
203
+ const current = await this.getCurrentIndexes()
204
+ const obsolete = current.filter((name) => !desired.includes(name))
205
+
206
+ if (obsolete.length > 0) {
207
+ console.info('Removing obsolete indexes', { indexes: obsolete.join(', ') })
208
+
209
+ await Promise.all(obsolete.map((name) => this.#collection.dropIndex(name)))
210
+ }
211
+ }
212
+
213
+ async getCurrentIndexes () {
214
+ try {
215
+ const array = await this.#collection.listIndexes().toArray()
216
+
217
+ return array.map(({ name }) => name).filter((name) => name !== '_id_')
218
+ } catch {
219
+ return []
220
+ }
221
+ }
222
+
223
+ checkFields (fields) {
224
+ const optional = []
225
+
226
+ for (const field of fields) {
227
+ if (!(field in this.#entity.schema.properties))
228
+ throw new Error(`Index field '${field}' is not defined.`)
229
+
230
+ if (!this.#entity.schema.required?.includes(field))
231
+ optional.push(field)
232
+ }
233
+
234
+ if (optional.length > 0) {
235
+ console.info('Index fields are optional, creating sparse index', { fields: optional.join(', ') })
236
+
237
+ return true
238
+ } else
239
+ return false
240
+ }
241
+
242
+ debug (method, attributes) {
243
+ console.debug('Database query', {
244
+ collection: this.#client.name,
245
+ method,
246
+ ...attributes
247
+ })
248
+ }
249
+ }
250
+
251
+ const INDEX_TYPES = {
252
+ 'asc': 1,
253
+ 'desc': -1,
254
+ 'hash': 'hashed'
80
255
  }
81
256
 
257
+ const ERR_DUPLICATE_KEY = 11000
258
+
82
259
  exports.Storage = Storage
package/src/translate.js CHANGED
@@ -7,12 +7,16 @@ const parse = { ...require('./translate/criteria'), ...require('./translate/opti
7
7
  * @returns {{criteria: Object, options: Object}}
8
8
  */
9
9
  const translate = (query) => {
10
- const result = { criteria: {}, options: {} }
10
+ const result = {
11
+ criteria: query?.criteria === undefined ? {} : parse.criteria(query.criteria),
12
+ options: query?.options === undefined ? {} : parse.options(query.options)
13
+ }
11
14
 
12
- if (query.criteria !== undefined) result.criteria = parse.criteria(query.criteria)
13
- if (query.options !== undefined) result.options = parse.options(query.options)
14
- if (query.id !== undefined) result.criteria._id = query.id
15
- if (query.version !== undefined) result.criteria._version = query.version
15
+ if (query?.id !== undefined)
16
+ result.criteria._id = query.id
17
+
18
+ if (query?.version !== undefined)
19
+ result.criteria._version = query.version
16
20
 
17
21
  return result
18
22
  }
@@ -1,12 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const { to, from } = require('../src/record')
4
- const { random } = require('@toa.io/generic')
3
+ const {
4
+ to,
5
+ from
6
+ } = require('../src/record')
5
7
 
6
8
  describe('to', () => {
7
9
  it('should rename id to _id', () => {
8
10
  /** @type {toa.core.storages.Record} */
9
- const entity = { id: '1', _version: 0 }
11
+ const entity = {
12
+ id: '1',
13
+ _version: 0
14
+ }
10
15
  const record = to(entity)
11
16
 
12
17
  expect(record).toMatchObject({ _id: '1' })
@@ -14,37 +19,32 @@ describe('to', () => {
14
19
 
15
20
  it('should not modify argument', () => {
16
21
  /** @type {toa.core.storages.Record} */
17
- const entity = { id: '1', _version: 0 }
22
+ const entity = {
23
+ id: '1',
24
+ _version: 0
25
+ }
18
26
 
19
27
  to(entity)
20
28
 
21
- expect(entity).toStrictEqual({ id: '1', _version: 0 })
22
- })
23
-
24
- it('should increment _version', () => {
25
- /** @type {toa.core.storages.Record} */
26
- const entity = { id: '1', _version: random() }
27
- const record = to(entity)
28
-
29
- expect(record).toMatchObject({ _version: entity._version + 1 })
29
+ expect(entity).toStrictEqual({
30
+ id: '1',
31
+ _version: 0
32
+ })
30
33
  })
31
34
  })
32
35
 
33
36
  describe('from', () => {
34
37
  it('should rename _id to id', () => {
35
38
  /** @type {toa.mongodb.Record} */
36
- const record = { _id: '1', _version: 0 }
39
+ const record = {
40
+ _id: '1',
41
+ _version: 0
42
+ }
37
43
  const entity = from(record)
38
44
 
39
- expect(entity).toStrictEqual({ id: '1', _version: 0 })
40
- })
41
-
42
- it('should not modify argument', () => {
43
- /** @type {toa.mongodb.Record} */
44
- const record = { _id: '1', _version: 0 }
45
-
46
- from(record)
47
-
48
- expect(record).toStrictEqual({ _id: '1', _version: 0 })
45
+ expect(entity).toStrictEqual({
46
+ id: '1',
47
+ _version: 0
48
+ })
49
49
  })
50
50
  })
@@ -1,29 +1,31 @@
1
1
  // noinspection ES6UnusedImports
2
2
 
3
3
  import type {
4
- Document,
5
- Filter,
6
- FindOneAndReplaceOptions,
7
- FindOneAndUpdateOptions,
8
- FindOptions,
9
- UpdateFilter
4
+ Document,
5
+ Filter,
6
+ FindOneAndReplaceOptions,
7
+ FindOneAndUpdateOptions,
8
+ FindOptions,
9
+ UpdateFilter,
10
10
  } from 'mongodb'
11
11
 
12
12
  import type { Connector } from '@toa.io/core'
13
13
  import type { Record } from './record'
14
14
 
15
- declare namespace toa.mongodb {
15
+ declare namespace toa.mongodb{
16
16
 
17
- interface Connection extends Connector {
18
- get(query: Filter<Record>, options?: FindOptions<Record>): Promise<Record>
17
+ interface Connection extends Connector{
18
+ get (query: Filter<Record>, options?: FindOptions<Record>): Promise<Record>
19
19
 
20
- find(query: Filter<Record>, options?: FindOptions<Record>): Promise<Record[]>
20
+ find (query: Filter<Record>, options?: FindOptions<Record>): Promise<Record[]>
21
21
 
22
- add(record: Record): Promise<boolean>
22
+ add (record: Record): Promise<boolean>
23
23
 
24
- replace(query: Filter<Record>, record: UpdateFilter<Record>, options?: FindOneAndReplaceOptions): Promise<any>
24
+ addMany (records: Record[]): Promise<boolean>
25
25
 
26
- update(query: Filter<Record>, update: UpdateFilter<Record>, options?: FindOneAndUpdateOptions): Promise<any>
27
- }
26
+ replace (query: Filter<Record>, record: UpdateFilter<Record>, options?: FindOneAndReplaceOptions): Promise<any>
27
+
28
+ update (query: Filter<Record>, update: UpdateFilter<Record>, options?: FindOneAndUpdateOptions): Promise<any>
29
+ }
28
30
 
29
31
  }
package/src/connection.js DELETED
@@ -1,95 +0,0 @@
1
- // noinspection JSCheckFunctionSignatures
2
-
3
- 'use strict'
4
-
5
- const { MongoClient } = require('mongodb')
6
- const { Connector } = require('@toa.io/core')
7
- const { console } = require('@toa.io/console')
8
- const { resolve } = require('@toa.io/pointer')
9
- const { ID } = require('./deployment')
10
-
11
- class Connection extends Connector {
12
- #locator
13
- /** @type {import('mongodb').MongoClient} */
14
- #client
15
- /** @type {import('mongodb').Collection<toa.mongodb.Record>} */
16
- #collection
17
-
18
- constructor (locator) {
19
- super()
20
-
21
- this.#locator = locator
22
- }
23
-
24
- async open () {
25
- const urls = await this.#resolveURLs()
26
- const db = this.#locator.namespace
27
- const collection = this.#locator.name
28
-
29
- this.#client = new MongoClient(urls[0], OPTIONS)
30
-
31
- await this.#client.connect()
32
-
33
- this.#collection = this.#client.db(db).collection(collection)
34
-
35
- console.info('Storage Mongo connected')
36
- }
37
-
38
- async close () {
39
- await this.#client?.close()
40
-
41
- console.info('Storage Mongo disconnected')
42
- }
43
-
44
- /** @hot */
45
- async get (query, options) {
46
- return /** @type {toa.mongodb.Record} */ this.#collection.findOne(query, options)
47
- }
48
-
49
- /** @hot */
50
- async find (query, options) {
51
- const cursor = await this.#collection.find(query, options)
52
-
53
- return cursor.toArray()
54
- }
55
-
56
- /** @hot */
57
- async add (record) {
58
- /** @type {boolean} */
59
- let result
60
-
61
- try {
62
- const response = await this.#collection.insertOne(record)
63
-
64
- result = response.acknowledged
65
- } catch (e) {
66
- if (e.code === 11000) result = false // duplicate id
67
- else throw e
68
- }
69
-
70
- return result
71
- }
72
-
73
- /** @hot */
74
- async replace (query, record, options) {
75
- return await this.#collection.findOneAndReplace(query, record, options)
76
- }
77
-
78
- /** @hot */
79
- async update (query, update, options) {
80
- return this.#collection.findOneAndUpdate(query, update, options)
81
- }
82
-
83
- async #resolveURLs () {
84
- if (process.env.TOA_DEV === '1') return ['mongodb://developer:secret@localhost']
85
- else return await resolve(ID, this.#locator.id)
86
- }
87
- }
88
-
89
- const OPTIONS = {
90
- useNewUrlParser: true,
91
- useUnifiedTopology: true,
92
- ignoreUndefined: true
93
- }
94
-
95
- exports.Connection = Connection