@toa.io/storages.mongodb 1.0.0-alpha.36 → 1.0.0-alpha.37

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.36",
3
+ "version": "1.0.0-alpha.37",
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,13 +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.36",
23
- "@toa.io/conveyor": "1.0.0-alpha.36",
24
- "@toa.io/core": "1.0.0-alpha.36",
25
- "@toa.io/generic": "1.0.0-alpha.36",
26
- "@toa.io/pointer": "1.0.0-alpha.36",
22
+ "@toa.io/console": "1.0.0-alpha.37",
23
+ "@toa.io/conveyor": "1.0.0-alpha.37",
24
+ "@toa.io/core": "1.0.0-alpha.37",
25
+ "@toa.io/generic": "1.0.0-alpha.37",
26
+ "@toa.io/pointer": "1.0.0-alpha.37",
27
27
  "mongodb": "6.3.0",
28
28
  "saslprep": "1.0.3"
29
29
  },
30
- "gitHead": "565b8309bc46dfde7bbb49bd760c2dca0c89b0ff"
30
+ "gitHead": "e78a9aedb64a52dc34b9271fd90791934863d0a6"
31
31
  }
package/src/client.js CHANGED
@@ -149,9 +149,7 @@ function getKey (db, urls) {
149
149
  }
150
150
 
151
151
  const OPTIONS = {
152
- ignoreUndefined: true,
153
- connectTimeoutMS: 0,
154
- serverSelectionTimeoutMS: 0
152
+ ignoreUndefined: true
155
153
  }
156
154
 
157
155
  const ALREADY_EXISTS = 48
package/src/factory.js CHANGED
@@ -1,15 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const { Client } = require('./client')
4
- const { Collection } = require('./collection')
5
4
  const { Storage } = require('./storage')
6
5
 
7
6
  class Factory {
8
7
  storage (locator, entity) {
9
8
  const client = new Client(locator)
10
- const connection = new Collection(client)
11
9
 
12
- return new Storage(connection, entity)
10
+ return new Storage(client, entity)
13
11
  }
14
12
  }
15
13
 
package/src/storage.js CHANGED
@@ -4,34 +4,40 @@ const { Connector, exceptions } = require('@toa.io/core')
4
4
 
5
5
  const { translate } = require('./translate')
6
6
  const { to, from } = require('./record')
7
+ const { ReturnDocument } = require('mongodb')
7
8
 
