mongodb 6.2.0 → 6.3.0-dev.20240108.sha.7f3ce0b

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.
Files changed (208) hide show
  1. package/lib/bulk/common.js +5 -1
  2. package/lib/bulk/common.js.map +1 -1
  3. package/lib/change_stream.js +4 -2
  4. package/lib/change_stream.js.map +1 -1
  5. package/lib/cmap/auth/gssapi.js +1 -1
  6. package/lib/cmap/auth/gssapi.js.map +1 -1
  7. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  8. package/lib/cmap/auth/mongocr.js +2 -2
  9. package/lib/cmap/auth/mongocr.js.map +1 -1
  10. package/lib/cmap/auth/mongodb_aws.js +2 -2
  11. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  12. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +2 -2
  13. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +1 -1
  15. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -1
  16. package/lib/cmap/auth/plain.js +1 -1
  17. package/lib/cmap/auth/plain.js.map +1 -1
  18. package/lib/cmap/auth/scram.js +3 -3
  19. package/lib/cmap/auth/scram.js.map +1 -1
  20. package/lib/cmap/auth/x509.js +1 -1
  21. package/lib/cmap/auth/x509.js.map +1 -1
  22. package/lib/cmap/command_monitoring_events.js +8 -5
  23. package/lib/cmap/command_monitoring_events.js.map +1 -1
  24. package/lib/cmap/commands.js +56 -11
  25. package/lib/cmap/commands.js.map +1 -1
  26. package/lib/cmap/connect.js +2 -2
  27. package/lib/cmap/connect.js.map +1 -1
  28. package/lib/cmap/connection.js +259 -331
  29. package/lib/cmap/connection.js.map +1 -1
  30. package/lib/cmap/connection_pool.js +2 -2
  31. package/lib/cmap/connection_pool.js.map +1 -1
  32. package/lib/cmap/errors.js +1 -1
  33. package/lib/cmap/errors.js.map +1 -1
  34. package/lib/cmap/stream_description.js +19 -0
  35. package/lib/cmap/stream_description.js.map +1 -1
  36. package/lib/cmap/wire_protocol/compression.js +57 -1
  37. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  38. package/lib/cmap/wire_protocol/on_data.js +112 -0
  39. package/lib/cmap/wire_protocol/on_data.js.map +1 -0
  40. package/lib/collection.js +11 -3
  41. package/lib/collection.js.map +1 -1
  42. package/lib/connection_string.js +29 -3
  43. package/lib/connection_string.js.map +1 -1
  44. package/lib/constants.js +19 -2
  45. package/lib/constants.js.map +1 -1
  46. package/lib/error.js +7 -7
  47. package/lib/error.js.map +1 -1
  48. package/lib/gridfs/download.js.map +1 -1
  49. package/lib/gridfs/upload.js.map +1 -1
  50. package/lib/index.js +7 -1
  51. package/lib/index.js.map +1 -1
  52. package/lib/mongo_client.js +14 -0
  53. package/lib/mongo_client.js.map +1 -1
  54. package/lib/mongo_logger.js +110 -14
  55. package/lib/mongo_logger.js.map +1 -1
  56. package/lib/mongo_types.js +12 -0
  57. package/lib/mongo_types.js.map +1 -1
  58. package/lib/operations/aggregate.js +3 -0
  59. package/lib/operations/aggregate.js.map +1 -1
  60. package/lib/operations/bulk_write.js +3 -0
  61. package/lib/operations/bulk_write.js.map +1 -1
  62. package/lib/operations/collections.js +3 -0
  63. package/lib/operations/collections.js.map +1 -1
  64. package/lib/operations/count.js +3 -0
  65. package/lib/operations/count.js.map +1 -1
  66. package/lib/operations/create_collection.js +3 -0
  67. package/lib/operations/create_collection.js.map +1 -1
  68. package/lib/operations/delete.js +3 -0
  69. package/lib/operations/delete.js.map +1 -1
  70. package/lib/operations/distinct.js +3 -0
  71. package/lib/operations/distinct.js.map +1 -1
  72. package/lib/operations/drop.js +6 -0
  73. package/lib/operations/drop.js.map +1 -1
  74. package/lib/operations/estimated_document_count.js +3 -0
  75. package/lib/operations/estimated_document_count.js.map +1 -1
  76. package/lib/operations/execute_operation.js +8 -2
  77. package/lib/operations/execute_operation.js.map +1 -1
  78. package/lib/operations/find.js +3 -0
  79. package/lib/operations/find.js.map +1 -1
  80. package/lib/operations/find_and_modify.js +5 -1
  81. package/lib/operations/find_and_modify.js.map +1 -1
  82. package/lib/operations/get_more.js +3 -0
  83. package/lib/operations/get_more.js.map +1 -1
  84. package/lib/operations/indexes.js +21 -0
  85. package/lib/operations/indexes.js.map +1 -1
  86. package/lib/operations/insert.js +6 -0
  87. package/lib/operations/insert.js.map +1 -1
  88. package/lib/operations/is_capped.js +3 -0
  89. package/lib/operations/is_capped.js.map +1 -1
  90. package/lib/operations/kill_cursors.js +3 -0
  91. package/lib/operations/kill_cursors.js.map +1 -1
  92. package/lib/operations/list_collections.js +3 -0
  93. package/lib/operations/list_collections.js.map +1 -1
  94. package/lib/operations/list_databases.js +3 -0
  95. package/lib/operations/list_databases.js.map +1 -1
  96. package/lib/operations/operation.js.map +1 -1
  97. package/lib/operations/options_operation.js +3 -0
  98. package/lib/operations/options_operation.js.map +1 -1
  99. package/lib/operations/profiling_level.js +3 -0
  100. package/lib/operations/profiling_level.js.map +1 -1
  101. package/lib/operations/remove_user.js +3 -0
  102. package/lib/operations/remove_user.js.map +1 -1
  103. package/lib/operations/rename.js +3 -0
  104. package/lib/operations/rename.js.map +1 -1
  105. package/lib/operations/run_command.js +6 -0
  106. package/lib/operations/run_command.js.map +1 -1
  107. package/lib/operations/search_indexes/create.js +3 -0
  108. package/lib/operations/search_indexes/create.js.map +1 -1
  109. package/lib/operations/search_indexes/drop.js +3 -0
  110. package/lib/operations/search_indexes/drop.js.map +1 -1
  111. package/lib/operations/search_indexes/update.js +3 -0
  112. package/lib/operations/search_indexes/update.js.map +1 -1
  113. package/lib/operations/set_profiling_level.js +3 -0
  114. package/lib/operations/set_profiling_level.js.map +1 -1
  115. package/lib/operations/stats.js +3 -0
  116. package/lib/operations/stats.js.map +1 -1
  117. package/lib/operations/update.js +3 -0
  118. package/lib/operations/update.js.map +1 -1
  119. package/lib/operations/validate_collection.js +3 -0
  120. package/lib/operations/validate_collection.js.map +1 -1
  121. package/lib/sdam/events.js +18 -0
  122. package/lib/sdam/events.js.map +1 -1
  123. package/lib/sdam/monitor.js +104 -77
  124. package/lib/sdam/monitor.js.map +1 -1
  125. package/lib/sdam/server.js +15 -15
  126. package/lib/sdam/server.js.map +1 -1
  127. package/lib/sdam/server_selection_events.js +85 -0
  128. package/lib/sdam/server_selection_events.js.map +1 -0
  129. package/lib/sdam/topology.js +40 -12
  130. package/lib/sdam/topology.js.map +1 -1
  131. package/lib/sessions.js +2 -2
  132. package/lib/sessions.js.map +1 -1
  133. package/lib/utils.js +58 -5
  134. package/lib/utils.js.map +1 -1
  135. package/mongodb.d.ts +150 -60
  136. package/package.json +22 -21
  137. package/src/bulk/common.ts +17 -5
  138. package/src/change_stream.ts +11 -5
  139. package/src/cmap/auth/gssapi.ts +1 -1
  140. package/src/cmap/auth/mongo_credentials.ts +2 -2
  141. package/src/cmap/auth/mongocr.ts +2 -6
  142. package/src/cmap/auth/mongodb_aws.ts +2 -2
  143. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +2 -2
  144. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +1 -1
  145. package/src/cmap/auth/plain.ts +1 -1
  146. package/src/cmap/auth/scram.ts +3 -3
  147. package/src/cmap/auth/x509.ts +1 -5
  148. package/src/cmap/command_monitoring_events.ts +37 -9
  149. package/src/cmap/commands.ts +69 -8
  150. package/src/cmap/connect.ts +2 -2
  151. package/src/cmap/connection.ts +390 -464
  152. package/src/cmap/connection_pool.ts +2 -2
  153. package/src/cmap/errors.ts +1 -1
  154. package/src/cmap/stream_description.ts +21 -1
  155. package/src/cmap/wire_protocol/compression.ts +72 -0
  156. package/src/cmap/wire_protocol/on_data.ts +132 -0
  157. package/src/collection.ts +14 -6
  158. package/src/connection_string.ts +31 -3
  159. package/src/constants.ts +17 -0
  160. package/src/error.ts +9 -7
  161. package/src/gridfs/download.ts +4 -2
  162. package/src/gridfs/upload.ts +8 -2
  163. package/src/index.ts +32 -11
  164. package/src/mongo_client.ts +59 -2
  165. package/src/mongo_logger.ts +277 -26
  166. package/src/mongo_types.ts +33 -3
  167. package/src/operations/aggregate.ts +4 -0
  168. package/src/operations/bulk_write.ts +4 -0
  169. package/src/operations/collections.ts +4 -0
  170. package/src/operations/count.ts +4 -0
  171. package/src/operations/create_collection.ts +4 -0
  172. package/src/operations/delete.ts +4 -0
  173. package/src/operations/distinct.ts +4 -0
  174. package/src/operations/drop.ts +8 -0
  175. package/src/operations/estimated_document_count.ts +4 -0
  176. package/src/operations/execute_operation.ts +8 -2
  177. package/src/operations/find.ts +4 -0
  178. package/src/operations/find_and_modify.ts +5 -1
  179. package/src/operations/get_more.ts +3 -0
  180. package/src/operations/indexes.ts +29 -0
  181. package/src/operations/insert.ts +8 -0
  182. package/src/operations/is_capped.ts +4 -0
  183. package/src/operations/kill_cursors.ts +4 -0
  184. package/src/operations/list_collections.ts +4 -0
  185. package/src/operations/list_databases.ts +4 -0
  186. package/src/operations/operation.ts +4 -0
  187. package/src/operations/options_operation.ts +3 -0
  188. package/src/operations/profiling_level.ts +4 -0
  189. package/src/operations/remove_user.ts +4 -0
  190. package/src/operations/rename.ts +4 -0
  191. package/src/operations/run_command.ts +8 -0
  192. package/src/operations/search_indexes/create.ts +4 -0
  193. package/src/operations/search_indexes/drop.ts +4 -0
  194. package/src/operations/search_indexes/update.ts +4 -0
  195. package/src/operations/set_profiling_level.ts +4 -0
  196. package/src/operations/stats.ts +4 -0
  197. package/src/operations/update.ts +4 -0
  198. package/src/operations/validate_collection.ts +4 -0
  199. package/src/sdam/events.ts +31 -3
  200. package/src/sdam/monitor.ts +150 -110
  201. package/src/sdam/server.ts +21 -23
  202. package/src/sdam/server_selection_events.ts +142 -0
  203. package/src/sdam/topology.ts +141 -42
  204. package/src/sessions.ts +3 -2
  205. package/src/utils.ts +63 -4
  206. package/lib/cmap/message_stream.js +0 -156
  207. package/lib/cmap/message_stream.js.map +0 -1
  208. package/src/cmap/message_stream.ts +0 -234
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasSessionSupport = exports.CryptoConnection = exports.Connection = void 0;
3
+ exports.CryptoConnection = exports.SizedMessageTransform = exports.Connection = exports.hasSessionSupport = void 0;
4
+ const stream_1 = require("stream");
4
5
  const timers_1 = require("timers");
