From 8b309e20884c2cb6d523aadf881ba3f919e0e684 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 30 Sep 2024 12:33:40 -0400 Subject: [PATCH 01/10] feat(NODE-6350): add typescript support to client bulkWrite API --- src/index.ts | 1 + src/mongo_client.ts | 10 +- .../client_bulk_write/command_builder.ts | 28 +- src/operations/client_bulk_write/common.ts | 73 +++-- src/operations/client_bulk_write/executor.ts | 10 +- test/types/client_bulk_write.test-d.ts | 258 ++++++++++++++++++ 6 files changed, 338 insertions(+), 42 deletions(-) create mode 100644 test/types/client_bulk_write.test-d.ts diff --git a/src/index.ts b/src/index.ts index 9538ce1d5cc..39d4df719de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -479,6 +479,7 @@ export type { export type { AnyClientBulkWriteModel, ClientBulkWriteError, + ClientBulkWriteModel, ClientBulkWriteOptions, ClientBulkWriteResult, ClientDeleteManyModel, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 49201910362..5658ae40b6b 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -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'; @@ -331,7 +331,6 @@ export type MongoClientEvents = Pick 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 = Record>( + models: ReadonlyArray>, options?: ClientBulkWriteOptions ): Promise { if (this.autoEncrypter) { @@ -498,7 +497,8 @@ export class MongoClient extends TypedEventEmitter implements '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, options).execute(); } /** diff --git a/src/operations/client_bulk_write/command_builder.ts b/src/operations/client_bulk_write/command_builder.ts index f85a91b16b5..e4ad79d5e76 100644 --- a/src/operations/client_bulk_write/command_builder.ts +++ b/src/operations/client_bulk_write/command_builder.ts @@ -36,7 +36,7 @@ const MESSAGE_OVERHEAD_BYTES = 1000; /** @internal */ export class ClientBulkWriteCommandBuilder { - models: AnyClientBulkWriteModel[]; + models: ReadonlyArray>; options: ClientBulkWriteOptions; pkFactory: PkFactory; /** The current index in the models array that is being processed. */ @@ -53,7 +53,7 @@ export class ClientBulkWriteCommandBuilder { * @param models - The client write models. */ constructor( - models: AnyClientBulkWriteModel[], + models: ReadonlyArray>, options: ClientBulkWriteOptions, pkFactory?: PkFactory ) { @@ -248,7 +248,7 @@ interface ClientInsertOperation { * @returns the operation. */ export const buildInsertOneOperation = ( - model: ClientInsertOneModel, + model: ClientInsertOneModel, index: number, pkFactory: PkFactory ): ClientInsertOperation => { @@ -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, + index: number +): Document => { return createDeleteOperation(model, index, false); }; @@ -285,7 +288,10 @@ 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, + index: number +): Document => { return createDeleteOperation(model, index, true); }; @@ -293,7 +299,7 @@ export const buildDeleteManyOperation = (model: ClientDeleteManyModel, index: nu * Creates a delete operation based on the parameters. */ function createDeleteOperation( - model: ClientDeleteOneModel | ClientDeleteManyModel, + model: ClientDeleteOneModel | ClientDeleteManyModel, index: number, multi: boolean ): ClientDeleteOperation { @@ -330,7 +336,7 @@ export interface ClientUpdateOperation { * @returns the operation. */ export const buildUpdateOneOperation = ( - model: ClientUpdateOneModel, + model: ClientUpdateOneModel, index: number ): ClientUpdateOperation => { return createUpdateOperation(model, index, false); @@ -343,7 +349,7 @@ export const buildUpdateOneOperation = ( * @returns the operation. */ export const buildUpdateManyOperation = ( - model: ClientUpdateManyModel, + model: ClientUpdateManyModel, index: number ): ClientUpdateOperation => { return createUpdateOperation(model, index, true); @@ -365,7 +371,7 @@ function validateUpdate(update: Document) { * Creates a delete operation based on the parameters. */ function createUpdateOperation( - model: ClientUpdateOneModel | ClientUpdateManyModel, + model: ClientUpdateOneModel | ClientUpdateManyModel, index: number, multi: boolean ): ClientUpdateOperation { @@ -413,7 +419,7 @@ export interface ClientReplaceOneOperation { * @returns the operation. */ export const buildReplaceOneOperation = ( - model: ClientReplaceOneModel, + model: ClientReplaceOneModel, index: number ): ClientReplaceOneOperation => { if (hasAtomicOperators(model.replacement)) { @@ -442,7 +448,7 @@ export const buildReplaceOneOperation = ( /** @internal */ export function buildOperation( - model: AnyClientBulkWriteModel, + model: AnyClientBulkWriteModel, index: number, pkFactory: PkFactory ): Document { diff --git a/src/operations/client_bulk_write/common.ts b/src/operations/client_bulk_write/common.ts index 11234cf4eac..d6dba2e2cd5 100644 --- a/src/operations/client_bulk_write/common.ts +++ b/src/operations/client_bulk_write/common.ts @@ -32,20 +32,20 @@ export interface ClientWriteModel { } /** @public */ -export interface ClientInsertOneModel extends ClientWriteModel { +export interface ClientInsertOneModel extends ClientWriteModel { name: 'insertOne'; /** The document to insert. */ - document: OptionalId; + document: OptionalId; } /** @public */ -export interface ClientDeleteOneModel extends ClientWriteModel { +export interface ClientDeleteOneModel 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; + filter: Filter; /** Specifies a collation. */ collation?: CollationOptions; /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ @@ -53,13 +53,13 @@ export interface ClientDeleteOneModel extends ClientWriteModel { } /** @public */ -export interface ClientDeleteManyModel extends ClientWriteModel { +export interface ClientDeleteManyModel 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; + filter: Filter; /** Specifies a collation. */ collation?: CollationOptions; /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ @@ -67,15 +67,15 @@ export interface ClientDeleteManyModel extends ClientWriteModel { } /** @public */ -export interface ClientReplaceOneModel extends ClientWriteModel { +export interface ClientReplaceOneModel 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; + filter: Filter; /** The document with which to replace the matched document. */ - replacement: WithoutId; + replacement: WithoutId; /** Specifies a collation. */ collation?: CollationOptions; /** 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 { } /** @public */ -export interface ClientUpdateOneModel extends ClientWriteModel { +export interface ClientUpdateOneModel 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; + filter: Filter; /** * The modifications to apply. The value can be either: * UpdateFilter - A document that contains update operator expressions, * Document[] - an aggregation pipeline. */ - update: UpdateFilter | Document[]; + update: UpdateFilter | Document[]; /** A set of filters specifying to which array elements an update should apply. */ arrayFilters?: Document[]; /** Specifies a collation. */ @@ -109,19 +109,19 @@ export interface ClientUpdateOneModel extends ClientWriteModel { } /** @public */ -export interface ClientUpdateManyModel extends ClientWriteModel { +export interface ClientUpdateManyModel 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; + filter: Filter; /** * The modifications to apply. The value can be either: * UpdateFilter - A document that contains update operator expressions, * Document[] - an aggregation pipeline. */ - update: UpdateFilter | Document[]; + update: UpdateFilter | Document[]; /** A set of filters specifying to which array elements an update should apply. */ arrayFilters?: Document[]; /** Specifies a collation. */ @@ -137,13 +137,42 @@ export interface ClientUpdateManyModel extends ClientWriteModel { * to MongoClient#bulkWrite. * @public */ -export type AnyClientBulkWriteModel = - | ClientInsertOneModel - | ClientReplaceOneModel - | ClientUpdateOneModel - | ClientUpdateManyModel - | ClientDeleteOneModel - | ClientDeleteManyModel; +export type AnyClientBulkWriteModel = + | ClientInsertOneModel + | ClientReplaceOneModel + | ClientUpdateOneModel + | ClientUpdateManyModel + | ClientDeleteOneModel + | ClientDeleteManyModel; + +/** + * Take a Typescript type that maps namespaces to schema types. + * @public + * + * @example + * ```ts + * type MongoDBSchemas = { + * 'db.books': Book; + * 'db.authors': Author; + * } + * + * const model: ClientBulkWriteModel = { + * 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 = Record +> = { + [Namespace in keyof SchemaMap]: AnyClientBulkWriteModel & { + namespace: Namespace; + }; +}[keyof SchemaMap]; /** @public */ export interface ClientBulkWriteResult { diff --git a/src/operations/client_bulk_write/executor.ts b/src/operations/client_bulk_write/executor.ts index 7475fcdab20..3b89065886c 100644 --- a/src/operations/client_bulk_write/executor.ts +++ b/src/operations/client_bulk_write/executor.ts @@ -1,3 +1,5 @@ +import { type Document } from 'bson'; + import { ClientBulkWriteCursor } from '../../cursor/client_bulk_write_cursor'; import { MongoClientBulkWriteError, @@ -22,9 +24,9 @@ import { ClientBulkWriteResultsMerger } from './results_merger'; * @internal */ export class ClientBulkWriteExecutor { - client: MongoClient; - options: ClientBulkWriteOptions; - operations: AnyClientBulkWriteModel[]; + private readonly client: MongoClient; + private readonly options: ClientBulkWriteOptions; + private readonly operations: ReadonlyArray>; /** * Instantiate the executor. @@ -34,7 +36,7 @@ export class ClientBulkWriteExecutor { */ constructor( client: MongoClient, - operations: AnyClientBulkWriteModel[], + operations: ReadonlyArray>, options?: ClientBulkWriteOptions ) { if (operations.length === 0) { diff --git a/test/types/client_bulk_write.test-d.ts b/test/types/client_bulk_write.test-d.ts new file mode 100644 index 00000000000..95d58d3f48c --- /dev/null +++ b/test/types/client_bulk_write.test-d.ts @@ -0,0 +1,258 @@ +import { expectError, expectType } from 'tsd'; + +import { + type ClientBulkWriteModel, + type ClientDeleteManyModel, + type ClientDeleteOneModel, + type ClientInsertOneModel, + type ClientReplaceOneModel, + type ClientUpdateManyModel, + type ClientUpdateOneModel, + type Document, + type Filter, + type MongoClient, + type OptionalId, + type UpdateFilter, + type UUID, + type WithoutId +} from '../mongodb'; + +declare const client: MongoClient; +type Book = { title: string; released: Date }; +type Author = { name: string; published: number }; +type Store = { _id: UUID }; + +// Baseline check that schema modifies the following fields for each type. +expectType['document']>(null as unknown as OptionalId); + +expectType['filter']>(null as unknown as Filter); +expectType['replacement']>(null as unknown as WithoutId); + +expectType['filter']>(null as unknown as Filter); +expectType['update']>( + null as unknown as UpdateFilter | Document[] +); + +expectType['filter']>(null as unknown as Filter); +expectType['update']>( + null as unknown as UpdateFilter | Document[] +); + +expectType['filter']>(null as unknown as Filter); +expectType['filter']>(null as unknown as Filter); + +client.bulkWrite([]); // empty should always work + +// No schemas - all correct use +client.bulkWrite([ + { + namespace: 'db.authors', + name: 'insertOne', + document: { name: 'bob', published: 2 } + }, + { + namespace: 'db.authors', + name: 'replaceOne', + filter: { name: 'bob' }, + replacement: { name: 'ann', published: 2 } + }, + { + namespace: 'db.authors', + name: 'updateOne', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { + namespace: 'db.authors', + name: 'updateMany', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { namespace: 'db.authors', name: 'deleteOne', filter: {} }, + { namespace: 'db.authors', name: 'deleteMany', filter: {} } +]); + +// No schemas - incorrect use - random namespaces, no type checking +client.bulkWrite([ + { + namespace: 'db.whatever', + name: 'insertOne', + document: { name: 'bob', randomKey: 2 } + }, + { + namespace: 'db.change', + name: 'replaceOne', + filter: { name: 'bob' }, + replacement: { name: 2, published: 2 } + }, + { + namespace: 'db.it', + name: 'updateOne', + filter: { name: 'bob', published: new Date() }, + update: {} + }, + { + namespace: 'db.up', + name: 'updateMany', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { namespace: 'db.random', name: 'deleteOne', filter: {} }, + { namespace: 'db.namespace', name: 'deleteMany', filter: {} } +]); + +// Operation names are still type checked when there is no schema +expectError({ + namespace: 'db.author', + name: 'insertLots', // Not an operation we support + document: { name: 'bob', published: 2 } +}); + +type MongoDBSchemas = { + 'db.books': Book; + 'db.authors': Author; + 'db.stores': Store; +}; + +expectError>({ + namespace: 'db.author', // Unknown namespace! a typo! + name: 'insertOne', + document: { name: 'bob', published: 2 } +}); + +expectError>({ + namespace: 'db.authors', + name: 'insertOne', + document: { name: 'bob', published: '' } // Incorrect type for known field +}); + +expectError>({ + namespace: 'db.authors', + name: 'insertOne', + document: { name: 'bob', publish: 2 } // unknown field! typo! +}); + +// Defined schemas - all correct use +client.bulkWrite([ + { + namespace: 'db.authors', + name: 'insertOne', + document: { name: 'bob', published: 2 } + }, + { + namespace: 'db.authors', + name: 'replaceOne', + filter: { name: 'bob' }, + replacement: { name: 'ann', published: 2 } + }, + { + namespace: 'db.authors', + name: 'updateOne', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { + namespace: 'db.authors', + name: 'updateMany', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { namespace: 'db.authors', name: 'deleteOne', filter: {} }, + { namespace: 'db.authors', name: 'deleteMany', filter: {} } +]); + +// Defined schemas - incorrect use +expectError( + client.bulkWrite([ + { + namespace: 'db.authors', + name: 'insertOne', + document: { name: 'bob', published: '' } // wrong type + }, + { + namespace: 'db.authors', + name: 'replaceOne', + filter: { name: 'bob' }, + replacement: { name: 'ann', publish: 2 } // key typo + }, + { + namespace: 'db.blah', // unknown namespace + name: 'updateOne', + filter: { name: 'bob', published: 2 }, + update: {} + }, + { + namespace: 'db.authors', + name: 'updateManyy', // unknown operation + filter: { name: 'bob', published: 2 }, + update: {} + }, + { namespace: 'db.authors', name: 'deleteOne', filter: {} }, + { namespace: 'db.authors', name: 'deleteMany', filter: {} } + ]) +); + +type MongoDBSchemasWithCalculations = { + // River Books uses star ratings + [key: `river-books.${string}`]: Book & { fiveStarRatings: number }; + // Ocean literature uses thumbs up for ratings + [key: `ocean-literature.${string}`]: Book & { thumbsUp: number }; +}; + +// correct use +client.bulkWrite([ + { + namespace: 'river-books.store0', + name: 'insertOne', + document: { title: 'abc', released: new Date(), fiveStarRatings: 10 } + }, + { + namespace: 'ocean-literature.store0', + name: 'insertOne', + document: { title: 'abc', released: new Date(), thumbsUp: 10 } + } +]); + +// prevented from changing each store's rating system! +expectError( + client.bulkWrite([ + { + namespace: 'river-books.store0', + name: 'insertOne', + document: { title: 'abc', released: new Date(), thumbsUp: 10 } + }, + { + namespace: 'ocean-literature.store0', + name: 'insertOne', + document: { title: 'abc', released: new Date(), fiveStarRatings: 10 } + } + ]) +); + +// Example partial use case: +// I want to make sure I don't mess up any namespaces but I don't want to define schemas: + +type MongoDBNamespaces = 'db.books' | 'db.authors' | 'db.stores'; + +client.bulkWrite<{ [K in MongoDBNamespaces]: Document }>([ + { + namespace: 'db.books', + name: 'insertOne', + document: { title: 'abc', released: 32n, blah_blah: 10 } // wrong type for released does not error + }, + { + namespace: 'db.authors', + name: 'insertOne', + document: { title: 'abc', released: 'yesterday', fiveStarRatings: 10 } + } +]); + +expectError( + client.bulkWrite<{ [K in MongoDBNamespaces]: Document }>([ + { + namespace: 'db.wrongNS', + name: 'insertOne', + document: { title: 'abc', released: new Date(), thumbsUp: 10 } + } + ]) +); From 80a8b38d30adbbe14056351f49944678aeb935fe Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 14 Oct 2024 22:34:36 +0200 Subject: [PATCH 02/10] feat: add acknowledged to result --- src/mongo_client.ts | 8 ++- src/operations/client_bulk_write/common.ts | 4 ++ src/operations/client_bulk_write/executor.ts | 4 +- .../client_bulk_write/results_merger.ts | 23 ++++++++ test/integration/crud/crud.prose.test.ts | 59 ++++++++++--------- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 5658ae40b6b..87b50b42f6a 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -491,14 +491,18 @@ export class MongoClient extends TypedEventEmitter implements async bulkWrite = Record>( models: ReadonlyArray>, options?: ClientBulkWriteOptions - ): Promise { + ): Promise { if (this.autoEncrypter) { throw new MongoInvalidArgumentError( 'MongoClient bulkWrite does not currently support automatic encryption.' ); } // We do not need schema type information past this point ("as any" is fine) - return await new ClientBulkWriteExecutor(this, models as any, options).execute(); + return await new ClientBulkWriteExecutor( + this, + models as any, + resolveOptions(this, options) + ).execute(); } /** diff --git a/src/operations/client_bulk_write/common.ts b/src/operations/client_bulk_write/common.ts index d6dba2e2cd5..3c9c5e222bb 100644 --- a/src/operations/client_bulk_write/common.ts +++ b/src/operations/client_bulk_write/common.ts @@ -176,6 +176,10 @@ export type ClientBulkWriteModel< /** @public */ export interface ClientBulkWriteResult { + /** + * Whether the bulk write was acknowledged. + */ + acknowledged: boolean; /** * The total number of documents inserted across all insert operations. */ diff --git a/src/operations/client_bulk_write/executor.ts b/src/operations/client_bulk_write/executor.ts index 3b89065886c..1ab6bbae15a 100644 --- a/src/operations/client_bulk_write/executor.ts +++ b/src/operations/client_bulk_write/executor.ts @@ -77,7 +77,7 @@ export class ClientBulkWriteExecutor { * for each, then merge the results into one. * @returns The result. */ - async execute(): Promise { + async execute(): Promise { // The command builder will take the user provided models and potential split the batch // into multiple commands due to size. const pkFactory = this.client.s.options.pkFactory; @@ -92,7 +92,7 @@ export class ClientBulkWriteExecutor { const operation = new ClientBulkWriteOperation(commandBuilder, this.options); await executeOperation(this.client, operation); } - return { ok: 1 }; + return ClientBulkWriteResultsMerger.unacknowledged(); } else { const resultsMerger = new ClientBulkWriteResultsMerger(this.options); // For each command will will create and exhaust a cursor for the results. diff --git a/src/operations/client_bulk_write/results_merger.ts b/src/operations/client_bulk_write/results_merger.ts index 8114523fde2..e591ab910a8 100644 --- a/src/operations/client_bulk_write/results_merger.ts +++ b/src/operations/client_bulk_write/results_merger.ts @@ -11,6 +11,21 @@ import { type ClientUpdateResult } from './common'; +/** + * Unacknowledged bulk writes are always the same. + */ +const UNACKNOWLEDGED = { + acknowledged: true, + insertedCount: 0, + upsertedCount: 0, + matchedCount: 0, + modifiedCount: 0, + deletedCount: 0, + insertResults: undefined, + updateResults: undefined, + deleteResults: undefined +}; + /** * Merges client bulk write cursor responses together into a single result. * @internal @@ -22,6 +37,13 @@ export class ClientBulkWriteResultsMerger { writeConcernErrors: Document[]; writeErrors: Map; + /** + * @returns The standard unacknowledged bulk write result. + */ + static unacknowledged(): ClientBulkWriteResult { + return UNACKNOWLEDGED; + } + /** * Instantiate the merger. * @param options - The options. @@ -32,6 +54,7 @@ export class ClientBulkWriteResultsMerger { this.writeConcernErrors = []; this.writeErrors = new Map(); this.result = { + acknowledged: true, insertedCount: 0, upsertedCount: 0, matchedCount: 0, diff --git a/test/integration/crud/crud.prose.test.ts b/test/integration/crud/crud.prose.test.ts index 9b5f58cdb4f..fef2a9862f2 100644 --- a/test/integration/crud/crud.prose.test.ts +++ b/test/integration/crud/crud.prose.test.ts @@ -6,6 +6,7 @@ import { type AnyClientBulkWriteModel, type ClientSession, type Collection, + type Document, MongoBulkWriteError, type MongoClient, MongoClientBulkWriteError, @@ -175,7 +176,7 @@ describe('CRUD Prose Spec Tests', () => { // firstEvent.operationId is equal to secondEvent.operationId. let client: MongoClient; let maxWriteBatchSize; - const models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -188,12 +189,12 @@ describe('CRUD Prose Spec Tests', () => { client.on('commandStarted', filterForCommands('bulkWrite', commands)); commands.length = 0; - Array.from({ length: maxWriteBatchSize + 1 }, () => { - models.push({ + models = Array.from({ length: maxWriteBatchSize + 1 }, () => { + return { namespace: 'db.coll', name: 'insertOne', document: { a: 'b' } - }); + }; }); }); @@ -243,7 +244,7 @@ describe('CRUD Prose Spec Tests', () => { let maxBsonObjectSize; let maxMessageSizeBytes; let numModels; - const models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -258,14 +259,14 @@ describe('CRUD Prose Spec Tests', () => { client.on('commandStarted', filterForCommands('bulkWrite', commands)); commands.length = 0; - Array.from({ length: numModels }, () => { - models.push({ + models = Array.from({ length: numModels }, () => { + return { name: 'insertOne', namespace: 'db.coll', document: { a: 'b'.repeat(maxBsonObjectSize - 500) } - }); + }; }); }); @@ -314,7 +315,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that two CommandStartedEvents were observed for the bulkWrite command. let client: MongoClient; let maxWriteBatchSize; - const models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -338,12 +339,12 @@ describe('CRUD Prose Spec Tests', () => { client.on('commandStarted', filterForCommands('bulkWrite', commands)); commands.length = 0; - Array.from({ length: maxWriteBatchSize + 1 }, () => { - models.push({ + models = Array.from({ length: maxWriteBatchSize + 1 }, () => { + return { namespace: 'db.coll', name: 'insertOne', document: { a: 'b' } - }); + }; }); }); @@ -382,7 +383,7 @@ describe('CRUD Prose Spec Tests', () => { // Construct a list of write models (referred to as models) with model repeated maxWriteBatchSize + 1 times. let client: MongoClient; let maxWriteBatchSize; - const models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -396,12 +397,12 @@ describe('CRUD Prose Spec Tests', () => { client.on('commandStarted', filterForCommands('bulkWrite', commands)); commands.length = 0; - Array.from({ length: maxWriteBatchSize + 1 }, () => { - models.push({ + models = Array.from({ length: maxWriteBatchSize + 1 }, () => { + return { namespace: 'db.coll', name: 'insertOne', document: { _id: 1 } - }); + }; }); }); @@ -471,7 +472,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that a CommandStartedEvent was observed for the getMore command. let client: MongoClient; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -545,7 +546,7 @@ describe('CRUD Prose Spec Tests', () => { let client: MongoClient; let session: ClientSession; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -632,7 +633,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that a CommandStartedEvent was observed for the killCursors command. let client: MongoClient; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: AnyClientBulkWriteModel[] = []; const getMoreCommands: CommandStartedEvent[] = []; const killCursorsCommands: CommandStartedEvent[] = []; @@ -803,7 +804,7 @@ describe('CRUD Prose Spec Tests', () => { let opsBytes; let numModels; let remainderBytes; - let models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -821,12 +822,12 @@ describe('CRUD Prose Spec Tests', () => { commands.length = 0; models = []; - Array.from({ length: numModels }, () => { - models.push({ + models = Array.from({ length: numModels }, () => { + return { namespace: 'db.coll', name: 'insertOne', document: { a: 'b'.repeat(maxBsonObjectSize - 57) } - }); + }; }); if (remainderBytes >= 217) { @@ -859,7 +860,7 @@ describe('CRUD Prose Spec Tests', () => { it('executes in a single batch', { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { - const sameNamespaceModel: AnyClientBulkWriteModel = { + const sameNamespaceModel: AnyClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b' } @@ -896,7 +897,7 @@ describe('CRUD Prose Spec Tests', () => { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { const namespace = `db.${'c'.repeat(200)}`; - const newNamespaceModel: AnyClientBulkWriteModel = { + const newNamespaceModel: AnyClientBulkWriteModel = { name: 'insertOne', namespace: namespace, document: { a: 'b' } @@ -950,7 +951,7 @@ describe('CRUD Prose Spec Tests', () => { it('raises a client error', { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { - const model: AnyClientBulkWriteModel = { + const model: AnyClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b'.repeat(maxMessageSizeBytes) } @@ -976,7 +977,7 @@ describe('CRUD Prose Spec Tests', () => { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { const namespace = `db.${'c'.repeat(maxMessageSizeBytes)}`; - const model: AnyClientBulkWriteModel = { + const model: AnyClientBulkWriteModel = { name: 'insertOne', namespace: namespace, document: { a: 'b' } @@ -1033,7 +1034,7 @@ describe('CRUD Prose Spec Tests', () => { }); it('raises a client side error', async function () { - const model: AnyClientBulkWriteModel = { + const model: AnyClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b' } @@ -1113,7 +1114,7 @@ describe('CRUD Prose Spec Tests', () => { let maxBsonObjectSize; let maxMessageSizeBytes; let numModels; - let models: AnyClientBulkWriteModel[] = []; + let models: AnyClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { From 7acf218c3d55807366cba2cc4804b39616cc6f87 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 14 Oct 2024 22:39:24 +0200 Subject: [PATCH 03/10] test: fix ts in prose test --- test/integration/crud/crud.prose.test.ts | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/integration/crud/crud.prose.test.ts b/test/integration/crud/crud.prose.test.ts index fef2a9862f2..fc13ae10683 100644 --- a/test/integration/crud/crud.prose.test.ts +++ b/test/integration/crud/crud.prose.test.ts @@ -3,7 +3,7 @@ import { once } from 'events'; import { type CommandStartedEvent } from '../../../mongodb'; import { - type AnyClientBulkWriteModel, + type ClientBulkWriteModel, type ClientSession, type Collection, type Document, @@ -176,7 +176,7 @@ describe('CRUD Prose Spec Tests', () => { // firstEvent.operationId is equal to secondEvent.operationId. let client: MongoClient; let maxWriteBatchSize; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -244,7 +244,7 @@ describe('CRUD Prose Spec Tests', () => { let maxBsonObjectSize; let maxMessageSizeBytes; let numModels; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -315,7 +315,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that two CommandStartedEvents were observed for the bulkWrite command. let client: MongoClient; let maxWriteBatchSize; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -383,7 +383,7 @@ describe('CRUD Prose Spec Tests', () => { // Construct a list of write models (referred to as models) with model repeated maxWriteBatchSize + 1 times. let client: MongoClient; let maxWriteBatchSize; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -391,7 +391,7 @@ describe('CRUD Prose Spec Tests', () => { await client.connect(); await client.db('db').collection('coll').drop(); const hello = await client.db('admin').command({ hello: 1 }); - await client.db('db').collection('coll').insertOne({ _id: 1 }); + await client.db('db').collection<{ _id?: number }>('coll').insertOne({ _id: 1 }); maxWriteBatchSize = hello.maxWriteBatchSize; client.on('commandStarted', filterForCommands('bulkWrite', commands)); @@ -472,7 +472,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that a CommandStartedEvent was observed for the getMore command. let client: MongoClient; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -546,7 +546,7 @@ describe('CRUD Prose Spec Tests', () => { let client: MongoClient; let session: ClientSession; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -633,7 +633,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that a CommandStartedEvent was observed for the killCursors command. let client: MongoClient; let maxBsonObjectSize; - const models: AnyClientBulkWriteModel[] = []; + const models: ClientBulkWriteModel[] = []; const getMoreCommands: CommandStartedEvent[] = []; const killCursorsCommands: CommandStartedEvent[] = []; @@ -804,7 +804,7 @@ describe('CRUD Prose Spec Tests', () => { let opsBytes; let numModels; let remainderBytes; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { @@ -860,7 +860,7 @@ describe('CRUD Prose Spec Tests', () => { it('executes in a single batch', { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { - const sameNamespaceModel: AnyClientBulkWriteModel = { + const sameNamespaceModel: ClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b' } @@ -897,7 +897,7 @@ describe('CRUD Prose Spec Tests', () => { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { const namespace = `db.${'c'.repeat(200)}`; - const newNamespaceModel: AnyClientBulkWriteModel = { + const newNamespaceModel: ClientBulkWriteModel = { name: 'insertOne', namespace: namespace, document: { a: 'b' } @@ -951,7 +951,7 @@ describe('CRUD Prose Spec Tests', () => { it('raises a client error', { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { - const model: AnyClientBulkWriteModel = { + const model: ClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b'.repeat(maxMessageSizeBytes) } @@ -977,7 +977,7 @@ describe('CRUD Prose Spec Tests', () => { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { const namespace = `db.${'c'.repeat(maxMessageSizeBytes)}`; - const model: AnyClientBulkWriteModel = { + const model: ClientBulkWriteModel = { name: 'insertOne', namespace: namespace, document: { a: 'b' } @@ -1034,7 +1034,7 @@ describe('CRUD Prose Spec Tests', () => { }); it('raises a client side error', async function () { - const model: AnyClientBulkWriteModel = { + const model: ClientBulkWriteModel = { name: 'insertOne', namespace: 'db.coll', document: { a: 'b' } @@ -1114,7 +1114,7 @@ describe('CRUD Prose Spec Tests', () => { let maxBsonObjectSize; let maxMessageSizeBytes; let numModels; - let models: AnyClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { From bac621adcc52130e5162d15d3862b7a049ebccc4 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 14 Oct 2024 22:57:46 +0200 Subject: [PATCH 04/10] test: fix unit test --- test/integration/crud/crud.prose.test.ts | 2 +- test/unit/operations/client_bulk_write/results_merger.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/crud/crud.prose.test.ts b/test/integration/crud/crud.prose.test.ts index fc13ae10683..1485567686c 100644 --- a/test/integration/crud/crud.prose.test.ts +++ b/test/integration/crud/crud.prose.test.ts @@ -315,7 +315,7 @@ describe('CRUD Prose Spec Tests', () => { // Assert that two CommandStartedEvents were observed for the bulkWrite command. let client: MongoClient; let maxWriteBatchSize; - let models: ClientBulkWriteModel[] = []; + let models: ClientBulkWriteModel[] = []; const commands: CommandStartedEvent[] = []; beforeEach(async function () { diff --git a/test/unit/operations/client_bulk_write/results_merger.test.ts b/test/unit/operations/client_bulk_write/results_merger.test.ts index c9a954e694a..d1ec999d059 100644 --- a/test/unit/operations/client_bulk_write/results_merger.test.ts +++ b/test/unit/operations/client_bulk_write/results_merger.test.ts @@ -45,6 +45,7 @@ describe('ClientBulkWriteResultsMerger', function () { it('initializes the result', function () { expect(resultsMerger.result).to.deep.equal({ + acknowledged: true, insertedCount: 0, upsertedCount: 0, matchedCount: 0, From b766cc3f6ee3f1fc69a8cc9c8f13de6b7d7b8f50 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 14 Oct 2024 23:36:11 +0200 Subject: [PATCH 05/10] feat: make bulk result readonly --- src/operations/client_bulk_write/common.ts | 18 +++--- src/operations/client_bulk_write/executor.ts | 6 +- .../client_bulk_write/results_merger.ts | 62 ++++++++++++++++++- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/operations/client_bulk_write/common.ts b/src/operations/client_bulk_write/common.ts index 3c9c5e222bb..b8e03d21022 100644 --- a/src/operations/client_bulk_write/common.ts +++ b/src/operations/client_bulk_write/common.ts @@ -179,39 +179,39 @@ export interface ClientBulkWriteResult { /** * Whether the bulk write was acknowledged. */ - acknowledged: boolean; + 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; + readonly insertResults?: ReadonlyMap; /** * The results of each individual update operation that was successfully performed. */ - updateResults?: Map; + readonly updateResults?: ReadonlyMap; /** * The results of each individual delete operation that was successfully performed. */ - deleteResults?: Map; + readonly deleteResults?: ReadonlyMap; } /** @public */ diff --git a/src/operations/client_bulk_write/executor.ts b/src/operations/client_bulk_write/executor.ts index 1ab6bbae15a..23c2d08f318 100644 --- a/src/operations/client_bulk_write/executor.ts +++ b/src/operations/client_bulk_write/executor.ts @@ -112,7 +112,7 @@ export class ClientBulkWriteExecutor { message: 'Mongo client bulk write encountered an error during execution' }); bulkWriteError.cause = error; - bulkWriteError.partialResult = resultsMerger.result; + bulkWriteError.partialResult = resultsMerger.bulkWriteResult; throw bulkWriteError; } else { // Client side errors are just thrown. @@ -128,11 +128,11 @@ export class ClientBulkWriteExecutor { }); error.writeConcernErrors = resultsMerger.writeConcernErrors; error.writeErrors = resultsMerger.writeErrors; - error.partialResult = resultsMerger.result; + error.partialResult = resultsMerger.bulkWriteResult; throw error; } - return resultsMerger.result; + return resultsMerger.bulkWriteResult; } } } diff --git a/src/operations/client_bulk_write/results_merger.ts b/src/operations/client_bulk_write/results_merger.ts index e591ab910a8..080c7451644 100644 --- a/src/operations/client_bulk_write/results_merger.ts +++ b/src/operations/client_bulk_write/results_merger.ts @@ -26,14 +26,53 @@ const UNACKNOWLEDGED = { deleteResults: undefined }; +interface InternalResult { + /** + * Whether the bulk write was acknowledged. + */ + acknowledged: boolean; + /** + * The total number of documents inserted across all insert operations. + */ + insertedCount: number; + /** + * The total number of documents upserted across all update operations. + */ + upsertedCount: number; + /** + * The total number of documents matched across all update operations. + */ + matchedCount: number; + /** + * The total number of documents modified across all update operations. + */ + modifiedCount: number; + /** + * The total number of documents deleted across all delete operations. + */ + deletedCount: number; + /** + * The results of each individual insert operation that was successfully performed. + */ + insertResults?: Map; + /** + * The results of each individual update operation that was successfully performed. + */ + updateResults?: Map; + /** + * The results of each individual delete operation that was successfully performed. + */ + deleteResults?: Map; +} + /** * Merges client bulk write cursor responses together into a single result. * @internal */ export class ClientBulkWriteResultsMerger { - result: ClientBulkWriteResult; - options: ClientBulkWriteOptions; - currentBatchOffset: number; + private result: InternalResult; + private options: ClientBulkWriteOptions; + private currentBatchOffset: number; writeConcernErrors: Document[]; writeErrors: Map; @@ -72,6 +111,23 @@ export class ClientBulkWriteResultsMerger { } } + /** + * Get the bulk write result object. + */ + get bulkWriteResult(): ClientBulkWriteResult { + return { + acknowledged: this.result.acknowledged, + insertedCount: this.result.insertedCount, + upsertedCount: this.result.upsertedCount, + matchedCount: this.result.matchedCount, + modifiedCount: this.result.modifiedCount, + deletedCount: this.result.deletedCount, + insertResults: this.result.insertResults, + updateResults: this.result.updateResults, + deleteResults: this.result.deleteResults + }; + } + /** * Merge the results in the cursor to the existing result. * @param currentBatchOffset - The offset index to the original models. From 94a5f24ed1cd0cc94a1b8f7e82a9e7706cbd25b2 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 15 Oct 2024 23:51:14 +0200 Subject: [PATCH 06/10] fix: prose test --- src/operations/client_bulk_write/results_merger.ts | 2 +- test/integration/crud/crud.prose.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operations/client_bulk_write/results_merger.ts b/src/operations/client_bulk_write/results_merger.ts index 080c7451644..7e835479c70 100644 --- a/src/operations/client_bulk_write/results_merger.ts +++ b/src/operations/client_bulk_write/results_merger.ts @@ -15,7 +15,7 @@ import { * Unacknowledged bulk writes are always the same. */ const UNACKNOWLEDGED = { - acknowledged: true, + acknowledged: false, insertedCount: 0, upsertedCount: 0, matchedCount: 0, diff --git a/test/integration/crud/crud.prose.test.ts b/test/integration/crud/crud.prose.test.ts index 1485567686c..8665d69a1f3 100644 --- a/test/integration/crud/crud.prose.test.ts +++ b/test/integration/crud/crud.prose.test.ts @@ -1155,7 +1155,7 @@ describe('CRUD Prose Spec Tests', () => { metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } }, async test() { const result = await client.bulkWrite(models, { ordered: false, writeConcern: { w: 0 } }); - expect(result).to.deep.equal({ ok: 1 }); + expect(result.acknowledged).to.be.false; expect(commands.length).to.equal(2); expect(commands[0].command.ops.length).to.equal(numModels - 1); expect(commands[0].command.writeConcern.w).to.equal(0); From ed65e4f579618607540722dda9f6361d3250037f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 16 Oct 2024 11:02:01 -0400 Subject: [PATCH 07/10] docs: add namespace documentation --- src/operations/client_bulk_write/common.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/operations/client_bulk_write/common.ts b/src/operations/client_bulk_write/common.ts index b8e03d21022..555865f930e 100644 --- a/src/operations/client_bulk_write/common.ts +++ b/src/operations/client_bulk_write/common.ts @@ -27,7 +27,14 @@ 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: `.`. + * All documents belong to a namespace. + * + * @see https://www.mongodb.com/docs/manual/reference/limits/#std-label-faq-dev-namespace + */ namespace: string; } From 0658de41b3397afbf83c3e6304d00b74c1fdbf0b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 16 Oct 2024 11:38:27 -0400 Subject: [PATCH 08/10] docs: A mapping of namespace strings to collections schemas. --- src/operations/client_bulk_write/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/client_bulk_write/common.ts b/src/operations/client_bulk_write/common.ts index 555865f930e..c5b96d217ae 100644 --- a/src/operations/client_bulk_write/common.ts +++ b/src/operations/client_bulk_write/common.ts @@ -153,7 +153,7 @@ export type AnyClientBulkWriteModel = | ClientDeleteManyModel; /** - * Take a Typescript type that maps namespaces to schema types. + * A mapping of namespace strings to collections schemas. * @public * * @example From 3714c111d37e4b329168e24b888b25b1ec76b799 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 16 Oct 2024 11:41:38 -0400 Subject: [PATCH 09/10] chore: ClientBulkWriteResultAccumulation --- src/operations/client_bulk_write/results_merger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operations/client_bulk_write/results_merger.ts b/src/operations/client_bulk_write/results_merger.ts index 7e835479c70..1a0f0a9f36b 100644 --- a/src/operations/client_bulk_write/results_merger.ts +++ b/src/operations/client_bulk_write/results_merger.ts @@ -26,7 +26,7 @@ const UNACKNOWLEDGED = { deleteResults: undefined }; -interface InternalResult { +interface ClientBulkWriteResultAccumulation { /** * Whether the bulk write was acknowledged. */ @@ -70,7 +70,7 @@ interface InternalResult { * @internal */ export class ClientBulkWriteResultsMerger { - private result: InternalResult; + private result: ClientBulkWriteResultAccumulation; private options: ClientBulkWriteOptions; private currentBatchOffset: number; writeConcernErrors: Document[]; From 4a783f34fccb00dd9991acc38fa184b5bde028aa Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 16 Oct 2024 12:10:18 -0400 Subject: [PATCH 10/10] chore: test fix --- test/types/client_bulk_write.test-d.ts | 58 ++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/test/types/client_bulk_write.test-d.ts b/test/types/client_bulk_write.test-d.ts index 95d58d3f48c..834b68b19cd 100644 --- a/test/types/client_bulk_write.test-d.ts +++ b/test/types/client_bulk_write.test-d.ts @@ -1,4 +1,4 @@ -import { expectError, expectType } from 'tsd'; +import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd'; import { type ClientBulkWriteModel, @@ -23,23 +23,26 @@ type Author = { name: string; published: number }; type Store = { _id: UUID }; // Baseline check that schema modifies the following fields for each type. -expectType['document']>(null as unknown as OptionalId); +declare const clientInsertOneModel: ClientInsertOneModel; +expectType>(clientInsertOneModel.document); -expectType['filter']>(null as unknown as Filter); -expectType['replacement']>(null as unknown as WithoutId); +declare const clientReplaceOneModel: ClientReplaceOneModel; +expectType>(clientReplaceOneModel.filter); +expectType>(clientReplaceOneModel.replacement); -expectType['filter']>(null as unknown as Filter); -expectType['update']>( - null as unknown as UpdateFilter | Document[] -); +declare const clientUpdateOneModel: ClientUpdateOneModel; +expectType>(clientUpdateOneModel.filter); +expectType | Document[]>(clientUpdateOneModel.update); -expectType['filter']>(null as unknown as Filter); -expectType['update']>( - null as unknown as UpdateFilter | Document[] -); +declare const clientUpdateManyModel: ClientUpdateManyModel; +expectType>(clientUpdateManyModel.filter); +expectType | Document[]>(clientUpdateManyModel.update); -expectType['filter']>(null as unknown as Filter); -expectType['filter']>(null as unknown as Filter); +declare const clientDeleteOneModel: ClientDeleteOneModel; +expectType>(clientDeleteOneModel.filter); + +declare const clientDeleteManyModel: ClientDeleteManyModel; +expectType>(clientDeleteManyModel.filter); client.bulkWrite([]); // empty should always work @@ -72,7 +75,7 @@ client.bulkWrite([ { namespace: 'db.authors', name: 'deleteMany', filter: {} } ]); -// No schemas - incorrect use - random namespaces, no type checking +// No schemas - random namespaces, no type checking client.bulkWrite([ { namespace: 'db.whatever', @@ -168,27 +171,40 @@ expectError( namespace: 'db.authors', name: 'insertOne', document: { name: 'bob', published: '' } // wrong type - }, + } + ]) +); + +expectError( + client.bulkWrite([ { namespace: 'db.authors', name: 'replaceOne', filter: { name: 'bob' }, replacement: { name: 'ann', publish: 2 } // key typo - }, + } + ]) +); + +expectError( + client.bulkWrite([ { namespace: 'db.blah', // unknown namespace name: 'updateOne', filter: { name: 'bob', published: 2 }, update: {} - }, + } + ]) +); + +expectError( + client.bulkWrite([ { namespace: 'db.authors', name: 'updateManyy', // unknown operation filter: { name: 'bob', published: 2 }, update: {} - }, - { namespace: 'db.authors', name: 'deleteOne', filter: {} }, - { namespace: 'db.authors', name: 'deleteMany', filter: {} } + } ]) );