mongodb 6.9.0 → 6.10.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.
Files changed (127) hide show
  1. package/lib/beta.d.ts +407 -12
  2. package/lib/bson.js +1 -0
  3. package/lib/bson.js.map +1 -1
  4. package/lib/bulk/common.js +60 -71
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/bulk/unordered.js +3 -3
  7. package/lib/bulk/unordered.js.map +1 -1
  8. package/lib/change_stream.js +3 -2
  9. package/lib/change_stream.js.map +1 -1
  10. package/lib/cmap/auth/mongo_credentials.js +2 -7
  11. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  12. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  13. package/lib/cmap/auth/mongodb_oidc/command_builders.js +1 -1
  14. package/lib/cmap/auth/mongodb_oidc/command_builders.js.map +1 -1
  15. package/lib/cmap/auth/mongodb_oidc/human_callback_workflow.js +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/human_callback_workflow.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_oidc/machine_workflow.js.map +1 -1
  18. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  19. package/lib/cmap/command_monitoring_events.js +10 -1
  20. package/lib/cmap/command_monitoring_events.js.map +1 -1
  21. package/lib/cmap/commands.js +50 -18
  22. package/lib/cmap/commands.js.map +1 -1
  23. package/lib/cmap/connection.js +9 -2
  24. package/lib/cmap/connection.js.map +1 -1
  25. package/lib/cmap/wire_protocol/constants.js +2 -2
  26. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  27. package/lib/cmap/wire_protocol/responses.js +26 -1
  28. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  29. package/lib/connection_string.js +1 -7
  30. package/lib/connection_string.js.map +1 -1
  31. package/lib/cursor/aggregation_cursor.js.map +1 -1
  32. package/lib/cursor/client_bulk_write_cursor.js +52 -0
  33. package/lib/cursor/client_bulk_write_cursor.js.map +1 -0
  34. package/lib/cursor/find_cursor.js.map +1 -1
  35. package/lib/db.js +1 -1
  36. package/lib/error.js +76 -3
  37. package/lib/error.js.map +1 -1
  38. package/lib/explain.js +6 -6
  39. package/lib/explain.js.map +1 -1
  40. package/lib/index.js +6 -3
  41. package/lib/index.js.map +1 -1
  42. package/lib/mongo_client.js +14 -0
  43. package/lib/mongo_client.js.map +1 -1
  44. package/lib/mongo_client_auth_providers.js +6 -2
  45. package/lib/mongo_client_auth_providers.js.map +1 -1
  46. package/lib/mongo_types.js.map +1 -1
  47. package/lib/operations/aggregate.js.map +1 -1
  48. package/lib/operations/client_bulk_write/client_bulk_write.js +83 -0
  49. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -0
  50. package/lib/operations/client_bulk_write/command_builder.js +154 -19
  51. package/lib/operations/client_bulk_write/command_builder.js.map +1 -1
  52. package/lib/operations/client_bulk_write/executor.js +109 -0
  53. package/lib/operations/client_bulk_write/executor.js.map +1 -0
  54. package/lib/operations/client_bulk_write/results_merger.js +204 -0
  55. package/lib/operations/client_bulk_write/results_merger.js.map +1 -0
  56. package/lib/operations/execute_operation.js +7 -0
  57. package/lib/operations/execute_operation.js.map +1 -1
  58. package/lib/operations/find.js.map +1 -1
  59. package/lib/operations/operation.js +5 -1
  60. package/lib/operations/operation.js.map +1 -1
  61. package/lib/operations/search_indexes/create.js.map +1 -1
  62. package/lib/operations/search_indexes/drop.js.map +1 -1
  63. package/lib/operations/search_indexes/update.js.map +1 -1
  64. package/lib/sdam/server.js +2 -1
  65. package/lib/sdam/server.js.map +1 -1
  66. package/lib/sdam/server_description.js +3 -0
  67. package/lib/sdam/server_description.js.map +1 -1
  68. package/lib/sdam/srv_polling.js +5 -1
  69. package/lib/sdam/srv_polling.js.map +1 -1
  70. package/lib/sdam/topology_description.js.map +1 -1
  71. package/lib/sessions.js +9 -2
  72. package/lib/sessions.js.map +1 -1
  73. package/lib/utils.js +25 -9
  74. package/lib/utils.js.map +1 -1
  75. package/lib/write_concern.js.map +1 -1
  76. package/mongodb.d.ts +407 -12
  77. package/package.json +1 -1
  78. package/src/beta.ts +1 -1
  79. package/src/bson.ts +3 -0
  80. package/src/bulk/common.ts +80 -120
  81. package/src/bulk/unordered.ts +3 -4
  82. package/src/change_stream.ts +5 -2
  83. package/src/cmap/auth/mongo_credentials.ts +2 -8
  84. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +1 -1
  85. package/src/cmap/auth/mongodb_oidc/command_builders.ts +1 -2
  86. package/src/cmap/auth/mongodb_oidc/human_callback_workflow.ts +1 -2
  87. package/src/cmap/auth/mongodb_oidc/machine_workflow.ts +1 -1
  88. package/src/cmap/auth/mongodb_oidc.ts +1 -2
  89. package/src/cmap/command_monitoring_events.ts +16 -2
  90. package/src/cmap/commands.ts +71 -25
  91. package/src/cmap/connection.ts +17 -4
  92. package/src/cmap/wire_protocol/constants.ts +2 -2
  93. package/src/cmap/wire_protocol/on_demand/document.ts +1 -2
  94. package/src/cmap/wire_protocol/responses.ts +31 -2
  95. package/src/connection_string.ts +2 -9
  96. package/src/cursor/aggregation_cursor.ts +2 -2
  97. package/src/cursor/client_bulk_write_cursor.ts +79 -0
  98. package/src/cursor/find_cursor.ts +2 -2
  99. package/src/db.ts +1 -1
  100. package/src/error.ts +98 -2
  101. package/src/explain.ts +47 -11
  102. package/src/index.ts +26 -1
  103. package/src/mongo_client.ts +29 -1
  104. package/src/mongo_client_auth_providers.ts +8 -2
  105. package/src/mongo_types.ts +2 -1
  106. package/src/operations/aggregate.ts +9 -1
  107. package/src/operations/client_bulk_write/client_bulk_write.ts +107 -0
  108. package/src/operations/client_bulk_write/command_builder.ts +216 -30
  109. package/src/operations/client_bulk_write/common.ts +148 -23
  110. package/src/operations/client_bulk_write/executor.ts +137 -0
  111. package/src/operations/client_bulk_write/results_merger.ts +260 -0
  112. package/src/operations/execute_operation.ts +8 -0
  113. package/src/operations/find.ts +8 -1
  114. package/src/operations/operation.ts +6 -1
  115. package/src/operations/search_indexes/create.ts +1 -2
  116. package/src/operations/search_indexes/drop.ts +1 -2
  117. package/src/operations/search_indexes/update.ts +1 -2
  118. package/src/sdam/server.ts +2 -1
  119. package/src/sdam/server_description.ts +9 -0
  120. package/src/sdam/srv_polling.ts +5 -2
  121. package/src/sdam/topology_description.ts +0 -1
  122. package/src/sessions.ts +16 -2
  123. package/src/utils.ts +40 -10
  124. package/src/write_concern.ts +4 -1
  125. package/lib/cmap/auth/mongocr.js +0 -35
  126. package/lib/cmap/auth/mongocr.js.map +0 -1
  127. package/src/cmap/auth/mongocr.ts +0 -38
