Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-6350): add typescript support to client bulkWrite API #4257

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ export type {
export type {
AnyClientBulkWriteModel,
ClientBulkWriteError,
ClientBulkWriteModel,
ClientBulkWriteOptions,
ClientBulkWriteResult,
ClientDeleteManyModel,
Expand Down
16 changes: 10 additions & 6 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
} from './mongo_logger';
import { TypedEventEmitter } from './mongo_types';
import {
type AnyClientBulkWriteModel,
type ClientBulkWriteModel,
type ClientBulkWriteOptions,
type ClientBulkWriteResult
} from './operations/client_bulk_write/common';
Expand Down Expand Up @@ -331,7 +331,6 @@ export type MongoClientEvents = Pick<TopologyEvents, (typeof MONGO_CLIENT_EVENTS
};

/** @internal */

const kOptions = Symbol('options');

/**
Expand Down Expand Up @@ -489,16 +488,21 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
* @param options - The client bulk write options.
* @returns A ClientBulkWriteResult for acknowledged writes and ok: 1 for unacknowledged writes.
*/
async bulkWrite(
models: AnyClientBulkWriteModel[],
async bulkWrite<SchemaMap extends Record<string, Document> = Record<string, Document>>(
models: ReadonlyArray<ClientBulkWriteModel<SchemaMap>>,
options?: ClientBulkWriteOptions
): Promise<ClientBulkWriteResult | { ok: 1 }> {
): Promise<ClientBulkWriteResult> {
if (this.autoEncrypter) {
throw new MongoInvalidArgumentError(
'MongoClient bulkWrite does not currently support automatic encryption.'
);
}
return await new ClientBulkWriteExecutor(this, models, options).execute();
// We do not need schema type information past this point ("as any" is fine)
return await new ClientBulkWriteExecutor(
this,
models as any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the executor just take a ReadonlyArray<ClientBulkWriteModel<Document>> instead? I think ReadonlyArray<ClientBulkWriteModel> will always be assignable and we won't need as any.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

models still isn't assignable

Argument of type 'readonly ClientBulkWriteModel<SchemaMap>[]' is not assignable to parameter of type 'readonly ClientBulkWriteModel<Document>[]'.
  Type 'ClientBulkWriteModel<SchemaMap>' is not assignable to type 'ClientBulkWriteModel<Document>'.
    Type 'ClientUpdateOneModel<SchemaMap[keyof SchemaMap]> & { namespace: keyof SchemaMap; }' is not assignable to type 'ClientBulkWriteModel<Document>'.
      Type 'ClientUpdateOneModel<SchemaMap[keyof SchemaMap]> & { namespace: keyof SchemaMap; }' is not assignable to type 'ClientUpdateOneModel<any> & { namespace: string; }'.
        Types of property 'update' are incompatible.
          Type 'Document[] | UpdateFilter<SchemaMap[keyof SchemaMap]>' is not assignable to type 'Document[] | UpdateFilter<any>'.
            Type 'UpdateFilter<SchemaMap[keyof SchemaMap]>' is not assignable to type 'Document[] | UpdateFilter<any>'.
              Type 'UpdateFilter<SchemaMap[keyof SchemaMap]>' is missing the following properties from type 'Document[]': length, pop, push, concat, and 29 more.

I think passing down the parameterization is the "correct" way but it has no value because we don't edit or depend on any schema type information.

resolveOptions(this, options)
).execute();
}

/**
Expand Down
28 changes: 17 additions & 11 deletions src/operations/client_bulk_write/command_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const MESSAGE_OVERHEAD_BYTES = 1000;

/** @internal */
export class ClientBulkWriteCommandBuilder {
models: AnyClientBulkWriteModel[];
models: ReadonlyArray<AnyClientBulkWriteModel<Document>>;
options: ClientBulkWriteOptions;
pkFactory: PkFactory;
/** The current index in the models array that is being processed. */
Expand All @@ -53,7 +53,7 @@ export class ClientBulkWriteCommandBuilder {
* @param models - The client write models.
*/
constructor(
models: AnyClientBulkWriteModel[],
models: ReadonlyArray<AnyClientBulkWriteModel<Document>>,
options: ClientBulkWriteOptions,
pkFactory?: PkFactory
) {
Expand Down Expand Up @@ -248,7 +248,7 @@ interface ClientInsertOperation {
* @returns the operation.
*/
export const buildInsertOneOperation = (
model: ClientInsertOneModel,
model: ClientInsertOneModel<Document>,
index: number,
pkFactory: PkFactory
): ClientInsertOperation => {
Expand All @@ -275,7 +275,10 @@ export interface ClientDeleteOperation {
* @param index - The namespace index.
* @returns the operation.
*/
export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: number): Document => {
export const buildDeleteOneOperation = (
model: ClientDeleteOneModel<Document>,
index: number
): Document => {
return createDeleteOperation(model, index, false);
};

Expand All @@ -285,15 +288,18 @@ export const buildDeleteOneOperation = (model: ClientDeleteOneModel, index: numb
* @param index - The namespace index.
* @returns the operation.
*/
export const buildDeleteManyOperation = (model: ClientDeleteManyModel, index: number): Document => {
export const buildDeleteManyOperation = (
model: ClientDeleteManyModel<Document>,
index: number
): Document => {
return createDeleteOperation(model, index, true);
};

/**
* Creates a delete operation based on the parameters.
*/
function createDeleteOperation(
model: ClientDeleteOneModel | ClientDeleteManyModel,
model: ClientDeleteOneModel<Document> | ClientDeleteManyModel<Document>,
index: number,
multi: boolean
): ClientDeleteOperation {
Expand Down Expand Up @@ -330,7 +336,7 @@ export interface ClientUpdateOperation {
* @returns the operation.
*/
export const buildUpdateOneOperation = (
model: ClientUpdateOneModel,
model: ClientUpdateOneModel<Document>,
index: number
): ClientUpdateOperation => {
return createUpdateOperation(model, index, false);
Expand All @@ -343,7 +349,7 @@ export const buildUpdateOneOperation = (
* @returns the operation.
*/
export const buildUpdateManyOperation = (
model: ClientUpdateManyModel,
model: ClientUpdateManyModel<Document>,
index: number
): ClientUpdateOperation => {
return createUpdateOperation(model, index, true);
Expand All @@ -365,7 +371,7 @@ function validateUpdate(update: Document) {
* Creates a delete operation based on the parameters.
*/
function createUpdateOperation(
model: ClientUpdateOneModel | ClientUpdateManyModel,
model: ClientUpdateOneModel<Document> | ClientUpdateManyModel<Document>,
index: number,
multi: boolean
): ClientUpdateOperation {
Expand Down Expand Up @@ -413,7 +419,7 @@ export interface ClientReplaceOneOperation {
* @returns the operation.
*/
export const buildReplaceOneOperation = (
model: ClientReplaceOneModel,
model: ClientReplaceOneModel<Document>,
index: number
): ClientReplaceOneOperation => {
if (hasAtomicOperators(model.replacement)) {
Expand Down Expand Up @@ -442,7 +448,7 @@ export const buildReplaceOneOperation = (

/** @internal */
export function buildOperation(
model: AnyClientBulkWriteModel,
model: AnyClientBulkWriteModel<Document>,
index: number,
pkFactory: PkFactory
): Document {
Expand Down
102 changes: 71 additions & 31 deletions src/operations/client_bulk_write/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,55 +27,62 @@ export interface ClientBulkWriteOptions extends CommandOperationOptions {

/** @public */
export interface ClientWriteModel {
/** The namespace for the write. */
/**
* The namespace for the write.
*
* A namespace is a combination of the database name and the name of the collection: `<database-name>.<collection>`.
* All documents belong to a namespace.
*
* @see https://www.mongodb.com/docs/manual/reference/limits/#std-label-faq-dev-namespace
*/
namespace: string;
}

/** @public */
export interface ClientInsertOneModel extends ClientWriteModel {
export interface ClientInsertOneModel<TSchema> extends ClientWriteModel {
name: 'insertOne';
/** The document to insert. */
document: OptionalId<Document>;
document: OptionalId<TSchema>;
}

/** @public */
export interface ClientDeleteOneModel extends ClientWriteModel {
export interface ClientDeleteOneModel<TSchema> extends ClientWriteModel {
name: 'deleteOne';
/**
* The filter used to determine if a document should be deleted.
* For a deleteOne operation, the first match is removed.
*/
filter: Filter<Document>;
filter: Filter<TSchema>;
/** Specifies a collation. */
collation?: CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: Hint;
}

/** @public */
export interface ClientDeleteManyModel extends ClientWriteModel {
export interface ClientDeleteManyModel<TSchema> extends ClientWriteModel {
name: 'deleteMany';
/**
* The filter used to determine if a document should be deleted.
* For a deleteMany operation, all matches are removed.
*/
filter: Filter<Document>;
filter: Filter<TSchema>;
/** Specifies a collation. */
collation?: CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
hint?: Hint;
}

/** @public */
export interface ClientReplaceOneModel extends ClientWriteModel {
export interface ClientReplaceOneModel<TSchema> extends ClientWriteModel {
name: 'replaceOne';
/**
* The filter used to determine if a document should be replaced.
* For a replaceOne operation, the first match is replaced.
*/
filter: Filter<Document>;
filter: Filter<TSchema>;
/** The document with which to replace the matched document. */
replacement: WithoutId<Document>;
replacement: WithoutId<TSchema>;
/** Specifies a collation. */
collation?: CollationOptions;
/** The index to use. If specified, then the query system will only consider plans using the hinted index. */
Expand All @@ -85,19 +92,19 @@ export interface ClientReplaceOneModel extends ClientWriteModel {
}

/** @public */
export interface ClientUpdateOneModel extends ClientWriteModel {
export interface ClientUpdateOneModel<TSchema> extends ClientWriteModel {
name: 'updateOne';
/**
* The filter used to determine if a document should be updated.
* For an updateOne operation, the first match is updated.
*/
filter: Filter<Document>;
filter: Filter<TSchema>;
/**
* The modifications to apply. The value can be either:
* UpdateFilter<Document> - A document that contains update operator expressions,
* Document[] - an aggregation pipeline.
*/
update: UpdateFilter<Document> | Document[];
update: UpdateFilter<TSchema> | Document[];
/** A set of filters specifying to which array elements an update should apply. */
arrayFilters?: Document[];
/** Specifies a collation. */
Expand All @@ -109,19 +116,19 @@ export interface ClientUpdateOneModel extends ClientWriteModel {
}

/** @public */
export interface ClientUpdateManyModel extends ClientWriteModel {
export interface ClientUpdateManyModel<TSchema> extends ClientWriteModel {
name: 'updateMany';
/**
* The filter used to determine if a document should be updated.
* For an updateMany operation, all matches are updated.
*/
filter: Filter<Document>;
filter: Filter<TSchema>;
/**
* The modifications to apply. The value can be either:
* UpdateFilter<Document> - A document that contains update operator expressions,
* Document[] - an aggregation pipeline.
*/
update: UpdateFilter<Document> | Document[];
update: UpdateFilter<TSchema> | Document[];
/** A set of filters specifying to which array elements an update should apply. */
arrayFilters?: Document[];
/** Specifies a collation. */
Expand All @@ -137,48 +144,81 @@ export interface ClientUpdateManyModel extends ClientWriteModel {
* to MongoClient#bulkWrite.
* @public
*/
export type AnyClientBulkWriteModel =
| ClientInsertOneModel
| ClientReplaceOneModel
| ClientUpdateOneModel
| ClientUpdateManyModel
| ClientDeleteOneModel
| ClientDeleteManyModel;
export type AnyClientBulkWriteModel<TSchema extends Document> =
| ClientInsertOneModel<TSchema>
| ClientReplaceOneModel<TSchema>
| ClientUpdateOneModel<TSchema>
| ClientUpdateManyModel<TSchema>
| ClientDeleteOneModel<TSchema>
| ClientDeleteManyModel<TSchema>;

/**
* A mapping of namespace strings to collections schemas.
* @public
*
* @example
* ```ts
* type MongoDBSchemas = {
* 'db.books': Book;
* 'db.authors': Author;
* }
*
* const model: ClientBulkWriteModel<MongoDBSchemas> = {
* namespace: 'db.books'
* name: 'insertOne',
* document: { title: 'Practical MongoDB Aggregations', authorName: 3 } // error `authorName` cannot be number
* };
* ```
*
* The type of the `namespace` field narrows other parts of the BulkWriteModel to use the correct schema for type assertions.
*
*/
export type ClientBulkWriteModel<
SchemaMap extends Record<string, Document> = Record<string, Document>
> = {
[Namespace in keyof SchemaMap]: AnyClientBulkWriteModel<SchemaMap[Namespace]> & {
namespace: Namespace;
};
}[keyof SchemaMap];

/** @public */
export interface ClientBulkWriteResult {
/**
* Whether the bulk write was acknowledged.
*/
readonly acknowledged: boolean;
/**
* The total number of documents inserted across all insert operations.
*/
insertedCount: number;
readonly insertedCount: number;
/**
* The total number of documents upserted across all update operations.
*/
upsertedCount: number;
readonly upsertedCount: number;
/**
* The total number of documents matched across all update operations.
*/
matchedCount: number;
readonly matchedCount: number;
/**
* The total number of documents modified across all update operations.
*/
modifiedCount: number;
readonly modifiedCount: number;
/**
* The total number of documents deleted across all delete operations.
*/
deletedCount: number;
readonly deletedCount: number;
/**
* The results of each individual insert operation that was successfully performed.
*/
insertResults?: Map<number, ClientInsertOneResult>;
readonly insertResults?: ReadonlyMap<number, ClientInsertOneResult>;
/**
* The results of each individual update operation that was successfully performed.
*/
updateResults?: Map<number, ClientUpdateResult>;
readonly updateResults?: ReadonlyMap<number, ClientUpdateResult>;
/**
* The results of each individual delete operation that was successfully performed.
*/
deleteResults?: Map<number, ClientDeleteResult>;
readonly deleteResults?: ReadonlyMap<number, ClientDeleteResult>;
}

/** @public */
Expand Down
Loading