8
9
  class Storage extends Connector {
9
- #connection
10
+ #client
11
+
12
+ /** @type {import('mongodb').Collection} */
13
+ #collection
10
14
  #entity
11
15
 
12
- constructor (connection, entity) {
16
+ constructor (client, entity) {
13
17
  super()
14
18
 
15
- this.#connection = connection
19
+ this.#client = client
16
20
  this.#entity = entity
17
21
 
18
- this.depends(connection)
22
+ this.depends(client)
19
23
  }
20
24
 
21
25
  async open () {
26
+ this.#collection = this.#client.collection
27
+
22
28
  await this.index()
23
29
  }
24
30
 
25
31
  async get (query) {
26
32
  const { criteria, options } = translate(query)
27
- const record = await this.#connection.get(criteria, options)
33
+ const record = await this.#collection.findOne(criteria, options)
28
34
 
29
35
  return from(record)
30
36
  }
31
37
 
32
38
  async find (query) {
33
39
  const { criteria, options } = translate(query)
34
- const recordset = await this.#connection.find(criteria, options)
40
+ const recordset = await this.#collection.find(criteria, options).toArray()
35
41
 
36
42
  return recordset.map((item) => from(item))
37
43
  }
@@ -39,12 +45,12 @@ class Storage extends Connector {
39
45
  async stream (query = undefined) {
40
46
  const { criteria, options } = translate(query)
41
47
 
42
- return await this.#connection.stream(criteria, options, from)
48
+ return await this.#collection.find(criteria, options).stream({ transform: from })
43
49
  }
44
50
 
45
51
  async add (entity) {
46
52
  const record = to(entity)
47
- const result = await this.#connection.add(record)
53
+ const result = await this.#collection.insertOne(record)
48
54
 
49
55
  return result.acknowledged
50
56
  }
@@ -54,41 +60,43 @@ class Storage extends Connector {
54
60
  _id: entity.id,
55
61
  _version: entity._version - 1
56
62
  }
57
- const result = await this.#connection.replace(criteria, to(entity))
63
+
64
+ const result = await this.#collection.findOneAndReplace(criteria, to(entity))
58
65
 
59
66
  return result !== null
60
67
  }
61
68
 
62
- async store (entity) {
69
+ async store (entity, attempt = 0) {
63
70
  try {
64
- if (entity._version === 1) {
71
+ if (entity._version === 1)
65
72
  return await this.add(entity)
66
- } else {
73
+ else
67
74
  return await this.set(entity)
68
- }
69
75
  } catch (error) {
70
76
  if (error.code === ERR_DUPLICATE_KEY) {
71
-
72
77
  const id = error.keyPattern === undefined
73
78
  ? error.message.includes(' index: _id_ ') // AWS DocumentDB
74
79
  : error.keyPattern._id === 1
75
80
 
76
- if (id) {
81
+ if (id)
77
82
  return false
78
- } else {
83
+ else
79
84
  throw new exceptions.DuplicateException()
80
- }
81
- } else {
85
+ } else if (error.cause?.code === 'ECONNREFUSED') {
86
+ // This is temporary and should be replaced with a class decorator.
87
+ if (attempt > 10)
88
+ throw error
89
+
90
+ await new Promise((resolve) => setTimeout(resolve, 1000))
91
+
92
+ return this.store(entity)
93
+ } else
82
94
  throw error
83
- }
84
95
  }
85
96
  }
86
97
 
87
98
  async upsert (query, changeset) {
88
- const {
89
- criteria,
90
- options
91
- } = translate(query)
99
+ const { criteria, options } = translate(query)
92
100
 
93
101
  if (!('_deleted' in changeset) || changeset._deleted === null) {
94
102
  delete criteria._deleted
@@ -100,13 +108,32 @@ class Storage extends Connector {
100
108
  $inc: { _version: 1 }
101
109
  }
102
110
 
103
- options.returnDocument = 'after'
111
+ options.returnDocument = ReturnDocument.AFTER
104
112
 
105
- const result = await this.#connection.update(criteria, update, options)
113
+ const result = await this.#collection.findOneAndUpdate(criteria, update, options)
106
114
 
107
115
  return from(result)
108
116
  }
109
117
 
118
+ async ensure (query, properties, state) {
119
+ let { criteria, options } = translate(query)
120
+
121
+ if (query === undefined)
122
+ criteria = properties
123
+
124
+ const update = { $setOnInsert: to(state) }
125
+
126
+ options.upsert = true
127
+ options.returnDocument = ReturnDocument.AFTER
128
+
129
+ const result = await this.#collection.findOneAndUpdate(criteria, update, options)
130
+
131
+ if (result._deleted !== undefined && result._deleted !== null)
132
+ return null
133
+ else
134
+ return from(result)
135
+ }
136
+
110
137
  async index () {
111
138
  const indexes = []
112
139
 
@@ -127,16 +154,13 @@ class Storage extends Connector {
127
154
 
128
155
  const sparse = this.checkFields(Object.keys(fields))
129
156
 
130
- await this.#connection.index(fields, {
131
- name,
132
- sparse
133
- })
157
+ await this.#collection.createIndex(fields, { name, sparse })
134
158
 
135
159
  indexes.push(name)
136
160
  }
137
161
  }
138
162
 
139
- await this.removeObsolete(indexes)
163
+ await this.removeObsoleteIndexes(indexes)
140
164
  }
141
165
 
