Skip to content

Commit 8b309e2

Browse files
nbbeekendurran
authored andcommitted
feat(NODE-6350): add typescript support to client bulkWrite API
1 parent 7fde8dd commit 8b309e2

File tree

6 files changed

+338
-42
lines changed

6 files changed

+338
-42
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: 5 additions & 5 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,17 @@ 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
495494
): Promise<ClientBulkWriteResult | { ok: 1 }> {
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(this, models as any, options).execute();
502502
}
503503

504504
/**

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: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,50 +32,50 @@ export interface ClientWriteModel {
3232
}
3333

3434
/** @public */
35-
export interface ClientInsertOneModel extends ClientWriteModel {
35+
export interface ClientInsertOneModel<TSchema> extends ClientWriteModel {
3636
name: 'insertOne';
3737
/** The document to insert. */
38-
document: OptionalId<Document>;
38+
document: OptionalId<TSchema>;
3939
}
4040

4141
/** @public */
42-
export interface ClientDeleteOneModel extends ClientWriteModel {
42+
export interface ClientDeleteOneModel<TSchema> extends ClientWriteModel {
4343
name: 'deleteOne';
4444
/**
4545
* The filter used to determine if a document should be deleted.
4646
* For a deleteOne operation, the first match is removed.
4747
*/
48-
filter: Filter<Document>;
48+
filter: Filter<TSchema>;
4949
/** Specifies a collation. */
5050
collation?: CollationOptions;
5151
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
5252
hint?: Hint;
5353
}
5454

5555
/** @public */
56-
export interface ClientDeleteManyModel extends ClientWriteModel {
56+
export interface ClientDeleteManyModel<TSchema> extends ClientWriteModel {
5757
name: 'deleteMany';
5858
/**
5959
* The filter used to determine if a document should be deleted.
6060
* For a deleteMany operation, all matches are removed.
6161
*/
62-
filter: Filter<Document>;
62+
filter: Filter<TSchema>;
6363
/** Specifies a collation. */
6464
collation?: CollationOptions;
6565
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
6666
hint?: Hint;
6767
}
6868

6969
/** @public */
70-
export interface ClientReplaceOneModel extends ClientWriteModel {
70+
export interface ClientReplaceOneModel<TSchema> extends ClientWriteModel {
7171
name: 'replaceOne';
7272
/**
7373
* The filter used to determine if a document should be replaced.
7474
* For a replaceOne operation, the first match is replaced.
7575
*/
76-
filter: Filter<Document>;
76+
filter: Filter<TSchema>;
7777
/** The document with which to replace the matched document. */
78-
replacement: WithoutId<Document>;
78+
replacement: WithoutId<TSchema>;
7979
/** Specifies a collation. */
8080
collation?: CollationOptions;
8181
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
@@ -85,19 +85,19 @@ export interface ClientReplaceOneModel extends ClientWriteModel {
8585
}
8686

8787
/** @public */
88-
export interface ClientUpdateOneModel extends ClientWriteModel {
88+
export interface ClientUpdateOneModel<TSchema> extends ClientWriteModel {
8989
name: 'updateOne';
9090
/**
9191
* The filter used to determine if a document should be updated.
9292
* For an updateOne operation, the first match is updated.
9393
*/
94-
filter: Filter<Document>;
94+
filter: Filter<TSchema>;
9595
/**
9696
* The modifications to apply. The value can be either:
9797
* UpdateFilter<Document> - A document that contains update operator expressions,
9898
* Document[] - an aggregation pipeline.
9999
*/
100-
update: UpdateFilter<Document> | Document[];
100+
update: UpdateFilter<TSchema> | Document[];
101101
/** A set of filters specifying to which array elements an update should apply. */
102102
arrayFilters?: Document[];
103103
/** Specifies a collation. */
@@ -109,19 +109,19 @@ export interface ClientUpdateOneModel extends ClientWriteModel {
109109
}
110110

111111
/** @public */
112-
export interface ClientUpdateManyModel extends ClientWriteModel {
112+
export interface ClientUpdateManyModel<TSchema> extends ClientWriteModel {
113113
name: 'updateMany';
114114
/**
115115
* The filter used to determine if a document should be updated.
116116
* For an updateMany operation, all matches are updated.
117117
*/
118-
filter: Filter<Document>;
118+
filter: Filter<TSchema>;
119119
/**
120120
* The modifications to apply. The value can be either:
121121
* UpdateFilter<Document> - A document that contains update operator expressions,
122122
* Document[] - an aggregation pipeline.
123123
*/
124-
update: UpdateFilter<Document> | Document[];
124+
update: UpdateFilter<TSchema> | Document[];
125125
/** A set of filters specifying to which array elements an update should apply. */
126126
arrayFilters?: Document[];
127127
/** Specifies a collation. */
@@ -137,13 +137,42 @@ export interface ClientUpdateManyModel extends ClientWriteModel {
137137
* to MongoClient#bulkWrite.
138138
* @public
139139
*/
140-
export type AnyClientBulkWriteModel =
141-
| ClientInsertOneModel
142-
| ClientReplaceOneModel
143-
| ClientUpdateOneModel
144-
| ClientUpdateManyModel
145-
| ClientDeleteOneModel
146-
| ClientDeleteManyModel;
140+
export type AnyClientBulkWriteModel<TSchema extends Document> =
141+
| ClientInsertOneModel<TSchema>
142+
| ClientReplaceOneModel<TSchema>
143+
| ClientUpdateOneModel<TSchema>
144+
| ClientUpdateManyModel<TSchema>
145+
| ClientDeleteOneModel<TSchema>
146+
| ClientDeleteManyModel<TSchema>;
147+
148+
/**
149+
* Take a Typescript type that maps namespaces to schema types.
150+
* @public
151+
*
152+
* @example
153+
* ```ts
154+
* type MongoDBSchemas = {
155+
* 'db.books': Book;
156+
* 'db.authors': Author;
157+
* }
158+
*
159+
* const model: ClientBulkWriteModel<MongoDBSchemas> = {
160+
* namespace: 'db.books'
161+
* name: 'insertOne',
162+
* document: { title: 'Practical MongoDB Aggregations', authorName: 3 } // error `authorName` cannot be number
163+
* };
164+
* ```
165+
*
166+
* The type of the `namespace` field narrows other parts of the BulkWriteModel to use the correct schema for type assertions.
167+
*
168+
*/
169+
export type ClientBulkWriteModel<
170+
SchemaMap extends Record<string, Document> = Record<string, Document>
171+
> = {
172+
[Namespace in keyof SchemaMap]: AnyClientBulkWriteModel<SchemaMap[Namespace]> & {
173+
namespace: Namespace;
174+
};
175+
}[keyof SchemaMap];
147176

148177
/** @public */
149178
export interface ClientBulkWriteResult {

src/operations/client_bulk_write/executor.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type Document } from 'bson';
2+
13
import { ClientBulkWriteCursor } from '../../cursor/client_bulk_write_cursor';
24
import {
35
MongoClientBulkWriteError,
@@ -22,9 +24,9 @@ import { ClientBulkWriteResultsMerger } from './results_merger';
2224
* @internal
2325
*/
2426
export class ClientBulkWriteExecutor {
25-
client: MongoClient;
26-
options: ClientBulkWriteOptions;
27-
operations: AnyClientBulkWriteModel[];
27+
private readonly client: MongoClient;
28+
private readonly options: ClientBulkWriteOptions;
29+
private readonly operations: ReadonlyArray<AnyClientBulkWriteModel<Document>>;
2830

2931
/**
3032
* Instantiate the executor.
@@ -34,7 +36,7 @@ export class ClientBulkWriteExecutor {
3436
*/
3537
constructor(
3638
client: MongoClient,
37-
operations: AnyClientBulkWriteModel[],
39+
operations: ReadonlyArray<AnyClientBulkWriteModel<Document>>,
3840
options?: ClientBulkWriteOptions
3941
) {
4042
if (operations.length === 0) {

0 commit comments

Comments
 (0)