Skip to content

Commit 30c61f2

Browse files
nbbeekendurran
andauthored
feat(NODE-6350): add typescript support to client bulkWrite API (#4257)
Co-authored-by: Durran Jordan <[email protected]>
1 parent e9e8bf5 commit 30c61f2

File tree

9 files changed

+500
-92
lines changed

9 files changed

+500
-92
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ export type {
479479
export type {
480480
AnyClientBulkWriteModel,
481481
ClientBulkWriteError,
482+
ClientBulkWriteModel,
482483
ClientBulkWriteOptions,
483484
ClientBulkWriteResult,
484485
ClientDeleteManyModel,

src/mongo_client.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
} from './mongo_logger';
3232
import { TypedEventEmitter } from './mongo_types';
3333
import {
34-
type AnyClientBulkWriteModel,
34+
type ClientBulkWriteModel,
3535
type ClientBulkWriteOptions,
3636
type ClientBulkWriteResult
3737
} from './operations/client_bulk_write/common';
@@ -331,7 +331,6 @@ export type MongoClientEvents = Pick<TopologyEvents, (typeof MONGO_CLIENT_EVENTS
331331
};
332332

333333
/** @internal */
334-
335334
const kOptions = Symbol('options');
336335

337336
/**
@@ -489,16 +488,21 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
489488
* @param options - The client bulk write options.
490489
* @returns A ClientBulkWriteResult for acknowledged writes and ok: 1 for unacknowledged writes.
491490
*/
492-
async bulkWrite(
493-
models: AnyClientBulkWriteModel[],
491+
async bulkWrite<SchemaMap extends Record<string, Document> = Record<string, Document>>(
492+
models: ReadonlyArray<ClientBulkWriteModel<SchemaMap>>,
494493
options?: ClientBulkWriteOptions
495-
): Promise<ClientBulkWriteResult | { ok: 1 }> {
494+
): Promise<ClientBulkWriteResult> {
496495
if (this.autoEncrypter) {
497496
throw new MongoInvalidArgumentError(
498497
'MongoClient bulkWrite does not currently support automatic encryption.'
499498
);
500499
}
501-
return await new ClientBulkWriteExecutor(this, models, options).execute();
500+
// We do not need schema type information past this point ("as any" is fine)
501+
return await new ClientBulkWriteExecutor(
502+
this,
503+
models as any,
504+
resolveOptions(this, options)
505+
).execute();
502506
}
503507

504508
/**

src/operations/client_bulk_write/command_builder.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const MESSAGE_OVERHEAD_BYTES = 1000;
3636

3737
/** @internal */
3838
export class ClientBulkWriteCommandBuilder {
39-
models: AnyClientBulkWriteModel[];
39+
models: ReadonlyArray<AnyClientBulkWriteModel<Document>>;
4040
options: ClientBulkWriteOptions;
4141
pkFactory: PkFactory;
4242
/** The current index in the models array that is being processed. */
@@ -53,7 +53,7 @@ export class ClientBulkWriteCommandBuilder {
5353
* @param models - The client write models.
5454
*/
5555
constructor(
56-
models: AnyClientBulkWriteModel[],
56+
models: ReadonlyArray<AnyClientBulkWriteModel<Document>>,
5757
options: ClientBulkWriteOptions,
5858
pkFactory?: PkFactory
5959
) {
@@ -248,7 +248,7 @@ interface ClientInsertOperation {
248248
* @returns the operation.
249249
*/
250250
export const buildInsertOneOperation = (
251-
model: ClientInsertOneModel,
251+
model: ClientInsertOneModel<Document>,
252252
index: number,
253253
pkFactory: PkFactory
254254
): ClientInsertOperation => {
@@ -275,7 +275,10 @@ export interface ClientDeleteOperation {
275275
* @param index - The namespace index.
276276
* @returns the operation.
277277
*/
278-
export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: number): Document => {
278+
export const buildDeleteOneOperation = (
279+
model: ClientDeleteOneModel<Document>,
280+
index: number
281+
): Document => {
279282
return createDeleteOperation(model, index, false);
280283
};
281284

@@ -285,15 +288,18 @@ export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: numb
285288
* @param index - The namespace index.
286289
* @returns the operation.
287290
*/
288-
export const buildDeleteManyOperation = (model: ClientDeleteManyModel, index: number): Document => {
291+
export const buildDeleteManyOperation = (
292+
model: ClientDeleteManyModel<Document>,
293+
index: number
294+
): Document => {
289295
return createDeleteOperation(model, index, true);
290296
};
291297

292298
/**
293299
* Creates a delete operation based on the parameters.
294300
*/
295301
function createDeleteOperation(
296-
model: ClientDeleteOneModel | ClientDeleteManyModel,
302+
model: ClientDeleteOneModel<Document> | ClientDeleteManyModel<Document>,
297303
index: number,
298304
multi: boolean
299305
): ClientDeleteOperation {
@@ -330,7 +336,7 @@ export interface ClientUpdateOperation {
330336
* @returns the operation.
331337
*/
332338
export const buildUpdateOneOperation = (
333-
model: ClientUpdateOneModel,
339+
model: ClientUpdateOneModel<Document>,
334340
index: number
335341
): ClientUpdateOperation => {
336342
return createUpdateOperation(model, index, false);
@@ -343,7 +349,7 @@ export const buildUpdateOneOperation = (
343349
* @returns the operation.
344350
*/
345351
export const buildUpdateManyOperation = (
346-
model: ClientUpdateManyModel,
352+
model: ClientUpdateManyModel<Document>,
347353
index: number
348354
): ClientUpdateOperation => {
349355
return createUpdateOperation(model, index, true);
@@ -365,7 +371,7 @@ function validateUpdate(update: Document) {
365371
* Creates a delete operation based on the parameters.
366372
*/
367373
function createUpdateOperation(
368-
model: ClientUpdateOneModel | ClientUpdateManyModel,
374+
model: ClientUpdateOneModel<Document> | ClientUpdateManyModel<Document>,
369375
index: number,
370376
multi: boolean
371377
): ClientUpdateOperation {
@@ -413,7 +419,7 @@ export interface ClientReplaceOneOperation {
413419
* @returns the operation.
414420
*/
415421
export const buildReplaceOneOperation = (
416-
model: ClientReplaceOneModel,
422+
model: ClientReplaceOneModel<Document>,
417423
index: number
418424
): ClientReplaceOneOperation => {
419425
if (hasAtomicOperators(model.replacement)) {
@@ -442,7 +448,7 @@ export const buildReplaceOneOperation = (
442448

443449
/** @internal */
444450
export function buildOperation(
445-
model: AnyClientBulkWriteModel,
451+
model: AnyClientBulkWriteModel<Document>,
446452
index: number,
447453
pkFactory: PkFactory
448454
): Document {

src/operations/client_bulk_write/common.ts

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,55 +27,62 @@ export interface ClientBulkWriteOptions extends CommandOperationOptions {
2727

2828
/** @public */
2929
export interface ClientWriteModel {
30-
/** The namespace for the write. */
30+
/**
31+
* The namespace for the write.
32+
*
33+
* A namespace is a combination of the database name and the name of the collection: `<database-name>.<collection>`.
34+
* All documents belong to a namespace.
35+
*
36+
* @see https://www.mongodb.com/docs/manual/reference/limits/#std-label-faq-dev-namespace
37+
*/
3138
namespace: string;
3239
}
3340

3441
/** @public */
35-
export interface ClientInsertOneModel extends ClientWriteModel {
42+
export interface ClientInsertOneModel<TSchema> extends ClientWriteModel {
3643
name: 'insertOne';
3744
/** The document to insert. */
38-
document: OptionalId<Document>;
45+
document: OptionalId<TSchema>;
3946
}
4047

4148
/** @public */
42-
export interface ClientDeleteOneModel extends ClientWriteModel {
49+
export interface ClientDeleteOneModel<TSchema> extends ClientWriteModel {
4350
name: 'deleteOne';
4451
/**
4552
* The filter used to determine if a document should be deleted.
4653
* For a deleteOne operation, the first match is removed.
4754
*/
48-
filter: Filter<Document>;
55+
filter: Filter<TSchema>;
4956
/** Specifies a collation. */
5057
collation?: CollationOptions;
5158
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
5259
hint?: Hint;
5360
}
5461

5562
/** @public */
56-
export interface ClientDeleteManyModel extends ClientWriteModel {
63+
export interface ClientDeleteManyModel<TSchema> extends ClientWriteModel {
5764
name: 'deleteMany';
5865
/**
5966
* The filter used to determine if a document should be deleted.
6067
* For a deleteMany operation, all matches are removed.
6168
*/
62-
filter: Filter<Document>;
69+
filter: Filter<TSchema>;
6370
/** Specifies a collation. */
6471
collation?: CollationOptions;
6572
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
6673
hint?: Hint;
6774
}
6875

6976
/** @public */
70-
export interface ClientReplaceOneModel extends ClientWriteModel {
77+
export interface ClientReplaceOneModel<TSchema> extends ClientWriteModel {
7178
name: 'replaceOne';
7279
/**
7380
* The filter used to determine if a document should be replaced.
7481
* For a replaceOne operation, the first match is replaced.
7582
*/
76-
filter: Filter<Document>;
83+
filter: Filter<TSchema>;
7784
/** The document with which to replace the matched document. */
78-
replacement: WithoutId<Document>;
85+
replacement: WithoutId<TSchema>;
7986
/** Specifies a collation. */
8087
collation?: CollationOptions;
8188
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
@@ -85,19 +92,19 @@ export interface ClientReplaceOneModel extends ClientWriteModel {
8592
}
8693

8794
/** @public */
88-
export interface ClientUpdateOneModel extends ClientWriteModel {
95+
export interface ClientUpdateOneModel<TSchema> extends ClientWriteModel {
8996
name: 'updateOne';
9097
/**
9198
* The filter used to determine if a document should be updated.
9299
* For an updateOne operation, the first match is updated.
93100
*/
94-
filter: Filter<Document>;
101+
filter: Filter<TSchema>;
95102
/**
96103
* The modifications to apply. The value can be either:
97104
* UpdateFilter<Document> - A document that contains update operator expressions,
98105
* Document[] - an aggregation pipeline.
99106
*/
100-
update: UpdateFilter<Document> | Document[];
107+
update: UpdateFilter<TSchema> | Document[];
101108
/** A set of filters specifying to which array elements an update should apply. */
102109
arrayFilters?: Document[];
103110
/** Specifies a collation. */
@@ -109,19 +116,19 @@ export interface ClientUpdateOneModel extends ClientWriteModel {
109116
}
110117

111118
/** @public */
112-
export interface ClientUpdateManyModel extends ClientWriteModel {
119+
export interface ClientUpdateManyModel<TSchema> extends ClientWriteModel {
113120
name: 'updateMany';
114121
/**
115122
* The filter used to determine if a document should be updated.
116123
* For an updateMany operation, all matches are updated.
117124
*/
118-
filter: Filter<Document>;
125+
filter: Filter<TSchema>;
119126
/**
120127
* The modifications to apply. The value can be either:
121128
* UpdateFilter<Document> - A document that contains update operator expressions,
122129
* Document[] - an aggregation pipeline.
123130
*/
124-
update: UpdateFilter<Document> | Document[];
131+
update: UpdateFilter<TSchema> | Document[];
125132
/** A set of filters specifying to which array elements an update should apply. */
126133
arrayFilters?: Document[];
127134
/** Specifies a collation. */
@@ -137,48 +144,81 @@ export interface ClientUpdateManyModel extends ClientWriteModel {
137144
* to MongoClient#bulkWrite.
138145
* @public
139146
*/
140-
export type AnyClientBulkWriteModel =
141-
| ClientInsertOneModel
142-
| ClientReplaceOneModel
143-
| ClientUpdateOneModel
144-
| ClientUpdateManyModel
145-
| ClientDeleteOneModel
146-
| ClientDeleteManyModel;
147+
export type AnyClientBulkWriteModel<TSchema extends Document> =
148+
| ClientInsertOneModel<TSchema>
149+
| ClientReplaceOneModel<TSchema>
150+
| ClientUpdateOneModel<TSchema>
151+
| ClientUpdateManyModel<TSchema>
152+
| ClientDeleteOneModel<TSchema>
153+
| ClientDeleteManyModel<TSchema>;
154+
155+
/**
156+
* A mapping of namespace strings to collections schemas.
157+
* @public
158+
*
159+
* @example
160+
* ```ts
161+
* type MongoDBSchemas = {
162+
* 'db.books': Book;
163+
* 'db.authors': Author;
164+
* }
165+
*
166+
* const model: ClientBulkWriteModel<MongoDBSchemas> = {
167+
* namespace: 'db.books'
168+
* name: 'insertOne',
169+
* document: { title: 'Practical MongoDB Aggregations', authorName: 3 } // error `authorName` cannot be number
170+
* };
171+
* ```
172+
*
173+
* The type of the `namespace` field narrows other parts of the BulkWriteModel to use the correct schema for type assertions.
174+
*
175+
*/
176+
export type ClientBulkWriteModel<
177+
SchemaMap extends Record<string, Document> = Record<string, Document>
178+
> = {
179+
[Namespace in keyof SchemaMap]: AnyClientBulkWriteModel<SchemaMap[Namespace]> & {
180+
namespace: Namespace;
181+
};
182+
}[keyof SchemaMap];
147183

148184
/** @public */
149185
export interface ClientBulkWriteResult {
186+
/**
187+
* Whether the bulk write was acknowledged.
188+
*/
189+
readonly acknowledged: boolean;
150190
/**
151191
* The total number of documents inserted across all insert operations.
152192
*/
153-
insertedCount: number;
193+
readonly insertedCount: number;
154194
/**
155195
* The total number of documents upserted across all update operations.
156196
*/
157-
upsertedCount: number;
197+
readonly upsertedCount: number;
158198
/**
159199
* The total number of documents matched across all update operations.
160200
*/
161-
matchedCount: number;
201+
readonly matchedCount: number;
162202
/**
163203
* The total number of documents modified across all update operations.
164204
*/
165-
modifiedCount: number;
205+
readonly modifiedCount: number;
166206
/**
167207
* The total number of documents deleted across all delete operations.
168208
*/
169-
deletedCount: number;
209+
readonly deletedCount: number;
170210
/**
171211
* The results of each individual insert operation that was successfully performed.
172212
*/
173-
insertResults?: Map<number, ClientInsertOneResult>;
213+
readonly insertResults?: ReadonlyMap<number, ClientInsertOneResult>;
174214
/**
175215
* The results of each individual update operation that was successfully performed.
176216
*/
177-
updateResults?: Map<number, ClientUpdateResult>;
217+
readonly updateResults?: ReadonlyMap<number, ClientUpdateResult>;
178218
/**
179219
* The results of each individual delete operation that was successfully performed.
180220
*/
181-
deleteResults?: Map<number, ClientDeleteResult>;
221+
readonly deleteResults?: ReadonlyMap<number, ClientDeleteResult>;
182222
}
183223

184224
/** @public */

0 commit comments

Comments
 (0)