@toa.io/storages.mongodb 1.0.0-alpha.0 → 1.0.0-alpha.11
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 +8 -7
- package/src/client.js +131 -0
- package/src/collection.js +69 -0
- package/src/factory.js +6 -4
- package/src/record.js +9 -3
- package/src/storage.js +149 -22
- package/test/record.test.js +32 -17
- package/types/connection.d.ts +16 -14
- package/src/connection.js +0 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/storages.mongodb",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
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.
|
|
23
|
-
"@toa.io/
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"
|
|
22
|
+
"@toa.io/console": "1.0.0-alpha.11",
|
|
23
|
+
"@toa.io/conveyor": "1.0.0-alpha.11",
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.11",
|
|
25
|
+
"@toa.io/generic": "1.0.0-alpha.11",
|
|
26
|
+
"@toa.io/pointer": "1.0.0-alpha.11",
|
|
27
|
+
"mongodb": "6.3.0",
|
|
27
28
|
"saslprep": "1.0.3"
|
|
28
29
|
},
|
|
29
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "e343ac81eef12957cfa5e520119b1276b8ec0ad2"
|
|
30
31
|
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
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 { Connector } = require('@toa.io/core')
|
|
10
|
+
const { resolve } = require('@toa.io/pointer')
|
|
11
|
+
const { ID } = require('./deployment')
|
|
12
|
+
const { MongoClient } = require('mongodb')
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @type {Record<string, Promise<Instance>>}
|
|
16
|
+
*/
|
|
17
|
+
const INSTANCES = {}
|
|
18
|
+
|
|
19
|
+
class Client extends Connector {
|
|
20
|
+
/**
|
|
21
|
+
* @public
|
|
22
|
+
* @type {import('mongodb').Collection}
|
|
23
|
+
*/
|
|
24
|
+
collection
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @private
|
|
28
|
+
* @type {Locator}
|
|
29
|
+
*/
|
|
30
|
+
locator
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @private
|
|
34
|
+
* @type {Instance}
|
|
35
|
+
*/
|
|
36
|
+
instance
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @private
|
|
40
|
+
* @type {string}
|
|
41
|
+
*/
|
|
42
|
+
key
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {Locator} locator
|
|
46
|
+
*/
|
|
47
|
+
constructor (locator) {
|
|
48
|
+
super()
|
|
49
|
+
|
|
50
|
+
this.locator = locator
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @protected
|
|
55
|
+
* @override
|
|
56
|
+
* @return {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
async open () {
|
|
59
|
+
const urls = await this.resolveURLs()
|
|
60
|
+
|
|
61
|
+
this.key = getKey(urls)
|
|
62
|
+
|
|
63
|
+
INSTANCES[this.key] ??= this.createInstance(urls)
|
|
64
|
+
|
|
65
|
+
this.instance = await INSTANCES[this.key]
|
|
66
|
+
this.instance.count++
|
|
67
|
+
|
|
68
|
+
this.collection = this.instance.client
|
|
69
|
+
.db(this.locator.namespace)
|
|
70
|
+
.collection(this.locator.name)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @protected
|
|
75
|
+
* @override
|
|
76
|
+
* @return {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
async close () {
|
|
79
|
+
const instance = await INSTANCES[this.key]
|
|
80
|
+
|
|
81
|
+
instance.count--
|
|
82
|
+
|
|
83
|
+
if (instance.count === 0) {
|
|
84
|
+
await instance.client.close()
|
|
85
|
+
delete INSTANCES[this.key]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @private
|
|
91
|
+
* @param {string[]} urls
|
|
92
|
+
* @return {Promise<Instance>}
|
|
93
|
+
*/
|
|
94
|
+
async createInstance (urls) {
|
|
95
|
+
const client = new MongoClient(urls.join(','), OPTIONS)
|
|
96
|
+
const hosts = urls.map((str) => new URL(str).host)
|
|
97
|
+
|
|
98
|
+
console.info('Connecting to MongoDB:', hosts.join(', '))
|
|
99
|
+
|
|
100
|
+
await client.connect()
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
count: 0,
|
|
104
|
+
client
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @private
|
|
110
|
+
* @return {Promise<string[]>}
|
|
111
|
+
*/
|
|
112
|
+
async resolveURLs () {
|
|
113
|
+
if (process.env.TOA_DEV === '1') {
|
|
114
|
+
return ['mongodb://developer:secret@localhost']
|
|
115
|
+
} else {
|
|
116
|
+
return await resolve(ID, this.locator.id)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getKey (urls) {
|
|
122
|
+
return urls.sort().join(' ')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const OPTIONS = {
|
|
126
|
+
ignoreUndefined: true,
|
|
127
|
+
connectTimeoutMS: 0,
|
|
128
|
+
serverSelectionTimeoutMS: 0
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
exports.Client = Client
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
const cursor = this.#collection.find(query, options)
|
|
29
|
+
|
|
30
|
+
return cursor.toArray()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @hot */
|
|
34
|
+
async add (record) {
|
|
35
|
+
return await this.#collection.insertOne(record)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @hot */
|
|
39
|
+
async replace (query, record, options) {
|
|
40
|
+
return await this.#collection.findOneAndReplace(query, record, options)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @hot */
|
|
44
|
+
async update (query, update, options) {
|
|
45
|
+
return this.#collection.findOneAndUpdate(query, update, options)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async index (keys, options) {
|
|
49
|
+
return this.#collection.createIndex(keys, options)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async indexes () {
|
|
53
|
+
try {
|
|
54
|
+
const array = await this.#collection.listIndexes().toArray()
|
|
55
|
+
|
|
56
|
+
return array.map(({ name }) => name).filter((name) => name !== '_id_')
|
|
57
|
+
} catch {
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async dropIndexes (names) {
|
|
63
|
+
const all = names.map((name) => this.#collection.dropIndex(name))
|
|
64
|
+
|
|
65
|
+
return Promise.all(all)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.Collection = Collection
|
package/src/factory.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { Client } = require('./client')
|
|
4
|
+
const { Collection } = require('./collection')
|
|
4
5
|
const { Storage } = require('./storage')
|
|
5
6
|
|
|
6
7
|
class Factory {
|
|
7
|
-
storage (locator) {
|
|
8
|
-
const
|
|
8
|
+
storage (locator, entity) {
|
|
9
|
+
const client = new Client(locator)
|
|
10
|
+
const connection = new Collection(client)
|
|
9
11
|
|
|
10
|
-
return new Storage(connection)
|
|
12
|
+
return new Storage(connection, entity)
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
package/src/record.js
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* @returns {toa.mongodb.Record}
|
|
6
6
|
*/
|
|
7
7
|
const to = (entity) => {
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
id,
|
|
10
|
+
...rest
|
|
11
|
+
} = entity
|
|
9
12
|
|
|
10
|
-
return /** @type {toa.mongodb.Record} */ { _id: id,
|
|
13
|
+
return /** @type {toa.mongodb.Record} */ { _id: id, ...rest }
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -17,7 +20,10 @@ const to = (entity) => {
|
|
|
17
20
|
const from = (record) => {
|
|
18
21
|
if (record === undefined || record === null) return null
|
|
19
22
|
|
|
20
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
_id,
|
|
25
|
+
...rest
|
|
26
|
+
} = record
|
|
21
27
|
|
|
22
28
|
return { id: _id, ...rest }
|
|
23
29
|
}
|
package/src/storage.js
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
Connector,
|
|
5
|
+
exceptions
|
|
6
|
+
} = require('@toa.io/core')
|
|
4
7
|
|
|
5
8
|
const { translate } = require('./translate')
|
|
6
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
to,
|
|
11
|
+
from
|
|
12
|
+
} = require('./record')
|
|
7
13
|
|
|
8
|
-
/**
|
|
9
|
-
* @implements {toa.core.Storage}
|
|
10
|
-
*/
|
|
11
14
|
class Storage extends Connector {
|
|
12
|
-
/** @type {toa.mongodb.Connection} */
|
|
13
15
|
#connection
|
|
16
|
+
#entity
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
* @param {toa.mongodb.Connection} connection
|
|
17
|
-
*/
|
|
18
|
-
constructor (connection) {
|
|
18
|
+
constructor (connection, entity) {
|
|
19
19
|
super()
|
|
20
20
|
|
|
21
21
|
this.#connection = connection
|
|
22
|
+
this.#entity = entity
|
|
22
23
|
|
|
23
24
|
this.depends(connection)
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
async open () {
|
|
28
|
+
await this.index()
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
async get (query) {
|
|
27
|
-
const {
|
|
32
|
+
const {
|
|
33
|
+
criteria,
|
|
34
|
+
options
|
|
35
|
+
} = translate(query)
|
|
28
36
|
|
|
29
37
|
const record = await this.#connection.get(criteria, options)
|
|
30
38
|
|
|
@@ -32,7 +40,10 @@ class Storage extends Connector {
|
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
async find (query) {
|
|
35
|
-
const {
|
|
43
|
+
const {
|
|
44
|
+
criteria,
|
|
45
|
+
options
|
|
46
|
+
} = translate(query)
|
|
36
47
|
const recordset = await this.#connection.find(criteria, options)
|
|
37
48
|
|
|
38
49
|
return recordset.map((item) => from(item))
|
|
@@ -40,33 +51,58 @@ class Storage extends Connector {
|
|
|
40
51
|
|
|
41
52
|
async add (entity) {
|
|
42
53
|
const record = to(entity)
|
|
54
|
+
const result = await this.#connection.add(record)
|
|
43
55
|
|
|
44
|
-
return
|
|
56
|
+
return result.acknowledged
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
async set (entity) {
|
|
48
|
-
const criteria = {
|
|
60
|
+
const criteria = {
|
|
61
|
+
_id: entity.id,
|
|
62
|
+
_version: entity._version - 1
|
|
63
|
+
}
|
|
49
64
|
const result = await this.#connection.replace(criteria, to(entity))
|
|
50
65
|
|
|
51
|
-
return result
|
|
66
|
+
return result !== null
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
async store (entity) {
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
try {
|
|
71
|
+
if (entity._version === 1) {
|
|
72
|
+
return await this.add(entity)
|
|
73
|
+
} else {
|
|
74
|
+
return await this.set(entity)
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error.code === ERR_DUPLICATE_KEY) {
|
|
78
|
+
return new exceptions.DuplicateException(Object.keys(error.keyValue))
|
|
79
|
+
} else {
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
}
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
async upsert (query, changeset, insert) {
|
|
60
|
-
const {
|
|
61
|
-
|
|
86
|
+
const {
|
|
87
|
+
criteria,
|
|
88
|
+
options
|
|
89
|
+
} = translate(query)
|
|
90
|
+
|
|
91
|
+
const update = {
|
|
92
|
+
$set: { ...changeset },
|
|
93
|
+
$inc: { _version: 1 }
|
|
94
|
+
}
|
|
62
95
|
|
|
63
96
|
if (insert !== undefined) {
|
|
64
97
|
delete insert._version
|
|
65
98
|
|
|
66
99
|
options.upsert = true
|
|
67
100
|
|
|
68
|
-
if (criteria._id !== undefined)
|
|
69
|
-
|
|
101
|
+
if (criteria._id !== undefined) {
|
|
102
|
+
insert._id = criteria._id
|
|
103
|
+
} else {
|
|
104
|
+
return null
|
|
105
|
+
} // this shouldn't ever happen
|
|
70
106
|
|
|
71
107
|
if (Object.keys(insert) > 0) update.$setOnInsert = insert
|
|
72
108
|
}
|
|
@@ -75,8 +111,99 @@ class Storage extends Connector {
|
|
|
75
111
|
|
|
76
112
|
const result = await this.#connection.update(criteria, update, options)
|
|
77
113
|
|
|
78
|
-
return from(result
|
|
114
|
+
return from(result)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async index () {
|
|
118
|
+
const indexes = []
|
|
119
|
+
|
|
120
|
+
if (this.#entity.unique !== undefined) {
|
|
121
|
+
for (const [name, fields] of Object.entries(this.#entity.unique)) {
|
|
122
|
+
const sparse = this.checkFields(fields)
|
|
123
|
+
const unique = await this.uniqueIndex(name, fields, sparse)
|
|
124
|
+
|
|
125
|
+
indexes.push(unique)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.#entity.index !== undefined) {
|
|
130
|
+
for (const [suffix, declaration] of Object.entries(this.#entity.index)) {
|
|
131
|
+
const name = 'index_' + suffix
|
|
132
|
+
const fields = Object.fromEntries(Object.entries(declaration)
|
|
133
|
+
.map(([name, type]) => [name, INDEX_TYPES[type]]))
|
|
134
|
+
|
|
135
|
+
const sparse = this.checkFields(Object.keys(fields))
|
|
136
|
+
|
|
137
|
+
await this.#connection.index(fields, {
|
|
138
|
+
name,
|
|
139
|
+
sparse
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
indexes.push(name)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await this.removeObsolete(indexes)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async uniqueIndex (name, properties, sparse = false) {
|
|
150
|
+
const fields = properties.reduce((acc, property) => {
|
|
151
|
+
acc[property] = 1
|
|
152
|
+
return acc
|
|
153
|
+
}, {})
|
|
154
|
+
|
|
155
|
+
name = 'unique_' + name
|
|
156
|
+
|
|
157
|
+
await this.#connection.index(fields, {
|
|
158
|
+
name,
|
|
159
|
+
unique: true,
|
|
160
|
+
sparse
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
return name
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async removeObsolete (desired) {
|
|
167
|
+
const current = await this.#connection.indexes()
|
|
168
|
+
const obsolete = current.filter((name) => !desired.includes(name))
|
|
169
|
+
|
|
170
|
+
if (obsolete.length > 0) {
|
|
171
|
+
console.info(`Remove obsolete indexes: [${obsolete.join(', ')}]`)
|
|
172
|
+
|
|
173
|
+
await this.#connection.dropIndexes(obsolete)
|
|
174
|
+
}
|
|
79
175
|
}
|
|
176
|
+
|
|
177
|
+
checkFields (fields) {
|
|
178
|
+
const optional = []
|
|
179
|
+
|
|
180
|
+
for (const field of fields) {
|
|
181
|
+
if (!(field in this.#entity.schema.properties)) {
|
|
182
|
+
throw new Error(`Index field '${field}' is not defined.`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!this.#entity.schema.required?.includes(field)) {
|
|
186
|
+
optional.push(field)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (optional.length > 0) {
|
|
191
|
+
console.info(`Index fields [${optional.join(', ')}] are optional, creating sparse index.`)
|
|
192
|
+
|
|
193
|
+
return true
|
|
194
|
+
} else {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const INDEX_TYPES = {
|
|
202
|
+
'asc': 1,
|
|
203
|
+
'desc': -1,
|
|
204
|
+
'hash': 'hashed'
|
|
80
205
|
}
|
|
81
206
|
|
|
207
|
+
const ERR_DUPLICATE_KEY = 11000
|
|
208
|
+
|
|
82
209
|
exports.Storage = Storage
|
package/test/record.test.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
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 = {
|
|
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,47 @@ describe('to', () => {
|
|
|
14
19
|
|
|
15
20
|
it('should not modify argument', () => {
|
|
16
21
|
/** @type {toa.core.storages.Record} */
|
|
17
|
-
const entity = {
|
|
22
|
+
const entity = {
|
|
23
|
+
id: '1',
|
|
24
|
+
_version: 0
|
|
25
|
+
}
|
|
18
26
|
|
|
19
27
|
to(entity)
|
|
20
28
|
|
|
21
|
-
expect(entity).toStrictEqual({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 = {
|
|
39
|
+
const record = {
|
|
40
|
+
_id: '1',
|
|
41
|
+
_version: 0
|
|
42
|
+
}
|
|
37
43
|
const entity = from(record)
|
|
38
44
|
|
|
39
|
-
expect(entity).toStrictEqual({
|
|
45
|
+
expect(entity).toStrictEqual({
|
|
46
|
+
id: '1',
|
|
47
|
+
_version: 0
|
|
48
|
+
})
|
|
40
49
|
})
|
|
41
50
|
|
|
42
51
|
it('should not modify argument', () => {
|
|
43
52
|
/** @type {toa.mongodb.Record} */
|
|
44
|
-
const record = {
|
|
53
|
+
const record = {
|
|
54
|
+
_id: '1',
|
|
55
|
+
_version: 0
|
|
56
|
+
}
|
|
45
57
|
|
|
46
58
|
from(record)
|
|
47
59
|
|
|
48
|
-
expect(record).toStrictEqual({
|
|
60
|
+
expect(record).toStrictEqual({
|
|
61
|
+
_id: '1',
|
|
62
|
+
_version: 0
|
|
63
|
+
})
|
|
49
64
|
})
|
|
50
65
|
})
|
package/types/connection.d.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
// noinspection ES6UnusedImports
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
17
|
+
interface Connection extends Connector{
|
|
18
|
+
get (query: Filter<Record>, options?: FindOptions<Record>): Promise<Record>
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
find (query: Filter<Record>, options?: FindOptions<Record>): Promise<Record[]>
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
add (record: Record): Promise<boolean>
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
addMany (records: Record[]): Promise<boolean>
|
|
25
25
|
|
|
26
|
-
|
|
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
|