142
166
  async uniqueIndex (name, properties, sparse = false) {
@@ -147,23 +171,29 @@ class Storage extends Connector {
147
171
 
148
172
  name = 'unique_' + name
149
173
 
150
- await this.#connection.index(fields, {
151
- name,
152
- unique: true,
153
- sparse
154
- })
174
+ await this.#collection.createIndex(fields, { name, unique: true, sparse })
155
175
 
156
176
  return name
157
177
  }
158
178
 
159
- async removeObsolete (desired) {
160
- const current = await this.#connection.indexes()
179
+ async removeObsoleteIndexes (desired) {
180
+ const current = await this.getCurrentIndexes()
161
181
  const obsolete = current.filter((name) => !desired.includes(name))
162
182
 
163
183
  if (obsolete.length > 0) {
164
184
  console.info(`Remove obsolete indexes: [${obsolete.join(', ')}]`)
165
185
 
166
- await this.#connection.dropIndexes(obsolete)
186
+ await Promise.all(obsolete.map((name) => this.#collection.dropIndex(name)))
187
+ }
188
+ }
189
+
190
+ async getCurrentIndexes () {
191
+ try {
192
+ const array = await this.#collection.listIndexes().toArray()
193
+
194
+ return array.map(({ name }) => name).filter((name) => name !== '_id_')
195
+ } catch {
196
+ return []
167
197
  }
168
198
  }
169
199
 
@@ -171,22 +201,19 @@ class Storage extends Connector {
171
201
  const optional = []
172
202
 
173
203
  for (const field of fields) {
174
- if (!(field in this.#entity.schema.properties)) {
204
+ if (!(field in this.#entity.schema.properties))
175
205
  throw new Error(`Index field '${field}' is not defined.`)
176
- }
177
206
 
178
- if (!this.#entity.schema.required?.includes(field)) {
207
+ if (!this.#entity.schema.required?.includes(field))
179
208
  optional.push(field)
180
- }
181
209
  }
182
210
 
183
211
  if (optional.length > 0) {
184
212
  console.info(`Index fields [${optional.join(', ')}] are optional, creating sparse index.`)
185
213
 
186
214
  return true
187
- } else {
215
+ } else
188
216
  return false
189
- }
190
217
  }
191
218
 
192
219
  }
package/src/collection.js DELETED
@@ -1,71 +0,0 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('@toa.io/core')
4
-
5
- class Collection extends Connector {
6
- #client
7
- #collection
8
-
9
- constructor (client) {
10
- super()
11
-
12
- this.#client = client
13
-
14
- this.depends(client)
15
- }
16
-
17
- async open () {
18
- this.#collection = this.#client.collection
19
- }
20
-
21
- /** @hot */
22
- async get (query, options) {
23
- return /** @type {toa.mongodb.Record} */ this.#collection.findOne(query, options)
24
- }
25
-
26
- /** @hot */
27
- async find (query, options) {
28
- return this.#collection.find(query, options).toArray()
29
- }
30
-
31
- async stream (query, options, transform) {
32
- return this.#collection.find(query, options).stream({ transform })
33
- }
34
-
35
- /** @hot */
36
- async add (record) {
37
- return await this.#collection.insertOne(record)
38
- }
39
-
40
- /** @hot */
41
- async replace (query, record, options) {
42
- return await this.#collection.findOneAndReplace(query, record, options)
43
- }
44
-
45
- /** @hot */
46
- async update (query, update, options) {
47
- return this.#collection.findOneAndUpdate(query, update, options)
48
- }
49
-
50
- async index (keys, options) {
51
- return this.#collection.createIndex(keys, options)
52
- }
53
-
54
- async indexes () {
55
- try {
56
- const array = await this.#collection.listIndexes().toArray()
57
-
58
- return array.map(({ name }) => name).filter((name) => name !== '_id_')
59
- } catch {
60
- return []
61
- }
62
- }
63
-
64
- async dropIndexes (names) {
65
- const all = names.map((name) => this.#collection.dropIndex(name))
66
-
67
- return Promise.all(all)
68
- }
69
- }
70
-
71
- exports.Collection = Collection