mongodb 6.1.0 → 6.2.0
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/lib/bulk/common.js +20 -8
- package/lib/bulk/common.js.map +1 -1
- package/lib/cmap/command_monitoring_events.js +1 -2
- package/lib/cmap/command_monitoring_events.js.map +1 -1
- package/lib/cmap/commands.js +12 -9
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connection.js +2 -3
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +15 -20
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/collection.js +4 -2
- package/lib/collection.js.map +1 -1
- package/lib/db.js +12 -19
- package/lib/db.js.map +1 -1
- package/lib/operations/rename.js +0 -1
- package/lib/operations/rename.js.map +1 -1
- package/lib/sdam/events.js +6 -3
- package/lib/sdam/events.js.map +1 -1
- package/lib/sdam/monitor.js +9 -8
- package/lib/sdam/monitor.js.map +1 -1
- package/lib/sdam/topology.js +11 -20
- package/lib/sdam/topology.js.map +1 -1
- package/lib/utils.js +26 -33
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +17 -2
- package/package.json +2 -2
- package/src/bulk/common.ts +38 -7
- package/src/cmap/command_monitoring_events.ts +4 -5
- package/src/cmap/commands.ts +11 -12
- package/src/cmap/connection.ts +2 -3
- package/src/cmap/connection_pool.ts +26 -30
- package/src/collection.ts +4 -4
- package/src/db.ts +13 -21
- package/src/index.ts +2 -1
- package/src/operations/rename.ts +1 -2
- package/src/sdam/events.ts +12 -3
- package/src/sdam/monitor.ts +22 -8
- package/src/sdam/topology.ts +17 -25
- package/src/utils.ts +28 -38
package/src/db.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as CONSTANTS from './constants';
|
|
|
6
6
|
import { AggregationCursor } from './cursor/aggregation_cursor';
|
|
7
7
|
import { ListCollectionsCursor } from './cursor/list_collections_cursor';
|
|
8
8
|
import { RunCommandCursor, type RunCursorCommandOptions } from './cursor/run_command_cursor';
|
|
9
|
-
import {
|
|
9
|
+
import { MongoInvalidArgumentError } from './error';
|
|
10
10
|
import type { MongoClient, PkFactory } from './mongo_client';
|
|
11
11
|
import type { TODO_NODE_3286 } from './mongo_types';
|
|
12
12
|
import type { AggregateOptions } from './operations/aggregate';
|
|
@@ -134,11 +134,13 @@ export class Db {
|
|
|
134
134
|
public static SYSTEM_JS_COLLECTION = CONSTANTS.SYSTEM_JS_COLLECTION;
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
* Creates a new Db instance
|
|
137
|
+
* Creates a new Db instance.
|
|
138
|
+
*
|
|
139
|
+
* Db name cannot contain a dot, the server may apply more restrictions when an operation is run.
|
|
138
140
|
*
|
|
139
141
|
* @param client - The MongoClient for the database.
|
|
140
142
|
* @param databaseName - The name of the database this instance represents.
|
|
141
|
-
* @param options - Optional settings for Db construction
|
|
143
|
+
* @param options - Optional settings for Db construction.
|
|
142
144
|
*/
|
|
143
145
|
constructor(client: MongoClient, databaseName: string, options?: DbOptions) {
|
|
144
146
|
options = options ?? {};
|
|
@@ -146,8 +148,10 @@ export class Db {
|
|
|
146
148
|
// Filter the options
|
|
147
149
|
options = filterOptions(options, DB_OPTIONS_ALLOW_LIST);
|
|
148
150
|
|
|
149
|
-
// Ensure
|
|
150
|
-
|
|
151
|
+
// Ensure there are no dots in database name
|
|
152
|
+
if (typeof databaseName === 'string' && databaseName.includes('.')) {
|
|
153
|
+
throw new MongoInvalidArgumentError(`Database names cannot contain the character '.'`);
|
|
154
|
+
}
|
|
151
155
|
|
|
152
156
|
// Internal state of the db object
|
|
153
157
|
this.s = {
|
|
@@ -218,6 +222,8 @@ export class Db {
|
|
|
218
222
|
* Create a new collection on a server with the specified options. Use this to create capped collections.
|
|
219
223
|
* More information about command options available at https://www.mongodb.com/docs/manual/reference/command/create/
|
|
220
224
|
*
|
|
225
|
+
* Collection namespace validation is performed server-side.
|
|
226
|
+
*
|
|
221
227
|
* @param name - The name of the collection to create
|
|
222
228
|
* @param options - Optional settings for the command
|
|
223
229
|
*/
|
|
@@ -294,6 +300,8 @@ export class Db {
|
|
|
294
300
|
/**
|
|
295
301
|
* Returns a reference to a MongoDB Collection. If it does not exist it will be created implicitly.
|
|
296
302
|
*
|
|
303
|
+
* Collection namespace validation is performed server-side.
|
|
304
|
+
*
|
|
297
305
|
* @param name - the collection name we wish to access.
|
|
298
306
|
* @returns return the new Collection instance
|
|
299
307
|
*/
|
|
@@ -519,19 +527,3 @@ export class Db {
|
|
|
519
527
|
return new RunCommandCursor(this, command, options);
|
|
520
528
|
}
|
|
521
529
|
}
|
|
522
|
-
|
|
523
|
-
// TODO(NODE-3484): Refactor into MongoDBNamespace
|
|
524
|
-
// Validate the database name
|
|
525
|
-
function validateDatabaseName(databaseName: string) {
|
|
526
|
-
if (typeof databaseName !== 'string')
|
|
527
|
-
throw new MongoInvalidArgumentError('Database name must be a string');
|
|
528
|
-
if (databaseName.length === 0)
|
|
529
|
-
throw new MongoInvalidArgumentError('Database name cannot be the empty string');
|
|
530
|
-
if (databaseName === '$external') return;
|
|
531
|
-
|
|
532
|
-
const invalidChars = [' ', '.', '$', '/', '\\'];
|
|
533
|
-
for (let i = 0; i < invalidChars.length; i++) {
|
|
534
|
-
if (databaseName.indexOf(invalidChars[i]) !== -1)
|
|
535
|
-
throw new MongoAPIError(`database names cannot contain the character '${invalidChars[i]}'`);
|
|
536
|
-
}
|
|
537
|
-
}
|
package/src/index.ts
CHANGED
package/src/operations/rename.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Document } from '../bson';
|
|
|
2
2
|
import { Collection } from '../collection';
|
|
3
3
|
import type { Server } from '../sdam/server';
|
|
4
4
|
import type { ClientSession } from '../sessions';
|
|
5
|
-
import {
|
|
5
|
+
import { MongoDBNamespace } from '../utils';
|
|
6
6
|
import { CommandOperation, type CommandOperationOptions } from './command';
|
|
7
7
|
import { Aspect, defineAspects } from './operation';
|
|
8
8
|
|
|
@@ -21,7 +21,6 @@ export class RenameOperation extends CommandOperation<Document> {
|
|
|
21
21
|
public newName: string,
|
|
22
22
|
public override options: RenameOptions
|
|
23
23
|
) {
|
|
24
|
-
checkCollectionName(newName);
|
|
25
24
|
super(collection, options);
|
|
26
25
|
this.ns = new MongoDBNamespace('admin', '$cmd');
|
|
27
26
|
}
|
package/src/sdam/events.ts
CHANGED
|
@@ -132,10 +132,13 @@ export class TopologyClosedEvent {
|
|
|
132
132
|
export class ServerHeartbeatStartedEvent {
|
|
133
133
|
/** The connection id for the command */
|
|
134
134
|
connectionId: string;
|
|
135
|
+
/** Is true when using the streaming protocol. */
|
|
136
|
+
awaited: boolean;
|
|
135
137
|
|
|
136
138
|
/** @internal */
|
|
137
|
-
constructor(connectionId: string) {
|
|
139
|
+
constructor(connectionId: string, awaited: boolean) {
|
|
138
140
|
this.connectionId = connectionId;
|
|
141
|
+
this.awaited = awaited;
|
|
139
142
|
}
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -151,12 +154,15 @@ export class ServerHeartbeatSucceededEvent {
|
|
|
151
154
|
duration: number;
|
|
152
155
|
/** The command reply */
|
|
153
156
|
reply: Document;
|
|
157
|
+
/** Is true when using the streaming protocol. */
|
|
158
|
+
awaited: boolean;
|
|
154
159
|
|
|
155
160
|
/** @internal */
|
|
156
|
-
constructor(connectionId: string, duration: number, reply: Document | null) {
|
|
161
|
+
constructor(connectionId: string, duration: number, reply: Document | null, awaited: boolean) {
|
|
157
162
|
this.connectionId = connectionId;
|
|
158
163
|
this.duration = duration;
|
|
159
164
|
this.reply = reply ?? {};
|
|
165
|
+
this.awaited = awaited;
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -172,11 +178,14 @@ export class ServerHeartbeatFailedEvent {
|
|
|
172
178
|
duration: number;
|
|
173
179
|
/** The command failure */
|
|
174
180
|
failure: Error;
|
|
181
|
+
/** Is true when using the streaming protocol. */
|
|
182
|
+
awaited: boolean;
|
|
175
183
|
|
|
176
184
|
/** @internal */
|
|
177
|
-
constructor(connectionId: string, duration: number, failure: Error) {
|
|
185
|
+
constructor(connectionId: string, duration: number, failure: Error, awaited: boolean) {
|
|
178
186
|
this.connectionId = connectionId;
|
|
179
187
|
this.duration = duration;
|
|
180
188
|
this.failure = failure;
|
|
189
|
+
this.awaited = awaited;
|
|
181
190
|
}
|
|
182
191
|
}
|
package/src/sdam/monitor.ts
CHANGED
|
@@ -209,7 +209,12 @@ function resetMonitorState(monitor: Monitor) {
|
|
|
209
209
|
|
|
210
210
|
function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
211
211
|
let start = now();
|
|
212
|
-
|
|
212
|
+
const topologyVersion = monitor[kServer].description.topologyVersion;
|
|
213
|
+
const isAwaitable = topologyVersion != null;
|
|
214
|
+
monitor.emit(
|
|
215
|
+
Server.SERVER_HEARTBEAT_STARTED,
|
|
216
|
+
new ServerHeartbeatStartedEvent(monitor.address, isAwaitable)
|
|
217
|
+
);
|
|
213
218
|
|
|
214
219
|
function failureHandler(err: Error) {
|
|
215
220
|
monitor[kConnection]?.destroy({ force: true });
|
|
@@ -217,7 +222,12 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
217
222
|
|
|
218
223
|
monitor.emit(
|
|
219
224
|
Server.SERVER_HEARTBEAT_FAILED,
|
|
220
|
-
new ServerHeartbeatFailedEvent(
|
|
225
|
+
new ServerHeartbeatFailedEvent(
|
|
226
|
+
monitor.address,
|
|
227
|
+
calculateDurationInMs(start),
|
|
228
|
+
err,
|
|
229
|
+
isAwaitable
|
|
230
|
+
)
|
|
221
231
|
);
|
|
222
232
|
|
|
223
233
|
const error = !(err instanceof MongoError)
|
|
@@ -237,8 +247,6 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
237
247
|
const { serverApi, helloOk } = connection;
|
|
238
248
|
const connectTimeoutMS = monitor.options.connectTimeoutMS;
|
|
239
249
|
const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS;
|
|
240
|
-
const topologyVersion = monitor[kServer].description.topologyVersion;
|
|
241
|
-
const isAwaitable = topologyVersion != null;
|
|
242
250
|
|
|
243
251
|
const cmd = {
|
|
244
252
|
[serverApi?.version || helloOk ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
|
|
@@ -278,17 +286,18 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
278
286
|
const duration =
|
|
279
287
|
isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start);
|
|
280
288
|
|
|
289
|
+
const awaited = isAwaitable && hello.topologyVersion != null;
|
|
281
290
|
monitor.emit(
|
|
282
291
|
Server.SERVER_HEARTBEAT_SUCCEEDED,
|
|
283
|
-
new ServerHeartbeatSucceededEvent(monitor.address, duration, hello)
|
|
292
|
+
new ServerHeartbeatSucceededEvent(monitor.address, duration, hello, awaited)
|
|
284
293
|
);
|
|
285
294
|
|
|
286
295
|
// if we are using the streaming protocol then we immediately issue another `started`
|
|
287
296
|
// event, otherwise the "check" is complete and return to the main monitor loop
|
|
288
|
-
if (
|
|
297
|
+
if (awaited) {
|
|
289
298
|
monitor.emit(
|
|
290
299
|
Server.SERVER_HEARTBEAT_STARTED,
|
|
291
|
-
new ServerHeartbeatStartedEvent(monitor.address)
|
|
300
|
+
new ServerHeartbeatStartedEvent(monitor.address, true)
|
|
292
301
|
);
|
|
293
302
|
start = now();
|
|
294
303
|
} else {
|
|
@@ -324,7 +333,12 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
324
333
|
monitor[kConnection] = conn;
|
|
325
334
|
monitor.emit(
|
|
326
335
|
Server.SERVER_HEARTBEAT_SUCCEEDED,
|
|
327
|
-
new ServerHeartbeatSucceededEvent(
|
|
336
|
+
new ServerHeartbeatSucceededEvent(
|
|
337
|
+
monitor.address,
|
|
338
|
+
calculateDurationInMs(start),
|
|
339
|
+
conn.hello,
|
|
340
|
+
false
|
|
341
|
+
)
|
|
328
342
|
);
|
|
329
343
|
|
|
330
344
|
callback(undefined, conn.hello);
|
package/src/sdam/topology.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { clearTimeout, setTimeout } from 'timers';
|
|
2
1
|
import { promisify } from 'util';
|
|
3
2
|
|
|
4
3
|
import type { BSONSerializeOptions, Document } from '../bson';
|
|
@@ -43,7 +42,8 @@ import {
|
|
|
43
42
|
List,
|
|
44
43
|
makeStateMachine,
|
|
45
44
|
ns,
|
|
46
|
-
shuffle
|
|
45
|
+
shuffle,
|
|
46
|
+
TimeoutController
|
|
47
47
|
} from '../utils';
|
|
48
48
|
import {
|
|
49
49
|
_advanceClusterTime,
|
|
@@ -94,8 +94,8 @@ export interface ServerSelectionRequest {
|
|
|
94
94
|
serverSelector: ServerSelector;
|
|
95
95
|
transaction?: Transaction;
|
|
96
96
|
callback: ServerSelectionCallback;
|
|
97
|
-
timer?: NodeJS.Timeout;
|
|
98
97
|
[kCancelled]?: boolean;
|
|
98
|
+
timeoutController: TimeoutController;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** @internal */
|
|
@@ -556,22 +556,20 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
556
556
|
const waitQueueMember: ServerSelectionRequest = {
|
|
557
557
|
serverSelector,
|
|
558
558
|
transaction,
|
|
559
|
-
callback
|
|
559
|
+
callback,
|
|
560
|
+
timeoutController: new TimeoutController(options.serverSelectionTimeoutMS)
|
|
560
561
|
};
|
|
561
562
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
waitQueueMember.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
this.description
|
|
570
|
-
);
|
|
563
|
+
waitQueueMember.timeoutController.signal.addEventListener('abort', () => {
|
|
564
|
+
waitQueueMember[kCancelled] = true;
|
|
565
|
+
waitQueueMember.timeoutController.clear();
|
|
566
|
+
const timeoutError = new MongoServerSelectionError(
|
|
567
|
+
`Server selection timed out after ${options.serverSelectionTimeoutMS} ms`,
|
|
568
|
+
this.description
|
|
569
|
+
);
|
|
571
570
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
571
|
+
waitQueueMember.callback(timeoutError);
|
|
572
|
+
});
|
|
575
573
|
|
|
576
574
|
this[kWaitQueue].push(waitQueueMember);
|
|
577
575
|
processWaitQueue(this);
|
|
@@ -842,9 +840,7 @@ function drainWaitQueue(queue: List<ServerSelectionRequest>, err?: MongoDriverEr
|
|
|
842
840
|
continue;
|
|
843
841
|
}
|
|
844
842
|
|
|
845
|
-
|
|
846
|
-
clearTimeout(waitQueueMember.timer);
|
|
847
|
-
}
|
|
843
|
+
waitQueueMember.timeoutController.clear();
|
|
848
844
|
|
|
849
845
|
if (!waitQueueMember[kCancelled]) {
|
|
850
846
|
waitQueueMember.callback(err);
|
|
@@ -878,9 +874,7 @@ function processWaitQueue(topology: Topology) {
|
|
|
878
874
|
? serverSelector(topology.description, serverDescriptions)
|
|
879
875
|
: serverDescriptions;
|
|
880
876
|
} catch (e) {
|
|
881
|
-
|
|
882
|
-
clearTimeout(waitQueueMember.timer);
|
|
883
|
-
}
|
|
877
|
+
waitQueueMember.timeoutController.clear();
|
|
884
878
|
|
|
885
879
|
waitQueueMember.callback(e);
|
|
886
880
|
continue;
|
|
@@ -917,9 +911,7 @@ function processWaitQueue(topology: Topology) {
|
|
|
917
911
|
transaction.pinServer(selectedServer);
|
|
918
912
|
}
|
|
919
913
|
|
|
920
|
-
|
|
921
|
-
clearTimeout(waitQueueMember.timer);
|
|
922
|
-
}
|
|
914
|
+
waitQueueMember.timeoutController.clear();
|
|
923
915
|
|
|
924
916
|
waitQueueMember.callback(undefined, selectedServer);
|
|
925
917
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
2
|
import type { SrvRecord } from 'dns';
|
|
3
3
|
import * as http from 'http';
|
|
4
|
+
import { clearTimeout, setTimeout } from 'timers';
|
|
4
5
|
import * as url from 'url';
|
|
5
6
|
import { URL } from 'url';
|
|
6
7
|
|
|
@@ -78,39 +79,6 @@ export function hostMatchesWildcards(host: string, wildcards: string[]): boolean
|
|
|
78
79
|
return false;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
/**
|
|
82
|
-
* Throws if collectionName is not a valid mongodb collection namespace.
|
|
83
|
-
* @internal
|
|
84
|
-
*/
|
|
85
|
-
export function checkCollectionName(collectionName: string): void {
|
|
86
|
-
if ('string' !== typeof collectionName) {
|
|
87
|
-
throw new MongoInvalidArgumentError('Collection name must be a String');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!collectionName || collectionName.indexOf('..') !== -1) {
|
|
91
|
-
throw new MongoInvalidArgumentError('Collection names cannot be empty');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
collectionName.indexOf('$') !== -1 &&
|
|
96
|
-
collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null
|
|
97
|
-
) {
|
|
98
|
-
// TODO(NODE-3483): Use MongoNamespace static method
|
|
99
|
-
throw new MongoInvalidArgumentError("Collection names must not contain '$'");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (collectionName.match(/^\.|\.$/) != null) {
|
|
103
|
-
// TODO(NODE-3483): Use MongoNamespace static method
|
|
104
|
-
throw new MongoInvalidArgumentError("Collection names must not start or end with '.'");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Validate that we are not passing 0x00 in the collection name
|
|
108
|
-
if (collectionName.indexOf('\x00') !== -1) {
|
|
109
|
-
// TODO(NODE-3483): Use MongoNamespace static method
|
|
110
|
-
throw new MongoInvalidArgumentError('Collection names cannot contain a null character');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
82
|
/**
|
|
115
83
|
* Ensure Hint field is in a shape we expect:
|
|
116
84
|
* - object of index names mapping to 1 or -1
|
|
@@ -386,11 +354,6 @@ export function maybeCallback<T>(
|
|
|
386
354
|
return;
|
|
387
355
|
}
|
|
388
356
|
|
|
389
|
-
/** @internal */
|
|
390
|
-
export function databaseNamespace(ns: string): string {
|
|
391
|
-
return ns.split('.')[0];
|
|
392
|
-
}
|
|
393
|
-
|
|
394
357
|
/**
|
|
395
358
|
* Synchronously Generate a UUIDv4
|
|
396
359
|
* @internal
|
|
@@ -1292,3 +1255,30 @@ export async function request(
|
|
|
1292
1255
|
req.end();
|
|
1293
1256
|
});
|
|
1294
1257
|
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* A custom AbortController that aborts after a specified timeout.
|
|
1261
|
+
*
|
|
1262
|
+
* If `timeout` is undefined or \<=0, the abort controller never aborts.
|
|
1263
|
+
*
|
|
1264
|
+
* This class provides two benefits over the built-in AbortSignal.timeout() method.
|
|
1265
|
+
* - This class provides a mechanism for cancelling the timeout
|
|
1266
|
+
* - This class supports infinite timeouts by interpreting a timeout of 0 as infinite. This is
|
|
1267
|
+
* consistent with existing timeout options in the Node driver (serverSelectionTimeoutMS, for example).
|
|
1268
|
+
* @internal
|
|
1269
|
+
*/
|
|
1270
|
+
export class TimeoutController extends AbortController {
|
|
1271
|
+
constructor(
|
|
1272
|
+
timeout = 0,
|
|
1273
|
+
private timeoutId = timeout > 0 ? setTimeout(() => this.abort(), timeout) : null
|
|
1274
|
+
) {
|
|
1275
|
+
super();
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
clear() {
|
|
1279
|
+
if (this.timeoutId != null) {
|
|
1280
|
+
clearTimeout(this.timeoutId);
|
|
1281
|
+
}
|
|
1282
|
+
this.timeoutId = null;
|
|
1283
|
+
}
|
|
1284
|
+
}
|