@toa.io/storages.mongodb 1.0.0-alpha.266 → 1.0.0-alpha.270
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/CHANGELOG.md +12 -0
- package/package.json +5 -5
- package/src/client.js +7 -5
- package/src/storage.js +80 -44
- package/src/monitoring.js +0 -87
- package/test/monitoring.test.js +0 -99
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.270](https://github.com/toa-io/toa/compare/v1.0.0-alpha.269...v1.0.0-alpha.270) (2026-08-31)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Performance Improvements
|
|
10
|
+
|
|
11
|
+
* **boot:** stop paying for what a composition does not need to start ([1619c27](https://github.com/toa-io/toa/commit/1619c2743072f4706939ce72f3074e615d26a91e))
|
|
12
|
+
* **storages.mongodb:** time the call instead of monitoring the command ([135f1a9](https://github.com/toa-io/toa/commit/135f1a97c753b1caed9291f85cea0521ef68b0e3))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
# [1.0.0-alpha.266](https://github.com/toa-io/toa/compare/v1.0.0-alpha.265...v1.0.0-alpha.266) (2026-08-29)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @toa.io/storages.mongodb
|
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.270",
|
|
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",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@toa.io/conveyor": "1.0.0-alpha.254",
|
|
23
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
23
|
+
"@toa.io/core": "1.0.0-alpha.270",
|
|
24
24
|
"@toa.io/generic": "1.0.0-alpha.254",
|
|
25
|
-
"@toa.io/pointer": "1.0.0-alpha.
|
|
25
|
+
"@toa.io/pointer": "1.0.0-alpha.270",
|
|
26
26
|
"mongodb": "7.2.0",
|
|
27
|
-
"openspan": "1.0.0-alpha.
|
|
27
|
+
"openspan": "1.0.0-alpha.270",
|
|
28
28
|
"saslprep": "1.0.3"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "94400bdd411b4c25074ffcf58184580ddd060268"
|
|
31
31
|
}
|
package/src/client.js
CHANGED
|
@@ -11,7 +11,6 @@ const { Connector } = require('@toa.io/core')
|
|
|
11
11
|
const { resolve } = require('@toa.io/pointer')
|
|
12
12
|
const { ID } = require('./deployment')
|
|
13
13
|
const { MongoClient } = require('mongodb')
|
|
14
|
-
const { monitor } = require('./monitoring')
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* @type {Record<string, Promise<Instance>>}
|
|
@@ -113,8 +112,6 @@ class Client extends Connector {
|
|
|
113
112
|
const client = new MongoClient(urls.join(','), OPTIONS)
|
|
114
113
|
const hosts = urls.map((str) => new URL(str).host)
|
|
115
114
|
|
|
116
|
-
monitor(client)
|
|
117
|
-
|
|
118
115
|
console.info('Connecting to MongoDB', { address: hosts.join(', ') })
|
|
119
116
|
|
|
120
117
|
await client.connect()
|
|
@@ -158,9 +155,14 @@ function getKey (db, urls) {
|
|
|
158
155
|
return db + ':' + urls.sort().join(' ')
|
|
159
156
|
}
|
|
160
157
|
|
|
158
|
+
/**
|
|
159
|
+
* `monitorCommands` is deliberately absent. It makes the driver materialize every reply
|
|
160
|
+
* eagerly to populate the monitoring event (`CommandSucceededEvent`), which defeats the
|
|
161
|
+
* lazy per-document deserialization a cursor exists for — a 100-document batch is then
|
|
162
|
+
* deserialized twice. `Storage` times its own calls instead.
|
|
163
|
+
*/
|
|
161
164
|
const OPTIONS = {
|
|
162
|
-
ignoreUndefined: true
|
|
163
|
-
monitorCommands: true
|
|
165
|
+
ignoreUndefined: true
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
const ALREADY_EXISTS = 48
|
package/src/storage.js
CHANGED
|
@@ -13,6 +13,9 @@ class Storage extends Connector {
|
|
|
13
13
|
#collection
|
|
14
14
|
#entity
|
|
15
15
|
|
|
16
|
+
/** @type {Map<string, object>} span options per driver method */
|
|
17
|
+
#spans = new Map()
|
|
18
|
+
|
|
16
19
|
constructor (client, entity) {
|
|
17
20
|
super()
|
|
18
21
|
|
|
@@ -29,6 +32,8 @@ class Storage extends Connector {
|
|
|
29
32
|
async open () {
|
|
30
33
|
this.#collection = this.#client.collection
|
|
31
34
|
|
|
35
|
+
this.#spans.clear()
|
|
36
|
+
|
|
32
37
|
await this.index()
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -40,9 +45,8 @@ class Storage extends Connector {
|
|
|
40
45
|
if (query?.id === undefined && query?.options?.deleted !== true)
|
|
41
46
|
criteria._deleted = null
|
|
42
47
|
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
const record = await this.#collection.findOne(criteria, options)
|
|
48
|
+
const record = await this.command('findOne', { criteria, options },
|
|
49
|
+
() => this.#collection.findOne(criteria, options))
|
|
46
50
|
|
|
47
51
|
return from(record)
|
|
48
52
|
}
|
|
@@ -53,23 +57,20 @@ class Storage extends Connector {
|
|
|
53
57
|
if (query?.options?.deleted !== true)
|
|
54
58
|
criteria._deleted = null
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
+
const recordset = sample === undefined
|
|
61
|
+
? await this.command('find', { criteria, options },
|
|
62
|
+
async () => await this.#collection.find(criteria, options).toArray())
|
|
63
|
+
: await this.aggregate(criteria, options, sample)
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const pipeline = toPipeline(criteria, options, sample)
|
|
64
|
-
|
|
65
|
-
this.debug('aggregate', { pipeline })
|
|
66
|
-
|
|
67
|
-
cursor = this.#collection.aggregate(pipeline)
|
|
68
|
-
}
|
|
65
|
+
return recordset.map((item) => from(item))
|
|
66
|
+
}
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
/** @private */
|
|
69
|
+
async aggregate (criteria, options, sample) {
|
|
70
|
+
const pipeline = toPipeline(criteria, options, sample)
|
|
71
71
|
|
|
72
|
-
return
|
|
72
|
+
return await this.command('aggregate', { pipeline },
|
|
73
|
+
async () => await this.#collection.aggregate(pipeline).toArray())
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
async stream (query = undefined) {
|
|
@@ -86,9 +87,8 @@ class Storage extends Connector {
|
|
|
86
87
|
async add (entity) {
|
|
87
88
|
const record = to(entity)
|
|
88
89
|
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
const result = await this.#collection.insertOne(record)
|
|
90
|
+
const result = await this.command('insertOne', { record },
|
|
91
|
+
() => this.#collection.insertOne(record))
|
|
92
92
|
|
|
93
93
|
return result.acknowledged
|
|
94
94
|
}
|
|
@@ -101,9 +101,8 @@ class Storage extends Connector {
|
|
|
101
101
|
|
|
102
102
|
const record = to(entity)
|
|
103
103
|
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
const result = await this.#collection.findOneAndReplace(criteria, record)
|
|
104
|
+
const result = await this.command('findOneAndReplace', { criteria, record },
|
|
105
|
+
() => this.#collection.findOneAndReplace(criteria, record))
|
|
107
106
|
|
|
108
107
|
return result !== null
|
|
109
108
|
}
|
|
@@ -162,11 +161,9 @@ class Storage extends Connector {
|
|
|
162
161
|
|
|
163
162
|
try {
|
|
164
163
|
await client.withSession(async (session) => {
|
|
165
|
-
await session.withTransaction(async () =>
|
|
166
|
-
this.
|
|
167
|
-
|
|
168
|
-
await this.#collection.bulkWrite(operations, { session })
|
|
169
|
-
})
|
|
164
|
+
await session.withTransaction(async () =>
|
|
165
|
+
await this.command('bulkWrite', { operations: operations.length },
|
|
166
|
+
async () => await this.#collection.bulkWrite(operations, { session })))
|
|
170
167
|
})
|
|
171
168
|
|
|
172
169
|
return true
|
|
@@ -197,9 +194,8 @@ class Storage extends Connector {
|
|
|
197
194
|
|
|
198
195
|
options.returnDocument = ReturnDocument.AFTER
|
|
199
196
|
|
|
200
|
-
this.
|
|
201
|
-
|
|
202
|
-
const result = await this.#collection.findOneAndUpdate(criteria, update, options)
|
|
197
|
+
const result = await this.command('findOneAndUpdate', { criteria, update, options },
|
|
198
|
+
() => this.#collection.findOneAndUpdate(criteria, update, options))
|
|
203
199
|
|
|
204
200
|
return from(result)
|
|
205
201
|
}
|
|
@@ -215,10 +211,9 @@ class Storage extends Connector {
|
|
|
215
211
|
options.upsert = true
|
|
216
212
|
options.returnDocument = ReturnDocument.AFTER
|
|
217
213
|
|
|
218
|
-
console.debug('Database query', { collection: this.#collection.collectionName, method: 'findOneAndUpdate', criteria, update, options })
|
|
219
|
-
|
|
220
214
|
try {
|
|
221
|
-
const result = await this
|
|
215
|
+
const result = await this.command('findOneAndUpdate', { criteria, update, options },
|
|
216
|
+
() => this.#collection.findOneAndUpdate(criteria, update, options))
|
|
222
217
|
|
|
223
218
|
if (result._deleted !== undefined && result._deleted !== null)
|
|
224
219
|
return null
|
|
@@ -232,19 +227,18 @@ class Storage extends Connector {
|
|
|
232
227
|
}
|
|
233
228
|
}
|
|
234
229
|
|
|
230
|
+
/** A component does not start before this returns, so the indexes are created at once. */
|
|
235
231
|
async index () {
|
|
236
|
-
const
|
|
232
|
+
const pending = []
|
|
237
233
|
|
|
238
|
-
if (this.#entity.unique !== undefined)
|
|
234
|
+
if (this.#entity.unique !== undefined)
|
|
239
235
|
for (const [name, fields] of Object.entries(this.#entity.unique)) {
|
|
240
236
|
const optional = this.getOptional(fields)
|
|
241
|
-
const unique = await this.uniqueIndex(name, fields, optional)
|
|
242
237
|
|
|
243
|
-
|
|
238
|
+
pending.push(this.uniqueIndex(name, fields, optional))
|
|
244
239
|
}
|
|
245
|
-
}
|
|
246
240
|
|
|
247
|
-
if (this.#entity.index !== undefined)
|
|
241
|
+
if (this.#entity.index !== undefined)
|
|
248
242
|
for (const [suffix, declaration] of Object.entries(this.#entity.index)) {
|
|
249
243
|
const name = 'index_' + suffix
|
|
250
244
|
const fields = Object.fromEntries(Object.entries(declaration)
|
|
@@ -255,12 +249,12 @@ class Storage extends Connector {
|
|
|
255
249
|
|
|
256
250
|
console.info('Creating index', { fields, options })
|
|
257
251
|
|
|
258
|
-
|
|
252
|
+
pending.push(this.#collection.createIndex(fields, options)
|
|
259
253
|
.catch((e) => console.warn('MongoDB index creation failed', { collection: this.#collection.collectionName, name, fields, error: e }))
|
|
260
|
-
|
|
261
|
-
indexes.push(name)
|
|
254
|
+
.then(() => name))
|
|
262
255
|
}
|
|
263
|
-
|
|
256
|
+
|
|
257
|
+
const indexes = await Promise.all(pending)
|
|
264
258
|
|
|
265
259
|
await this.removeObsoleteIndexes(indexes)
|
|
266
260
|
}
|
|
@@ -322,6 +316,48 @@ class Storage extends Connector {
|
|
|
322
316
|
return optional
|
|
323
317
|
}
|
|
324
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Names, logs and times a call into the driver. The driver's own command monitoring is
|
|
321
|
+
* off (see `client.js`), so this is where a query becomes a span.
|
|
322
|
+
*
|
|
323
|
+
* @private
|
|
324
|
+
*/
|
|
325
|
+
async command (method, attributes, task) {
|
|
326
|
+
this.debug(method, attributes)
|
|
327
|
+
|
|
328
|
+
return console.span(this.span(method), task)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The span of a driver method is the same object every time: the collection is fixed
|
|
333
|
+
* for a storage, and nothing downstream writes to what it is given.
|
|
334
|
+
*
|
|
335
|
+
* @private
|
|
336
|
+
*/
|
|
337
|
+
span (method) {
|
|
338
|
+
let options = this.#spans.get(method)
|
|
339
|
+
|
|
340
|
+
if (options === undefined) {
|
|
341
|
+
const collection = this.#collection.collectionName
|
|
342
|
+
|
|
343
|
+
options = {
|
|
344
|
+
name: `${method} ${collection}`,
|
|
345
|
+
kind: 'client',
|
|
346
|
+
// https://opentelemetry.io/docs/specs/semconv/database/mongodb/
|
|
347
|
+
attributes: {
|
|
348
|
+
'db.system': 'mongodb',
|
|
349
|
+
'db.namespace': this.#collection.dbName,
|
|
350
|
+
'db.operation.name': method,
|
|
351
|
+
'db.collection.name': collection
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this.#spans.set(method, options)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return options
|
|
359
|
+
}
|
|
360
|
+
|
|
325
361
|
debug (method, attributes) {
|
|
326
362
|
console.debug('MongoDB query', {
|
|
327
363
|
collection: this.#collection.collectionName,
|
package/src/monitoring.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { create, current, record } = require('openspan')
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Records MongoDB commands as client spans using the driver command monitoring events.
|
|
7
|
-
* Requires the client to be created with `monitorCommands: true`.
|
|
8
|
-
*
|
|
9
|
-
* Commands executed outside of a sampled trace context are not recorded.
|
|
10
|
-
*
|
|
11
|
-
* @param {import('mongodb').MongoClient} client
|
|
12
|
-
*/
|
|
13
|
-
function monitor (client) {
|
|
14
|
-
/** @type {Map<number, { context: object, time: number, attributes: object }>} */
|
|
15
|
-
const pending = new Map()
|
|
16
|
-
|
|
17
|
-
client.on('commandStarted', (event) => {
|
|
18
|
-
if (IGNORE.has(event.commandName))
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
const parent = current()
|
|
22
|
-
|
|
23
|
-
if (parent === undefined || !parent.sampled)
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
if (pending.size >= CAPACITY) // should never happen
|
|
27
|
-
pending.delete(pending.keys().next().value)
|
|
28
|
-
|
|
29
|
-
// https://opentelemetry.io/docs/specs/semconv/database/mongodb/
|
|
30
|
-
const attributes = {
|
|
31
|
-
'db.system': 'mongodb',
|
|
32
|
-
'db.namespace': event.databaseName,
|
|
33
|
-
'db.operation.name': event.commandName
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const collection = event.command?.[event.commandName]
|
|
37
|
-
|
|
38
|
-
if (typeof collection === 'string')
|
|
39
|
-
attributes['db.collection.name'] = collection
|
|
40
|
-
|
|
41
|
-
pending.set(event.requestId, {
|
|
42
|
-
context: create(parent),
|
|
43
|
-
time: Date.now(),
|
|
44
|
-
attributes
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
client.on('commandSucceeded', (event) => complete(event))
|
|
49
|
-
client.on('commandFailed', (event) => complete(event, 'error'))
|
|
50
|
-
|
|
51
|
-
function complete (event, status) {
|
|
52
|
-
const started = pending.get(event.requestId)
|
|
53
|
-
|
|
54
|
-
if (started === undefined)
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
pending.delete(event.requestId)
|
|
58
|
-
|
|
59
|
-
const collection = started.attributes['db.collection.name']
|
|
60
|
-
|
|
61
|
-
const span = {
|
|
62
|
-
name: collection === undefined
|
|
63
|
-
? event.commandName
|
|
64
|
-
: `${event.commandName} ${collection}`,
|
|
65
|
-
traceId: started.context.traceId,
|
|
66
|
-
spanId: started.context.spanId,
|
|
67
|
-
parentId: started.context.parentId,
|
|
68
|
-
kind: 'client',
|
|
69
|
-
time: started.time,
|
|
70
|
-
duration: event.duration,
|
|
71
|
-
attributes: started.attributes,
|
|
72
|
-
service: started.context.service
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (status !== undefined)
|
|
76
|
-
span.status = status
|
|
77
|
-
|
|
78
|
-
record(span)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// internal commands, including authentication and connection management
|
|
83
|
-
const IGNORE = new Set(['hello', 'ismaster', 'ping', 'endSessions', 'saslStart', 'saslContinue'])
|
|
84
|
-
|
|
85
|
-
const CAPACITY = 1024
|
|
86
|
-
|
|
87
|
-
exports.monitor = monitor
|
package/test/monitoring.test.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { EventEmitter } = require('node:events')
|
|
4
|
-
const { create, exporting, run, traces } = require('openspan')
|
|
5
|
-
const { monitor } = require('../src/monitoring')
|
|
6
|
-
|
|
7
|
-
let client
|
|
8
|
-
let spans
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
client = new EventEmitter()
|
|
12
|
-
spans = []
|
|
13
|
-
|
|
14
|
-
exporting([{ export: (span) => spans.push(span) }])
|
|
15
|
-
monitor(client)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
traces()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const started = {
|
|
23
|
-
requestId: 1,
|
|
24
|
-
commandName: 'find',
|
|
25
|
-
databaseName: 'toa-dev',
|
|
26
|
-
command: { find: 'pots', filter: {} }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const succeeded = { requestId: 1, commandName: 'find', duration: 1.5 }
|
|
30
|
-
const failed = { requestId: 1, commandName: 'find', duration: 0.5, failure: 'boom' }
|
|
31
|
-
|
|
32
|
-
it('should record a command as a client span', () => {
|
|
33
|
-
const context = create()
|
|
34
|
-
|
|
35
|
-
run(context, () => client.emit('commandStarted', started))
|
|
36
|
-
client.emit('commandSucceeded', succeeded)
|
|
37
|
-
|
|
38
|
-
expect(spans).toHaveLength(1)
|
|
39
|
-
|
|
40
|
-
expect(spans[0]).toMatchObject({
|
|
41
|
-
name: 'find pots',
|
|
42
|
-
traceId: context.traceId,
|
|
43
|
-
parentId: context.spanId,
|
|
44
|
-
kind: 'client',
|
|
45
|
-
duration: 1.5,
|
|
46
|
-
attributes: {
|
|
47
|
-
'db.system': 'mongodb',
|
|
48
|
-
'db.namespace': 'toa-dev',
|
|
49
|
-
'db.operation.name': 'find',
|
|
50
|
-
'db.collection.name': 'pots'
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
expect(spans[0].status).toBeUndefined()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should record failed commands with error status', () => {
|
|
58
|
-
run(create(), () => client.emit('commandStarted', started))
|
|
59
|
-
client.emit('commandFailed', failed)
|
|
60
|
-
|
|
61
|
-
expect(spans).toHaveLength(1)
|
|
62
|
-
expect(spans[0].status).toBe('error')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should not record commands outside of a trace context', () => {
|
|
66
|
-
client.emit('commandStarted', started)
|
|
67
|
-
client.emit('commandSucceeded', succeeded)
|
|
68
|
-
|
|
69
|
-
expect(spans).toHaveLength(0)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('should not record commands of unsampled traces', () => {
|
|
73
|
-
const context = { ...create(), sampled: false }
|
|
74
|
-
|
|
75
|
-
run(context, () => client.emit('commandStarted', started))
|
|
76
|
-
client.emit('commandSucceeded', succeeded)
|
|
77
|
-
|
|
78
|
-
expect(spans).toHaveLength(0)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('should ignore internal commands', () => {
|
|
82
|
-
run(create(), () => {
|
|
83
|
-
client.emit('commandStarted', { ...started, commandName: 'hello', command: { hello: 1 } })
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
client.emit('commandSucceeded', { ...succeeded, commandName: 'hello' })
|
|
87
|
-
|
|
88
|
-
expect(spans).toHaveLength(0)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should name spans without a collection by the command', () => {
|
|
92
|
-
const start = { ...started, commandName: 'listIndexes', command: { listIndexes: 1 } }
|
|
93
|
-
|
|
94
|
-
run(create(), () => client.emit('commandStarted', start))
|
|
95
|
-
client.emit('commandSucceeded', { ...succeeded, commandName: 'listIndexes' })
|
|
96
|
-
|
|
97
|
-
expect(spans).toHaveLength(1)
|
|
98
|
-
expect(spans[0].name).toBe('listIndexes')
|
|
99
|
-
})
|