5
6
  const util_1 = require("util");
6
7
  const constants_1 = require("../constants");
@@ -10,84 +11,66 @@ const sessions_1 = require("../sessions");
10
11
  const utils_1 = require("../utils");
11
12
  const command_monitoring_events_1 = require("./command_monitoring_events");
12
13
  const commands_1 = require("./commands");
13
- const message_stream_1 = require("./message_stream");
14
14
  const stream_description_1 = require("./stream_description");
15
+ const compression_1 = require("./wire_protocol/compression");
16
+ const on_data_1 = require("./wire_protocol/on_data");
15
17
  const shared_1 = require("./wire_protocol/shared");
16
18
  /** @internal */
17
- const kStream = Symbol('stream');
18
- /** @internal */
19
- const kQueue = Symbol('queue');
20
- /** @internal */
21
- const kMessageStream = Symbol('messageStream');
22
- /** @internal */
23
- const kGeneration = Symbol('generation');
24
- /** @internal */
25
- const kLastUseTime = Symbol('lastUseTime');
26
- /** @internal */
27
- const kClusterTime = Symbol('clusterTime');
28
- /** @internal */
29
- const kDescription = Symbol('description');
30
- /** @internal */
31
- const kHello = Symbol('hello');
32
- /** @internal */
33
- const kAutoEncrypter = Symbol('autoEncrypter');
34
- /** @internal */
35
- const kDelayedTimeoutId = Symbol('delayedTimeoutId');
36
- const INVALID_QUEUE_SIZE = 'Connection internal queue contains more than 1 operation description';
19
+ function hasSessionSupport(conn) {
20
+ const description = conn.description;
21
+ return description.logicalSessionTimeoutMinutes != null;
22
+ }
23
+ exports.hasSessionSupport = hasSessionSupport;
24
+ function streamIdentifier(stream, options) {
25
+ if (options.proxyHost) {
26
+ // If proxy options are specified, the properties of `stream` itself
27
+ // will not accurately reflect what endpoint this is connected to.
28
+ return options.hostAddress.toString();
29
+ }
30
+ const { remoteAddress, remotePort } = stream;
31
+ if (typeof remoteAddress === 'string' && typeof remotePort === 'number') {
32
+ return utils_1.HostAddress.fromHostPort(remoteAddress, remotePort).toString();
33
+ }
34
+ return (0, utils_1.uuidV4)().toString('hex');
35
+ }
37
36
  /** @internal */