@@ -0,0 +1,107 @@
1
+ import { MongoClientBulkWriteExecutionError, ServerType } from '../../beta';
2
+ import { ClientBulkWriteCursorResponse } from '../../cmap/wire_protocol/responses';
3
+ import type { Server } from '../../sdam/server';
4
+ import type { ClientSession } from '../../sessions';
5
+ import { MongoDBNamespace } from '../../utils';
6
+ import { CommandOperation } from '../command';
7
+ import { Aspect, defineAspects } from '../operation';
8
+ import { type ClientBulkWriteCommandBuilder } from './command_builder';
9
+ import { type ClientBulkWriteOptions } from './common';
10
+
11
+ /**
12
+ * Executes a single client bulk write operation within a potential batch.
13
+ * @internal
14
+ */
15
+ export class ClientBulkWriteOperation extends CommandOperation<ClientBulkWriteCursorResponse> {
16
+ commandBuilder: ClientBulkWriteCommandBuilder;
17
+ override options: ClientBulkWriteOptions;
18
+
19
+ override get commandName() {
20
+ return 'bulkWrite' as const;
21
+ }
22
+
23
+ constructor(commandBuilder: ClientBulkWriteCommandBuilder, options: ClientBulkWriteOptions) {
24
+ super(undefined, options);
25
+ this.commandBuilder = commandBuilder;
26
+ this.options = options;
27
+ this.ns = new MongoDBNamespace('admin', '$cmd');
28
+ }
29
+
30
+ override resetBatch(): boolean {
31
+ return this.commandBuilder.resetBatch();
32
+ }
33
+
34
+ override get canRetryWrite(): boolean {
35
+ return this.commandBuilder.isBatchRetryable;
36
+ }
37
+
38
+ /**
39
+ * Execute the command. Superclass will handle write concern, etc.
40
+ * @param server - The server.
41
+ * @param session - The session.
42
+ * @returns The response.
43
+ */
44
+ override async execute(
45
+ server: Server,
46
+ session: ClientSession | undefined
47
+ ): Promise<ClientBulkWriteCursorResponse> {
48
+ let command;
49
+
50
+ if (server.description.type === ServerType.LoadBalancer) {
51
+ if (session) {
52
+ let connection;
53
+ if (!session.pinnedConnection) {
54
+ // Checkout a connection to build the command.
55
+ connection = await server.pool.checkOut();
56
+ // Pin the connection to the session so it get used to execute the command and we do not
57
+ // perform a double check-in/check-out.
58
+ session.pin(connection);
59
+ } else {
60
+ connection = session.pinnedConnection;
61
+ }
62
+ command = this.commandBuilder.buildBatch(
63
+ connection.hello?.maxMessageSizeBytes,
64
+ connection.hello?.maxWriteBatchSize,
65
+ connection.hello?.maxBsonObjectSize
66
+ );
67
+ } else {
68
+ throw new MongoClientBulkWriteExecutionError(
69
+ 'Session provided to the client bulk write operation must be present.'
70
+ );
71
+ }
72
+ } else {
73
+ // At this point we have a server and the auto connect code has already
74
+ // run in executeOperation, so the server description will be populated.
75
+ // We can use that to build the command.
76
+ if (
77
+ !server.description.maxWriteBatchSize ||
78
+ !server.description.maxMessageSizeBytes ||
79
+ !server.description.maxBsonObjectSize
80
+ ) {
81
+ throw new MongoClientBulkWriteExecutionError(
82
+ 'In order to execute a client bulk write, both maxWriteBatchSize, maxMessageSizeBytes and maxBsonObjectSize must be provided by the servers hello response.'
83
+ );
84
+ }
85
+ command = this.commandBuilder.buildBatch(
86
+ server.description.maxMessageSizeBytes,
87
+ server.description.maxWriteBatchSize,
88
+ server.description.maxBsonObjectSize
89
+ );
90
+ }
91
+
92
+ // Check after the batch is built if we cannot retry it and override the option.
93
+ if (!this.canRetryWrite) {
94
+ this.options.willRetryWrite = false;
95
+ }
96
+ return await super.executeCommand(server, session, command, ClientBulkWriteCursorResponse);
97
+ }
98
+ }
99
+
100
+ // Skipping the collation as it goes on the individual ops.
101
+ defineAspects(ClientBulkWriteOperation, [
102
+ Aspect.WRITE_OPERATION,
103
+ Aspect.SKIP_COLLATION,
104
+ Aspect.CURSOR_CREATING,
105
+ Aspect.RETRYABLE,
106
+ Aspect.COMMAND_BATCHING
107
+ ]);
@@ -1,6 +1,9 @@
1
- import { type Document } from '../../bson';
1
+ import { BSON, type Document } from '../../bson';
2
2
  import { DocumentSequence } from '../../cmap/commands';