38
37
  class Connection extends mongo_types_1.TypedEventEmitter {
39
38
  constructor(stream, options) {
40
39
  super();
41
- this.commandAsync = (0, util_1.promisify)((ns, cmd, options, callback) => this.command(ns, cmd, options, callback));
40
+ this.delayedTimeoutId = null;
41
+ this.clusterTime = null;
42
42
  this.id = options.id;
43
43
  this.address = streamIdentifier(stream, options);
44
44
  this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
45
45
  this.monitorCommands = options.monitorCommands;
46
46
  this.serverApi = options.serverApi;
47
- this.closed = false;
48
- this[kHello] = null;
49
- this[kClusterTime] = null;
50
- this[kDescription] = new stream_description_1.StreamDescription(this.address, options);
51
- this[kGeneration] = options.generation;
52
- this[kLastUseTime] = (0, utils_1.now)();
53
- // setup parser stream and message handling
54
- this[kQueue] = new Map();
55
- this[kMessageStream] = new message_stream_1.MessageStream({
56
- ...options,
57
- maxBsonMessageSize: this.hello?.maxBsonMessageSize
58
- });
59
- this[kStream] = stream;
60
- this[kDelayedTimeoutId] = null;
61
- this[kMessageStream].on('message', message => this.onMessage(message));
62
- this[kMessageStream].on('error', error => this.onError(error));
63
- this[kStream].on('close', () => this.onClose());
64
- this[kStream].on('timeout', () => this.onTimeout());
65
- this[kStream].on('error', () => {
66
- /* ignore errors, listen to `close` instead */
67
- });
68
- // hook the message stream up to the passed in stream
69
- this[kStream].pipe(this[kMessageStream]);
70
- this[kMessageStream].pipe(this[kStream]);
47
+ this.description = new stream_description_1.StreamDescription(this.address, options);
48
+ this.generation = options.generation;
49
+ this.lastUseTime = (0, utils_1.now)();
50
+ this.socket = stream;
51
+ this.controller = new AbortController();
52
+ this.messageStream = this.socket
53
+ .on('error', this.onError.bind(this))
54
+ .pipe(new SizedMessageTransform({ connection: this }))
55
+ .on('error', this.onError.bind(this));
56
+ this.socket.on('close', this.onClose.bind(this));
57
+ this.socket.on('timeout', this.onTimeout.bind(this));
58
+ const socketWrite = (0, util_1.promisify)(this.socket.write.bind(this.socket));
59
+ this.socketWrite = async (buffer) => {
60
+ return (0, utils_1.abortable)(socketWrite(buffer), { signal: this.controller.signal });
61
+ };
71
62
  }
72
- get description() {
73
- return this[kDescription];
63
+ /** Indicates that the connection (including underlying TCP socket) has been closed. */
64
+ get closed() {
65
+ return this.controller.signal.aborted;
74
66
  }
75
67
  get hello() {
76
- return this[kHello];
68
+ return this.description.hello;
77
69
  }
78
70
  // the `connect` method stores the result of the handshake hello on the connection
79
71
  set hello(response) {
80
- this[kDescription].receiveResponse(response);
81
- this[kDescription] = Object.freeze(this[kDescription]);
82
- // TODO: remove this, and only use the `StreamDescription` in the future
83
- this[kHello] = response;
84
- }
85
- // Set the whether the message stream is for a monitoring connection.
86
- set isMonitoringConnection(value) {
87
- this[kMessageStream].isMonitoringConnection = value;
88
- }
89
- get isMonitoringConnection() {
90
- return this[kMessageStream].isMonitoringConnection;
72
+ this.description.receiveResponse(response);
73
+ Object.freeze(this.description);
91
74
  }
92
75
  get serviceId() {
93
76
  return this.hello?.serviceId;
@@ -95,119 +78,38 @@ class Connection extends mongo_types_1.TypedEventEmitter {
95
78
  get loadBalanced() {
96
79
  return this.description.loadBalanced;
97
80
  }
98
- get generation() {
99
- return this[kGeneration] || 0;
100
- }
101
- set generation(generation) {
102
- this[kGeneration] = generation;
103
- }
104
81
  get idleTime() {
105
- return (0, utils_1.calculateDurationInMs)(this[kLastUseTime]);
82
+ return (0, utils_1.calculateDurationInMs)(this.lastUseTime);
106
83
  }
107
- get clusterTime() {
108
- return this[kClusterTime];
84
+ get hasSessionSupport() {
85
+ return this.description.logicalSessionTimeoutMinutes != null;
109
86
  }
110
- get stream() {
111
- return this[kStream];
87
+ get supportsOpMsg() {
88
+ return (this.description != null &&
89
+ (0, utils_1.maxWireVersion)(this) >= 6 &&
90
+ !this.description.__nodejs_mock_server__);
112
91
  }
113
92
  markAvailable() {
114
- this[kLastUseTime] = (0, utils_1.now)();
93
+ this.lastUseTime = (0, utils_1.now)();
115
94
  }
116
95
  onError(error) {
117
- this.cleanup(true, error);
96
+ this.cleanup(error);
118
97
  }
119
98
  onClose() {
120
99
  const message = `connection ${this.id} to ${this.address} closed`;
121
- this.cleanup(true, new error_1.MongoNetworkError(message));
100
+ this.cleanup(new error_1.MongoNetworkError(message));
122
101
  }
123
102
  onTimeout() {
124
- this[kDelayedTimeoutId] = (0, timers_1.setTimeout)(() => {
103
+ this.delayedTimeoutId = (0, timers_1.setTimeout)(() => {
125
104
  const message = `connection ${this.id} to ${this.address} timed out`;
126
105
  const beforeHandshake = this.hello == null;
127
- this.cleanup(true, new error_1.MongoNetworkTimeoutError(message, { beforeHandshake }));
106
+ this.cleanup(new error_1.MongoNetworkTimeoutError(message, { beforeHandshake }));
128
107
  }, 1).unref(); // No need for this timer to hold the event loop open
129
108
  }
130
- onMessage(message) {
131
- const delayedTimeoutId = this[kDelayedTimeoutId];
132
- if (delayedTimeoutId != null) {
133
- (0, timers_1.clearTimeout)(delayedTimeoutId);
134
- this[kDelayedTimeoutId] = null;
135
- }
136
- const socketTimeoutMS = this[kStream].timeout ?? 0;
137
- this[kStream].setTimeout(0);
138
- // always emit the message, in case we are streaming
139
- this.emit('message', message);
140
- let operationDescription = this[kQueue].get(message.responseTo);
141
- if (!operationDescription && this.isMonitoringConnection) {
142
- // This is how we recover when the initial hello's requestId is not
143
- // the responseTo when hello responses have been skipped:
144
- // First check if the map is of invalid size
145
- if (this[kQueue].size > 1) {
146
- this.cleanup(true, new error_1.MongoRuntimeError(INVALID_QUEUE_SIZE));
147
- }
148
- else {
149
- // Get the first orphaned operation description.
150
- const entry = this[kQueue].entries().next();
151
- if (entry.value != null) {
152
- const [requestId, orphaned] = entry.value;
153
- // If the orphaned operation description exists then set it.
154
- operationDescription = orphaned;
155
- // Remove the entry with the bad request id from the queue.
156
- this[kQueue].delete(requestId);
157
- }
158
- }
159
- }
160
- if (!operationDescription) {
161
- return;
162
- }
163
- const callback = operationDescription.cb;
164
- // SERVER-45775: For exhaust responses we should be able to use the same requestId to
165
- // track response, however the server currently synthetically produces remote requests
166
- // making the `responseTo` change on each response
167
- this[kQueue].delete(message.responseTo);
168
- if ('moreToCome' in message && message.moreToCome) {
169
- // If the operation description check above does find an orphaned
170
- // description and sets the operationDescription then this line will put one
171
- // back in the queue with the correct requestId and will resolve not being able
172
- // to find the next one via the responseTo of the next streaming hello.
173
- this[kQueue].set(message.requestId, operationDescription);
174
- this[kStream].setTimeout(socketTimeoutMS);
175
- }
176
- try {
177
- // Pass in the entire description because it has BSON parsing options
178
- message.parse(operationDescription);
179
- }
180
- catch (err) {
181
- // If this error is generated by our own code, it will already have the correct class applied
182
- // if it is not, then it is coming from a catastrophic data parse failure or the BSON library
183
- // in either case, it should not be wrapped
184
- callback(err);
185
- return;
186
- }
187
- if (message.documents[0]) {
188
- const document = message.documents[0];
189
- const session = operationDescription.session;
190
- if (session) {
191
- (0, sessions_1.updateSessionFromResponse)(session, document);
192
- }
193
- if (document.$clusterTime) {
194
- this[kClusterTime] = document.$clusterTime;
195
- this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
196
- }
197
- if (document.writeConcernError) {
198
- callback(new error_1.MongoWriteConcernError(document.writeConcernError, document), document);
199
- return;
200
- }
201
- if (document.ok === 0 || document.$err || document.errmsg || document.code) {
202
- callback(new error_1.MongoServerError(document));
203
- return;
204
- }
205
- }
206
- callback(undefined, message.documents[0]);
207
- }
208
109
  destroy(options, callback) {
209
110
  if (this.closed) {
210
- process.nextTick(() => callback?.());
111
+ if (typeof callback === 'function')
112
+ process.nextTick(callback);
211
113
  return;
212
114
  }
213
115
  if (typeof callback === 'function') {
@@ -219,7 +121,7 @@ class Connection extends mongo_types_1.TypedEventEmitter {
219
121
  this.removeAllListeners(Connection.PINNED);
220
122
  this.removeAllListeners(Connection.UNPINNED);
221
123
  const message = `connection ${this.id} to ${this.address} closed`;
222
- this.cleanup(options.force, new error_1.MongoNetworkError(message));
124
+ this.cleanup(new error_1.MongoNetworkError(message));
223
125
  }
224
126
  /**
225
127
  * A method that cleans up the connection. When `force` is true, this method
@@ -229,40 +131,17 @@ class Connection extends mongo_types_1.TypedEventEmitter {
229
131
  *
230
132
  * This method does nothing if the connection is already closed.
231
133
  */
232
- cleanup(force, error) {
134
+ cleanup(error) {
233
135
  if (this.closed) {
234
136
  return;
235
137
  }
236
- this.closed = true;
237
- const completeCleanup = () => {
238
- for (const op of this[kQueue].values()) {
239
- op.cb(error);
240
- }
241
- this[kQueue].clear();
242
- this.emit(Connection.CLOSE);
243
- };
244
- this[kStream].removeAllListeners();
245
- this[kMessageStream].removeAllListeners();
246
- this[kMessageStream].destroy();
247
- if (force) {
248
- this[kStream].destroy();
249
- completeCleanup();
250
- return;
251
- }
252
- if (!this[kStream].writableEnded) {
253
- this[kStream].end(() => {
254
- this[kStream].destroy();
255
- completeCleanup();
256
- });
257
- }
258
- else {
259
- completeCleanup();
260
- }
138
+ this.socket.destroy();
139
+ this.controller.abort(error);
140
+ this.emit(Connection.CLOSE);
261
141
  }
262
- command(ns, command, options, callback) {
142
+ prepareCommand(db, command, options) {
263
143
  let cmd = { ...command };
264
144
  const readPreference = (0, shared_1.getReadPreference)(options);
265
- const shouldUseOpMsg = supportsOpMsg(this);
266
145
  const session = options?.session;
267
146
  let clusterTime = this.clusterTime;
268
147
  if (this.serverApi) {
@@ -273,45 +152,179 @@ class Connection extends mongo_types_1.TypedEventEmitter {
273
152
  if (deprecationErrors != null)
274
153
  cmd.apiDeprecationErrors = deprecationErrors;
275
154
  }
276
- if (hasSessionSupport(this) && session) {
155
+ if (this.hasSessionSupport && session) {
277
156
  if (session.clusterTime &&
278
157
  clusterTime &&
279
158
  session.clusterTime.clusterTime.greaterThan(clusterTime.clusterTime)) {
280
159
  clusterTime = session.clusterTime;
281
160
  }
282
- const err = (0, sessions_1.applySession)(session, cmd, options);
283
- if (err) {
284
- return callback(err);
285
- }
161
+ const sessionError = (0, sessions_1.applySession)(session, cmd, options);
162
+ if (sessionError)
163
+ throw sessionError;
286
164
  }
287
165
  else if (session?.explicit) {
288
- return callback(new error_1.MongoCompatibilityError('Current topology does not support sessions'));
166
+ throw new error_1.MongoCompatibilityError('Current topology does not support sessions');
289
167
  }
290
168
  // if we have a known cluster time, gossip it
291
169
  if (clusterTime) {
292
170
  cmd.$clusterTime = clusterTime;
293
171
  }
294
- if ((0, shared_1.isSharded)(this) && !shouldUseOpMsg && readPreference && readPreference.mode !== 'primary') {
172
+ if ((0, shared_1.isSharded)(this) &&
173
+ !this.supportsOpMsg &&
174
+ readPreference &&
175
+ readPreference.mode !== 'primary') {
295
176
  cmd = {
296
177
  $query: cmd,
297
178
  $readPreference: readPreference.toJSON()
298
179
  };
299
180
  }
300
- const commandOptions = Object.assign({
181
+ const commandOptions = {
301
182
  numberToSkip: 0,
302
183
  numberToReturn: -1,
303
184
  checkKeys: false,
304
185
  // This value is not overridable
305
- secondaryOk: readPreference.secondaryOk()
306
- }, options);
307
- const message = shouldUseOpMsg
308
- ? new commands_1.Msg(ns.db, cmd, commandOptions)
309
- : new commands_1.Query(ns.db, cmd, commandOptions);
186
+ secondaryOk: readPreference.secondaryOk(),
187
+ ...options,
188
+ readPreference // ensure we pass in ReadPreference instance
189
+ };
190
+ const message = this.supportsOpMsg
191
+ ? new commands_1.OpMsgRequest(db, cmd, commandOptions)
192
+ : new commands_1.OpQueryRequest(db, cmd, commandOptions);
193
+ return message;
194
+ }
195
+ async *sendWire(message, options) {
196
+ this.controller.signal.throwIfAborted();
197
+ if (typeof options.socketTimeoutMS === 'number') {
198
+ this.socket.setTimeout(options.socketTimeoutMS);
199
+ }
200
+ else if (this.socketTimeoutMS !== 0) {
201
+ this.socket.setTimeout(this.socketTimeoutMS);
202
+ }
310
203
  try {
311
- write(this, message, commandOptions, callback);
204
+ await this.writeCommand(message, {
205
+ agreedCompressor: this.description.compressor ?? 'none',
206
+ zlibCompressionLevel: this.description.zlibCompressionLevel
207
+ });
208
+ if (options.noResponse) {
209
+ yield { ok: 1 };
210
+ return;
211
+ }
212
+ this.controller.signal.throwIfAborted();
213
+ for await (const response of this.readMany()) {
214
+ this.socket.setTimeout(0);
215
+ response.parse(options);
216
+ const [document] = response.documents;
217
+ if (!Buffer.isBuffer(document)) {
218
+ const { session } = options;
219
+ if (session) {
220
+ (0, sessions_1.updateSessionFromResponse)(session, document);
221
+ }
222
+ if (document.$clusterTime) {
223
+ this.clusterTime = document.$clusterTime;
224
+ this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
225
+ }
226
+ }
227
+ yield document;
228
+ this.controller.signal.throwIfAborted();
229
+ if (typeof options.socketTimeoutMS === 'number') {
230
+ this.socket.setTimeout(options.socketTimeoutMS);
231
+ }
232
+ else if (this.socketTimeoutMS !== 0) {
233
+ this.socket.setTimeout(this.socketTimeoutMS);
234
+ }
235
+ }
312
236
  }
313
- catch (err) {
314
- callback(err);
237
+ finally {
238
+ this.socket.setTimeout(0);
239
+ }
240
+ }
241
+ async *sendCommand(ns, command, options = {}) {
242
+ const message = this.prepareCommand(ns.db, command, options);
243
+ let started = 0;
244
+ if (this.monitorCommands) {
245
+ started = (0, utils_1.now)();
246
+ this.emit(Connection.COMMAND_STARTED, new command_monitoring_events_1.CommandStartedEvent(this, message, this.description.serverConnectionId));
247
+ }
248
+ let document;
249
+ try {
250
+ this.controller.signal.throwIfAborted();
251
+ for await (document of this.sendWire(message, options)) {
252
+ if (!Buffer.isBuffer(document) && document.writeConcernError) {
253
+ throw new error_1.MongoWriteConcernError(document.writeConcernError, document);
254
+ }
255
+ if (!Buffer.isBuffer(document) &&
256
+ (document.ok === 0 || document.$err || document.errmsg || document.code)) {
257
+ throw new error_1.MongoServerError(document);
258
+ }
259
+ if (this.monitorCommands) {
260
+ this.emit(Connection.COMMAND_SUCCEEDED, new command_monitoring_events_1.CommandSucceededEvent(this, message, options.noResponse ? undefined : document, started, this.description.serverConnectionId));
261
+ }
262
+ yield document;
263
+ this.controller.signal.throwIfAborted();
264
+ }
265
+ }
266
+ catch (error) {
267
+ if (this.monitorCommands) {
268
+ if (error.name === 'MongoWriteConcernError') {
269
+ this.emit(Connection.COMMAND_SUCCEEDED, new command_monitoring_events_1.CommandSucceededEvent(this, message, options.noResponse ? undefined : document, started, this.description.serverConnectionId));
270
+ }
271
+ else {
272
+ this.emit(Connection.COMMAND_FAILED, new command_monitoring_events_1.CommandFailedEvent(this, message, error, started, this.description.serverConnectionId));
273
+ }
274
+ }
275
+ throw error;
276
+ }
277
+ }
278
+ async command(ns, command, options = {}) {
279
+ this.controller.signal.throwIfAborted();
280
+ for await (const document of this.sendCommand(ns, command, options)) {
281
+ return document;
282
+ }
283
+ throw new error_1.MongoUnexpectedServerResponseError('Unable to get response from server');
284
+ }
285
+ exhaustCommand(ns, command, options, replyListener) {
286
+ const exhaustLoop = async () => {
287
+ this.controller.signal.throwIfAborted();
288
+ for await (const reply of this.sendCommand(ns, command, options)) {
289
+ replyListener(undefined, reply);
290
+ this.controller.signal.throwIfAborted();
291
+ }
292
+ throw new error_1.MongoUnexpectedServerResponseError('Server ended moreToCome unexpectedly');
293
+ };
294
+ exhaustLoop().catch(replyListener);
295
+ }
296
+ /**
297
+ * @internal
298
+ *
299
+ * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method
300
+ * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).
301
+ */
302
+ async writeCommand(command, options) {
303
+ const finalCommand = options.agreedCompressor === 'none' || !commands_1.OpCompressedRequest.canCompress(command)
304
+ ? command
305
+ : new commands_1.OpCompressedRequest(command, {
306
+ agreedCompressor: options.agreedCompressor ?? 'none',
307
+ zlibCompressionLevel: options.zlibCompressionLevel ?? 0
308
+ });
309
+ const buffer = Buffer.concat(await finalCommand.toBin());
310
+ return this.socketWrite(buffer);
311
+ }
312
+ /**
313
+ * @internal
314
+ *
315
+ * Returns an async generator that yields full wire protocol messages from the underlying socket. This function
316
+ * yields messages until `moreToCome` is false or not present in a response, or the caller cancels the request
317
+ * by calling `return` on the generator.
318
+ *
319
+ * Note that `for-await` loops call `return` automatically when the loop is exited.
320
+ */
321
+ async *readMany() {
322
+ for await (const message of (0, on_data_1.onData)(this.messageStream, { signal: this.controller.signal })) {
323
+ const response = await (0, compression_1.decompressResponse)(message);
324
+ yield response;
325
+ if (!response.moreToCome) {
326
+ return;
327
+ }
315
328
  }
316
329
  }
317
330
  }
@@ -326,32 +339,57 @@ Connection.CLUSTER_TIME_RECEIVED = constants_1.CLUSTER_TIME_RECEIVED;
326
339
  /** @event */
327
340
  Connection.CLOSE = constants_1.CLOSE;
328
341
  /** @event */
329
- Connection.MESSAGE = constants_1.MESSAGE;
330
- /** @event */
331
342
  Connection.PINNED = constants_1.PINNED;
332
343
  /** @event */
333
344
  Connection.UNPINNED = constants_1.UNPINNED;
334
345
  exports.Connection = Connection;
335
346
  /** @internal */
347
+ class SizedMessageTransform extends stream_1.Transform {
348
+ constructor({ connection }) {
349
+ super({ objectMode: false });
350
+ this.bufferPool = new utils_1.BufferPool();
351
+ this.connection = connection;
352
+ }
353
+ _transform(chunk, encoding, callback) {
354
+ if (this.connection.delayedTimeoutId != null) {
355
+ (0, timers_1.clearTimeout)(this.connection.delayedTimeoutId);
356
+ this.connection.delayedTimeoutId = null;
357
+ }
358
+ this.bufferPool.append(chunk);
359
+ const sizeOfMessage = this.bufferPool.getInt32();
360
+ if (sizeOfMessage == null) {
361
+ return callback();
362
+ }
363
+ if (sizeOfMessage < 0) {
364
+ return callback(new error_1.MongoParseError(`Invalid message size: ${sizeOfMessage}, too small`));
365
+ }
366
+ if (sizeOfMessage > this.bufferPool.length) {
367
+ return callback();
368
+ }
369
+ const message = this.bufferPool.read(sizeOfMessage);
370
+ return callback(null, message);
371
+ }
372
+ }
373
+ exports.SizedMessageTransform = SizedMessageTransform;
374
+ /** @internal */
336
375
  class CryptoConnection extends Connection {
337
376
  constructor(stream, options) {
338
377
  super(stream, options);
339
- this[kAutoEncrypter] = options.autoEncrypter;
378
+ this.autoEncrypter = options.autoEncrypter;
340
379
  }
341
380
  /** @internal @override */
342
- command(ns, cmd, options, callback) {
343
- const autoEncrypter = this[kAutoEncrypter];
381
+ async command(ns, cmd, options) {
382
+ const { autoEncrypter } = this;
344
383
  if (!autoEncrypter) {
345
- return callback(new error_1.MongoMissingDependencyError('No AutoEncrypter available for encryption'));
384
+ throw new error_1.MongoMissingDependencyError('No AutoEncrypter available for encryption');
346
385
  }
347
386
  const serverWireVersion = (0, utils_1.maxWireVersion)(this);
348
387
  if (serverWireVersion === 0) {
349
388
  // This means the initial handshake hasn't happened yet
350
- return super.command(ns, cmd, options, callback);
389
+ return super.command(ns, cmd, options);
351
390
  }
352
391
  if (serverWireVersion < 8) {
353
- callback(new error_1.MongoCompatibilityError('Auto-encryption requires a minimum MongoDB version of 4.2'));
354
- return;
392
+ throw new error_1.MongoCompatibilityError('Auto-encryption requires a minimum MongoDB version of 4.2');
355
393
  }
356
394
  // Save sort or indexKeys based on the command being run
357
395
  // the encrypt API serializes our JS objects to BSON to pass to the native code layer
@@ -363,130 +401,20 @@ class CryptoConnection extends Connection {
363
401
  const indexKeys = cmd.createIndexes
364
402
  ? cmd.indexes.map((index) => index.key)
365
403
  : null;
366
- autoEncrypter.encrypt(ns.toString(), cmd, options).then(encrypted => {
367
- // Replace the saved values
368
- if (sort != null && (cmd.find || cmd.findAndModify)) {
369
- encrypted.sort = sort;
370
- }
371
- if (indexKeys != null && cmd.createIndexes) {
372
- for (const [offset, index] of indexKeys.entries()) {
373
- // @ts-expect-error `encrypted` is a generic "command", but we've narrowed for only `createIndexes` commands here
374
- encrypted.indexes[offset].key = index;
375
- }
376
- }
377
- super.command(ns, encrypted, options, (err, response) => {
378
- if (err || response == null) {
379
- callback(err, response);
380
- return;
381
- }
382
- autoEncrypter.decrypt(response, options).then(res => callback(undefined, res), err => callback(err));
383
- });
384
- }, err => {
385
- if (err) {
386
- callback(err, null);
387
- }
388
- });
389
- }
390
- }
391
- exports.CryptoConnection = CryptoConnection;
392
- /** @internal */
393
- function hasSessionSupport(conn) {
394
- const description = conn.description;
395
- return description.logicalSessionTimeoutMinutes != null;
396
- }
397
- exports.hasSessionSupport = hasSessionSupport;
398
- function supportsOpMsg(conn) {
399
- const description = conn.description;
400
- if (description == null) {
401
- return false;
402
- }
403
- return (0, utils_1.maxWireVersion)(conn) >= 6 && !description.__nodejs_mock_server__;
404
- }
405
- function streamIdentifier(stream, options) {
406
- if (options.proxyHost) {
407
- // If proxy options are specified, the properties of `stream` itself
408
- // will not accurately reflect what endpoint this is connected to.
409
- return options.hostAddress.toString();
410
- }
411
- const { remoteAddress, remotePort } = stream;
412
- if (typeof remoteAddress === 'string' && typeof remotePort === 'number') {
413
- return utils_1.HostAddress.fromHostPort(remoteAddress, remotePort).toString();
414
- }
415
- return (0, utils_1.uuidV4)().toString('hex');
416
- }
417
- function write(conn, command, options, callback) {
418
- options = options ?? {};
419
- const operationDescription = {
420
- requestId: command.requestId,
421
- cb: callback,
422
- session: options.session,
423
- noResponse: typeof options.noResponse === 'boolean' ? options.noResponse : false,
424
- documentsReturnedIn: options.documentsReturnedIn,
425
- // for BSON parsing
426
- useBigInt64: typeof options.useBigInt64 === 'boolean' ? options.useBigInt64 : false,
427
- promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
428
- promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
429
- promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
430
- bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
431
- enableUtf8Validation: typeof options.enableUtf8Validation === 'boolean' ? options.enableUtf8Validation : true,
432
- raw: typeof options.raw === 'boolean' ? options.raw : false,
433
- started: 0
434
- };
435
- if (conn[kDescription] && conn[kDescription].compressor) {
436
- operationDescription.agreedCompressor = conn[kDescription].compressor;
437
- if (conn[kDescription].zlibCompressionLevel) {
438
- operationDescription.zlibCompressionLevel = conn[kDescription].zlibCompressionLevel;
404
+ const encrypted = await autoEncrypter.encrypt(ns.toString(), cmd, options);
405
+ // Replace the saved values
406
+ if (sort != null && (cmd.find || cmd.findAndModify)) {
407
+ encrypted.sort = sort;
439
408
  }
440
- }
441
- if (typeof options.socketTimeoutMS === 'number') {
442
- conn[kStream].setTimeout(options.socketTimeoutMS);
443
- }
444
- else if (conn.socketTimeoutMS !== 0) {
445
- conn[kStream].setTimeout(conn.socketTimeoutMS);
446
- }
447
- // if command monitoring is enabled we need to modify the callback here
448
- if (conn.monitorCommands) {
449
- conn.emit(Connection.COMMAND_STARTED, new command_monitoring_events_1.CommandStartedEvent(conn, command));
450
- operationDescription.started = (0, utils_1.now)();
451
- operationDescription.cb = (err, reply) => {
452
- // Command monitoring spec states that if ok is 1, then we must always emit
453
- // a command succeeded event, even if there's an error. Write concern errors
454
- // will have an ok: 1 in their reply.
455
- if (err && reply?.ok !== 1) {
456
- conn.emit(Connection.COMMAND_FAILED, new command_monitoring_events_1.CommandFailedEvent(conn, command, err, operationDescription.started));
409
+ if (indexKeys != null && cmd.createIndexes) {
410
+ for (const [offset, index] of indexKeys.entries()) {
411
+ // @ts-expect-error `encrypted` is a generic "command", but we've narrowed for only `createIndexes` commands here
412
+ encrypted.indexes[offset].key = index;
457
413
  }
458
- else {
459
- if (reply && (reply.ok === 0 || reply.$err)) {
460
- conn.emit(Connection.COMMAND_FAILED, new command_monitoring_events_1.CommandFailedEvent(conn, command, reply, operationDescription.started));
461
- }
462
- else {
463
- conn.emit(Connection.COMMAND_SUCCEEDED, new command_monitoring_events_1.CommandSucceededEvent(conn, command, reply, operationDescription.started));
464
- }
465
- }
466
- if (typeof callback === 'function') {
467
- // Since we're passing through the reply with the write concern error now, we
468
- // need it not to be provided to the original callback in this case so
469
- // retryability does not get tricked into thinking the command actually
470
- // succeeded.
471
- callback(err, err instanceof error_1.MongoWriteConcernError ? undefined : reply);
472
- }
473
- };
474
- }
475
- if (!operationDescription.noResponse) {
476
- conn[kQueue].set(operationDescription.requestId, operationDescription);
477
- }
478
- try {
479
- conn[kMessageStream].writeCommand(command, operationDescription);
480
- }
481
- catch (e) {
482
- if (!operationDescription.noResponse) {
483
- conn[kQueue].delete(operationDescription.requestId);
484
- operationDescription.cb(e);
485
- return;
486
414
  }
487
- }
488
- if (operationDescription.noResponse) {
489
- operationDescription.cb();
415
+ const response = await super.command(ns, encrypted, options);
416
+ return autoEncrypter.decrypt(response, options);
490
417
  }
491
418
  }
419
+ exports.CryptoConnection = CryptoConnection;
492
420
  //# sourceMappingURL=connection.js.map