3
+ import { MongoAPIError, MongoInvalidArgumentError } from '../../error';
4
+ import { type PkFactory } from '../../mongo_client';
3
5
  import type { Filter, OptionalId, UpdateFilter, WithoutId } from '../../mongo_types';
6
+ import { DEFAULT_PK_FACTORY, hasAtomicOperators } from '../../utils';
4
7
  import { type CollationOptions } from '../command';
5
8
  import { type Hint } from '../operation';
6
9
  import type {
@@ -23,20 +26,44 @@ export interface ClientBulkWriteCommand {
23
26
  nsInfo: DocumentSequence;
24
27
  bypassDocumentValidation?: boolean;
25
28
  let?: Document;
29
+ comment?: any;
26
30
  }
27
31
 
32
+ /**
33
+ * The bytes overhead for the extra fields added post command generation.
34
+ */
35
+ const MESSAGE_OVERHEAD_BYTES = 1000;
36
+
28
37
  /** @internal */
29
38
  export class ClientBulkWriteCommandBuilder {
30
- models: AnyClientBulkWriteModel[];
39
+ models: ReadonlyArray<AnyClientBulkWriteModel<Document>>;
31
40
  options: ClientBulkWriteOptions;
41
+ pkFactory: PkFactory;
42
+ /** The current index in the models array that is being processed. */
43
+ currentModelIndex: number;
44
+ /** The model index that the builder was on when it finished the previous batch. Used for resets when retrying. */
45
+ previousModelIndex: number;
46
+ /** The last array of operations that were created. Used by the results merger for indexing results. */
47
+ lastOperations: Document[];
48
+ /** Returns true if the current batch being created has no multi-updates. */
49
+ isBatchRetryable: boolean;
32
50
 
33
51
  /**
34
52
  * Create the command builder.
35
53
  * @param models - The client write models.
36
54
  */
37
- constructor(models: AnyClientBulkWriteModel[], options: ClientBulkWriteOptions) {
55
+ constructor(
56
+ models: ReadonlyArray<AnyClientBulkWriteModel<Document>>,
57
+ options: ClientBulkWriteOptions,
58
+ pkFactory?: PkFactory
59
+ ) {
38
60
  this.models = models;
39
61
  this.options = options;
62
+ this.pkFactory = pkFactory ?? DEFAULT_PK_FACTORY;
63
+ this.currentModelIndex = 0;
64
+ this.previousModelIndex = 0;
65
+ this.lastOperations = [];
66
+ this.isBatchRetryable = true;
40
67
  }
41
68
 
42
69
  /**
@@ -51,34 +78,135 @@ export class ClientBulkWriteCommandBuilder {
51
78
  }
52
79
 
53
80
  /**
54
- * Build the bulk write commands from the models.
81
+ * Determines if there is another batch to process.
82
+ * @returns True if not all batches have been built.
83
+ */
84
+ hasNextBatch(): boolean {
85
+ return this.currentModelIndex < this.models.length;
86
+ }
87
+
88
+ /**
89
+ * When we need to retry a command we need to set the current
90
+ * model index back to its previous value.
91
+ */
92
+ resetBatch(): boolean {
93
+ this.currentModelIndex = this.previousModelIndex;
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * Build a single batch of a client bulk write command.
99
+ * @param maxMessageSizeBytes - The max message size in bytes.
100
+ * @param maxWriteBatchSize - The max write batch size.
101
+ * @returns The client bulk write command.
55
102
  */
56
- buildCommands(): ClientBulkWriteCommand[] {
57
- // Iterate the models to build the ops and nsInfo fields.
58
- const operations = [];
103
+ buildBatch(
104
+ maxMessageSizeBytes: number,
105
+ maxWriteBatchSize: number,
106
+ maxBsonObjectSize: number
107
+ ): ClientBulkWriteCommand {
108
+ // We start by assuming the batch has no multi-updates, so it is retryable
109
+ // until we find them.
110
+ this.isBatchRetryable = true;
111
+ let commandLength = 0;
59
112
  let currentNamespaceIndex = 0;
113
+ const command: ClientBulkWriteCommand = this.baseCommand();
60
114
  const namespaces = new Map<string, number>();
61
- for (const model of this.models) {
115
+ // In the case of retries we need to mark where we started this batch.
116
+ this.previousModelIndex = this.currentModelIndex;
117
+
118
+ while (this.currentModelIndex < this.models.length) {
119
+ const model = this.models[this.currentModelIndex];
62
120
  const ns = model.namespace;
63
- const index = namespaces.get(ns);
64
- if (index != null) {
65
- operations.push(buildOperation(model, index));
121
+ const nsIndex = namespaces.get(ns);
122
+
123
+ // Multi updates are not retryable.
124
+ if (model.name === 'deleteMany' || model.name === 'updateMany') {
125
+ this.isBatchRetryable = false;
126
+ }
127
+
128
+ if (nsIndex != null) {
129
+ // Build the operation and serialize it to get the bytes buffer.
130
+ const operation = buildOperation(model, nsIndex, this.pkFactory);
131
+ let operationBuffer;
132
+ try {
133
+ operationBuffer = BSON.serialize(operation);
134
+ } catch (cause) {
135
+ throw new MongoInvalidArgumentError(`Could not serialize operation to BSON`, { cause });
136
+ }
137
+
138
+ validateBufferSize('ops', operationBuffer, maxBsonObjectSize);
139
+
140
+ // Check if the operation buffer can fit in the command. If it can,
141
+ // then add the operation to the document sequence and increment the
142
+ // current length as long as the ops don't exceed the maxWriteBatchSize.
143
+ if (
144
+ commandLength + operationBuffer.length < maxMessageSizeBytes &&
145
+ command.ops.documents.length < maxWriteBatchSize
146
+ ) {
147
+ // Pushing to the ops document sequence returns the total byte length of the document sequence.
148
+ commandLength = MESSAGE_OVERHEAD_BYTES + command.ops.push(operation, operationBuffer);
149
+ // Increment the builder's current model index.
150
+ this.currentModelIndex++;
151
+ } else {
152
+ // The operation cannot fit in the current command and will need to
153
+ // go in the next batch. Exit the loop.
154
+ break;
155
+ }
66
156
  } else {
157
+ // The namespace is not already in the nsInfo so we will set it in the map, and
158
+ // construct our nsInfo and ops documents and buffers.
67
159
  namespaces.set(ns, currentNamespaceIndex);
68
- operations.push(buildOperation(model, currentNamespaceIndex));
69
- currentNamespaceIndex++;
160
+ const nsInfo = { ns: ns };
161
+ const operation = buildOperation(model, currentNamespaceIndex, this.pkFactory);
162
+ let nsInfoBuffer;
163
+ let operationBuffer;
164
+ try {
165
+ nsInfoBuffer = BSON.serialize(nsInfo);
166
+ operationBuffer = BSON.serialize(operation);
167
+ } catch (cause) {
168
+ throw new MongoInvalidArgumentError(`Could not serialize ns info to BSON`, { cause });
169
+ }
170
+
171
+ validateBufferSize('nsInfo', nsInfoBuffer, maxBsonObjectSize);
172
+ validateBufferSize('ops', operationBuffer, maxBsonObjectSize);
173
+
174
+ // Check if the operation and nsInfo buffers can fit in the command. If they
175
+ // can, then add the operation and nsInfo to their respective document
176
+ // sequences and increment the current length as long as the ops don't exceed
177
+ // the maxWriteBatchSize.
178
+ if (
179
+ commandLength + nsInfoBuffer.length + operationBuffer.length < maxMessageSizeBytes &&
180
+ command.ops.documents.length < maxWriteBatchSize
181
+ ) {
182
+ // Pushing to the ops document sequence returns the total byte length of the document sequence.
183
+ commandLength =
184
+ MESSAGE_OVERHEAD_BYTES +
185
+ command.nsInfo.push(nsInfo, nsInfoBuffer) +
186
+ command.ops.push(operation, operationBuffer);
187
+ // We've added a new namespace, increment the namespace index.
188
+ currentNamespaceIndex++;
189
+ // Increment the builder's current model index.
190
+ this.currentModelIndex++;
191
+ } else {
192
+ // The operation cannot fit in the current command and will need to
193
+ // go in the next batch. Exit the loop.
194
+ break;
195
+ }
70
196
  }
71
197
  }
198
+ // Set the last operations and return the command.
199
+ this.lastOperations = command.ops.documents;
200
+ return command;
201
+ }
72
202
 
73
- const nsInfo = Array.from(namespaces.keys(), ns => ({ ns }));
74
-
75
- // The base command.
203
+ private baseCommand(): ClientBulkWriteCommand {
76
204
  const command: ClientBulkWriteCommand = {
77
205
  bulkWrite: 1,
78
206
  errorsOnly: this.errorsOnly,
79
207
  ordered: this.options.ordered ?? true,
80
- ops: new DocumentSequence(operations),
81
- nsInfo: new DocumentSequence(nsInfo)
208
+ ops: new DocumentSequence('ops'),
209
+ nsInfo: new DocumentSequence('nsInfo')
82
210
  };
83
211
  // Add bypassDocumentValidation if it was present in the options.
84
212
  if (this.options.bypassDocumentValidation != null) {
@@ -88,7 +216,22 @@ export class ClientBulkWriteCommandBuilder {
88
216
  if (this.options.let) {
89
217
  command.let = this.options.let;
90
218
  }
91
- return [command];
219
+
220
+ // we check for undefined specifically here to allow falsy values
221
+ // eslint-disable-next-line no-restricted-syntax
222
+ if (this.options.comment !== undefined) {
223
+ command.comment = this.options.comment;
224
+ }
225
+
226
+ return command;
227
+ }
228
+ }
229
+
230
+ function validateBufferSize(name: string, buffer: Uint8Array, maxBsonObjectSize: number) {
231
+ if (buffer.length > maxBsonObjectSize) {
232
+ throw new MongoInvalidArgumentError(
233
+ `Client bulk write operation ${name} of length ${buffer.length} exceeds the max bson object size of ${maxBsonObjectSize}`
234
+ );
92
235
  }
93
236
  }
94
237
 
@@ -105,13 +248,15 @@ interface ClientInsertOperation {
105
248
  * @returns the operation.
106
249
  */
107
250
  export const buildInsertOneOperation = (
108
- model: ClientInsertOneModel,
109
- index: number
251
+ model: ClientInsertOneModel<Document>,
252
+ index: number,
253
+ pkFactory: PkFactory
110
254
  ): ClientInsertOperation => {
111
255
  const document: ClientInsertOperation = {
112
256
  insert: index,
113
257
  document: model.document
114
258
  };
259
+ document.document._id = model.document._id ?? pkFactory.createPk();
115
260
  return document;
116
261
  };
117
262
 
@@ -130,7 +275,10 @@ export interface ClientDeleteOperation {
130
275
  * @param index - The namespace index.
131
276
  * @returns the operation.
132
277
  */
133
- export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: number): Document => {
278
+ export const buildDeleteOneOperation = (
279
+ model: ClientDeleteOneModel<Document>,
280
+ index: number
281
+ ): Document => {
134
282
  return createDeleteOperation(model, index, false);
135
283
  };
136
284
 
@@ -140,7 +288,10 @@ export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: numb
140
288
  * @param index - The namespace index.
141
289
  * @returns the operation.
142
290
  */
143
- export const buildDeleteManyOperation = (model: ClientDeleteManyModel, index: number): Document => {
291
+ export const buildDeleteManyOperation = (
292
+ model: ClientDeleteManyModel<Document>,
293
+ index: number
294
+ ): Document => {
144
295
  return createDeleteOperation(model, index, true);
145
296
  };
146
297
 
@@ -148,7 +299,7 @@ export const buildDeleteManyOperation = (model: ClientDeleteManyModel, index: nu
148
299
  * Creates a delete operation based on the parameters.
149
300
  */
150
301
  function createDeleteOperation(
151
- model: ClientDeleteOneModel | ClientDeleteManyModel,
302
+ model: ClientDeleteOneModel<Document> | ClientDeleteManyModel<Document>,
152
303
  index: number,
153
304
  multi: boolean
154
305
  ): ClientDeleteOperation {
@@ -175,6 +326,7 @@ export interface ClientUpdateOperation {
175
326
  hint?: Hint;
176
327
  upsert?: boolean;
177
328
  arrayFilters?: Document[];
329
+ collation?: CollationOptions;
178
330
  }
179
331
 
180
332
  /**
@@ -184,7 +336,7 @@ export interface ClientUpdateOperation {
184
336
  * @returns the operation.
185
337
  */
186
338
  export const buildUpdateOneOperation = (
187
- model: ClientUpdateOneModel,
339
+ model: ClientUpdateOneModel<Document>,
188
340
  index: number
189
341
  ): ClientUpdateOperation => {
190
342
  return createUpdateOperation(model, index, false);
@@ -197,20 +349,37 @@ export const buildUpdateOneOperation = (
197
349
  * @returns the operation.
198
350
  */
199
351
  export const buildUpdateManyOperation = (
200
- model: ClientUpdateManyModel,
352
+ model: ClientUpdateManyModel<Document>,
201
353
  index: number
202
354
  ): ClientUpdateOperation => {
203
355
  return createUpdateOperation(model, index, true);
204
356
  };
205
357
 
358
+ /**
359
+ * Validate the update document.
360
+ * @param update - The update document.
361
+ */
362
+ function validateUpdate(update: Document) {
363
+ if (!hasAtomicOperators(update)) {
364
+ throw new MongoAPIError(
365
+ 'Client bulk write update models must only contain atomic modifiers (start with $) and must not be empty.'
366
+ );
367
+ }
368
+ }
369
+
206
370
  /**
207
371
  * Creates a delete operation based on the parameters.
208
372
  */
209
373
  function createUpdateOperation(
210
- model: ClientUpdateOneModel | ClientUpdateManyModel,
374
+ model: ClientUpdateOneModel<Document> | ClientUpdateManyModel<Document>,
211
375
  index: number,
212
376
  multi: boolean
213
377
  ): ClientUpdateOperation {
378
+ // Update documents provided in UpdateOne and UpdateMany write models are
379
+ // required only to contain atomic modifiers (i.e. keys that start with "$").
380
+ // Drivers MUST throw an error if an update document is empty or if the
381
+ // document's first key does not start with "$".
382
+ validateUpdate(model.update);
214
383
  const document: ClientUpdateOperation = {
215
384
  update: index,
216
385
  multi: multi,
@@ -226,6 +395,9 @@ function createUpdateOperation(
226
395
  if (model.arrayFilters) {
227
396
  document.arrayFilters = model.arrayFilters;
228
397
  }
398
+ if (model.collation) {
399
+ document.collation = model.collation;
400
+ }
229
401
  return document;
230
402
  }
231
403
 
@@ -237,6 +409,7 @@ export interface ClientReplaceOneOperation {
237
409
  updateMods: WithoutId<Document>;
238
410
  hint?: Hint;
239
411
  upsert?: boolean;
412
+ collation?: CollationOptions;
240
413
  }
241
414
 
242
415
  /**
@@ -246,9 +419,15 @@ export interface ClientReplaceOneOperation {
246
419
  * @returns the operation.
247
420
  */
248
421
  export const buildReplaceOneOperation = (
249
- model: ClientReplaceOneModel,
422
+ model: ClientReplaceOneModel<Document>,
250
423
  index: number
251
424
  ): ClientReplaceOneOperation => {
425
+ if (hasAtomicOperators(model.replacement)) {
426
+ throw new MongoAPIError(
427
+ 'Client bulk write replace models must not contain atomic modifiers (start with $) and must not be empty.'
428
+ );
429
+ }
430
+
252
431
  const document: ClientReplaceOneOperation = {
253
432
  update: index,
254
433
  multi: false,
@@ -261,14 +440,21 @@ export const buildReplaceOneOperation = (
261
440
  if (model.upsert) {
262
441
  document.upsert = model.upsert;
263
442
  }
443
+ if (model.collation) {
444
+ document.collation = model.collation;
445
+ }
264
446
  return document;
265
447
  };
266
448
 
267
449
  /** @internal */
268
- export function buildOperation(model: AnyClientBulkWriteModel, index: number): Document {
450
+ export function buildOperation(
451
+ model: AnyClientBulkWriteModel<Document>,
452
+ index: number,
453
+ pkFactory: PkFactory
454
+ ): Document {
269
455
  switch (model.name) {
270
456
  case 'insertOne':
271
- return buildInsertOneOperation(model, index);
457
+ return buildInsertOneOperation(model, index, pkFactory);
272
458
  case 'deleteOne':
273
459
  return buildDeleteOneOperation(model, index);
274
460
  case 'deleteMany':